diff --git a/vector/src/main/java/im/vector/riotredesign/core/platform/StateView.kt b/vector/src/main/java/im/vector/riotredesign/core/platform/StateView.kt index e75fb26cb6..a6fa745732 100755 --- a/vector/src/main/java/im/vector/riotredesign/core/platform/StateView.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/platform/StateView.kt @@ -17,9 +17,9 @@ package im.vector.riotredesign.core.platform import android.content.Context +import android.graphics.drawable.Drawable import android.util.AttributeSet import android.view.View -import android.view.ViewGroup import android.widget.FrameLayout import im.vector.riotredesign.R import kotlinx.android.synthetic.main.view_state.view.* @@ -30,7 +30,7 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? sealed class State { object Content : State() object Loading : State() - data class Empty(val message: CharSequence? = null) : State() + data class Empty(val title: CharSequence? = null, val image: Drawable? = null, val message: CharSequence? = null) : State() data class Error(val message: CharSequence? = null) : State() } @@ -52,7 +52,7 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? init { View.inflate(context, R.layout.view_state, this) - layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) + layoutParams = LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT) errorRetryView.setOnClickListener { eventCallback?.onRetryClicked() } @@ -74,16 +74,18 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? emptyView.visibility = View.INVISIBLE contentView?.visibility = View.INVISIBLE } - is StateView.State.Empty -> { + is StateView.State.Empty -> { progressBar.visibility = View.INVISIBLE errorView.visibility = View.INVISIBLE emptyView.visibility = View.VISIBLE + emptyImageView.setImageDrawable(newState.image) emptyMessageView.text = newState.message + emptyTitleView.text = newState.title if (contentView != null) { contentView!!.visibility = View.INVISIBLE } } - is StateView.State.Error -> { + is StateView.State.Error -> { progressBar.visibility = View.INVISIBLE errorView.visibility = View.VISIBLE emptyView.visibility = View.INVISIBLE diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt index f8141e149b..47534b8b81 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt @@ -85,12 +85,16 @@ class GroupListViewModel(initialState: GroupListViewState, session .rx().liveGroupSummaries() .map { - val myUser = session.getUser(session.sessionParams.credentials.userId) - val allCommunityGroup = GroupSummary( - groupId = ALL_COMMUNITIES_GROUP_ID, - displayName = "All Communities", - avatarUrl = myUser?.avatarUrl ?: "") - listOf(allCommunityGroup) + it + if (it.isEmpty()) { + it + } else { + val myUser = session.getUser(session.sessionParams.credentials.userId) + val allCommunityGroup = GroupSummary( + groupId = ALL_COMMUNITIES_GROUP_ID, + displayName = "All Communities", + avatarUrl = myUser?.avatarUrl ?: "") + listOf(allCommunityGroup) + it + } } .execute { async -> val newSelectedGroup = selectedGroup ?: async()?.firstOrNull() diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListAnimator.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListAnimator.kt new file mode 100644 index 0000000000..37dbdf9b6b --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListAnimator.kt @@ -0,0 +1,32 @@ +/* + * 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 + +import androidx.recyclerview.widget.DefaultItemAnimator + +private const val ANIM_DURATION_IN_MILLIS = 100L + +class RoomListAnimator : DefaultItemAnimator() { + + init { + addDuration = ANIM_DURATION_IN_MILLIS + removeDuration = ANIM_DURATION_IN_MILLIS + moveDuration = 0 + changeDuration = 0 + } + +} \ No newline at end of file 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 fedf61eb92..3846333709 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 @@ -91,6 +91,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback { val layoutManager = LinearLayoutManager(context) val stateRestorer = LayoutManagerStateRestorer(layoutManager).register() epoxyRecyclerView.layoutManager = layoutManager + epoxyRecyclerView.itemAnimator = RoomListAnimator() roomController.callback = this roomController.addModelBuildListener { it.dispatchTo(stateRestorer) } stateView.contentView = epoxyRecyclerView @@ -98,22 +99,40 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback { } private fun renderState(state: RoomListViewState) { - when (state.asyncRooms) { + when (state.asyncFilteredRooms) { is Incomplete -> renderLoading() is Success -> renderSuccess(state) - is Fail -> renderFailure(state.asyncRooms.error) + is Fail -> renderFailure(state.asyncFilteredRooms.error) } } private fun renderSuccess(state: RoomListViewState) { - if (state.asyncRooms().isNullOrEmpty()) { - stateView.state = StateView.State.Empty(getString(R.string.room_list_empty)) + val allRooms = state.asyncRooms() + val filteredRooms = state.asyncFilteredRooms() + if (filteredRooms.isNullOrEmpty()) { + renderEmptyState(allRooms) } else { stateView.state = StateView.State.Content } roomController.setData(state) } + private fun renderEmptyState(allRooms: List?) { + val hasNoRoom = allRooms.isNullOrEmpty() + val emptyState = when (roomListParams.displayMode) { + DisplayMode.HOME -> { + if (hasNoRoom) { + StateView.State.Empty(getString(R.string.room_list_catchup_welcome_title), null, getString(R.string.room_list_catchup_welcome_body)) + } else { + StateView.State.Empty(getString(R.string.room_list_catchup_empty_title), null, getString(R.string.room_list_catchup_empty_body)) + } + } + DisplayMode.PEOPLE -> StateView.State.Empty() + DisplayMode.ROOMS -> StateView.State.Empty() + } + stateView.state = emptyState + } + private fun renderLoading() { stateView.state = StateView.State.Loading } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt index 68b158e49c..585d857200 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt @@ -87,6 +87,12 @@ class RoomListViewModel(initialState: RoomListViewState, private fun observeRoomSummaries() { + homeRoomListObservableSource + .observe() + .execute { asyncRooms -> + copy(asyncRooms = asyncRooms) + } + homeRoomListObservableSource .observe() .flatMapSingle { @@ -96,7 +102,7 @@ class RoomListViewModel(initialState: RoomListViewState, } .map { buildRoomSummaries(it) } .execute { async -> - copy(asyncRooms = async) + copy(asyncFilteredRooms = async) } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewState.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewState.kt index dfe0b7b665..476dfac3cf 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewState.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewState.kt @@ -17,15 +17,12 @@ package im.vector.riotredesign.features.home.room.list import androidx.annotation.StringRes -import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState -import com.airbnb.mvrx.Uninitialized import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotredesign.R data class RoomListViewState( val displayMode: RoomListFragment.DisplayMode, - val asyncRooms: Async = Uninitialized, val isInviteExpanded: Boolean = true, val isFavouriteRoomsExpanded: Boolean = true, val isDirectRoomsExpanded: Boolean = true, @@ -71,5 +68,5 @@ enum class RoomCategory(@StringRes val titleRes: Int) { } fun RoomSummaries?.isNullOrEmpty(): Boolean { - return this == null || isEmpty() + return this == null || this.values.flatten().isEmpty() } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt index e25560d72f..633deb9041 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt @@ -31,7 +31,7 @@ class RoomSummaryController(private val stringProvider: StringProvider, var callback: Callback? = null override fun buildModels(viewState: RoomListViewState) { - val roomSummaries = viewState.asyncRooms() + val roomSummaries = viewState.asyncFilteredRooms() roomSummaries?.forEach { (category, summaries) -> if (summaries.isEmpty()) { return@forEach diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryItem.kt index 0177757bec..f9ada8a5f5 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryItem.kt @@ -46,11 +46,13 @@ abstract class RoomSummaryItem : VectorEpoxyModel() { holder.titleView.text = roomName holder.lastEventTimeView.text = lastEventTime holder.lastEventView.text = lastFormattedEvent + holder.unreadCounterBadgeView.render(unreadCount, showHighlighted) AvatarRenderer.render(avatarUrl, roomId, roomName.toString(), holder.avatarImageView) } class Holder : VectorEpoxyHolder() { val titleView by bind(R.id.roomNameView) + val unreadCounterBadgeView by bind(R.id.roomUnreadCounterBadgeView) val lastEventView by bind(R.id.roomLastEventView) val lastEventTimeView by bind(R.id.roomLastEventTimeView) val avatarImageView by bind(R.id.roomAvatarImageView) diff --git a/vector/src/main/res/layout/item_room.xml b/vector/src/main/res/layout/item_room.xml index e53dc9495c..677d274255 100644 --- a/vector/src/main/res/layout/item_room.xml +++ b/vector/src/main/res/layout/item_room.xml @@ -9,12 +9,12 @@ android:background="?attr/selectableItemBackground" android:clickable="true" android:focusable="true" - android:paddingBottom="16dp" - android:paddingTop="16dp" android:paddingStart="8dp" android:paddingLeft="8dp" + android:paddingTop="8dp" android:paddingEnd="16dp" - android:paddingRight="16dp"> + android:paddingRight="16dp" + android:paddingBottom="8dp"> - + android:layout_marginRight="8dp" + android:gravity="center" + android:minWidth="16dp" + android:minHeight="16dp" + android:paddingLeft="4dp" + android:paddingRight="4dp" + android:textColor="@android:color/white" + android:textSize="10sp" + app:layout_constraintEnd_toStartOf="@+id/roomLastEventTimeView" + app:layout_constraintStart_toEndOf="@+id/roomNameView" + app:layout_constraintTop_toTopOf="@+id/roomAvatarImageView" + tools:background="@drawable/bg_unread_highlight" + tools:text="4" /> + + + diff --git a/vector/src/main/res/layout/item_room_category.xml b/vector/src/main/res/layout/item_room_category.xml index ecb6141f81..5fda236670 100644 --- a/vector/src/main/res/layout/item_room_category.xml +++ b/vector/src/main/res/layout/item_room_category.xml @@ -1,6 +1,5 @@ - + tools:text="@string/room_recents_favourites" /> - - + tools:background="@drawable/bg_unread_highlight" + tools:text="24" /> - \ No newline at end of file + \ No newline at end of file diff --git a/vector/src/main/res/layout/view_state.xml b/vector/src/main/res/layout/view_state.xml index 2441893b08..91803d86f1 100644 --- a/vector/src/main/res/layout/view_state.xml +++ b/vector/src/main/res/layout/view_state.xml @@ -2,8 +2,7 @@ @@ -11,13 +10,14 @@ android:id="@+id/progressBar" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" /> + android:layout_gravity="center" /> @@ -48,9 +48,26 @@ android:id="@+id/emptyView" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_gravity="center" android:orientation="vertical" android:padding="8dp"> + + + + - - - \ 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 5a37c39307..a115f102bf 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -7,6 +7,10 @@ "Sent you an invitation" Invited by %s + You’re all caught up! + You have no more unread messages + Welcome home! + Catch up on unread messages here Reactions Agree