Home: continue room list rework.

This commit is contained in:
ganfra 2019-05-17 17:07:02 +02:00 committed by Benoit Marty
parent c0fd06fd2d
commit eb2344a43f
12 changed files with 165 additions and 82 deletions

View File

@ -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

View File

@ -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()

View File

@ -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
}

}

View File

@ -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<RoomSummary>?) {
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
}

View File

@ -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)
}
}


View File

@ -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<RoomSummaries> = 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()
}

View File

@ -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

View File

@ -46,11 +46,13 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
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<TextView>(R.id.roomNameView)
val unreadCounterBadgeView by bind<UnreadCounterBadgeView>(R.id.roomUnreadCounterBadgeView)
val lastEventView by bind<TextView>(R.id.roomLastEventView)
val lastEventTimeView by bind<TextView>(R.id.roomLastEventTimeView)
val avatarImageView by bind<ImageView>(R.id.roomAvatarImageView)

View File

@ -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">

<ImageView
android:id="@+id/roomAvatarImageView"
@ -29,30 +29,41 @@

<TextView
android:id="@+id/roomNameView"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginEnd="8dp"
android:duplicateParentState="true"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/black_87"
android:textSize="14sp"
app:layout_constraintEnd_toStartOf="@+id/roomLastEventTimeView"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@+id/roomUnreadCounterBadgeView"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/roomAvatarImageView"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toTopOf="@+id/roomAvatarImageView"
tools:text="@tools:sample/full_names" />

<TextView
android:id="@+id/roomLastEventView"
android:layout_width="0dp"

<im.vector.riotredesign.features.home.room.list.UnreadCounterBadgeView
android:id="@+id/roomUnreadCounterBadgeView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black_38"
android:textSize="14sp"
android:maxLines="2"
android:ellipsize="end"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/roomNameView"
app:layout_constraintTop_toBottomOf="@+id/roomNameView"
tools:text="@tools:sample/lorem/random" />
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" />

<TextView
android:id="@+id/roomLastEventTimeView"
@ -63,9 +74,23 @@
android:textColor="@color/black_38"
android:textSize="12sp"
app:layout_constraintBaseline_toBaselineOf="@id/messageMemberNameView"
app:layout_constraintTop_toTopOf="@+id/roomAvatarImageView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/messageMemberNameView"
tools:text="@tools:sample/date/hhmm" />


<TextView
android:id="@+id/roomLastEventView"
android:layout_width="0dp"
android:layout_height="34dp"
android:ellipsize="end"
android:maxLines="2"
android:textColor="@color/black_38"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/roomNameView"
app:layout_constraintTop_toBottomOf="@+id/roomNameView"
tools:text="@tools:sample/lorem/random" />


</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/roomCategoryRootView"
android:layout_width="match_parent"
@ -9,6 +8,7 @@
android:clickable="true"
android:focusable="true"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingLeft="16dp"
android:paddingTop="8dp"
@ -19,49 +19,32 @@

<TextView
android:id="@+id/roomCategoryTitleView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:drawableStart="@drawable/ic_expand_more_white"
android:drawableLeft="@drawable/ic_expand_more_white"
android:drawableTint="@color/bluey_grey_two"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:textColor="@color/bluey_grey_two"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/roomCategoryUnreadCounterBadgeView"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/direct_chats_header" />
tools:text="@string/room_recents_favourites" />

<im.vector.riotredesign.features.home.room.list.UnreadCounterBadgeView
android:id="@+id/roomCategoryUnreadCounterBadgeView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:minWidth="24dp"
android:minHeight="24dp"
android:paddingLeft="4dp"
android:minWidth="16dp"
android:minHeight="16dp"
android:paddingRight="4dp"
android:paddingLeft="4dp"
android:textSize="10sp"
android:textColor="@android:color/white"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/roomCategoryAddButton"
app:layout_constraintTop_toTopOf="parent"
tools:background="@drawable/bg_unread_highlight"
tools:text="4" />

<ImageView
android:id="@+id/roomCategoryAddButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:scaleType="centerInside"
android:src="@drawable/ic_add_circle_white"
android:tint="@color/bluey_grey_two"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
tools:background="@drawable/bg_unread_highlight"
tools:text="24" />


</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@ -2,8 +2,7 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="40dp"
android:layout_height="match_parent"
android:padding="8dp"
tools:parentTag="android.widget.FrameLayout">

@ -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" />


<LinearLayout
android:id="@+id/errorView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical"
android:padding="8dp">

@ -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">

<TextView
android:id="@+id/emptyTitleView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="40dp"
android:gravity="center"
android:textColor="@android:color/black"
android:textSize="16sp" />

<ImageView
android:id="@+id/emptyImageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="40dp" />

<TextView
android:id="@+id/emptyMessageView"
android:layout_width="wrap_content"
@ -60,14 +77,6 @@
android:textColor="@android:color/black"
android:textSize="16sp" />


<ImageView
android:id="@+id/emptyImageView"
android:layout_width="190dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="8dp" />

</LinearLayout>

</merge>

View File

@ -7,6 +7,10 @@
<string name="send_you_invite">"Sent you an invitation"</string>
<string name="invited_by">Invited by %s</string>

<string name="room_list_catchup_empty_title">Youre all caught up!</string>
<string name="room_list_catchup_empty_body">You have no more unread messages</string>
<string name="room_list_catchup_welcome_title">Welcome home!</string>
<string name="room_list_catchup_welcome_body">Catch up on unread messages here</string>

<string name="title_activity_emoji_reaction_picker">Reactions</string>
<string name="reactions_agree">Agree</string>