code review and cleanup
This commit is contained in:
parent
9016688aec
commit
47f47e40c4
|
@ -2,7 +2,7 @@ Changes in RiotX 0.16.0 (2020-XX-XX)
|
|||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- Polls and Bot Buttons (MSC 2192)
|
||||
- Polls and Bot Buttons (MSC 2192 matrix-org/matrix-doc#2192)
|
||||
|
||||
Improvements 🙌:
|
||||
- Show confirmation dialog before deleting a message (#967)
|
||||
|
|
|
@ -19,13 +19,12 @@ package im.vector.matrix.android.api.session.events.model
|
|||
* Constants defining known event relation types from Matrix specifications
|
||||
*/
|
||||
object RelationType {
|
||||
|
||||
/** Lets you define an event which annotates an existing event.*/
|
||||
const val ANNOTATION = "m.annotation"
|
||||
/** Lets you define an event which replaces an existing event.*/
|
||||
const val REPLACE = "m.replace"
|
||||
/** Lets you define an event which references an existing event.*/
|
||||
const val REFERENCE = "m.reference"
|
||||
/** Lets you define an event which references an existing event.*/
|
||||
/** Lets you define an event which adds a response to an existing event.*/
|
||||
const val RESPONSE = "m.response"
|
||||
}
|
||||
|
|
|
@ -20,13 +20,13 @@ import com.squareup.moshi.JsonClass
|
|||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
||||
|
||||
enum class OptionsType(val value: String) {
|
||||
POLL("m.pool"),
|
||||
BUTTONS("m.buttons"),
|
||||
}
|
||||
// Possible values for optionType
|
||||
const val OPTION_TYPE_POLL = "m.pool"
|
||||
const val OPTION_TYPE_BUTTONS = "m.buttons"
|
||||
|
||||
/**
|
||||
* Polls and bot buttons are m.room.message events with a msgtype of m.options,
|
||||
* Ref: https://github.com/matrix-org/matrix-doc/pull/2192
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MessageOptionsContent(
|
||||
|
@ -35,20 +35,7 @@ data class MessageOptionsContent(
|
|||
@Json(name = "body") override val body: String,
|
||||
@Json(name = "label") val label: String?,
|
||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||
@Json(name = "options") val options: List<OptionItems>? = null,
|
||||
@Json(name = "options") val options: List<OptionItem>? = null,
|
||||
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||
) : MessageContent
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class OptionItems(
|
||||
@Json(name = "label") val label: String?,
|
||||
@Json(name = "value") val value: String?
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MessagePollResponseContent(
|
||||
@Json(name = "msgtype") override val msgType: String = MessageType.MSGTYPE_RESPONSE,
|
||||
@Json(name = "body") override val body: String,
|
||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||
) : MessageContent
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.api.session.room.model.message
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
||||
|
||||
/**
|
||||
* Ref: https://github.com/matrix-org/matrix-doc/pull/2192
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MessagePollResponseContent(
|
||||
@Json(name = "msgtype") override val msgType: String = MessageType.MSGTYPE_RESPONSE,
|
||||
@Json(name = "body") override val body: String,
|
||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||
) : MessageContent
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.api.session.room.model.message
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* Ref: https://github.com/matrix-org/matrix-doc/pull/2192
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class OptionItem(
|
||||
@Json(name = "label") val label: String?,
|
||||
@Json(name = "value") val value: String?
|
||||
)
|
|
@ -19,6 +19,7 @@ package im.vector.matrix.android.api.session.room.send
|
|||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||
import im.vector.matrix.android.api.session.room.model.message.OptionItem
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
|
@ -61,6 +62,14 @@ interface SendService {
|
|||
*/
|
||||
fun sendMedias(attachments: List<ContentAttachmentData>): Cancelable
|
||||
|
||||
/**
|
||||
* Send a poll to the room.
|
||||
* @param question the question
|
||||
* @param options list of (label, value)
|
||||
* @return a [Cancelable]
|
||||
*/
|
||||
fun sendPoll(question: String, options: List<OptionItem>): Cancelable
|
||||
|
||||
/**
|
||||
* Method to send a poll response.
|
||||
* @param pollEventId the poll currently replied to
|
||||
|
@ -71,12 +80,7 @@ interface SendService {
|
|||
fun sendOptionsReply(pollEventId: String, optionIndex: Int, optionValue: String): Cancelable
|
||||
|
||||
/**
|
||||
* @param options list of (label, value)
|
||||
*/
|
||||
fun sendPoll(question: String, options: List<Pair<String, String>>)
|
||||
|
||||
/**
|
||||
* Redacts (delete) the given event.
|
||||
* Redact (delete) the given event.
|
||||
* @param event The event to redact
|
||||
* @param reason Optional reason string
|
||||
*/
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package im.vector.matrix.android.internal.database.mapper
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
* Copyright 2020 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.
|
||||
|
@ -15,6 +14,8 @@ package im.vector.matrix.android.internal.database.mapper
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.database.mapper
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.toContent
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.PollResponseAggregatedSummary
|
||||
|
|
|
@ -19,7 +19,7 @@ import io.realm.RealmList
|
|||
import io.realm.RealmObject
|
||||
|
||||
/**
|
||||
* Keep the latest state of edition of a message
|
||||
* Keep the latest state of a poll
|
||||
*/
|
||||
internal open class PollResponseAggregatedSummaryEntity(
|
||||
// For now we persist this a JSON for greater flexibility
|
||||
|
|
|
@ -16,10 +16,16 @@
|
|||
|
||||
package im.vector.matrix.android.internal.database.query
|
||||
|
||||
import android.service.autofill.Validators.not
|
||||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.matrix.android.internal.database.model.*
|
||||
import io.realm.*
|
||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmList
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.RealmResults
|
||||
import io.realm.Sort
|
||||
import io.realm.kotlin.where
|
||||
|
||||
internal fun TimelineEventEntity.Companion.where(realm: Realm, roomId: String, eventId: String): RealmQuery<TimelineEventEntity> {
|
||||
|
@ -55,7 +61,8 @@ internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm,
|
|||
val sendingTimelineEvents = roomEntity.sendingTimelineEvents.where().filterTypes(filterTypes)
|
||||
val liveEvents = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)?.timelineEvents?.where()?.filterTypes(filterTypes)
|
||||
if (filterContentRelation) {
|
||||
liveEvents?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.EDIT_TYPE)
|
||||
liveEvents
|
||||
?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.EDIT_TYPE)
|
||||
?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.RESPONSE_TYPE)
|
||||
}
|
||||
val query = if (includesSending && sendingTimelineEvents.findAll().isNotEmpty()) {
|
||||
|
|
|
@ -46,8 +46,8 @@ object MoshiProvider {
|
|||
.registerSubtype(MessageVideoContent::class.java, MessageType.MSGTYPE_VIDEO)
|
||||
.registerSubtype(MessageLocationContent::class.java, MessageType.MSGTYPE_LOCATION)
|
||||
.registerSubtype(MessageFileContent::class.java, MessageType.MSGTYPE_FILE)
|
||||
.registerSubtype(MessageOptionsContent::class.java, MessageType.MSGTYPE_OPTIONS)
|
||||
.registerSubtype(MessageVerificationRequestContent::class.java, MessageType.MSGTYPE_VERIFICATION_REQUEST)
|
||||
.registerSubtype(MessageOptionsContent::class.java, MessageType.MSGTYPE_OPTIONS)
|
||||
.registerSubtype(MessagePollResponseContent::class.java, MessageType.MSGTYPE_RESPONSE)
|
||||
)
|
||||
.add(SerializeNulls.JSON_ADAPTER_FACTORY)
|
||||
|
|
|
@ -28,6 +28,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.isImageMessage
|
||||
import im.vector.matrix.android.api.session.events.model.isTextMessage
|
||||
import im.vector.matrix.android.api.session.room.model.message.OptionItem
|
||||
import im.vector.matrix.android.api.session.room.send.SendService
|
||||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
|
@ -80,7 +81,13 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||
val event = localEchoEventFactory.createFormattedTextEvent(roomId, TextContent(text, formattedText), msgType).also {
|
||||
createLocalEcho(it)
|
||||
}
|
||||
return sendEvent(event)
|
||||
}
|
||||
|
||||
override fun sendPoll(question: String, options: List<OptionItem>): Cancelable {
|
||||
val event = localEchoEventFactory.createPollEvent(roomId, question, options).also {
|
||||
createLocalEcho(it)
|
||||
}
|
||||
return sendEvent(event)
|
||||
}
|
||||
|
||||
|
@ -91,13 +98,6 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||
return sendEvent(event)
|
||||
}
|
||||
|
||||
override fun sendPoll(question: String, options: List<Pair<String, String>>) {
|
||||
localEchoEventFactory.createPollEvent(roomId, question, options).also {
|
||||
createLocalEcho(it)
|
||||
sendEvent(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendEvent(event: Event): Cancelable {
|
||||
// Encrypted room handling
|
||||
return if (cryptoService.isRoomEncrypted(roomId)) {
|
||||
|
@ -172,123 +172,123 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun clearSendingQueue() {
|
||||
timelineSendEventWorkCommon.cancelAllWorks(roomId)
|
||||
workManagerProvider.workManager.cancelUniqueWork(buildWorkName(UPLOAD_WORK))
|
||||
override fun clearSendingQueue() {
|
||||
timelineSendEventWorkCommon.cancelAllWorks(roomId)
|
||||
workManagerProvider.workManager.cancelUniqueWork(buildWorkName(UPLOAD_WORK))
|
||||
|
||||
// Replace the worker chains with a AlwaysSuccessfulWorker, to ensure the queues are well emptied
|
||||
workManagerProvider.matrixOneTimeWorkRequestBuilder<AlwaysSuccessfulWorker>()
|
||||
.build().let {
|
||||
timelineSendEventWorkCommon.postWork(roomId, it, ExistingWorkPolicy.REPLACE)
|
||||
// Replace the worker chains with a AlwaysSuccessfulWorker, to ensure the queues are well emptied
|
||||
workManagerProvider.matrixOneTimeWorkRequestBuilder<AlwaysSuccessfulWorker>()
|
||||
.build().let {
|
||||
timelineSendEventWorkCommon.postWork(roomId, it, ExistingWorkPolicy.REPLACE)
|
||||
|
||||
// need to clear also image sending queue
|
||||
workManagerProvider.workManager
|
||||
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.REPLACE, it)
|
||||
.enqueue()
|
||||
}
|
||||
taskExecutor.executorScope.launch {
|
||||
localEchoRepository.clearSendingQueue(roomId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun resendAllFailedMessages() {
|
||||
taskExecutor.executorScope.launch {
|
||||
val eventsToResend = localEchoRepository.getAllFailedEventsToResend(roomId)
|
||||
eventsToResend.forEach {
|
||||
sendEvent(it)
|
||||
// need to clear also image sending queue
|
||||
workManagerProvider.workManager
|
||||
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.REPLACE, it)
|
||||
.enqueue()
|
||||
}
|
||||
localEchoRepository.updateSendState(roomId, eventsToResend.mapNotNull { it.eventId }, SendState.UNSENT)
|
||||
}
|
||||
}
|
||||
|
||||
override fun sendMedia(attachment: ContentAttachmentData): Cancelable {
|
||||
// Create an event with the media file path
|
||||
val event = localEchoEventFactory.createMediaEvent(roomId, attachment).also {
|
||||
createLocalEcho(it)
|
||||
}
|
||||
return internalSendMedia(event, attachment)
|
||||
}
|
||||
|
||||
private fun internalSendMedia(localEcho: Event, attachment: ContentAttachmentData): Cancelable {
|
||||
val isRoomEncrypted = cryptoService.isRoomEncrypted(roomId)
|
||||
|
||||
val uploadWork = createUploadMediaWork(localEcho, attachment, isRoomEncrypted, startChain = true)
|
||||
val sendWork = createSendEventWork(localEcho, false)
|
||||
|
||||
if (isRoomEncrypted) {
|
||||
val encryptWork = createEncryptEventWork(localEcho, false /*not start of chain, take input error*/)
|
||||
|
||||
val op: Operation = workManagerProvider.workManager
|
||||
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
|
||||
.then(encryptWork)
|
||||
.then(sendWork)
|
||||
.enqueue()
|
||||
op.result.addListener(Runnable {
|
||||
if (op.result.isCancelled) {
|
||||
Timber.e("CHAIN WAS CANCELLED")
|
||||
} else if (op.state.value is Operation.State.FAILURE) {
|
||||
Timber.e("CHAIN DID FAIL")
|
||||
}
|
||||
}, workerFutureListenerExecutor)
|
||||
} else {
|
||||
workManagerProvider.workManager
|
||||
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
|
||||
.then(sendWork)
|
||||
.enqueue()
|
||||
}
|
||||
|
||||
return CancelableWork(workManagerProvider.workManager, sendWork.id)
|
||||
}
|
||||
|
||||
private fun createLocalEcho(event: Event) {
|
||||
localEchoEventFactory.createLocalEcho(event)
|
||||
}
|
||||
|
||||
private fun buildWorkName(identifier: String): String {
|
||||
return "${roomId}_$identifier"
|
||||
}
|
||||
|
||||
private fun createEncryptEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||
// Same parameter
|
||||
val params = EncryptEventWorker.Params(sessionId, roomId, event)
|
||||
val sendWorkData = WorkerParamsFactory.toData(params)
|
||||
|
||||
return workManagerProvider.matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
|
||||
.setConstraints(WorkManagerProvider.workConstraints)
|
||||
.setInputData(sendWorkData)
|
||||
.startChain(startChain)
|
||||
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||
val sendContentWorkerParams = SendEventWorker.Params(sessionId, roomId, event)
|
||||
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||
|
||||
return timelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
|
||||
}
|
||||
|
||||
private fun createRedactEventWork(event: Event, reason: String?): OneTimeWorkRequest {
|
||||
val redactEvent = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason).also {
|
||||
createLocalEcho(it)
|
||||
}
|
||||
val sendContentWorkerParams = RedactEventWorker.Params(sessionId, redactEvent.eventId!!, roomId, event.eventId, reason)
|
||||
val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||
return timelineSendEventWorkCommon.createWork<RedactEventWorker>(redactWorkData, true)
|
||||
}
|
||||
|
||||
private fun createUploadMediaWork(event: Event,
|
||||
attachment: ContentAttachmentData,
|
||||
isRoomEncrypted: Boolean,
|
||||
startChain: Boolean): OneTimeWorkRequest {
|
||||
val uploadMediaWorkerParams = UploadContentWorker.Params(sessionId, roomId, event, attachment, isRoomEncrypted)
|
||||
val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams)
|
||||
|
||||
return workManagerProvider.matrixOneTimeWorkRequestBuilder<UploadContentWorker>()
|
||||
.setConstraints(WorkManagerProvider.workConstraints)
|
||||
.startChain(startChain)
|
||||
.setInputData(uploadWorkData)
|
||||
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
taskExecutor.executorScope.launch {
|
||||
localEchoRepository.clearSendingQueue(roomId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun resendAllFailedMessages() {
|
||||
taskExecutor.executorScope.launch {
|
||||
val eventsToResend = localEchoRepository.getAllFailedEventsToResend(roomId)
|
||||
eventsToResend.forEach {
|
||||
sendEvent(it)
|
||||
}
|
||||
localEchoRepository.updateSendState(roomId, eventsToResend.mapNotNull { it.eventId }, SendState.UNSENT)
|
||||
}
|
||||
}
|
||||
|
||||
override fun sendMedia(attachment: ContentAttachmentData): Cancelable {
|
||||
// Create an event with the media file path
|
||||
val event = localEchoEventFactory.createMediaEvent(roomId, attachment).also {
|
||||
createLocalEcho(it)
|
||||
}
|
||||
return internalSendMedia(event, attachment)
|
||||
}
|
||||
|
||||
private fun internalSendMedia(localEcho: Event, attachment: ContentAttachmentData): Cancelable {
|
||||
val isRoomEncrypted = cryptoService.isRoomEncrypted(roomId)
|
||||
|
||||
val uploadWork = createUploadMediaWork(localEcho, attachment, isRoomEncrypted, startChain = true)
|
||||
val sendWork = createSendEventWork(localEcho, false)
|
||||
|
||||
if (isRoomEncrypted) {
|
||||
val encryptWork = createEncryptEventWork(localEcho, false /*not start of chain, take input error*/)
|
||||
|
||||
val op: Operation = workManagerProvider.workManager
|
||||
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
|
||||
.then(encryptWork)
|
||||
.then(sendWork)
|
||||
.enqueue()
|
||||
op.result.addListener(Runnable {
|
||||
if (op.result.isCancelled) {
|
||||
Timber.e("CHAIN WAS CANCELLED")
|
||||
} else if (op.state.value is Operation.State.FAILURE) {
|
||||
Timber.e("CHAIN DID FAIL")
|
||||
}
|
||||
}, workerFutureListenerExecutor)
|
||||
} else {
|
||||
workManagerProvider.workManager
|
||||
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
|
||||
.then(sendWork)
|
||||
.enqueue()
|
||||
}
|
||||
|
||||
return CancelableWork(workManagerProvider.workManager, sendWork.id)
|
||||
}
|
||||
|
||||
private fun createLocalEcho(event: Event) {
|
||||
localEchoEventFactory.createLocalEcho(event)
|
||||
}
|
||||
|
||||
private fun buildWorkName(identifier: String): String {
|
||||
return "${roomId}_$identifier"
|
||||
}
|
||||
|
||||
private fun createEncryptEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||
// Same parameter
|
||||
val params = EncryptEventWorker.Params(sessionId, roomId, event)
|
||||
val sendWorkData = WorkerParamsFactory.toData(params)
|
||||
|
||||
return workManagerProvider.matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
|
||||
.setConstraints(WorkManagerProvider.workConstraints)
|
||||
.setInputData(sendWorkData)
|
||||
.startChain(startChain)
|
||||
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||
val sendContentWorkerParams = SendEventWorker.Params(sessionId, roomId, event)
|
||||
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||
|
||||
return timelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
|
||||
}
|
||||
|
||||
private fun createRedactEventWork(event: Event, reason: String?): OneTimeWorkRequest {
|
||||
val redactEvent = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason).also {
|
||||
createLocalEcho(it)
|
||||
}
|
||||
val sendContentWorkerParams = RedactEventWorker.Params(sessionId, redactEvent.eventId!!, roomId, event.eventId, reason)
|
||||
val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||
return timelineSendEventWorkCommon.createWork<RedactEventWorker>(redactWorkData, true)
|
||||
}
|
||||
|
||||
private fun createUploadMediaWork(event: Event,
|
||||
attachment: ContentAttachmentData,
|
||||
isRoomEncrypted: Boolean,
|
||||
startChain: Boolean): OneTimeWorkRequest {
|
||||
val uploadMediaWorkerParams = UploadContentWorker.Params(sessionId, roomId, event, attachment, isRoomEncrypted)
|
||||
val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams)
|
||||
|
||||
return workManagerProvider.matrixOneTimeWorkRequestBuilder<UploadContentWorker>()
|
||||
.setConstraints(WorkManagerProvider.workConstraints)
|
||||
.startChain(startChain)
|
||||
.setInputData(uploadWorkData)
|
||||
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,8 +42,8 @@ import im.vector.matrix.android.api.session.room.model.message.MessageTextConten
|
|||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.OptionItems
|
||||
import im.vector.matrix.android.api.session.room.model.message.OptionsType
|
||||
import im.vector.matrix.android.api.session.room.model.message.OPTION_TYPE_POLL
|
||||
import im.vector.matrix.android.api.session.room.model.message.OptionItem
|
||||
import im.vector.matrix.android.api.session.room.model.message.ThumbnailInfo
|
||||
import im.vector.matrix.android.api.session.room.model.message.VideoInfo
|
||||
import im.vector.matrix.android.api.session.room.model.message.isReply
|
||||
|
@ -153,13 +153,13 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
|
||||
fun createPollEvent(roomId: String,
|
||||
question: String,
|
||||
options: List<Pair<String, String>>): Event {
|
||||
options: List<OptionItem>): Event {
|
||||
val compatLabel = buildString {
|
||||
append("[Poll] ")
|
||||
append(question)
|
||||
append("\n")
|
||||
options.forEach {
|
||||
append("\n")
|
||||
append(it.second)
|
||||
append(it.value)
|
||||
}
|
||||
}
|
||||
return createEvent(
|
||||
|
@ -167,10 +167,8 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
MessageOptionsContent(
|
||||
body = compatLabel,
|
||||
label = question,
|
||||
optionType = OptionsType.POLL.value,
|
||||
options = options.map {
|
||||
OptionItems(it.first, it.second)
|
||||
}
|
||||
optionType = OPTION_TYPE_POLL,
|
||||
options = options.toList()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -265,11 +265,11 @@ object CommandParser {
|
|||
}
|
||||
Command.POLL.command -> {
|
||||
val rawCommand = textMessage.substring(Command.POLL.command.length).trim()
|
||||
val splited = rawCommand.split("|").map { it.trim() }
|
||||
if (splited.size > 2) {
|
||||
ParsedCommand.SendPoll(splited[0], splited.subList(1, splited.size))
|
||||
val split = rawCommand.split("|").map { it.trim() }
|
||||
if (split.size > 2) {
|
||||
ParsedCommand.SendPoll(split[0], split.subList(1, split.size))
|
||||
} else {
|
||||
ParsedCommand.ErrorUnknownSlashCommand(slashCommand)
|
||||
ParsedCommand.ErrorSyntax(Command.POLL)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
|
|
|
@ -53,8 +53,7 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
|||
data class ResendMessage(val eventId: String) : RoomDetailAction()
|
||||
data class RemoveFailedEcho(val eventId: String) : RoomDetailAction()
|
||||
|
||||
data class ReplyToOptionsPoll(val eventId: String, val optionIndex: Int, val optionValue: String) : RoomDetailAction()
|
||||
data class ReplyToOptionsButtons(val eventId: String, val optionIndex: Int, val optionValue: String) : RoomDetailAction()
|
||||
data class ReplyToOptions(val eventId: String, val optionIndex: Int, val optionValue: String) : RoomDetailAction()
|
||||
|
||||
data class ReportContent(
|
||||
val eventId: String,
|
||||
|
|
|
@ -43,6 +43,7 @@ import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
|||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
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.OptionItem
|
||||
import im.vector.matrix.android.api.session.room.model.message.getFileUrl
|
||||
import im.vector.matrix.android.api.session.room.model.tombstone.RoomTombstoneContent
|
||||
import im.vector.matrix.android.api.session.room.read.ReadService
|
||||
|
@ -199,8 +200,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action)
|
||||
is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages()
|
||||
is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages()
|
||||
is RoomDetailAction.ReplyToOptionsPoll -> replyToPoll(action)
|
||||
is RoomDetailAction.ReplyToOptionsButtons -> replyToButtons(action)
|
||||
is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action)
|
||||
is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
|
||||
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
|
||||
is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
|
||||
|
@ -424,8 +424,8 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
is ParsedCommand.SendPoll -> {
|
||||
room.sendPoll(slashCommandResult.question, slashCommandResult.options.mapIndexed { index, s -> s to "$index. $s" })
|
||||
is ParsedCommand.SendPoll -> {
|
||||
room.sendPoll(slashCommandResult.question, slashCommandResult.options.mapIndexed { index, s -> OptionItem(s, "$index. $s") })
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
|
@ -862,11 +862,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
}
|
||||
}
|
||||
|
||||
private fun replyToPoll(action: RoomDetailAction.ReplyToOptionsPoll) {
|
||||
room.sendOptionsReply(action.eventId, action.optionIndex, action.optionValue)
|
||||
}
|
||||
|
||||
private fun replyToButtons(action: RoomDetailAction.ReplyToOptionsButtons) {
|
||||
private fun handleReplyToOptions(action: RoomDetailAction.ReplyToOptions) {
|
||||
room.sendOptionsReply(action.eventId, action.optionIndex, action.optionValue)
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,8 @@ import im.vector.matrix.android.api.session.room.model.message.MessageTextConten
|
|||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.OptionsType
|
||||
import im.vector.matrix.android.api.session.room.model.message.OPTION_TYPE_BUTTONS
|
||||
import im.vector.matrix.android.api.session.room.model.message.OPTION_TYPE_POLL
|
||||
import im.vector.matrix.android.api.session.room.model.message.getFileUrl
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||
|
@ -152,24 +153,29 @@ class MessageItemFactory @Inject constructor(
|
|||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? {
|
||||
if (messageContent.optionType == OptionsType.POLL.value) {
|
||||
return MessagePollItem_()
|
||||
.attributes(attributes)
|
||||
.callback(callback)
|
||||
.informationData(informationData)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.optionsContent(messageContent)
|
||||
.highlighted(highlight)
|
||||
} else if (messageContent.optionType == OptionsType.BUTTONS.value) {
|
||||
return MessageOptionsItem_()
|
||||
.attributes(attributes)
|
||||
.callback(callback)
|
||||
.informationData(informationData)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.optionsContent(messageContent)
|
||||
.highlighted(highlight)
|
||||
} else {
|
||||
return null
|
||||
return when (messageContent.optionType) {
|
||||
OPTION_TYPE_POLL -> {
|
||||
MessagePollItem_()
|
||||
.attributes(attributes)
|
||||
.callback(callback)
|
||||
.informationData(informationData)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.optionsContent(messageContent)
|
||||
.highlighted(highlight)
|
||||
}
|
||||
OPTION_TYPE_BUTTONS -> {
|
||||
MessageOptionsItem_()
|
||||
.attributes(attributes)
|
||||
.callback(callback)
|
||||
.informationData(informationData)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.optionsContent(messageContent)
|
||||
.highlighted(highlight)
|
||||
}
|
||||
else -> {
|
||||
// Not supported optionType
|
||||
buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -60,8 +60,7 @@ abstract class MessageOptionsItem : AbsMessageItem<MessageOptionsItem.Holder>()
|
|||
holder.buttonContainer.addView(materialButton, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
materialButton.text = option.label
|
||||
materialButton.setOnClickListener {
|
||||
callback?.onTimelineItemAction(RoomDetailAction.ReplyToOptionsButtons(relatedEventId, index, option.value
|
||||
?: "$index"))
|
||||
callback?.onTimelineItemAction(RoomDetailAction.ReplyToOptions(relatedEventId, index, option.value ?: "$index"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -145,8 +145,7 @@ abstract class MessagePollItem : AbsMessageItem<MessagePollItem.Holder>() {
|
|||
val optionIndex = buttons.indexOf(it)
|
||||
if (optionIndex != -1 && pollId != null) {
|
||||
val compatValue = if (optionIndex < optionValues?.size ?: 0) optionValues?.get(optionIndex) else null
|
||||
callback?.onTimelineItemAction(RoomDetailAction.ReplyToOptionsPoll(pollId!!, optionIndex, compatValue
|
||||
?: "$optionIndex"))
|
||||
callback?.onTimelineItemAction(RoomDetailAction.ReplyToOptions(pollId!!, optionIndex, compatValue ?: "$optionIndex"))
|
||||
}
|
||||
})
|
||||
buttons.forEach { it.setOnClickListener(clickListener) }
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
android:id="@+id/optionLabelText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:textStyle="normal"
|
||||
tools:text="What would you like to do?" />
|
||||
|
||||
|
|
|
@ -11,22 +11,24 @@
|
|||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="4dp"
|
||||
android:tint="@color/riotx_accent"
|
||||
android:src="@drawable/ic_poll" />
|
||||
android:src="@drawable/ic_poll"
|
||||
android:tint="@color/riotx_accent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pollLabelText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="4dp"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="What would you like to do?" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
|
@ -34,21 +36,27 @@
|
|||
style="@style/Style.Vector.Poll.Button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="Create Github issue" />
|
||||
android:visibility="gone"
|
||||
tools:text="Create Github issue"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/pollButton2"
|
||||
style="@style/Style.Vector.Poll.Button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="Search Github" />
|
||||
android:visibility="gone"
|
||||
tools:text="Search Github"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/pollButton3"
|
||||
style="@style/Style.Vector.Poll.Button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="Logout" />
|
||||
android:visibility="gone"
|
||||
tools:text="Logout"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/pollButton4"
|
||||
|
@ -56,7 +64,8 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
tools:text="Option 4" />
|
||||
tools:text="Option 4"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/pollButton5"
|
||||
|
@ -64,7 +73,8 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
tools:text="Option 5" />
|
||||
tools:text="Option 5"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/pollResultsWrapper"
|
||||
|
@ -73,7 +83,9 @@
|
|||
android:background="@drawable/bg_attachment_type_selector"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp">
|
||||
android:paddingEnd="8dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<im.vector.riotx.features.home.room.detail.timeline.item.PollResultLineView
|
||||
android:id="@+id/pollResult1"
|
||||
|
@ -121,9 +133,11 @@
|
|||
android:id="@+id/pollInfosText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start"
|
||||
android:layout_marginTop="2dp"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
android:textSize="12sp"
|
||||
tools:text="12 votes - Final Results" />
|
||||
android:visibility="gone"
|
||||
tools:text="12 votes - Final Results"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
|
@ -1,6 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.button.MaterialButton xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="@style/VectorButtonStyleInlineBot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="Create Github issue" />
|
||||
tools:text="Create Github issue" />
|
||||
|
|
Loading…
Reference in New Issue