diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt index ba34e0e01d..b6b0555433 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt @@ -53,7 +53,8 @@ data class RoomSummary constructor( val typingUsers: List<SenderInfo>, val inviterId: String? = null, val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS, - val roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null + val roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null, + val hasFailedSending: Boolean = false ) { val isVersioned: Boolean diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt index fb155316ee..cdd38ad2f6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt @@ -62,7 +62,8 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa encryptionEventTs = roomSummaryEntity.encryptionEventTs, breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex, roomEncryptionTrustLevel = roomSummaryEntity.roomEncryptionTrustLevel, - inviterId = roomSummaryEntity.inviterId + inviterId = roomSummaryEntity.inviterId, + hasFailedSending = roomSummaryEntity.hasFailedSending ) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt index acfd484deb..eb49844ed6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt @@ -51,7 +51,8 @@ internal open class RoomSummaryEntity( var isEncrypted: Boolean = false, var encryptionEventTs: Long? = 0, var roomEncryptionTrustLevelStr: String? = null, - var inviterId: String? = null + var inviterId: String? = null, + var hasFailedSending: Boolean = false ) : RealmObject() { private var membershipStr: String = Membership.NONE.name diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt index fb1cc8136a..db1b01fd7a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt @@ -93,9 +93,12 @@ internal fun TimelineEventEntity.Companion.findAllInRoomWithSendStates(realm: Re roomId: String, sendStates: List<SendState>) : RealmResults<TimelineEventEntity> { - val sendStatesStr = sendStates.map { it.name }.toTypedArray() - return realm.where<TimelineEventEntity>() - .equalTo(TimelineEventEntityFields.ROOM_ID, roomId) - .`in`(TimelineEventEntityFields.ROOT.SEND_STATE_STR, sendStatesStr) + return whereRoomId(realm, roomId) + .filterSendStates(sendStates) .findAll() } + +internal fun RealmQuery<TimelineEventEntity>.filterSendStates(sendStates: List<SendState>): RealmQuery<TimelineEventEntity> { + val sendStatesStr = sendStates.map { it.name }.toTypedArray() + return `in`(TimelineEventEntityFields.ROOT.SEND_STATE_STR, sendStatesStr) +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoRepository.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoRepository.kt index 3c6ce786a5..bd6927032a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoRepository.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoRepository.kt @@ -82,7 +82,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private realm.insert(eventInsertEntity) val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return@writeAsync roomEntity.sendingTimelineEvents.add(0, timelineEventEntity) - roomSummaryUpdater.update(realm, roomId) + roomSummaryUpdater.updateSendingInformation(realm, roomId) } } @@ -96,6 +96,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private } else { sendingEventEntity.sendState = sendState } + roomSummaryUpdater.update(realm, sendingEventEntity.roomId) } } } @@ -115,6 +116,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private monarchy.awaitTransaction { realm -> TimelineEventEntity.where(realm, roomId = roomId, eventId = localEcho.root.eventId ?: "").findFirst()?.deleteFromRealm() EventEntity.where(realm, eventId = localEcho.root.eventId ?: "").findFirst()?.deleteFromRealm() + roomSummaryUpdater.updateSendingInformation(realm, roomId) } } @@ -125,6 +127,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private .forEach { it.root?.sendState = SendState.UNSENT } + roomSummaryUpdater.updateSendingInformation(realm, roomId) } } @@ -134,6 +137,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private timelineEvents.forEach { it.root?.sendState = sendState } + roomSummaryUpdater.updateSendingInformation(realm, roomId) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/summary/RoomSummaryUpdater.kt index d61ebc4fc1..342926f65f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/summary/RoomSummaryUpdater.kt @@ -25,6 +25,7 @@ import im.vector.matrix.android.api.session.room.model.RoomAliasesContent import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent import im.vector.matrix.android.api.session.room.model.RoomNameContent import im.vector.matrix.android.api.session.room.model.RoomTopicContent +import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import im.vector.matrix.android.internal.crypto.crosssigning.SessionToCryptoRoomMembersUpdate import im.vector.matrix.android.internal.database.mapper.ContentMapper @@ -34,6 +35,7 @@ import im.vector.matrix.android.internal.database.model.EventEntityFields import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity +import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates import im.vector.matrix.android.internal.database.query.getOrCreate import im.vector.matrix.android.internal.database.query.getOrNull import im.vector.matrix.android.internal.database.query.isEventRead @@ -145,6 +147,7 @@ internal class RoomSummaryUpdater @Inject constructor( } else if (roomSummaryEntity.membership != Membership.INVITE) { roomSummaryEntity.inviterId = null } + roomSummaryEntity.updateHasFailedSending() if (latestPreviewableEvent?.root?.type == EventType.ENCRYPTED && latestPreviewableEvent.root?.decryptionResultJson == null) { Timber.v("Should decrypt ${latestPreviewableEvent.eventId}") @@ -167,6 +170,17 @@ internal class RoomSummaryUpdater @Inject constructor( } } + private fun RoomSummaryEntity.updateHasFailedSending() { + hasFailedSending = TimelineEventEntity.findAllInRoomWithSendStates(realm, roomId, SendState.HAS_FAILED_STATES).isNotEmpty() + } + + fun updateSendingInformation(realm: Realm, roomId: String) { + val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId) + roomSummaryEntity.updateHasFailedSending() + roomSummaryEntity.latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, + filterTypes = PREVIEWABLE_TYPES, filterContentRelation = true) + } + fun updateShieldTrust(realm: Realm, roomId: String, trust: RoomEncryptionTrustLevel?) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index a396152f6b..912eb90a0f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -30,6 +30,7 @@ import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixPatterns import im.vector.matrix.android.api.NoOpMatrixCallback +import im.vector.matrix.android.api.extensions.orFalse import im.vector.matrix.android.api.query.QueryStringValue import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.crypto.MXCryptoError @@ -415,11 +416,11 @@ class RoomDetailViewModel @AssistedInject constructor( R.id.clear_message_queue -> // For now always disable when not in developer mode, worker cancellation is not working properly timeline.pendingEventCount() > 0 && vectorPreferences.developerMode() - R.id.resend_all -> timeline.failedToDeliverEventCount() > 0 - R.id.clear_all -> timeline.failedToDeliverEventCount() > 0 + R.id.resend_all -> state.asyncRoomSummary()?.hasFailedSending == true + R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true R.id.open_matrix_apps -> true R.id.voice_call, - R.id.video_call -> room.canStartCall() && webRtcPeerConnectionManager.currentCall == null + R.id.video_call -> state.asyncRoomSummary()?.canStartCall == true && webRtcPeerConnectionManager.currentCall == null R.id.hangup_call -> webRtcPeerConnectionManager.currentCall != null else -> false } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItem.kt index 04193dba0d..0eebd12ff4 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItem.kt @@ -52,6 +52,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() { @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: View.OnClickListener? = null @EpoxyAttribute var showSelected: Boolean = false @@ -72,6 +73,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() { avatarRenderer.render(matrixItem, holder.avatarImageView) holder.roomAvatarDecorationImageView.isVisible = encryptionTrustLevel != null holder.roomAvatarDecorationImageView.setImageResource(encryptionTrustLevel.toImageRes()) + holder.roomAvatarFailSendingImageView.isVisible = hasFailedSending renderSelection(holder, showSelected) holder.typingView.setTextOrHide(typingMessage) holder.lastEventView.isInvisible = holder.typingView.isVisible @@ -106,6 +108,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() { val avatarCheckedImageView by bind<ImageView>(R.id.roomAvatarCheckedImageView) val avatarImageView by bind<ImageView>(R.id.roomAvatarImageView) val roomAvatarDecorationImageView by bind<ImageView>(R.id.roomAvatarDecorationImageView) + val roomAvatarFailSendingImageView by bind<ImageView>(R.id.roomAvatarFailSendingImageView) val rootView by bind<ViewGroup>(R.id.itemRoomLayout) } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt index f33166504d..cd2bb2d7d0 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt @@ -109,6 +109,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor .lastFormattedEvent(latestFormattedEvent) .showHighlighted(showHighlighted) .showSelected(showSelected) + .hasFailedSending(roomSummary.hasFailedSending) .unreadNotificationCount(unreadCount) .hasUnreadMessage(roomSummary.hasUnreadMessages) .hasDraft(roomSummary.userDrafts.isNotEmpty()) diff --git a/vector/src/main/res/layout/item_room.xml b/vector/src/main/res/layout/item_room.xml index 84fc0956a2..cf148043ec 100644 --- a/vector/src/main/res/layout/item_room.xml +++ b/vector/src/main/res/layout/item_room.xml @@ -47,6 +47,17 @@ </FrameLayout> + <ImageView + android:id="@+id/roomAvatarFailSendingImageView" + android:layout_width="16dp" + android:layout_height="16dp" + app:layout_constraintCircle="@id/roomAvatarContainer" + app:layout_constraintCircleAngle="45" + app:layout_constraintCircleRadius="30dp" + tools:ignore="MissingConstraints" + android:src="@drawable/ic_warning_small" + /> + <ImageView android:id="@+id/roomAvatarDecorationImageView" android:layout_width="24dp"