Room list: rework invitations

This commit is contained in:
Benoit Marty 2019-07-01 16:29:04 +02:00
parent b25098c52d
commit 07309c90e1
7 changed files with 238 additions and 9 deletions

View File

@ -29,7 +29,7 @@ import im.vector.riotredesign.features.themes.ThemeUtils
/**
* Set a text in the TextView, or set visibility to GONE if the text is null
*/
fun TextView.setTextOrHide(newText: String?, hideWhenBlank: Boolean = true) {
fun TextView.setTextOrHide(newText: CharSequence?, hideWhenBlank: Boolean = true) {
if (newText == null
|| (newText.isBlank() && hideWhenBlank)) {
isVisible = false

View File

@ -0,0 +1,64 @@
/*
* 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 android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
import im.vector.riotredesign.core.extensions.setTextOrHide
import im.vector.riotredesign.features.home.AvatarRenderer
@EpoxyModelClass(layout = R.layout.item_room_invitation)
abstract class RoomInvitationItem : VectorEpoxyModel<RoomInvitationItem.Holder>() {
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var roomName: CharSequence
@EpoxyAttribute lateinit var roomId: String
@EpoxyAttribute var secondLine: CharSequence? = null
@EpoxyAttribute var avatarUrl: String? = null
@EpoxyAttribute var listener: (() -> Unit)? = null
@EpoxyAttribute var acceptListener: (() -> Unit)? = null
@EpoxyAttribute var rejectListener: (() -> Unit)? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.rootView.setOnClickListener { listener?.invoke() }
holder.acceptView.setOnClickListener { acceptListener?.invoke() }
holder.rejectView.setOnClickListener { rejectListener?.invoke() }
holder.titleView.text = roomName
holder.subtitleView.setTextOrHide(secondLine)
avatarRenderer.render(avatarUrl, roomId, roomName.toString(), holder.avatarImageView)
}
class Holder : VectorEpoxyHolder() {
val titleView by bind<TextView>(R.id.roomInvitationNameView)
val subtitleView by bind<TextView>(R.id.roomInvitationSubTitle)
val acceptView by bind<Button>(R.id.roomInvitationAccept)
val rejectView by bind<Button>(R.id.roomInvitationReject)
val avatarImageView by bind<ImageView>(R.id.roomInvitationAvatarImageView)
val rootView by bind<ViewGroup>(R.id.itemRoomInvitationLayout)
}
}

View File

@ -45,7 +45,7 @@ data class RoomListParams(
) : Parcelable
class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, OnBackPressed, FabMenuView.Listener {
class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, FabMenuView.Listener {
enum class DisplayMode(@StringRes val titleRes: Int) {
HOME(R.string.bottom_action_home),
@ -135,7 +135,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, O
val stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
epoxyRecyclerView.layoutManager = layoutManager
epoxyRecyclerView.itemAnimator = RoomListAnimator()
roomController.callback = this
roomController.listener = this
roomController.addModelBuildListener { it.dispatchTo(stateRestorer) }
stateView.contentView = epoxyRecyclerView
epoxyRecyclerView.setController(roomController)
@ -233,6 +233,14 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, O
roomListViewModel.accept(RoomListActions.SelectRoom(room))
}
override fun onAcceptRoomInvitation(room: RoomSummary) {
vectorBaseActivity.notImplemented("Accept room invitation")
}
override fun onRejectRoomInvitation(room: RoomSummary) {
vectorBaseActivity.notImplemented("Reject room invitation")
}
override fun onToggleRoomCategory(roomCategory: RoomCategory) {
roomListViewModel.accept(RoomListActions.ToggleCategory(roomCategory))
}

View File

@ -26,7 +26,7 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
private val roomSummaryItemFactory: RoomSummaryItemFactory
) : TypedEpoxyController<RoomListViewState>() {
var callback: Callback? = null
var listener: Listener? = null
override fun buildModels(viewState: RoomListViewState) {
val roomSummaries = viewState.asyncFilteredRooms()
@ -36,7 +36,7 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
} else {
val isExpanded = viewState.isCategoryExpanded(category)
buildRoomCategory(viewState, summaries, category.titleRes, viewState.isCategoryExpanded(category)) {
callback?.onToggleRoomCategory(category)
listener?.onToggleRoomCategory(category)
}
if (isExpanded) {
buildRoomModels(summaries)
@ -76,14 +76,16 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
private fun buildRoomModels(summaries: List<RoomSummary>) {
summaries.forEach { roomSummary ->
roomSummaryItemFactory
.create(roomSummary) { callback?.onRoomSelected(it) }
.create(roomSummary, listener)
.addTo(this)
}
}
interface Callback {
interface Listener {
fun onToggleRoomCategory(roomCategory: RoomCategory)
fun onRoomSelected(room: RoomSummary)
fun onRejectRoomInvitation(room: RoomSummary)
fun onAcceptRoomInvitation(room: RoomSummary)
}
}

View File

@ -18,12 +18,15 @@ package im.vector.riotredesign.features.home.room.list
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
import im.vector.riotredesign.core.extensions.localDateTime
import im.vector.riotredesign.core.resources.ColorProvider
import im.vector.riotredesign.core.resources.DateProvider
import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.features.home.AvatarRenderer
import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
@ -34,9 +37,39 @@ import javax.inject.Inject
class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatter: NoticeEventFormatter,
private val timelineDateFormatter: TimelineDateFormatter,
private val colorProvider: ColorProvider,
private val stringProvider: StringProvider,
private val avatarRenderer: AvatarRenderer) {
fun create(roomSummary: RoomSummary, onRoomSelected: (RoomSummary) -> Unit): RoomSummaryItem {
fun create(roomSummary: RoomSummary, listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
return when (roomSummary.membership) {
Membership.INVITE -> createInvitationItem(roomSummary, listener)
else -> createRoomItem(roomSummary, listener)
}
}
private fun createInvitationItem(roomSummary: RoomSummary, listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
val secondLine = if (roomSummary.isDirect) {
roomSummary.latestEvent?.root?.senderId
} else {
roomSummary.latestEvent?.root?.senderId?.let {
stringProvider.getString(R.string.invited_by, it)
}
}
return RoomInvitationItem_()
.id(roomSummary.roomId)
.avatarRenderer(avatarRenderer)
.roomId(roomSummary.roomId)
.secondLine(secondLine)
.acceptListener { listener?.onAcceptRoomInvitation(roomSummary) }
.rejectListener { listener?.onRejectRoomInvitation(roomSummary) }
.roomName(roomSummary.displayName)
.avatarUrl(roomSummary.avatarUrl)
.listener { listener?.onRoomSelected(roomSummary) }
}
private fun createRoomItem(roomSummary: RoomSummary, listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
val unreadCount = roomSummary.notificationCount
val showHighlighted = roomSummary.highlightCount > 0
@ -84,6 +117,7 @@ class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatte
.avatarUrl(roomSummary.avatarUrl)
.showHighlighted(showHighlighted)
.unreadCount(unreadCount)
.listener { onRoomSelected(roomSummary) }
.listener { listener?.onRoomSelected(roomSummary) }
}
}

View File

@ -0,0 +1,111 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/itemRoomInvitationLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?riotx_background"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground">
<ImageView
android:id="@+id/roomInvitationAvatarImageView"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="12dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
<!-- Margin bottom does not work, so I use space -->
<Space
android:id="@+id/roomInvitationAvatarBottomSpace"
android:layout_width="0dp"
android:layout_height="12dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/roomInvitationAvatarImageView"
tools:layout_marginStart="20dp" />
<TextView
android:id="@+id/roomInvitationNameView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginLeft="@dimen/layout_horizontal_margin"
android:layout_marginTop="12dp"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
android:drawableEnd="@drawable/ic_arrow_right"
android:drawablePadding="8dp"
android:duplicateParentState="true"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?riotx_text_primary"
android:textSize="15sp"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/roomInvitationAvatarImageView"
app:layout_constraintTop_toTopOf="parent"
tools:text="@sample/matrix.json/data/displayName" />
<TextView
android:id="@+id/roomInvitationSubTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:ellipsize="end"
android:maxLines="2"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="@+id/roomInvitationNameView"
app:layout_constraintStart_toStartOf="@+id/roomInvitationNameView"
app:layout_constraintTop_toBottomOf="@+id/roomInvitationNameView"
tools:text="@sample/matrix.json/data/message" />
<!-- Margin bottom does not work, so I use space -->
<Space
android:id="@+id/roomLastEventBottomSpace"
android:layout_width="0dp"
android:layout_height="7dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/roomInvitationSubTitle"
tools:layout_marginStart="120dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/roomInvitationAccept"
style="@style/VectorButtonStyle"
android:layout_marginTop="8dp"
android:minWidth="122dp"
android:text="@string/accept"
app:layout_constraintEnd_toEndOf="@+id/roomInvitationNameView"
app:layout_constraintTop_toBottomOf="@+id/roomLastEventBottomSpace" />
<com.google.android.material.button.MaterialButton
android:id="@+id/roomInvitationReject"
style="@style/VectorButtonStyleOutlined"
android:layout_marginEnd="@dimen/layout_vertical_margin"
android:minWidth="122dp"
android:text="@string/reject"
android:textAllCaps="true"
android:textColor="?riotx_text_primary"
app:layout_constraintEnd_toStartOf="@+id/roomInvitationAccept"
app:layout_constraintTop_toTopOf="@+id/roomInvitationAccept" />
<View
android:id="@+id/roomInvitationDividerView"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginTop="5dp"
android:background="?riotx_header_panel_border_mobile"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/roomInvitationAccept" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -142,6 +142,16 @@
<item name="colorControlHighlight">?colorAccent</item>
</style>
<style name="VectorButtonStyleOutlined" parent="Widget.MaterialComponents.Button.OutlinedButton">
<item name="android:textStyle">bold</item>
<item name="android:textAllCaps">false</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:background">@null</item>
<!--item name="android:textColor">?colorAccent</item-->
<item name="colorControlHighlight">?colorAccent</item>
</style>
<style name="AlerterButton" parent="Widget.AppCompat.Button.Borderless.Colored">
<item name="colorAccent">@android:color/white</item>
<item name="android:textColor">@android:color/white</item>