Merge pull request #1783 from vector-im/feature/sending_retry

Feature/sending retry
This commit is contained in:
ganfra 2020-07-24 10:35:24 +02:00 committed by GitHub
commit 25f8a9418a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 154 additions and 146 deletions

View File

@ -5,7 +5,8 @@ Features ✨:
-
Improvements 🙌:
-
- Sending events is now retried only 3 times, so we avoid blocking the sending queue too long.
- Display warning when fail to send events in room list
Bugfix 🐛:
- Fix theme issue on Room directory screen (#1613)

View File

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

View File

@ -19,7 +19,7 @@ import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
import im.vector.matrix.android.internal.session.room.send.LocalEchoUpdater
import im.vector.matrix.android.internal.session.room.send.LocalEchoRepository
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.awaitCallback
import javax.inject.Inject
@ -35,7 +35,7 @@ internal interface EncryptEventTask : Task<EncryptEventTask.Params, Event> {
internal class DefaultEncryptEventTask @Inject constructor(
// private val crypto: CryptoService
private val localEchoUpdater: LocalEchoUpdater
private val localEchoRepository: LocalEchoRepository
) : EncryptEventTask {
override suspend fun execute(params: EncryptEventTask.Params): Event {
if (!params.crypto.isRoomEncrypted(params.roomId)) return params.event
@ -44,7 +44,7 @@ internal class DefaultEncryptEventTask @Inject constructor(
throw IllegalArgumentException()
}
localEchoUpdater.updateSendState(localEvent.eventId, SendState.ENCRYPTING)
localEchoRepository.updateSendState(localEvent.eventId, SendState.ENCRYPTING)
val localMutableContent = localEvent.content?.toMutableMap() ?: mutableMapOf()
params.keepKeys?.forEach {

View File

@ -20,7 +20,7 @@ import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.session.room.send.LocalEchoUpdater
import im.vector.matrix.android.internal.session.room.send.LocalEchoRepository
import im.vector.matrix.android.internal.session.room.send.SendResponse
import im.vector.matrix.android.internal.task.Task
import org.greenrobot.eventbus.EventBus
@ -34,7 +34,7 @@ internal interface SendEventTask : Task<SendEventTask.Params, String> {
}
internal class DefaultSendEventTask @Inject constructor(
private val localEchoUpdater: LocalEchoUpdater,
private val localEchoRepository: LocalEchoRepository,
private val encryptEventTask: DefaultEncryptEventTask,
private val roomAPI: RoomAPI,
private val eventBus: EventBus) : SendEventTask {
@ -44,7 +44,7 @@ internal class DefaultSendEventTask @Inject constructor(
val localId = event.eventId!!
try {
localEchoUpdater.updateSendState(localId, SendState.SENDING)
localEchoRepository.updateSendState(localId, SendState.SENDING)
val executeRequest = executeRequest<SendResponse>(eventBus) {
apiCall = roomAPI.send(
localId,
@ -53,10 +53,10 @@ internal class DefaultSendEventTask @Inject constructor(
eventType = event.type
)
}
localEchoUpdater.updateSendState(localId, SendState.SENT)
localEchoRepository.updateSendState(localId, SendState.SENT)
return executeRequest.eventId
} catch (e: Throwable) {
localEchoUpdater.updateSendState(localId, SendState.UNDELIVERED)
localEchoRepository.updateSendState(localId, SendState.UNDELIVERED)
throw e
}
}

View File

@ -20,7 +20,7 @@ import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.session.room.send.LocalEchoUpdater
import im.vector.matrix.android.internal.session.room.send.LocalEchoRepository
import im.vector.matrix.android.internal.session.room.send.SendResponse
import im.vector.matrix.android.internal.task.Task
import org.greenrobot.eventbus.EventBus
@ -34,7 +34,7 @@ internal interface SendVerificationMessageTask : Task<SendVerificationMessageTas
}
internal class DefaultSendVerificationMessageTask @Inject constructor(
private val localEchoUpdater: LocalEchoUpdater,
private val localEchoRepository: LocalEchoRepository,
private val encryptEventTask: DefaultEncryptEventTask,
private val roomAPI: RoomAPI,
private val eventBus: EventBus) : SendVerificationMessageTask {
@ -44,7 +44,7 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
val localId = event.eventId!!
try {
localEchoUpdater.updateSendState(localId, SendState.SENDING)
localEchoRepository.updateSendState(localId, SendState.SENDING)
val executeRequest = executeRequest<SendResponse>(eventBus) {
apiCall = roomAPI.send(
localId,
@ -53,10 +53,10 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
eventType = event.type
)
}
localEchoUpdater.updateSendState(localId, SendState.SENT)
localEchoRepository.updateSendState(localId, SendState.SENT)
return executeRequest.eventId
} catch (e: Throwable) {
localEchoUpdater.updateSendState(localId, SendState.UNDELIVERED)
localEchoRepository.updateSendState(localId, SendState.UNDELIVERED)
throw e
}
}

View File

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

View File

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

View File

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

View File

@ -128,6 +128,7 @@ internal class DefaultSendService @AssistedInject constructor(
override fun resendTextMessage(localEcho: TimelineEvent): Cancelable? {
if (localEcho.root.isTextMessage() && localEcho.root.sendState.hasFailed()) {
localEchoRepository.updateSendState(localEcho.eventId, SendState.UNSENT)
return sendEvent(localEcho.root)
}
return null

View File

@ -52,7 +52,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
) : SessionWorkerParams
@Inject lateinit var crypto: CryptoService
@Inject lateinit var localEchoUpdater: LocalEchoUpdater
@Inject lateinit var localEchoRepository: LocalEchoRepository
override suspend fun doWork(): Result {
Timber.v("Start Encrypt work")
@ -74,7 +74,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
if (localEvent.eventId == null) {
return Result.success()
}
localEchoUpdater.updateSendState(localEvent.eventId, SendState.ENCRYPTING)
localEchoRepository.updateSendState(localEvent.eventId, SendState.ENCRYPTING)
val localMutableContent = localEvent.content?.toMutableMap() ?: mutableMapOf()
params.keepKeys?.forEach {
@ -116,7 +116,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
senderCurve25519Key = result.eventContent["sender_key"] as? String,
claimedEd25519Key = crypto.getMyDevice().fingerprint()
)
localEchoUpdater.updateEncryptedEcho(localEvent.eventId, safeResult.eventContent, decryptionLocalEcho)
localEchoRepository.updateEncryptedEcho(localEvent.eventId, safeResult.eventContent, decryptionLocalEcho)
}
val nextWorkerParams = SendEventWorker.Params(params.sessionId, encryptedEvent)
@ -126,7 +126,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
is Failure.CryptoError -> SendState.FAILED_UNKNOWN_DEVICES
else -> SendState.UNDELIVERED
}
localEchoUpdater.updateSendState(localEvent.eventId, sendState)
localEchoRepository.updateSendState(localEvent.eventId, sendState)
// always return success, or the chain will be stuck for ever!
val nextWorkerParams = SendEventWorker.Params(params.sessionId, localEvent, error?.localizedMessage
?: "Error")

View File

@ -17,6 +17,7 @@
package im.vector.matrix.android.internal.session.room.send
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
@ -24,7 +25,9 @@ import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.database.helper.nextId
import im.vector.matrix.android.internal.database.mapper.ContentMapper
import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.mapper.toEntity
@ -36,8 +39,8 @@ 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.where
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.session.room.summary.RoomSummaryUpdater
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
import im.vector.matrix.android.internal.session.room.summary.RoomSummaryUpdater
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimeline
import im.vector.matrix.android.internal.util.awaitTransaction
import io.realm.Realm
@ -79,7 +82,33 @@ 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)
}
}
fun updateSendState(eventId: String, sendState: SendState) {
Timber.v("Update local state of $eventId to ${sendState.name}")
monarchy.writeAsync { realm ->
val sendingEventEntity = EventEntity.where(realm, eventId).findFirst()
if (sendingEventEntity != null) {
if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) {
// If already synced, do not put as sent
} else {
sendingEventEntity.sendState = sendState
}
roomSummaryUpdater.updateSendingInformation(realm, sendingEventEntity.roomId)
}
}
}
fun updateEncryptedEcho(eventId: String, encryptedContent: Content, mxEventDecryptionResult: MXEventDecryptionResult) {
monarchy.writeAsync { realm ->
val sendingEventEntity = EventEntity.where(realm, eventId).findFirst()
if (sendingEventEntity != null) {
sendingEventEntity.type = EventType.ENCRYPTED
sendingEventEntity.content = ContentMapper.map(encryptedContent)
sendingEventEntity.setDecryptionResult(mxEventDecryptionResult)
}
}
}
@ -87,16 +116,18 @@ 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)
}
}
suspend fun clearSendingQueue(roomId: String) {
monarchy.awaitTransaction { realm ->
RoomEntity.where(realm, roomId).findFirst()?.let { room ->
room.sendingTimelineEvents.forEach {
it.root?.sendState = SendState.UNDELIVERED
}
TimelineEventEntity
.findAllInRoomWithSendStates(realm, roomId, SendState.IS_SENDING_STATES)
.forEach {
it.root?.sendState = SendState.UNSENT
}
roomSummaryUpdater.updateSendingInformation(realm, roomId)
}
}
@ -106,6 +137,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
timelineEvents.forEach {
it.root?.sendState = sendState
}
roomSummaryUpdater.updateSendingInformation(realm, roomId)
}
}

View File

@ -1,57 +0,0 @@
/*
* 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.matrix.android.internal.session.room.send
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.database.mapper.ContentMapper
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.SessionDatabase
import timber.log.Timber
import javax.inject.Inject
internal class LocalEchoUpdater @Inject constructor(@SessionDatabase private val monarchy: Monarchy) {
fun updateSendState(eventId: String, sendState: SendState) {
Timber.v("Update local state of $eventId to ${sendState.name}")
monarchy.writeAsync { realm ->
val sendingEventEntity = EventEntity.where(realm, eventId).findFirst()
if (sendingEventEntity != null) {
if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) {
// If already synced, do not put as sent
} else {
sendingEventEntity.sendState = sendState
}
}
}
}
fun updateEncryptedEcho(eventId: String, encryptedContent: Content, mxEventDecryptionResult: MXEventDecryptionResult) {
monarchy.writeAsync { realm ->
val sendingEventEntity = EventEntity.where(realm, eventId).findFirst()
if (sendingEventEntity != null) {
sendingEventEntity.type = EventType.ENCRYPTED
sendingEventEntity.content = ContentMapper.map(encryptedContent)
sendingEventEntity.setDecryptionResult(mxEventDecryptionResult)
}
}
}
}

View File

@ -54,7 +54,7 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
@Inject lateinit var workManagerProvider: WorkManagerProvider
@Inject lateinit var timelineSendEventWorkCommon: TimelineSendEventWorkCommon
@Inject lateinit var localEchoUpdater: LocalEchoUpdater
@Inject lateinit var localEchoRepository: LocalEchoRepository
override suspend fun doWork(): Result {
Timber.v("Start dispatch sending multiple event work")
@ -67,7 +67,7 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
if (params.lastFailureMessage != null) {
params.events.forEach { event ->
event.eventId?.let { localEchoUpdater.updateSendState(it, SendState.UNDELIVERED) }
event.eventId?.let { localEchoRepository.updateSendState(it, SendState.UNDELIVERED) }
}
// Transmit the error if needed?
return Result.success(inputData)

View File

@ -33,6 +33,8 @@ import org.greenrobot.eventbus.EventBus
import timber.log.Timber
import javax.inject.Inject
private const val MAX_NUMBER_OF_RETRY_BEFORE_FAILING = 3
/**
* Possible previous worker: [EncryptEventWorker] or first worker
* Possible next worker : None
@ -63,7 +65,7 @@ internal class SendEventWorker(context: Context,
)
}
@Inject lateinit var localEchoUpdater: LocalEchoUpdater
@Inject lateinit var localEchoRepository: LocalEchoRepository
@Inject lateinit var roomAPI: RoomAPI
@Inject lateinit var eventBus: EventBus
@ -74,16 +76,15 @@ internal class SendEventWorker(context: Context,
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
sessionComponent.inject(this)
if (params.eventId == null || params.roomId == null || params.type == null) {
// compat with old params, make it fail if any
if (params.event?.eventId != null) {
localEchoUpdater.updateSendState(params.event.eventId, SendState.UNDELIVERED)
localEchoRepository.updateSendState(params.event.eventId, SendState.UNDELIVERED)
}
return Result.success()
}
if (params.lastFailureMessage != null) {
localEchoUpdater.updateSendState(params.eventId, SendState.UNDELIVERED)
localEchoRepository.updateSendState(params.eventId, SendState.UNDELIVERED)
// Transmit the error
return Result.success(inputData)
.also { Timber.e("Work cancelled due to input error from parent") }
@ -92,21 +93,22 @@ internal class SendEventWorker(context: Context,
sendEvent(params.eventId, params.roomId, params.type, params.contentStr)
Result.success()
} catch (exception: Throwable) {
if (exception.shouldBeRetried()) {
Result.retry()
// It does start from 0, we want it to stop if it fails the third time
val currentAttemptCount = runAttemptCount + 1
if (currentAttemptCount >= MAX_NUMBER_OF_RETRY_BEFORE_FAILING || !exception.shouldBeRetried()) {
localEchoRepository.updateSendState(params.eventId, SendState.UNDELIVERED)
return Result.success()
} else {
localEchoUpdater.updateSendState(params.eventId, SendState.UNDELIVERED)
// always return success, or the chain will be stuck for ever!
Result.success()
Result.retry()
}
}
}
private suspend fun sendEvent(eventId: String, roomId: String, type: String, contentStr: String?) {
localEchoUpdater.updateSendState(eventId, SendState.SENDING)
localEchoRepository.updateSendState(eventId, SendState.SENDING)
executeRequest<SendResponse>(eventBus) {
apiCall = roomAPI.send(eventId, roomId, type, contentStr)
}
localEchoUpdater.updateSendState(eventId, SendState.SENT)
localEchoRepository.updateSendState(eventId, SendState.SENT)
}
}

View File

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

View File

@ -342,21 +342,18 @@ internal class DefaultTimeline(
private fun updateLoadingStates(results: RealmResults<TimelineEventEntity>) {
val lastCacheEvent = results.lastOrNull()
val lastBuiltEvent = builtEvents.lastOrNull()
val firstCacheEvent = results.firstOrNull()
val firstBuiltEvent = builtEvents.firstOrNull()
val chunkEntity = getLiveChunk()
updateState(Timeline.Direction.FORWARDS) {
it.copy(
hasMoreInCache = firstBuiltEvent != null && firstBuiltEvent.displayIndex < firstCacheEvent?.displayIndex ?: Int.MIN_VALUE,
hasMoreInCache = !builtEventsIdMap.containsKey(firstCacheEvent?.eventId),
hasReachedEnd = chunkEntity?.isLastForward ?: false
)
}
updateState(Timeline.Direction.BACKWARDS) {
it.copy(
hasMoreInCache = lastBuiltEvent == null || lastBuiltEvent.displayIndex > lastCacheEvent?.displayIndex ?: Int.MAX_VALUE,
hasMoreInCache = !builtEventsIdMap.containsKey(lastCacheEvent?.eventId),
hasReachedEnd = chunkEntity?.isLastBackward ?: false || lastCacheEvent?.root?.type == EventType.STATE_ROOM_CREATE
)
}

View File

@ -415,11 +415,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
}

View File

@ -69,7 +69,7 @@ class MessageActionsEpoxyController @Inject constructor(
id("send_state")
showProgress(false)
text(stringProvider.getString(R.string.unable_to_send_message))
drawableStart(R.drawable.ic_warning_small)
drawableStart(R.drawable.ic_warning_badge)
}
}

View File

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

View File

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

View File

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:pathData="M8,8m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0"
android:fillColor="#FF4B55"/>
<path
android:pathData="M8,3L8,3A1,1 0,0 1,9 4L9,8A1,1 0,0 1,8 9L8,9A1,1 0,0 1,7 8L7,4A1,1 0,0 1,8 3z"
android:fillColor="#ffffff"/>
<path
android:pathData="M8,11L8,11A1,1 0,0 1,9 12L9,12A1,1 0,0 1,8 13L8,13A1,1 0,0 1,7 12L7,12A1,1 0,0 1,8 11z"
android:fillColor="#ffffff"/>
</vector>

View File

@ -1,14 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="14dp"
android:height="14dp"
android:viewportWidth="14"
android:viewportHeight="14">
<path
android:pathData="M7,12.852A6,6 0,1 0,7 0.852a6,6 0,0 0,0 12zM7,1.452a1.8,1.8 0,0 1,1.8 1.8L8.8,6.852a1.8,1.8 0,1 1,-3.6 0L5.2,3.252A1.8,1.8 0,0 1,7 1.452zM7,12.252a1.8,1.8 0,1 0,0 -3.6,1.8 1.8,0 0,0 0,3.6z"
android:strokeLineJoin="round"
android:strokeWidth="1.44"
android:fillColor="#FF4B55"
android:fillType="evenOdd"
android:strokeColor="#FF4B55"
android:strokeLineCap="round"/>
</vector>

View File

@ -158,17 +158,12 @@
<im.vector.riotx.features.home.room.detail.composer.ComposerEditText
android:id="@+id/composerEditText"
style="@style/ComposerEditTextStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:hint="@string/room_message_placeholder_not_encrypted"
android:inputType="textCapSentences|textMultiLine"
android:maxHeight="200dp"
android:minHeight="48dp"
android:nextFocusLeft="@id/composerEditText"
android:nextFocusUp="@id/composerEditText"
android:padding="8dp"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/attachmentButton"
app:layout_constraintStart_toEndOf="@+id/composer_shield"

View File

@ -168,16 +168,11 @@
<im.vector.riotx.features.home.room.detail.composer.ComposerEditText
android:id="@+id/composerEditText"
style="@style/ComposerEditTextStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:hint="@string/room_message_placeholder_not_encrypted"
android:inputType="textCapSentences|textMultiLine"
android:minHeight="48dp"
android:nextFocusLeft="@id/composerEditText"
android:nextFocusUp="@id/composerEditText"
android:padding="8dp"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/sendButton"
app:layout_constraintStart_toEndOf="@id/composer_avatar_view"

View File

@ -83,7 +83,7 @@
android:layout_marginEnd="16dp"
android:text="@string/reset_secure_backup_warning"
android:textColor="@color/riotx_destructive_accent"
android:drawableStart="@drawable/ic_warning_small"
android:drawableStart="@drawable/ic_warning_badge"
android:drawablePadding="4dp"
android:textSize="14sp" />

View File

@ -24,7 +24,7 @@
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:drawableStart="@drawable/ic_warning_small"
android:drawableStart="@drawable/ic_warning_badge"
android:drawablePadding="4dp"
android:textColor="?riotx_text_secondary"
android:textStyle="bold"

View File

@ -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_badge"
/>
<ImageView
android:id="@+id/roomAvatarDecorationImageView"
android:layout_width="24dp"

View File

@ -71,7 +71,7 @@
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_marginStart="2dp"
android:src="@drawable/ic_warning_small"
android:src="@drawable/ic_warning_badge"
android:visibility="gone"
android:layout_toEndOf="@+id/viewStubContainer"
android:layout_alignTop="@+id/viewStubContainer"

View File

@ -23,7 +23,7 @@
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_marginStart="2dp"
android:src="@drawable/ic_warning_small"
android:src="@drawable/ic_warning_badge"
android:visibility="gone"
app:layout_constraintStart_toEndOf="@id/messageThumbnailView"
app:layout_constraintTop_toTopOf="@id/messageThumbnailView"

View File

@ -123,15 +123,11 @@
<im.vector.riotx.features.home.room.detail.composer.ComposerEditText
android:id="@+id/composerEditText"
style="@style/ComposerEditTextStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:inputType="textCapSentences|textMultiLine"
android:nextFocusLeft="@id/composerEditText"
android:nextFocusUp="@id/composerEditText"
android:padding="8dp"
android:textColor="?vctr_message_text_color"
android:textSize="14sp"
tools:hint="@string/room_message_placeholder_not_encrypted"
tools:ignore="MissingConstraints" />

View File

@ -385,4 +385,14 @@
<item name="android:maxHeight">40dp</item>
</style>
<style name="ComposerEditTextStyle" parent="Widget.AppCompat.EditText">
<item name="android:background">@android:color/transparent</item>
<item name="android:inputType">textCapSentences|textMultiLine</item>
<item name="android:maxLines">6</item>
<item name="android:minHeight">48dp</item>
<item name="android:padding">8dp</item>
<item name="android:textSize">15sp</item>
<item name="android:textColor">?vctr_message_text_color</item>
</style>
</resources>