mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 05:31:21 +03:00
Merge pull request #1783 from vector-im/feature/sending_retry
Feature/sending retry
This commit is contained in:
commit
25f8a9418a
31 changed files with 154 additions and 146 deletions
|
@ -5,7 +5,8 @@ Features ✨:
|
||||||
-
|
-
|
||||||
|
|
||||||
Improvements 🙌:
|
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 🐛:
|
Bugfix 🐛:
|
||||||
- Fix theme issue on Room directory screen (#1613)
|
- Fix theme issue on Room directory screen (#1613)
|
||||||
|
|
|
@ -53,7 +53,8 @@ data class RoomSummary constructor(
|
||||||
val typingUsers: List<SenderInfo>,
|
val typingUsers: List<SenderInfo>,
|
||||||
val inviterId: String? = null,
|
val inviterId: String? = null,
|
||||||
val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS,
|
val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS,
|
||||||
val roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null
|
val roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null,
|
||||||
|
val hasFailedSending: Boolean = false
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val isVersioned: Boolean
|
val isVersioned: Boolean
|
||||||
|
|
|
@ -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.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
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.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.task.Task
|
||||||
import im.vector.matrix.android.internal.util.awaitCallback
|
import im.vector.matrix.android.internal.util.awaitCallback
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -35,7 +35,7 @@ internal interface EncryptEventTask : Task<EncryptEventTask.Params, Event> {
|
||||||
|
|
||||||
internal class DefaultEncryptEventTask @Inject constructor(
|
internal class DefaultEncryptEventTask @Inject constructor(
|
||||||
// private val crypto: CryptoService
|
// private val crypto: CryptoService
|
||||||
private val localEchoUpdater: LocalEchoUpdater
|
private val localEchoRepository: LocalEchoRepository
|
||||||
) : EncryptEventTask {
|
) : EncryptEventTask {
|
||||||
override suspend fun execute(params: EncryptEventTask.Params): Event {
|
override suspend fun execute(params: EncryptEventTask.Params): Event {
|
||||||
if (!params.crypto.isRoomEncrypted(params.roomId)) return params.event
|
if (!params.crypto.isRoomEncrypted(params.roomId)) return params.event
|
||||||
|
@ -44,7 +44,7 @@ internal class DefaultEncryptEventTask @Inject constructor(
|
||||||
throw IllegalArgumentException()
|
throw IllegalArgumentException()
|
||||||
}
|
}
|
||||||
|
|
||||||
localEchoUpdater.updateSendState(localEvent.eventId, SendState.ENCRYPTING)
|
localEchoRepository.updateSendState(localEvent.eventId, SendState.ENCRYPTING)
|
||||||
|
|
||||||
val localMutableContent = localEvent.content?.toMutableMap() ?: mutableMapOf()
|
val localMutableContent = localEvent.content?.toMutableMap() ?: mutableMapOf()
|
||||||
params.keepKeys?.forEach {
|
params.keepKeys?.forEach {
|
||||||
|
|
|
@ -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.api.session.room.send.SendState
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
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.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.session.room.send.SendResponse
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
@ -34,7 +34,7 @@ internal interface SendEventTask : Task<SendEventTask.Params, String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultSendEventTask @Inject constructor(
|
internal class DefaultSendEventTask @Inject constructor(
|
||||||
private val localEchoUpdater: LocalEchoUpdater,
|
private val localEchoRepository: LocalEchoRepository,
|
||||||
private val encryptEventTask: DefaultEncryptEventTask,
|
private val encryptEventTask: DefaultEncryptEventTask,
|
||||||
private val roomAPI: RoomAPI,
|
private val roomAPI: RoomAPI,
|
||||||
private val eventBus: EventBus) : SendEventTask {
|
private val eventBus: EventBus) : SendEventTask {
|
||||||
|
@ -44,7 +44,7 @@ internal class DefaultSendEventTask @Inject constructor(
|
||||||
val localId = event.eventId!!
|
val localId = event.eventId!!
|
||||||
|
|
||||||
try {
|
try {
|
||||||
localEchoUpdater.updateSendState(localId, SendState.SENDING)
|
localEchoRepository.updateSendState(localId, SendState.SENDING)
|
||||||
val executeRequest = executeRequest<SendResponse>(eventBus) {
|
val executeRequest = executeRequest<SendResponse>(eventBus) {
|
||||||
apiCall = roomAPI.send(
|
apiCall = roomAPI.send(
|
||||||
localId,
|
localId,
|
||||||
|
@ -53,10 +53,10 @@ internal class DefaultSendEventTask @Inject constructor(
|
||||||
eventType = event.type
|
eventType = event.type
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
localEchoUpdater.updateSendState(localId, SendState.SENT)
|
localEchoRepository.updateSendState(localId, SendState.SENT)
|
||||||
return executeRequest.eventId
|
return executeRequest.eventId
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
localEchoUpdater.updateSendState(localId, SendState.UNDELIVERED)
|
localEchoRepository.updateSendState(localId, SendState.UNDELIVERED)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.api.session.room.send.SendState
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
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.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.session.room.send.SendResponse
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
@ -34,7 +34,7 @@ internal interface SendVerificationMessageTask : Task<SendVerificationMessageTas
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultSendVerificationMessageTask @Inject constructor(
|
internal class DefaultSendVerificationMessageTask @Inject constructor(
|
||||||
private val localEchoUpdater: LocalEchoUpdater,
|
private val localEchoRepository: LocalEchoRepository,
|
||||||
private val encryptEventTask: DefaultEncryptEventTask,
|
private val encryptEventTask: DefaultEncryptEventTask,
|
||||||
private val roomAPI: RoomAPI,
|
private val roomAPI: RoomAPI,
|
||||||
private val eventBus: EventBus) : SendVerificationMessageTask {
|
private val eventBus: EventBus) : SendVerificationMessageTask {
|
||||||
|
@ -44,7 +44,7 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
|
||||||
val localId = event.eventId!!
|
val localId = event.eventId!!
|
||||||
|
|
||||||
try {
|
try {
|
||||||
localEchoUpdater.updateSendState(localId, SendState.SENDING)
|
localEchoRepository.updateSendState(localId, SendState.SENDING)
|
||||||
val executeRequest = executeRequest<SendResponse>(eventBus) {
|
val executeRequest = executeRequest<SendResponse>(eventBus) {
|
||||||
apiCall = roomAPI.send(
|
apiCall = roomAPI.send(
|
||||||
localId,
|
localId,
|
||||||
|
@ -53,10 +53,10 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
|
||||||
eventType = event.type
|
eventType = event.type
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
localEchoUpdater.updateSendState(localId, SendState.SENT)
|
localEchoRepository.updateSendState(localId, SendState.SENT)
|
||||||
return executeRequest.eventId
|
return executeRequest.eventId
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
localEchoUpdater.updateSendState(localId, SendState.UNDELIVERED)
|
localEchoRepository.updateSendState(localId, SendState.UNDELIVERED)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,8 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
|
||||||
encryptionEventTs = roomSummaryEntity.encryptionEventTs,
|
encryptionEventTs = roomSummaryEntity.encryptionEventTs,
|
||||||
breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex,
|
breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex,
|
||||||
roomEncryptionTrustLevel = roomSummaryEntity.roomEncryptionTrustLevel,
|
roomEncryptionTrustLevel = roomSummaryEntity.roomEncryptionTrustLevel,
|
||||||
inviterId = roomSummaryEntity.inviterId
|
inviterId = roomSummaryEntity.inviterId,
|
||||||
|
hasFailedSending = roomSummaryEntity.hasFailedSending
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,8 @@ internal open class RoomSummaryEntity(
|
||||||
var isEncrypted: Boolean = false,
|
var isEncrypted: Boolean = false,
|
||||||
var encryptionEventTs: Long? = 0,
|
var encryptionEventTs: Long? = 0,
|
||||||
var roomEncryptionTrustLevelStr: String? = null,
|
var roomEncryptionTrustLevelStr: String? = null,
|
||||||
var inviterId: String? = null
|
var inviterId: String? = null,
|
||||||
|
var hasFailedSending: Boolean = false
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
private var membershipStr: String = Membership.NONE.name
|
private var membershipStr: String = Membership.NONE.name
|
||||||
|
|
|
@ -93,9 +93,12 @@ internal fun TimelineEventEntity.Companion.findAllInRoomWithSendStates(realm: Re
|
||||||
roomId: String,
|
roomId: String,
|
||||||
sendStates: List<SendState>)
|
sendStates: List<SendState>)
|
||||||
: RealmResults<TimelineEventEntity> {
|
: RealmResults<TimelineEventEntity> {
|
||||||
val sendStatesStr = sendStates.map { it.name }.toTypedArray()
|
return whereRoomId(realm, roomId)
|
||||||
return realm.where<TimelineEventEntity>()
|
.filterSendStates(sendStates)
|
||||||
.equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
|
|
||||||
.`in`(TimelineEventEntityFields.ROOT.SEND_STATE_STR, sendStatesStr)
|
|
||||||
.findAll()
|
.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)
|
||||||
|
}
|
||||||
|
|
|
@ -128,6 +128,7 @@ internal class DefaultSendService @AssistedInject constructor(
|
||||||
|
|
||||||
override fun resendTextMessage(localEcho: TimelineEvent): Cancelable? {
|
override fun resendTextMessage(localEcho: TimelineEvent): Cancelable? {
|
||||||
if (localEcho.root.isTextMessage() && localEcho.root.sendState.hasFailed()) {
|
if (localEcho.root.isTextMessage() && localEcho.root.sendState.hasFailed()) {
|
||||||
|
localEchoRepository.updateSendState(localEcho.eventId, SendState.UNSENT)
|
||||||
return sendEvent(localEcho.root)
|
return sendEvent(localEcho.root)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -52,7 +52,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
|
||||||
) : SessionWorkerParams
|
) : SessionWorkerParams
|
||||||
|
|
||||||
@Inject lateinit var crypto: CryptoService
|
@Inject lateinit var crypto: CryptoService
|
||||||
@Inject lateinit var localEchoUpdater: LocalEchoUpdater
|
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
Timber.v("Start Encrypt work")
|
Timber.v("Start Encrypt work")
|
||||||
|
@ -74,7 +74,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
|
||||||
if (localEvent.eventId == null) {
|
if (localEvent.eventId == null) {
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
localEchoUpdater.updateSendState(localEvent.eventId, SendState.ENCRYPTING)
|
localEchoRepository.updateSendState(localEvent.eventId, SendState.ENCRYPTING)
|
||||||
|
|
||||||
val localMutableContent = localEvent.content?.toMutableMap() ?: mutableMapOf()
|
val localMutableContent = localEvent.content?.toMutableMap() ?: mutableMapOf()
|
||||||
params.keepKeys?.forEach {
|
params.keepKeys?.forEach {
|
||||||
|
@ -116,7 +116,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
|
||||||
senderCurve25519Key = result.eventContent["sender_key"] as? String,
|
senderCurve25519Key = result.eventContent["sender_key"] as? String,
|
||||||
claimedEd25519Key = crypto.getMyDevice().fingerprint()
|
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)
|
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
|
is Failure.CryptoError -> SendState.FAILED_UNKNOWN_DEVICES
|
||||||
else -> SendState.UNDELIVERED
|
else -> SendState.UNDELIVERED
|
||||||
}
|
}
|
||||||
localEchoUpdater.updateSendState(localEvent.eventId, sendState)
|
localEchoRepository.updateSendState(localEvent.eventId, sendState)
|
||||||
// always return success, or the chain will be stuck for ever!
|
// always return success, or the chain will be stuck for ever!
|
||||||
val nextWorkerParams = SendEventWorker.Params(params.sessionId, localEvent, error?.localizedMessage
|
val nextWorkerParams = SendEventWorker.Params(params.sessionId, localEvent, error?.localizedMessage
|
||||||
?: "Error")
|
?: "Error")
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.matrix.android.internal.session.room.send
|
package im.vector.matrix.android.internal.session.room.send
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
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.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
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.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.model.message.MessageType
|
||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
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.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.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.TimelineEventMapper
|
||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
import im.vector.matrix.android.internal.database.mapper.toEntity
|
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.findAllInRoomWithSendStates
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
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.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.session.room.timeline.DefaultTimeline
|
||||||
import im.vector.matrix.android.internal.util.awaitTransaction
|
import im.vector.matrix.android.internal.util.awaitTransaction
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
@ -79,7 +82,33 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
|
||||||
realm.insert(eventInsertEntity)
|
realm.insert(eventInsertEntity)
|
||||||
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return@writeAsync
|
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return@writeAsync
|
||||||
roomEntity.sendingTimelineEvents.add(0, timelineEventEntity)
|
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 ->
|
monarchy.awaitTransaction { realm ->
|
||||||
TimelineEventEntity.where(realm, roomId = roomId, eventId = localEcho.root.eventId ?: "").findFirst()?.deleteFromRealm()
|
TimelineEventEntity.where(realm, roomId = roomId, eventId = localEcho.root.eventId ?: "").findFirst()?.deleteFromRealm()
|
||||||
EventEntity.where(realm, eventId = localEcho.root.eventId ?: "").findFirst()?.deleteFromRealm()
|
EventEntity.where(realm, eventId = localEcho.root.eventId ?: "").findFirst()?.deleteFromRealm()
|
||||||
|
roomSummaryUpdater.updateSendingInformation(realm, roomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun clearSendingQueue(roomId: String) {
|
suspend fun clearSendingQueue(roomId: String) {
|
||||||
monarchy.awaitTransaction { realm ->
|
monarchy.awaitTransaction { realm ->
|
||||||
RoomEntity.where(realm, roomId).findFirst()?.let { room ->
|
TimelineEventEntity
|
||||||
room.sendingTimelineEvents.forEach {
|
.findAllInRoomWithSendStates(realm, roomId, SendState.IS_SENDING_STATES)
|
||||||
it.root?.sendState = SendState.UNDELIVERED
|
.forEach {
|
||||||
}
|
it.root?.sendState = SendState.UNSENT
|
||||||
}
|
}
|
||||||
|
roomSummaryUpdater.updateSendingInformation(realm, roomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +137,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
|
||||||
timelineEvents.forEach {
|
timelineEvents.forEach {
|
||||||
it.root?.sendState = sendState
|
it.root?.sendState = sendState
|
||||||
}
|
}
|
||||||
|
roomSummaryUpdater.updateSendingInformation(realm, roomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -54,7 +54,7 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
|
||||||
|
|
||||||
@Inject lateinit var workManagerProvider: WorkManagerProvider
|
@Inject lateinit var workManagerProvider: WorkManagerProvider
|
||||||
@Inject lateinit var timelineSendEventWorkCommon: TimelineSendEventWorkCommon
|
@Inject lateinit var timelineSendEventWorkCommon: TimelineSendEventWorkCommon
|
||||||
@Inject lateinit var localEchoUpdater: LocalEchoUpdater
|
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
Timber.v("Start dispatch sending multiple event work")
|
Timber.v("Start dispatch sending multiple event work")
|
||||||
|
@ -67,7 +67,7 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
|
||||||
|
|
||||||
if (params.lastFailureMessage != null) {
|
if (params.lastFailureMessage != null) {
|
||||||
params.events.forEach { event ->
|
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?
|
// Transmit the error if needed?
|
||||||
return Result.success(inputData)
|
return Result.success(inputData)
|
||||||
|
|
|
@ -33,6 +33,8 @@ import org.greenrobot.eventbus.EventBus
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private const val MAX_NUMBER_OF_RETRY_BEFORE_FAILING = 3
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Possible previous worker: [EncryptEventWorker] or first worker
|
* Possible previous worker: [EncryptEventWorker] or first worker
|
||||||
* Possible next worker : None
|
* 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 roomAPI: RoomAPI
|
||||||
@Inject lateinit var eventBus: EventBus
|
@Inject lateinit var eventBus: EventBus
|
||||||
|
|
||||||
|
@ -74,16 +76,15 @@ internal class SendEventWorker(context: Context,
|
||||||
|
|
||||||
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
||||||
sessionComponent.inject(this)
|
sessionComponent.inject(this)
|
||||||
|
|
||||||
if (params.eventId == null || params.roomId == null || params.type == null) {
|
if (params.eventId == null || params.roomId == null || params.type == null) {
|
||||||
// compat with old params, make it fail if any
|
// compat with old params, make it fail if any
|
||||||
if (params.event?.eventId != null) {
|
if (params.event?.eventId != null) {
|
||||||
localEchoUpdater.updateSendState(params.event.eventId, SendState.UNDELIVERED)
|
localEchoRepository.updateSendState(params.event.eventId, SendState.UNDELIVERED)
|
||||||
}
|
}
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
if (params.lastFailureMessage != null) {
|
if (params.lastFailureMessage != null) {
|
||||||
localEchoUpdater.updateSendState(params.eventId, SendState.UNDELIVERED)
|
localEchoRepository.updateSendState(params.eventId, SendState.UNDELIVERED)
|
||||||
// Transmit the error
|
// Transmit the error
|
||||||
return Result.success(inputData)
|
return Result.success(inputData)
|
||||||
.also { Timber.e("Work cancelled due to input error from parent") }
|
.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)
|
sendEvent(params.eventId, params.roomId, params.type, params.contentStr)
|
||||||
Result.success()
|
Result.success()
|
||||||
} catch (exception: Throwable) {
|
} catch (exception: Throwable) {
|
||||||
if (exception.shouldBeRetried()) {
|
// It does start from 0, we want it to stop if it fails the third time
|
||||||
Result.retry()
|
val currentAttemptCount = runAttemptCount + 1
|
||||||
|
if (currentAttemptCount >= MAX_NUMBER_OF_RETRY_BEFORE_FAILING || !exception.shouldBeRetried()) {
|
||||||
|
localEchoRepository.updateSendState(params.eventId, SendState.UNDELIVERED)
|
||||||
|
return Result.success()
|
||||||
} else {
|
} else {
|
||||||
localEchoUpdater.updateSendState(params.eventId, SendState.UNDELIVERED)
|
Result.retry()
|
||||||
// always return success, or the chain will be stuck for ever!
|
|
||||||
Result.success()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun sendEvent(eventId: String, roomId: String, type: String, contentStr: String?) {
|
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) {
|
executeRequest<SendResponse>(eventBus) {
|
||||||
apiCall = roomAPI.send(eventId, roomId, type, contentStr)
|
apiCall = roomAPI.send(eventId, roomId, type, contentStr)
|
||||||
}
|
}
|
||||||
localEchoUpdater.updateSendState(eventId, SendState.SENT)
|
localEchoRepository.updateSendState(eventId, SendState.SENT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.RoomCanonicalAliasContent
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomNameContent
|
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.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.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
import im.vector.matrix.android.internal.crypto.crosssigning.SessionToCryptoRoomMembersUpdate
|
import im.vector.matrix.android.internal.crypto.crosssigning.SessionToCryptoRoomMembersUpdate
|
||||||
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
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.RoomMemberSummaryEntityFields
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
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.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.getOrCreate
|
||||||
import im.vector.matrix.android.internal.database.query.getOrNull
|
import im.vector.matrix.android.internal.database.query.getOrNull
|
||||||
import im.vector.matrix.android.internal.database.query.isEventRead
|
import im.vector.matrix.android.internal.database.query.isEventRead
|
||||||
|
@ -145,6 +147,7 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||||
} else if (roomSummaryEntity.membership != Membership.INVITE) {
|
} else if (roomSummaryEntity.membership != Membership.INVITE) {
|
||||||
roomSummaryEntity.inviterId = null
|
roomSummaryEntity.inviterId = null
|
||||||
}
|
}
|
||||||
|
roomSummaryEntity.updateHasFailedSending()
|
||||||
|
|
||||||
if (latestPreviewableEvent?.root?.type == EventType.ENCRYPTED && latestPreviewableEvent.root?.decryptionResultJson == null) {
|
if (latestPreviewableEvent?.root?.type == EventType.ENCRYPTED && latestPreviewableEvent.root?.decryptionResultJson == null) {
|
||||||
Timber.v("Should decrypt ${latestPreviewableEvent.eventId}")
|
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,
|
fun updateShieldTrust(realm: Realm,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
trust: RoomEncryptionTrustLevel?) {
|
trust: RoomEncryptionTrustLevel?) {
|
||||||
|
|
|
@ -342,21 +342,18 @@ internal class DefaultTimeline(
|
||||||
|
|
||||||
private fun updateLoadingStates(results: RealmResults<TimelineEventEntity>) {
|
private fun updateLoadingStates(results: RealmResults<TimelineEventEntity>) {
|
||||||
val lastCacheEvent = results.lastOrNull()
|
val lastCacheEvent = results.lastOrNull()
|
||||||
val lastBuiltEvent = builtEvents.lastOrNull()
|
|
||||||
val firstCacheEvent = results.firstOrNull()
|
val firstCacheEvent = results.firstOrNull()
|
||||||
val firstBuiltEvent = builtEvents.firstOrNull()
|
|
||||||
val chunkEntity = getLiveChunk()
|
val chunkEntity = getLiveChunk()
|
||||||
|
|
||||||
updateState(Timeline.Direction.FORWARDS) {
|
updateState(Timeline.Direction.FORWARDS) {
|
||||||
it.copy(
|
it.copy(
|
||||||
hasMoreInCache = firstBuiltEvent != null && firstBuiltEvent.displayIndex < firstCacheEvent?.displayIndex ?: Int.MIN_VALUE,
|
hasMoreInCache = !builtEventsIdMap.containsKey(firstCacheEvent?.eventId),
|
||||||
hasReachedEnd = chunkEntity?.isLastForward ?: false
|
hasReachedEnd = chunkEntity?.isLastForward ?: false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateState(Timeline.Direction.BACKWARDS) {
|
updateState(Timeline.Direction.BACKWARDS) {
|
||||||
it.copy(
|
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
|
hasReachedEnd = chunkEntity?.isLastBackward ?: false || lastCacheEvent?.root?.type == EventType.STATE_ROOM_CREATE
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -415,11 +415,11 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
R.id.clear_message_queue ->
|
R.id.clear_message_queue ->
|
||||||
// For now always disable when not in developer mode, worker cancellation is not working properly
|
// For now always disable when not in developer mode, worker cancellation is not working properly
|
||||||
timeline.pendingEventCount() > 0 && vectorPreferences.developerMode()
|
timeline.pendingEventCount() > 0 && vectorPreferences.developerMode()
|
||||||
R.id.resend_all -> timeline.failedToDeliverEventCount() > 0
|
R.id.resend_all -> state.asyncRoomSummary()?.hasFailedSending == true
|
||||||
R.id.clear_all -> timeline.failedToDeliverEventCount() > 0
|
R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true
|
||||||
R.id.open_matrix_apps -> true
|
R.id.open_matrix_apps -> true
|
||||||
R.id.voice_call,
|
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
|
R.id.hangup_call -> webRtcPeerConnectionManager.currentCall != null
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@ class MessageActionsEpoxyController @Inject constructor(
|
||||||
id("send_state")
|
id("send_state")
|
||||||
showProgress(false)
|
showProgress(false)
|
||||||
text(stringProvider.getString(R.string.unable_to_send_message))
|
text(stringProvider.getString(R.string.unable_to_send_message))
|
||||||
drawableStart(R.drawable.ic_warning_small)
|
drawableStart(R.drawable.ic_warning_badge)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
|
||||||
@EpoxyAttribute var hasUnreadMessage: Boolean = false
|
@EpoxyAttribute var hasUnreadMessage: Boolean = false
|
||||||
@EpoxyAttribute var hasDraft: Boolean = false
|
@EpoxyAttribute var hasDraft: Boolean = false
|
||||||
@EpoxyAttribute var showHighlighted: 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 itemLongClickListener: View.OnLongClickListener? = null
|
||||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: View.OnClickListener? = null
|
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: View.OnClickListener? = null
|
||||||
@EpoxyAttribute var showSelected: Boolean = false
|
@EpoxyAttribute var showSelected: Boolean = false
|
||||||
|
@ -72,6 +73,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
|
||||||
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
||||||
holder.roomAvatarDecorationImageView.isVisible = encryptionTrustLevel != null
|
holder.roomAvatarDecorationImageView.isVisible = encryptionTrustLevel != null
|
||||||
holder.roomAvatarDecorationImageView.setImageResource(encryptionTrustLevel.toImageRes())
|
holder.roomAvatarDecorationImageView.setImageResource(encryptionTrustLevel.toImageRes())
|
||||||
|
holder.roomAvatarFailSendingImageView.isVisible = hasFailedSending
|
||||||
renderSelection(holder, showSelected)
|
renderSelection(holder, showSelected)
|
||||||
holder.typingView.setTextOrHide(typingMessage)
|
holder.typingView.setTextOrHide(typingMessage)
|
||||||
holder.lastEventView.isInvisible = holder.typingView.isVisible
|
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 avatarCheckedImageView by bind<ImageView>(R.id.roomAvatarCheckedImageView)
|
||||||
val avatarImageView by bind<ImageView>(R.id.roomAvatarImageView)
|
val avatarImageView by bind<ImageView>(R.id.roomAvatarImageView)
|
||||||
val roomAvatarDecorationImageView by bind<ImageView>(R.id.roomAvatarDecorationImageView)
|
val roomAvatarDecorationImageView by bind<ImageView>(R.id.roomAvatarDecorationImageView)
|
||||||
|
val roomAvatarFailSendingImageView by bind<ImageView>(R.id.roomAvatarFailSendingImageView)
|
||||||
val rootView by bind<ViewGroup>(R.id.itemRoomLayout)
|
val rootView by bind<ViewGroup>(R.id.itemRoomLayout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,6 +109,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
|
||||||
.lastFormattedEvent(latestFormattedEvent)
|
.lastFormattedEvent(latestFormattedEvent)
|
||||||
.showHighlighted(showHighlighted)
|
.showHighlighted(showHighlighted)
|
||||||
.showSelected(showSelected)
|
.showSelected(showSelected)
|
||||||
|
.hasFailedSending(roomSummary.hasFailedSending)
|
||||||
.unreadNotificationCount(unreadCount)
|
.unreadNotificationCount(unreadCount)
|
||||||
.hasUnreadMessage(roomSummary.hasUnreadMessages)
|
.hasUnreadMessage(roomSummary.hasUnreadMessages)
|
||||||
.hasDraft(roomSummary.userDrafts.isNotEmpty())
|
.hasDraft(roomSummary.userDrafts.isNotEmpty())
|
||||||
|
|
15
vector/src/main/res/drawable/ic_warning_badge.xml
Normal file
15
vector/src/main/res/drawable/ic_warning_badge.xml
Normal 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>
|
|
@ -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>
|
|
|
@ -158,17 +158,12 @@
|
||||||
|
|
||||||
<im.vector.riotx.features.home.room.detail.composer.ComposerEditText
|
<im.vector.riotx.features.home.room.detail.composer.ComposerEditText
|
||||||
android:id="@+id/composerEditText"
|
android:id="@+id/composerEditText"
|
||||||
|
style="@style/ComposerEditTextStyle"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:hint="@string/room_message_placeholder_not_encrypted"
|
android:hint="@string/room_message_placeholder_not_encrypted"
|
||||||
android:inputType="textCapSentences|textMultiLine"
|
|
||||||
android:maxHeight="200dp"
|
|
||||||
android:minHeight="48dp"
|
|
||||||
android:nextFocusLeft="@id/composerEditText"
|
android:nextFocusLeft="@id/composerEditText"
|
||||||
android:nextFocusUp="@id/composerEditText"
|
android:nextFocusUp="@id/composerEditText"
|
||||||
android:padding="8dp"
|
|
||||||
android:textSize="14sp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/attachmentButton"
|
app:layout_constraintEnd_toStartOf="@+id/attachmentButton"
|
||||||
app:layout_constraintStart_toEndOf="@+id/composer_shield"
|
app:layout_constraintStart_toEndOf="@+id/composer_shield"
|
||||||
|
|
|
@ -168,16 +168,11 @@
|
||||||
|
|
||||||
<im.vector.riotx.features.home.room.detail.composer.ComposerEditText
|
<im.vector.riotx.features.home.room.detail.composer.ComposerEditText
|
||||||
android:id="@+id/composerEditText"
|
android:id="@+id/composerEditText"
|
||||||
|
style="@style/ComposerEditTextStyle"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
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:nextFocusLeft="@id/composerEditText"
|
||||||
android:nextFocusUp="@id/composerEditText"
|
android:nextFocusUp="@id/composerEditText"
|
||||||
android:padding="8dp"
|
|
||||||
android:textSize="14sp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/sendButton"
|
app:layout_constraintEnd_toStartOf="@+id/sendButton"
|
||||||
app:layout_constraintStart_toEndOf="@id/composer_avatar_view"
|
app:layout_constraintStart_toEndOf="@id/composer_avatar_view"
|
||||||
|
|
|
@ -83,7 +83,7 @@
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:text="@string/reset_secure_backup_warning"
|
android:text="@string/reset_secure_backup_warning"
|
||||||
android:textColor="@color/riotx_destructive_accent"
|
android:textColor="@color/riotx_destructive_accent"
|
||||||
android:drawableStart="@drawable/ic_warning_small"
|
android:drawableStart="@drawable/ic_warning_badge"
|
||||||
android:drawablePadding="4dp"
|
android:drawablePadding="4dp"
|
||||||
android:textSize="14sp" />
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:drawableStart="@drawable/ic_warning_small"
|
android:drawableStart="@drawable/ic_warning_badge"
|
||||||
android:drawablePadding="4dp"
|
android:drawablePadding="4dp"
|
||||||
android:textColor="?riotx_text_secondary"
|
android:textColor="?riotx_text_secondary"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
|
|
|
@ -47,6 +47,17 @@
|
||||||
|
|
||||||
</FrameLayout>
|
</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
|
<ImageView
|
||||||
android:id="@+id/roomAvatarDecorationImageView"
|
android:id="@+id/roomAvatarDecorationImageView"
|
||||||
android:layout_width="24dp"
|
android:layout_width="24dp"
|
||||||
|
|
|
@ -71,7 +71,7 @@
|
||||||
android:layout_width="14dp"
|
android:layout_width="14dp"
|
||||||
android:layout_height="14dp"
|
android:layout_height="14dp"
|
||||||
android:layout_marginStart="2dp"
|
android:layout_marginStart="2dp"
|
||||||
android:src="@drawable/ic_warning_small"
|
android:src="@drawable/ic_warning_badge"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:layout_toEndOf="@+id/viewStubContainer"
|
android:layout_toEndOf="@+id/viewStubContainer"
|
||||||
android:layout_alignTop="@+id/viewStubContainer"
|
android:layout_alignTop="@+id/viewStubContainer"
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
android:layout_width="14dp"
|
android:layout_width="14dp"
|
||||||
android:layout_height="14dp"
|
android:layout_height="14dp"
|
||||||
android:layout_marginStart="2dp"
|
android:layout_marginStart="2dp"
|
||||||
android:src="@drawable/ic_warning_small"
|
android:src="@drawable/ic_warning_badge"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintStart_toEndOf="@id/messageThumbnailView"
|
app:layout_constraintStart_toEndOf="@id/messageThumbnailView"
|
||||||
app:layout_constraintTop_toTopOf="@id/messageThumbnailView"
|
app:layout_constraintTop_toTopOf="@id/messageThumbnailView"
|
||||||
|
|
|
@ -123,15 +123,11 @@
|
||||||
|
|
||||||
<im.vector.riotx.features.home.room.detail.composer.ComposerEditText
|
<im.vector.riotx.features.home.room.detail.composer.ComposerEditText
|
||||||
android:id="@+id/composerEditText"
|
android:id="@+id/composerEditText"
|
||||||
|
style="@style/ComposerEditTextStyle"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:inputType="textCapSentences|textMultiLine"
|
|
||||||
android:nextFocusLeft="@id/composerEditText"
|
android:nextFocusLeft="@id/composerEditText"
|
||||||
android:nextFocusUp="@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:hint="@string/room_message_placeholder_not_encrypted"
|
||||||
tools:ignore="MissingConstraints" />
|
tools:ignore="MissingConstraints" />
|
||||||
|
|
||||||
|
|
|
@ -385,4 +385,14 @@
|
||||||
<item name="android:maxHeight">40dp</item>
|
<item name="android:maxHeight">40dp</item>
|
||||||
</style>
|
</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>
|
</resources>
|
Loading…
Reference in a new issue