code review and cleanup

This commit is contained in:
Benoit Marty 2020-02-12 15:06:22 +01:00
parent 9016688aec
commit 47f47e40c4
21 changed files with 290 additions and 218 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
@ -425,7 +425,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
popDraft()
}
is ParsedCommand.SendPoll -> {
room.sendPoll(slashCommandResult.question, slashCommandResult.options.mapIndexed { index, s -> s to "$index. $s" })
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)
}

View File

@ -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_()
return when (messageContent.optionType) {
OPTION_TYPE_POLL -> {
MessagePollItem_()
.attributes(attributes)
.callback(callback)
.informationData(informationData)
.leftGuideline(avatarSizeProvider.leftGuideline)
.optionsContent(messageContent)
.highlighted(highlight)
} else if (messageContent.optionType == OptionsType.BUTTONS.value) {
return MessageOptionsItem_()
}
OPTION_TYPE_BUTTONS -> {
MessageOptionsItem_()
.attributes(attributes)
.callback(callback)
.informationData(informationData)
.leftGuideline(avatarSizeProvider.leftGuideline)
.optionsContent(messageContent)
.highlighted(highlight)
} else {
return null
}
else -> {
// Not supported optionType
buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
}
}
}

View File

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

View File

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

View File

@ -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?" />

View File

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

View File

@ -1,3 +1,4 @@
<?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"