Home: continue room list rework.
This commit is contained in:
parent
c0fd06fd2d
commit
eb2344a43f
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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">You’re 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>
|
||||
|
|
Loading…
Reference in New Issue