Merge pull request #5860 from vector-im/feature/eric/replace-search-room-subheader

Replaces subtitle in Search Rooms with room context rather than last event
This commit is contained in:
Eric Decanini 2022-05-30 11:13:10 +02:00 committed by GitHub
commit eeb7d60e59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 504 additions and 88 deletions

1
changelog.d/5860.feature Normal file
View File

@ -0,0 +1 @@
Adds space or user id as a subtitle under rooms in search

View File

@ -62,6 +62,7 @@ data class RoomSummary(
val roomType: String? = null,
val spaceParents: List<SpaceParentInfo>? = null,
val spaceChildren: List<SpaceChildInfo>? = null,
val flattenParents: List<RoomSummary> = emptyList(),
val flattenParentIds: List<String> = emptyList(),
val roomEncryptionAlgorithm: RoomEncryptionAlgorithm? = null
) {

View File

@ -127,7 +127,7 @@ internal class DefaultRoomService @Inject constructor(
override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
pagedListConfig: PagedList.Config,
sortOrder: RoomSortOrder): UpdatableLivePageResult {
return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder)
return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder, getFlattenedParents = true)
}
override fun getRoomCountLive(queryParams: RoomSummaryQueryParams): LiveData<Int> {

View File

@ -43,7 +43,6 @@ internal class RoomChildRelationInfo(
data class SpaceChildInfo(
val roomId: String,
val order: String?,
// val autoJoin: Boolean,
val viaServers: List<String>
)
@ -60,18 +59,13 @@ internal class RoomChildRelationInfo(
fun getDirectChildrenDescriptions(): List<SpaceChildInfo> {
return CurrentStateEventEntity.whereType(realm, roomId, EventType.STATE_SPACE_CHILD)
.findAll()
// .also {
// Timber.v("## Space: Found ${it.count()} m.space.child state events for $roomId")
// }
.mapNotNull {
ContentMapper.map(it.root?.content).toModel<SpaceChildContent>()?.let { scc ->
// Timber.v("## Space child desc state event $scc")
// Children where via is not present are ignored.
scc.via?.let { via ->
SpaceChildInfo(
roomId = it.stateKey,
order = scc.validOrder(),
// autoJoin = scc.autoJoin ?: false,
viaServers = via
)
}
@ -83,17 +77,13 @@ internal class RoomChildRelationInfo(
fun getParentDescriptions(): List<SpaceParentInfo> {
return CurrentStateEventEntity.whereType(realm, roomId, EventType.STATE_SPACE_PARENT)
.findAll()
// .also {
// Timber.v("## Space: Found ${it.count()} m.space.parent state events for $roomId")
// }
.mapNotNull {
ContentMapper.map(it.root?.content).toModel<SpaceParentContent>()?.let { scc ->
// Timber.v("## Space parent desc state event $scc")
ContentMapper.map(it.root?.content).toModel<SpaceParentContent>()?.let { spaceParentContent ->
// Parent where via is not present are ignored.
scc.via?.let { via ->
spaceParentContent.via?.let { via ->
SpaceParentInfo(
roomId = it.stateKey,
canonical = scc.canonical ?: false,
canonical = spaceParentContent.canonical ?: false,
viaServers = via,
stateEventSender = it.root?.sender ?: ""
)

View File

@ -187,13 +187,14 @@ internal class RoomSummaryDataSource @Inject constructor(
fun getUpdatablePagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
pagedListConfig: PagedList.Config,
sortOrder: RoomSortOrder): UpdatableLivePageResult {
sortOrder: RoomSortOrder,
getFlattenedParents: Boolean = false): UpdatableLivePageResult {
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
roomSummariesQuery(realm, queryParams).process(sortOrder)
}
val dataSourceFactory = realmDataSourceFactory.map {
roomSummaryMapper.map(it)
}
}.map { if (getFlattenedParents) it.getWithParents() else it }
val boundaries = MutableLiveData(ResultBoundaries())
@ -232,6 +233,13 @@ internal class RoomSummaryDataSource @Inject constructor(
}
}
private fun RoomSummary.getWithParents(): RoomSummary {
val parents = flattenParentIds.mapNotNull { parentId ->
getRoomSummary(parentId)
}
return copy(flattenParents = parents)
}
fun getCountLive(queryParams: RoomSummaryQueryParams): LiveData<Int> {
val liveRooms = monarchy.findAllManagedWithChanges {
roomSummariesQuery(it, queryParams)

View File

@ -68,7 +68,7 @@ abstract class CollapsableTypedEpoxyController<T> :
}
override fun buildModels() {
check(isBuildingModels()) {
check(isBuildingModels) {
("You cannot call `buildModels` directly. Call `setData` instead to trigger a model " +
"refresh with new data.")
}

View File

@ -211,7 +211,7 @@ class RoomListFragment @Inject constructor(
RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.isVisible = true
RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.isVisible = true
RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.isVisible = true
else -> Unit // No button in this mode
RoomListDisplayMode.FILTERED -> Unit // No button in this mode
}
views.createChatRoomButton.debouncedClicks {
@ -237,7 +237,7 @@ class RoomListFragment @Inject constructor(
RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.hide()
RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.hide()
RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.hide()
else -> Unit
RoomListDisplayMode.FILTERED -> Unit
}
}
}
@ -294,7 +294,7 @@ class RoomListFragment @Inject constructor(
val contentAdapter =
when {
section.livePages != null -> {
pagedControllerFactory.createRoomSummaryPagedController()
pagedControllerFactory.createRoomSummaryPagedController(roomListParams.displayMode)
.also { controller ->
section.livePages.observe(viewLifecycleOwner) { pl ->
controller.submitList(pl)
@ -316,7 +316,7 @@ class RoomListFragment @Inject constructor(
)
}
}
section.isExpanded.observe(viewLifecycleOwner) { _ ->
section.isExpanded.observe(viewLifecycleOwner) {
refreshCollapseStates()
}
controller.listener = this
@ -337,14 +337,14 @@ class RoomListFragment @Inject constructor(
checkEmptyState()
}
observeItemCount(section, sectionAdapter)
section.isExpanded.observe(viewLifecycleOwner) { _ ->
section.isExpanded.observe(viewLifecycleOwner) {
refreshCollapseStates()
}
controller.listener = this
}
}
else -> {
pagedControllerFactory.createRoomSummaryListController()
pagedControllerFactory.createRoomSummaryListController(roomListParams.displayMode)
.also { controller ->
section.liveList?.observe(viewLifecycleOwner) { list ->
controller.setData(list)
@ -366,7 +366,7 @@ class RoomListFragment @Inject constructor(
)
}
}
section.isExpanded.observe(viewLifecycleOwner) { _ ->
section.isExpanded.observe(viewLifecycleOwner) {
refreshCollapseStates()
}
controller.listener = this
@ -402,7 +402,7 @@ class RoomListFragment @Inject constructor(
RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.show()
RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.show()
RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.show()
else -> Unit
RoomListDisplayMode.FILTERED -> Unit
}
}
}
@ -498,7 +498,7 @@ class RoomListFragment @Inject constructor(
isBigImage = true,
message = getString(R.string.room_list_rooms_empty_body)
)
else ->
RoomListDisplayMode.FILTERED ->
// Always display the content in this mode, because if the footer
StateView.State.Content
}

View File

@ -323,9 +323,9 @@ class RoomListSectionBuilderSpace(
{
it.memberships = Membership.activeMemberships()
},
{ qpm ->
{ queryParams ->
val name = stringProvider.getString(R.string.bottom_action_rooms)
val updatableFilterLivePageResult = session.roomService().getFilteredPagedRoomSummariesLive(qpm)
val updatableFilterLivePageResult = session.roomService().getFilteredPagedRoomSummariesLive(queryParams)
onUpdatable(updatableFilterLivePageResult)
val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow()

View File

@ -18,9 +18,9 @@ package im.vector.app.features.home.room.list
import android.view.HapticFeedbackConstants
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
@ -36,6 +36,7 @@ import im.vector.app.core.ui.views.PresenceStateImageView
import im.vector.app.core.ui.views.ShieldImageView
import im.vector.app.features.displayname.getBestName
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.themes.ThemeUtils
import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
@ -45,48 +46,102 @@ import org.matrix.android.sdk.api.util.MatrixItem
@EpoxyModelClass(layout = R.layout.item_room)
abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
@EpoxyAttribute lateinit var typingMessage: String
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute
lateinit var typingMessage: String
@EpoxyAttribute lateinit var lastFormattedEvent: EpoxyCharSequence
@EpoxyAttribute lateinit var lastEventTime: String
@EpoxyAttribute var encryptionTrustLevel: RoomEncryptionTrustLevel? = null
@EpoxyAttribute var userPresence: UserPresence? = null
@EpoxyAttribute var showPresence: Boolean = false
@EpoxyAttribute var izPublic: Boolean = false
@EpoxyAttribute var unreadNotificationCount: Int = 0
@EpoxyAttribute var hasUnreadMessage: Boolean = false
@EpoxyAttribute var hasDraft: Boolean = false
@EpoxyAttribute var showHighlighted: Boolean = false
@EpoxyAttribute var hasFailedSending: Boolean = false
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemLongClickListener: View.OnLongClickListener? = null
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: ClickListener? = null
@EpoxyAttribute var showSelected: Boolean = false
@EpoxyAttribute
lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute
lateinit var matrixItem: MatrixItem
@EpoxyAttribute
var displayMode: RoomListDisplayMode = RoomListDisplayMode.PEOPLE
@EpoxyAttribute
lateinit var subtitle: String
@EpoxyAttribute
lateinit var lastFormattedEvent: EpoxyCharSequence
@EpoxyAttribute
lateinit var lastEventTime: String
@EpoxyAttribute
var encryptionTrustLevel: RoomEncryptionTrustLevel? = null
@EpoxyAttribute
var userPresence: UserPresence? = null
@EpoxyAttribute
var showPresence: Boolean = false
@EpoxyAttribute @JvmField
var isPublic: Boolean = false
@EpoxyAttribute
var unreadNotificationCount: Int = 0
@EpoxyAttribute
var hasUnreadMessage: Boolean = false
@EpoxyAttribute
var hasDraft: Boolean = false
@EpoxyAttribute
var showHighlighted: Boolean = false
@EpoxyAttribute
var hasFailedSending: Boolean = false
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var itemLongClickListener: View.OnLongClickListener? = null
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var itemClickListener: ClickListener? = null
@EpoxyAttribute
var showSelected: Boolean = false
override fun bind(holder: Holder) {
super.bind(holder)
renderDisplayMode(holder)
holder.rootView.onClick(itemClickListener)
holder.rootView.setOnLongClickListener {
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
itemLongClickListener?.onLongClick(it) ?: false
}
holder.titleView.text = matrixItem.getBestName()
holder.lastEventTimeView.text = lastEventTime
holder.lastEventView.text = lastFormattedEvent.charSequence
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted))
holder.unreadIndentIndicator.isVisible = hasUnreadMessage
holder.draftView.isVisible = hasDraft
avatarRenderer.render(matrixItem, holder.avatarImageView)
holder.roomAvatarDecorationImageView.render(encryptionTrustLevel)
holder.roomAvatarPublicDecorationImageView.isVisible = izPublic
holder.roomAvatarPublicDecorationImageView.isVisible = isPublic
holder.roomAvatarFailSendingImageView.isVisible = hasFailedSending
renderSelection(holder, showSelected)
holder.typingView.setTextOrHide(typingMessage)
holder.lastEventView.isInvisible = holder.typingView.isVisible
holder.roomAvatarPresenceImageView.render(showPresence, userPresence)
}
private fun renderDisplayMode(holder: Holder) = when (displayMode) {
RoomListDisplayMode.ROOMS,
RoomListDisplayMode.PEOPLE,
RoomListDisplayMode.NOTIFICATIONS -> renderForDefaultDisplayMode(holder)
RoomListDisplayMode.FILTERED -> renderForFilteredDisplayMode(holder)
}
private fun renderForDefaultDisplayMode(holder: Holder) {
holder.subtitleView.text = lastFormattedEvent.charSequence
holder.lastEventTimeView.text = lastEventTime
holder.typingView.setTextOrHide(typingMessage)
holder.subtitleView.isInvisible = holder.typingView.isVisible
}
private fun renderForFilteredDisplayMode(holder: Holder) {
holder.subtitleView.text = subtitle
}
override fun unbind(holder: Holder) {
holder.rootView.setOnClickListener(null)
holder.rootView.setOnLongClickListener(null)
@ -110,7 +165,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
val titleView by bind<TextView>(R.id.roomNameView)
val unreadCounterBadgeView by bind<UnreadCounterBadgeView>(R.id.roomUnreadCounterBadgeView)
val unreadIndentIndicator by bind<View>(R.id.roomUnreadIndicator)
val lastEventView by bind<TextView>(R.id.roomLastEventView)
val subtitleView by bind<TextView>(R.id.subtitleView)
val typingView by bind<TextView>(R.id.roomTypingView)
val draftView by bind<ImageView>(R.id.roomDraftBadge)
val lastEventTimeView by bind<TextView>(R.id.roomLastEventTimeView)
@ -120,6 +175,6 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
val roomAvatarPublicDecorationImageView by bind<ImageView>(R.id.roomAvatarPublicDecorationImageView)
val roomAvatarFailSendingImageView by bind<ImageView>(R.id.roomAvatarFailSendingImageView)
val roomAvatarPresenceImageView by bind<PresenceStateImageView>(R.id.roomAvatarPresenceImageView)
val rootView by bind<ViewGroup>(R.id.itemRoomLayout)
val rootView by bind<ConstraintLayout>(R.id.itemRoomLayout)
}
}

View File

@ -0,0 +1,134 @@
/*
* 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.app.features.home.room.list
import android.view.HapticFeedbackConstants
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.amulyakhare.textdrawable.TextDrawable
import im.vector.app.R
import im.vector.app.core.epoxy.ClickListener
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.epoxy.onClick
import im.vector.app.core.ui.views.PresenceStateImageView
import im.vector.app.core.ui.views.ShieldImageView
import im.vector.app.features.displayname.getBestName
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.themes.ThemeUtils
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
import org.matrix.android.sdk.api.session.presence.model.UserPresence
import org.matrix.android.sdk.api.util.MatrixItem
@EpoxyModelClass(layout = R.layout.item_room_centered)
abstract class RoomSummaryItemCentered : VectorEpoxyModel<RoomSummaryItemCentered.Holder>() {
@EpoxyAttribute
lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute
lateinit var matrixItem: MatrixItem
@EpoxyAttribute
var displayMode: RoomListDisplayMode = RoomListDisplayMode.PEOPLE
@EpoxyAttribute
var encryptionTrustLevel: RoomEncryptionTrustLevel? = null
@EpoxyAttribute
var userPresence: UserPresence? = null
@EpoxyAttribute
var showPresence: Boolean = false
@EpoxyAttribute @JvmField
var isPublic: Boolean = false
@EpoxyAttribute
var unreadNotificationCount: Int = 0
@EpoxyAttribute
var hasUnreadMessage: Boolean = false
@EpoxyAttribute
var hasDraft: Boolean = false
@EpoxyAttribute
var hasFailedSending: Boolean = false
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var itemLongClickListener: View.OnLongClickListener? = null
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var itemClickListener: ClickListener? = null
@EpoxyAttribute
var showSelected: Boolean = false
override fun bind(holder: Holder) {
super.bind(holder)
holder.rootView.onClick(itemClickListener)
holder.rootView.setOnLongClickListener {
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
itemLongClickListener?.onLongClick(it) ?: false
}
holder.titleView.text = matrixItem.getBestName()
avatarRenderer.render(matrixItem, holder.avatarImageView)
holder.roomAvatarDecorationImageView.render(encryptionTrustLevel)
holder.roomAvatarPublicDecorationImageView.isVisible = isPublic
holder.roomAvatarFailSendingImageView.isVisible = hasFailedSending
renderSelection(holder, showSelected)
holder.roomAvatarPresenceImageView.render(showPresence, userPresence)
}
override fun unbind(holder: Holder) {
holder.rootView.setOnClickListener(null)
holder.rootView.setOnLongClickListener(null)
avatarRenderer.clear(holder.avatarImageView)
super.unbind(holder)
}
private fun renderSelection(holder: Holder, isSelected: Boolean) {
if (isSelected) {
holder.avatarCheckedImageView.visibility = View.VISIBLE
val backgroundColor = ThemeUtils.getColor(holder.view.context, R.attr.colorPrimary)
val backgroundDrawable = TextDrawable.builder().buildRound("", backgroundColor)
holder.avatarImageView.setImageDrawable(backgroundDrawable)
} else {
holder.avatarCheckedImageView.visibility = View.GONE
avatarRenderer.render(matrixItem, holder.avatarImageView)
}
}
class Holder : VectorEpoxyHolder() {
val titleView by bind<TextView>(R.id.roomNameView)
val avatarCheckedImageView by bind<ImageView>(R.id.roomAvatarCheckedImageView)
val avatarImageView by bind<ImageView>(R.id.roomAvatarImageView)
val roomAvatarDecorationImageView by bind<ShieldImageView>(R.id.roomAvatarDecorationImageView)
val roomAvatarPublicDecorationImageView by bind<ImageView>(R.id.roomAvatarPublicDecorationImageView)
val roomAvatarFailSendingImageView by bind<ImageView>(R.id.roomAvatarFailSendingImageView)
val roomAvatarPresenceImageView by bind<PresenceStateImageView>(R.id.roomAvatarPresenceImageView)
val rootView by bind<ConstraintLayout>(R.id.itemRoomLayout)
}
}

View File

@ -26,6 +26,7 @@ import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
import im.vector.app.features.home.room.typing.TypingHelper
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
@ -46,13 +47,16 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
fun create(roomSummary: RoomSummary,
roomChangeMembershipStates: Map<String, ChangeMembershipState>,
selectedRoomIds: Set<String>,
displayMode: RoomListDisplayMode,
listener: RoomListListener?): VectorEpoxyModel<*> {
return when (roomSummary.membership) {
Membership.INVITE -> {
val changeMembershipState = roomChangeMembershipStates[roomSummary.roomId] ?: ChangeMembershipState.Unknown
createInvitationItem(roomSummary, changeMembershipState, listener)
}
else -> createRoomItem(roomSummary, selectedRoomIds, listener?.let { it::onRoomClicked }, listener?.let { it::onRoomLongClicked })
else -> createRoomItem(
roomSummary, selectedRoomIds, displayMode, listener?.let { it::onRoomClicked }, listener?.let { it::onRoomLongClicked }
)
}
}
@ -105,9 +109,11 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
fun createRoomItem(
roomSummary: RoomSummary,
selectedRoomIds: Set<String>,
displayMode: RoomListDisplayMode,
onClick: ((RoomSummary) -> Unit)?,
onLongClick: ((RoomSummary) -> Boolean)?
): VectorEpoxyModel<*> {
val subtitle = getSearchResultSubtitle(roomSummary)
val unreadCount = roomSummary.notificationCount
val showHighlighted = roomSummary.highlightCount > 0
val showSelected = selectedRoomIds.contains(roomSummary.roomId)
@ -118,28 +124,84 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
latestFormattedEvent = displayableEventFormatter.format(latestEvent, roomSummary.isDirect, roomSummary.isDirect.not())
latestEventTime = dateFormatter.format(latestEvent.root.originServerTs, DateFormatKind.ROOM_LIST)
}
val typingMessage = typingHelper.getTypingMessage(roomSummary.typingUsers)
return RoomSummaryItem_()
.id(roomSummary.roomId)
.avatarRenderer(avatarRenderer)
// We do not display shield in the room list anymore
// .encryptionTrustLevel(roomSummary.roomEncryptionTrustLevel)
.izPublic(roomSummary.isPublic)
.showPresence(roomSummary.isDirect)
.userPresence(roomSummary.directUserPresence)
.matrixItem(roomSummary.toMatrixItem())
.lastEventTime(latestEventTime)
.typingMessage(typingMessage)
.lastFormattedEvent(latestFormattedEvent.toEpoxyCharSequence())
.showHighlighted(showHighlighted)
.showSelected(showSelected)
.hasFailedSending(roomSummary.hasFailedSending)
.unreadNotificationCount(unreadCount)
.hasUnreadMessage(roomSummary.hasUnreadMessages)
.hasDraft(roomSummary.userDrafts.isNotEmpty())
.itemLongClickListener { _ ->
onLongClick?.invoke(roomSummary) ?: false
}
.itemClickListener { onClick?.invoke(roomSummary) }
return if (subtitle.isBlank() && displayMode == RoomListDisplayMode.FILTERED) {
createCenteredRoomSummaryItem(roomSummary, displayMode, showSelected, unreadCount, onClick, onLongClick)
} else {
createRoomSummaryItem(
roomSummary, displayMode, subtitle, latestEventTime, typingMessage,
latestFormattedEvent, showHighlighted, showSelected, unreadCount, onClick, onLongClick
)
}
}
private fun createRoomSummaryItem(
roomSummary: RoomSummary,
displayMode: RoomListDisplayMode,
subtitle: String,
latestEventTime: String,
typingMessage: String,
latestFormattedEvent: CharSequence,
showHighlighted: Boolean,
showSelected: Boolean,
unreadCount: Int,
onClick: ((RoomSummary) -> Unit)?,
onLongClick: ((RoomSummary) -> Boolean)?
) = RoomSummaryItem_()
.id(roomSummary.roomId)
.avatarRenderer(avatarRenderer)
// We do not display shield in the room list anymore
// .encryptionTrustLevel(roomSummary.roomEncryptionTrustLevel)
.displayMode(displayMode)
.subtitle(subtitle)
.isPublic(roomSummary.isPublic)
.showPresence(roomSummary.isDirect)
.userPresence(roomSummary.directUserPresence)
.matrixItem(roomSummary.toMatrixItem())
.lastEventTime(latestEventTime)
.typingMessage(typingMessage)
.lastFormattedEvent(latestFormattedEvent.toEpoxyCharSequence())
.showHighlighted(showHighlighted)
.showSelected(showSelected)
.hasFailedSending(roomSummary.hasFailedSending)
.unreadNotificationCount(unreadCount)
.hasUnreadMessage(roomSummary.hasUnreadMessages)
.hasDraft(roomSummary.userDrafts.isNotEmpty())
.itemLongClickListener { _ -> onLongClick?.invoke(roomSummary) ?: false }
.itemClickListener { onClick?.invoke(roomSummary) }
private fun createCenteredRoomSummaryItem(
roomSummary: RoomSummary,
displayMode: RoomListDisplayMode,
showSelected: Boolean,
unreadCount: Int,
onClick: ((RoomSummary) -> Unit)?,
onLongClick: ((RoomSummary) -> Boolean)?
) = RoomSummaryItemCentered_()
.id(roomSummary.roomId)
.avatarRenderer(avatarRenderer)
// We do not display shield in the room list anymore
// .encryptionTrustLevel(roomSummary.roomEncryptionTrustLevel)
.displayMode(displayMode)
.isPublic(roomSummary.isPublic)
.showPresence(roomSummary.isDirect)
.userPresence(roomSummary.directUserPresence)
.matrixItem(roomSummary.toMatrixItem())
.showSelected(showSelected)
.hasFailedSending(roomSummary.hasFailedSending)
.unreadNotificationCount(unreadCount)
.hasUnreadMessage(roomSummary.hasUnreadMessages)
.hasDraft(roomSummary.userDrafts.isNotEmpty())
.itemLongClickListener { _ -> onLongClick?.invoke(roomSummary) ?: false }
.itemClickListener { onClick?.invoke(roomSummary) }
private fun getSearchResultSubtitle(roomSummary: RoomSummary): String {
val userId = roomSummary.directUserId
val spaceName = roomSummary.spaceParents?.firstOrNull()?.roomSummary?.name
val canonicalAlias = roomSummary.canonicalAlias
return (userId ?: spaceName ?: canonicalAlias).orEmpty()
}
}

View File

@ -16,17 +16,19 @@
package im.vector.app.features.home.room.list
import im.vector.app.features.home.RoomListDisplayMode
import org.matrix.android.sdk.api.session.room.model.RoomSummary
class RoomSummaryListController(
private val roomSummaryItemFactory: RoomSummaryItemFactory
private val roomSummaryItemFactory: RoomSummaryItemFactory,
private val displayMode: RoomListDisplayMode
) : CollapsableTypedEpoxyController<List<RoomSummary>>() {
var listener: RoomListListener? = null
override fun buildModels(data: List<RoomSummary>?) {
data?.forEach {
add(roomSummaryItemFactory.create(it, emptyMap(), emptySet(), listener))
add(roomSummaryItemFactory.create(it, emptyMap(), emptySet(), displayMode, listener))
}
}
}

View File

@ -19,11 +19,13 @@ package im.vector.app.features.home.room.list
import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.paging.PagedListEpoxyController
import im.vector.app.core.utils.createUIHandler
import im.vector.app.features.home.RoomListDisplayMode
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.RoomSummary
class RoomSummaryPagedController(
private val roomSummaryItemFactory: RoomSummaryItemFactory
private val roomSummaryItemFactory: RoomSummaryItemFactory,
private val displayMode: RoomListDisplayMode
) : PagedListEpoxyController<RoomSummary>(
// Important it must match the PageList builder notify Looper
modelBuildingHandler = createUIHandler()
@ -57,6 +59,6 @@ class RoomSummaryPagedController(
override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> {
// for place holder if enabled
item ?: return RoomSummaryItemPlaceHolder_().apply { id(currentPosition) }
return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), listener)
return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), displayMode, listener)
}
}

View File

@ -16,18 +16,19 @@
package im.vector.app.features.home.room.list
import im.vector.app.features.home.RoomListDisplayMode
import javax.inject.Inject
class RoomSummaryPagedControllerFactory @Inject constructor(
private val roomSummaryItemFactory: RoomSummaryItemFactory
) {
fun createRoomSummaryPagedController(): RoomSummaryPagedController {
return RoomSummaryPagedController(roomSummaryItemFactory)
fun createRoomSummaryPagedController(displayMode: RoomListDisplayMode): RoomSummaryPagedController {
return RoomSummaryPagedController(roomSummaryItemFactory, displayMode)
}
fun createRoomSummaryListController(): RoomSummaryListController {
return RoomSummaryListController(roomSummaryItemFactory)
fun createRoomSummaryListController(displayMode: RoomListDisplayMode): RoomSummaryListController {
return RoomSummaryListController(roomSummaryItemFactory, displayMode)
}
fun createSuggestedRoomListController(): SuggestedRoomListController {

View File

@ -22,6 +22,7 @@ import im.vector.app.R
import im.vector.app.core.epoxy.loadingItem
import im.vector.app.core.epoxy.noResultItem
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.home.room.list.RoomSummaryItemFactory
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import javax.inject.Inject
@ -53,7 +54,13 @@ class IncomingShareController @Inject constructor(private val roomSummaryItemFac
} else {
roomSummaries.forEach { roomSummary ->
roomSummaryItemFactory
.createRoomItem(roomSummary, data.selectedRoomIds, callback?.let { it::onRoomClicked }, callback?.let { it::onRoomLongClicked })
.createRoomItem(
roomSummary,
data.selectedRoomIds,
RoomListDisplayMode.FILTERED,
callback?.let { it::onRoomClicked },
callback?.let { it::onRoomLongClicked }
)
.addTo(this)
}
}

View File

@ -183,7 +183,7 @@
tools:text="@tools:sample/date/hhmm" />
<TextView
android:id="@+id/roomLastEventView"
android:id="@+id/subtitleView"
style="@style/Widget.Vector.TextView.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
@ -213,7 +213,8 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/roomNameView"
app:layout_constraintTop_toBottomOf="@id/roomNameView"
tools:text="Alice is typing…" />
tools:text="Alice is typing…"
tools:visibility="gone" />
<!-- Margin bottom does not work, so I use space -->
<Space
@ -221,7 +222,7 @@
android:layout_width="0dp"
android:layout_height="7dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomLastEventView"
app:layout_constraintTop_toBottomOf="@id/subtitleView"
tools:layout_marginStart="120dp" />
<androidx.constraintlayout.widget.Barrier

View File

@ -0,0 +1,152 @@
<?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/itemRoomLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:colorBackground"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
tools:viewBindingIgnore="true">
<FrameLayout
android:id="@+id/roomAvatarContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="12dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/roomAvatarImageView"
android:layout_width="56dp"
android:layout_height="56dp"
android:importantForAccessibility="no"
tools:src="@sample/room_round_avatars" />
<ImageView
android:id="@+id/roomAvatarCheckedImageView"
android:layout_width="56dp"
android:layout_height="56dp"
android:contentDescription="@string/a11y_checked"
android:scaleType="centerInside"
android:src="@drawable/ic_material_done"
app:tint="@android:color/white"
tools:ignore="MissingPrefix" />
</FrameLayout>
<ImageView
android:id="@+id/roomAvatarFailSendingImageView"
android:layout_width="16dp"
android:layout_height="16dp"
android:contentDescription="@string/a11y_error_some_message_not_sent"
android:src="@drawable/ic_warning_badge"
app:layout_constraintCircle="@id/roomAvatarContainer"
app:layout_constraintCircleAngle="45"
app:layout_constraintCircleRadius="30dp"
tools:ignore="MissingConstraints" />
<!-- Note: this is always gone now -->
<im.vector.app.core.ui.views.ShieldImageView
android:id="@+id/roomAvatarDecorationImageView"
android:layout_width="24dp"
android:layout_height="24dp"
app:layout_constraintCircle="@id/roomAvatarContainer"
app:layout_constraintCircleAngle="135"
app:layout_constraintCircleRadius="28dp"
tools:ignore="MissingConstraints"
tools:visibility="gone" />
<ImageView
android:id="@+id/roomAvatarPublicDecorationImageView"
android:layout_width="20dp"
android:layout_height="20dp"
android:background="@drawable/background_circle"
android:contentDescription="@string/a11y_public_room"
android:padding="2dp"
android:src="@drawable/ic_public_room"
android:visibility="gone"
app:layout_constraintCircle="@id/roomAvatarContainer"
app:layout_constraintCircleAngle="135"
app:layout_constraintCircleRadius="28dp"
tools:ignore="MissingConstraints"
tools:visibility="visible" />
<im.vector.app.core.ui.views.PresenceStateImageView
android:id="@+id/roomAvatarPresenceImageView"
android:layout_width="16dp"
android:layout_height="16dp"
android:background="@drawable/background_circle"
android:importantForAccessibility="no"
android:padding="2dp"
android:visibility="gone"
app:layout_constraintCircle="@id/roomAvatarContainer"
app:layout_constraintCircleAngle="135"
app:layout_constraintCircleRadius="28dp"
tools:ignore="MissingConstraints"
tools:layout_constraintCircleRadius="8dp"
tools:src="@drawable/ic_presence_offline"
tools:visibility="visible" />
<!-- Margin bottom does not work, so I use space -->
<Space
android:id="@+id/roomAvatarBottomSpace"
android:layout_width="0dp"
android:layout_height="12dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomAvatarContainer"
tools:layout_marginStart="20dp" />
<TextView
android:id="@+id/roomNameView"
style="@style/Widget.Vector.TextView.Subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginEnd="8dp"
android:duplicateParentState="true"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?vctr_content_primary"
android:textStyle="bold"
app:layout_constrainedWidth="true"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/roomAvatarContainer"
app:layout_constraintTop_toTopOf="@id/roomAvatarContainer"
app:layout_constraintBottom_toBottomOf="@id/roomAvatarContainer"
tools:text="@sample/users.json/data/displayName" />
<TextView
android:id="@+id/roomTypingView"
style="@style/Widget.Vector.TextView.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:maxLines="2"
android:textAlignment="viewStart"
android:textColor="?colorPrimary"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/roomNameView"
app:layout_constraintTop_toBottomOf="@id/roomNameView"
tools:text="Alice is typing…"
tools:visibility="gone" />
<!-- We use vctr_list_separator_system here for a better rendering -->
<View
android:id="@+id/roomDividerView"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="?vctr_list_separator_system"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>