Message Poll UX, and model
This commit is contained in:
parent
7c5bb4ff5b
commit
a0aebed3f7
@ -26,4 +26,6 @@ object RelationType {
|
|||||||
const val REPLACE = "m.replace"
|
const val REPLACE = "m.replace"
|
||||||
/** Lets you define an event which references an existing event.*/
|
/** Lets you define an event which references an existing event.*/
|
||||||
const val REFERENCE = "m.reference"
|
const val REFERENCE = "m.reference"
|
||||||
|
/** Lets you define an event which references an existing event.*/
|
||||||
|
const val RESPONSE = "m.response"
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
* 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
|
||||||
|
|
||||||
|
enum class OptionsType(val value: String) {
|
||||||
|
POLL("m.pool"),
|
||||||
|
BUTTONS("m.buttons"),
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Polls and bot buttons are m.room.message events with a msgtype of m.options,
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class MessageOptionsContent(
|
||||||
|
@Json(name = "msgtype") override val type: String,
|
||||||
|
@Json(name = "type") val optionType: String? = null,
|
||||||
|
@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 = "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 type: 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
|
@ -25,6 +25,9 @@ object MessageType {
|
|||||||
const val MSGTYPE_VIDEO = "m.video"
|
const val MSGTYPE_VIDEO = "m.video"
|
||||||
const val MSGTYPE_LOCATION = "m.location"
|
const val MSGTYPE_LOCATION = "m.location"
|
||||||
const val MSGTYPE_FILE = "m.file"
|
const val MSGTYPE_FILE = "m.file"
|
||||||
|
const val MSGTYPE_OPTIONS = "m.options"
|
||||||
|
const val MSGTYPE_RESPONSE = "m.response"
|
||||||
|
const val MSGTYPE_POLL_CLOSED = "m.poll_closed"
|
||||||
const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request"
|
const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request"
|
||||||
// Add, in local, a fake message type in order to StickerMessage can inherit Message class
|
// Add, in local, a fake message type in order to StickerMessage can inherit Message class
|
||||||
// Because sticker isn't a message type but a event type without msgtype field
|
// Because sticker isn't a message type but a event type without msgtype field
|
||||||
|
@ -25,5 +25,6 @@ data class ReactionInfo(
|
|||||||
@Json(name = "event_id") override val eventId: String,
|
@Json(name = "event_id") override val eventId: String,
|
||||||
val key: String,
|
val key: String,
|
||||||
// always null for reaction
|
// always null for reaction
|
||||||
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null
|
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null,
|
||||||
|
@Json(name = "option") override val option: Int? = null
|
||||||
) : RelationContent
|
) : RelationContent
|
||||||
|
@ -23,4 +23,5 @@ interface RelationContent {
|
|||||||
val type: String?
|
val type: String?
|
||||||
val eventId: String?
|
val eventId: String?
|
||||||
val inReplyTo: ReplyToContent?
|
val inReplyTo: ReplyToContent?
|
||||||
|
val option: Int?
|
||||||
}
|
}
|
||||||
|
@ -22,5 +22,6 @@ import com.squareup.moshi.JsonClass
|
|||||||
data class RelationDefaultContent(
|
data class RelationDefaultContent(
|
||||||
@Json(name = "rel_type") override val type: String?,
|
@Json(name = "rel_type") override val type: String?,
|
||||||
@Json(name = "event_id") override val eventId: String?,
|
@Json(name = "event_id") override val eventId: String?,
|
||||||
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null
|
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null,
|
||||||
|
@Json(name = "option") override val option: Int? = null
|
||||||
) : RelationContent
|
) : RelationContent
|
||||||
|
@ -61,6 +61,13 @@ interface SendService {
|
|||||||
*/
|
*/
|
||||||
fun sendMedias(attachments: List<ContentAttachmentData>): Cancelable
|
fun sendMedias(attachments: List<ContentAttachmentData>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to send a list of media asynchronously.
|
||||||
|
* @param attachments the list of media to send
|
||||||
|
* @return a [Cancelable]
|
||||||
|
*/
|
||||||
|
fun sendPollReply(pollEventId: String, optionIndex: Int, optionValue: String): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redacts (delete) the given event.
|
* Redacts (delete) the given event.
|
||||||
* @param event The event to redact
|
* @param event The event to redact
|
||||||
|
@ -46,6 +46,7 @@ object MoshiProvider {
|
|||||||
.registerSubtype(MessageVideoContent::class.java, MessageType.MSGTYPE_VIDEO)
|
.registerSubtype(MessageVideoContent::class.java, MessageType.MSGTYPE_VIDEO)
|
||||||
.registerSubtype(MessageLocationContent::class.java, MessageType.MSGTYPE_LOCATION)
|
.registerSubtype(MessageLocationContent::class.java, MessageType.MSGTYPE_LOCATION)
|
||||||
.registerSubtype(MessageFileContent::class.java, MessageType.MSGTYPE_FILE)
|
.registerSubtype(MessageFileContent::class.java, MessageType.MSGTYPE_FILE)
|
||||||
|
.registerSubtype(MessageOptionsContent::class.java, MessageType.MSGTYPE_OPTIONS)
|
||||||
.registerSubtype(MessageVerificationRequestContent::class.java, MessageType.MSGTYPE_VERIFICATION_REQUEST)
|
.registerSubtype(MessageVerificationRequestContent::class.java, MessageType.MSGTYPE_VERIFICATION_REQUEST)
|
||||||
)
|
)
|
||||||
.add(SerializeNulls.JSON_ADAPTER_FACTORY)
|
.add(SerializeNulls.JSON_ADAPTER_FACTORY)
|
||||||
|
@ -84,6 +84,13 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||||||
return sendEvent(event)
|
return sendEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun sendPollReply(pollEventId: String, optionIndex: Int, optionValue: String): Cancelable {
|
||||||
|
val event = localEchoEventFactory.createPollReplyEvent(roomId, pollEventId, optionIndex, optionValue).also {
|
||||||
|
saveLocalEcho(it)
|
||||||
|
}
|
||||||
|
return sendEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
private fun sendEvent(event: Event): Cancelable {
|
private fun sendEvent(event: Event): Cancelable {
|
||||||
// Encrypted room handling
|
// Encrypted room handling
|
||||||
return if (cryptoService.isRoomEncrypted(roomId)) {
|
return if (cryptoService.isRoomEncrypted(roomId)) {
|
||||||
|
@ -36,6 +36,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
|||||||
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageFormat
|
import im.vector.matrix.android.api.session.room.model.message.MessageFormat
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessagePollResponseContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||||
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.model.message.MessageVerificationRequestContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
||||||
@ -132,6 +133,21 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createPollReplyEvent(roomId: String,
|
||||||
|
pollEventId: String,
|
||||||
|
optionIndex: Int,
|
||||||
|
optionLabel: String): Event {
|
||||||
|
return createEvent(roomId,
|
||||||
|
MessagePollResponseContent(
|
||||||
|
body = optionLabel,
|
||||||
|
relatesTo = RelationDefaultContent(
|
||||||
|
type = RelationType.RESPONSE,
|
||||||
|
option = optionIndex,
|
||||||
|
eventId = pollEventId)
|
||||||
|
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
fun createReplaceTextOfReply(roomId: String,
|
fun createReplaceTextOfReply(roomId: String,
|
||||||
eventReplaced: TimelineEvent,
|
eventReplaced: TimelineEvent,
|
||||||
originalEvent: TimelineEvent,
|
originalEvent: TimelineEvent,
|
||||||
|
@ -53,6 +53,8 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
|||||||
data class ResendMessage(val eventId: String) : RoomDetailAction()
|
data class ResendMessage(val eventId: String) : RoomDetailAction()
|
||||||
data class RemoveFailedEcho(val eventId: String) : RoomDetailAction()
|
data class RemoveFailedEcho(val eventId: String) : RoomDetailAction()
|
||||||
|
|
||||||
|
data class ReplyToPoll(val eventId: String, val optionIndex: Int, val optionValue: String) : RoomDetailAction()
|
||||||
|
|
||||||
data class ReportContent(
|
data class ReportContent(
|
||||||
val eventId: String,
|
val eventId: String,
|
||||||
val senderId: String?,
|
val senderId: String?,
|
||||||
|
@ -199,6 +199,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||||||
is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action)
|
is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action)
|
||||||
is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages()
|
is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages()
|
||||||
is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages()
|
is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages()
|
||||||
|
is RoomDetailAction.ReplyToPoll -> replyToPoll(action)
|
||||||
is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
|
is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
|
||||||
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
|
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
|
||||||
is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
|
is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
|
||||||
@ -855,6 +856,10 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun replyToPoll(action: RoomDetailAction.ReplyToPoll) {
|
||||||
|
room.sendPollReply(action.eventId, action.optionIndex, action.optionValue)
|
||||||
|
}
|
||||||
|
|
||||||
private fun observeSyncState() {
|
private fun observeSyncState() {
|
||||||
session.rx()
|
session.rx()
|
||||||
.liveSyncState()
|
.liveSyncState()
|
||||||
|
@ -33,6 +33,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageEmoteConte
|
|||||||
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageOptionsContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||||
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.model.message.MessageVerificationRequestContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
||||||
@ -57,7 +58,6 @@ import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformat
|
|||||||
import im.vector.riotx.features.home.room.detail.timeline.helper.MessageItemAttributesFactory
|
import im.vector.riotx.features.home.room.detail.timeline.helper.MessageItemAttributesFactory
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.AbsMessageItem
|
import im.vector.riotx.features.home.room.detail.timeline.item.AbsMessageItem
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem
|
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageBlockCodeItem
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageBlockCodeItem
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageBlockCodeItem_
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageBlockCodeItem_
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageFileItem
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageFileItem
|
||||||
@ -137,10 +137,21 @@ class MessageItemFactory @Inject constructor(
|
|||||||
is MessageFileContent -> buildFileMessageItem(messageContent, informationData, highlight, callback, attributes)
|
is MessageFileContent -> buildFileMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, highlight, callback, attributes)
|
is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes)
|
is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
|
is MessageOptionsContent -> buildPollMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback)
|
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildPollMessageItem(messageContent: MessageOptionsContent, informationData: MessageInformationData, highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? {
|
||||||
|
return MessagePollItem_()
|
||||||
|
.attributes(attributes)
|
||||||
|
.callback(callback)
|
||||||
|
.informationData(informationData)
|
||||||
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
|
.optionsContent(messageContent)
|
||||||
|
.highlighted(highlight)
|
||||||
|
}
|
||||||
|
|
||||||
private fun buildAudioMessageItem(messageContent: MessageAudioContent,
|
private fun buildAudioMessageItem(messageContent: MessageAudioContent,
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
informationData: MessageInformationData,
|
informationData: MessageInformationData,
|
||||||
@ -228,9 +239,10 @@ class MessageItemFactory @Inject constructor(
|
|||||||
private fun buildNotHandledMessageItem(messageContent: MessageContent,
|
private fun buildNotHandledMessageItem(messageContent: MessageContent,
|
||||||
informationData: MessageInformationData,
|
informationData: MessageInformationData,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
callback: TimelineEventController.Callback?): DefaultItem? {
|
callback: TimelineEventController.Callback?,
|
||||||
val text = stringProvider.getString(R.string.rendering_event_error_type_of_message_not_handled, messageContent.msgType)
|
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
||||||
return defaultItemFactory.create(text, informationData, highlight, callback)
|
// For compatibility reason we should display the body
|
||||||
|
return buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildImageMessageItem(messageContent: MessageImageInfoContent,
|
private fun buildImageMessageItem(messageContent: MessageImageInfoContent,
|
||||||
|
@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
* 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.riotx.features.home.room.detail.timeline.item
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageOptionsContent
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.extensions.setTextOrHide
|
||||||
|
import im.vector.riotx.core.utils.DebouncedClickListener
|
||||||
|
import im.vector.riotx.features.home.room.detail.RoomDetailAction
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
|
||||||
|
abstract class MessagePollItem : AbsMessageItem<MessagePollItem.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var optionsContent: MessageOptionsContent? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var callback: TimelineEventController.Callback? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var informationData: MessageInformationData? = null
|
||||||
|
|
||||||
|
override fun getViewType() = STUB_ID
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
|
|
||||||
|
holder.pollId = informationData?.eventId
|
||||||
|
holder.callback = callback
|
||||||
|
holder.optionValues = optionsContent?.options?.map { it.value ?: it.label }
|
||||||
|
|
||||||
|
renderSendState(holder.view, holder.labelText)
|
||||||
|
|
||||||
|
holder.labelText.setTextOrHide(optionsContent?.label)
|
||||||
|
|
||||||
|
val buttons = listOf(holder.button1, holder.button2, holder.button3, holder.button4, holder.button5)
|
||||||
|
|
||||||
|
buttons.forEach { it.isVisible = false }
|
||||||
|
|
||||||
|
optionsContent?.options?.forEachIndexed { index, item ->
|
||||||
|
if (index < buttons.size) {
|
||||||
|
buttons[index].let {
|
||||||
|
it.text = item.label
|
||||||
|
it.isVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val resultLines = listOf(holder.result1, holder.result2, holder.result3, holder.result4, holder.result5)
|
||||||
|
|
||||||
|
resultLines.forEach { it.isVisible = false }
|
||||||
|
optionsContent?.options?.forEachIndexed { index, item ->
|
||||||
|
if (index < resultLines.size) {
|
||||||
|
resultLines[index].let {
|
||||||
|
it.label = item.label
|
||||||
|
it.optionSelected = index == 0
|
||||||
|
it.percent = "20%"
|
||||||
|
it.isVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
holder.infoText.text = holder.view.context.resources.getQuantityString(R.plurals.poll_info, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun unbind(holder: Holder) {
|
||||||
|
holder.pollId = null
|
||||||
|
holder.callback = null
|
||||||
|
holder.optionValues = null
|
||||||
|
super.unbind(holder)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : AbsMessageItem.Holder(STUB_ID) {
|
||||||
|
|
||||||
|
var pollId: String? = null
|
||||||
|
var optionValues : List<String?>? = null
|
||||||
|
var callback: TimelineEventController.Callback? = null
|
||||||
|
|
||||||
|
val button1 by bind<Button>(R.id.pollButton1)
|
||||||
|
val button2 by bind<Button>(R.id.pollButton2)
|
||||||
|
val button3 by bind<Button>(R.id.pollButton3)
|
||||||
|
val button4 by bind<Button>(R.id.pollButton4)
|
||||||
|
val button5 by bind<Button>(R.id.pollButton5)
|
||||||
|
|
||||||
|
val result1 by bind<PollResultLineView>(R.id.pollResult1)
|
||||||
|
val result2 by bind<PollResultLineView>(R.id.pollResult2)
|
||||||
|
val result3 by bind<PollResultLineView>(R.id.pollResult3)
|
||||||
|
val result4 by bind<PollResultLineView>(R.id.pollResult4)
|
||||||
|
val result5 by bind<PollResultLineView>(R.id.pollResult5)
|
||||||
|
|
||||||
|
val labelText by bind<TextView>(R.id.pollLabelText)
|
||||||
|
val infoText by bind<TextView>(R.id.pollInfosText)
|
||||||
|
|
||||||
|
override fun bindView(itemView: View) {
|
||||||
|
super.bindView(itemView)
|
||||||
|
val buttons = listOf(button1, button2, button3, button4, button5)
|
||||||
|
val clickListener = DebouncedClickListener(View.OnClickListener {
|
||||||
|
val optionIndex = buttons.indexOf(it)
|
||||||
|
if (optionIndex != -1 && pollId != null) {
|
||||||
|
val compatValue = if (optionIndex < optionValues?.size ?: 0) optionValues?.get(optionIndex) else null
|
||||||
|
callback?.onAction(RoomDetailAction.ReplyToPoll(pollId!!, optionIndex, compatValue ?: "$optionIndex"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
buttons.forEach { it.setOnClickListener(clickListener) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val STUB_ID = R.id.messagePollStub
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
* 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.riotx.features.home.room.detail.timeline.item
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import butterknife.BindView
|
||||||
|
import butterknife.ButterKnife
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.extensions.setTextOrHide
|
||||||
|
|
||||||
|
class PollResultLineView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
|
@BindView(R.id.pollResultItemLabel)
|
||||||
|
lateinit var labelTextView: TextView
|
||||||
|
|
||||||
|
@BindView(R.id.pollResultItemPercent)
|
||||||
|
lateinit var percentTextView: TextView
|
||||||
|
|
||||||
|
@BindView(R.id.pollResultItemSelectedIcon)
|
||||||
|
lateinit var selectedIcon: ImageView
|
||||||
|
|
||||||
|
var label: String? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
labelTextView.setTextOrHide(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
var percent: String? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
percentTextView.setTextOrHide(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
var optionSelected: Boolean = false
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
selectedIcon.visibility = if (value) View.VISIBLE else View.INVISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
inflate(context, R.layout.item_timeline_event_poll_result_item, this)
|
||||||
|
orientation = HORIZONTAL
|
||||||
|
ButterKnife.bind(this)
|
||||||
|
|
||||||
|
val typedArray = context.obtainStyledAttributes(attrs,
|
||||||
|
R.styleable.PollResultLineView, 0, 0)
|
||||||
|
label = typedArray.getString(R.styleable.PollResultLineView_optionName) ?: ""
|
||||||
|
percent = typedArray.getString(R.styleable.PollResultLineView_optionCount) ?: ""
|
||||||
|
optionSelected = typedArray.getBoolean(R.styleable.PollResultLineView_optionSelected, false)
|
||||||
|
}
|
||||||
|
}
|
@ -105,6 +105,13 @@
|
|||||||
android:layout_marginEnd="56dp"
|
android:layout_marginEnd="56dp"
|
||||||
android:layout="@layout/item_timeline_event_redacted_stub" />
|
android:layout="@layout/item_timeline_event_redacted_stub" />
|
||||||
|
|
||||||
|
<ViewStub
|
||||||
|
android:id="@+id/messagePollStub"
|
||||||
|
style="@style/TimelineContentStubBaseParams"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="56dp"
|
||||||
|
android:layout="@layout/item_timeline_event_poll_stub" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
tools:parentTag="android.widget.LinearLayout">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/pollResultItemSelectedIcon"
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="2dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:paddingEnd="2dp"
|
||||||
|
android:src="@drawable/ic_check_white_24dp"
|
||||||
|
android:tint="?riotx_text_secondary"
|
||||||
|
android:contentDescription="@string/poll_item_selected_aria" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pollResultItemLabel"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="Open a Github Issue" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pollResultItemPercent"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="47%" />
|
||||||
|
</merge>
|
108
vector/src/main/res/layout/item_timeline_event_poll_stub.xml
Normal file
108
vector/src/main/res/layout/item_timeline_event_poll_stub.xml
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pollLabelText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="What would you like to do?" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/pollButton1"
|
||||||
|
style="@style/Style.Vector.Poll.Button"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="Create Github issue" />
|
||||||
|
|
||||||
|
<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" />
|
||||||
|
|
||||||
|
<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" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/pollButton4"
|
||||||
|
style="@style/Style.Vector.Poll.Button"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="Option 4" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/pollButton5"
|
||||||
|
style="@style/Style.Vector.Poll.Button"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="Option 5" />
|
||||||
|
|
||||||
|
<im.vector.riotx.features.home.room.detail.timeline.item.PollResultLineView
|
||||||
|
android:id="@+id/pollResult1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
tools:optionName="Create Github issue"
|
||||||
|
tools:optionCount="40%"
|
||||||
|
tools:optionSelected="true"
|
||||||
|
/>
|
||||||
|
<im.vector.riotx.features.home.room.detail.timeline.item.PollResultLineView
|
||||||
|
android:id="@+id/pollResult2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
tools:optionName="Search Github"
|
||||||
|
tools:optionCount="60%"
|
||||||
|
tools:optionSelected="false"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<im.vector.riotx.features.home.room.detail.timeline.item.PollResultLineView
|
||||||
|
android:id="@+id/pollResult3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
tools:optionName="Logout"
|
||||||
|
tools:optionCount="0%"
|
||||||
|
tools:optionSelected="false"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<im.vector.riotx.features.home.room.detail.timeline.item.PollResultLineView
|
||||||
|
android:id="@+id/pollResult4"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
tools:optionName="Option 4"
|
||||||
|
tools:optionCount="0%"
|
||||||
|
tools:optionSelected="false"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<im.vector.riotx.features.home.room.detail.timeline.item.PollResultLineView
|
||||||
|
android:id="@+id/pollResult5"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
tools:optionName="Option 5"
|
||||||
|
tools:optionCount="0%"
|
||||||
|
tools:optionSelected="false"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pollInfosText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="?riotx_text_secondary"
|
||||||
|
android:gravity="start"
|
||||||
|
android:textSize="12sp"
|
||||||
|
tools:text="12 votes - Final Results" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -97,4 +97,11 @@
|
|||||||
<attr name="riotx_highlighted_message_background" format="reference" />
|
<attr name="riotx_highlighted_message_background" format="reference" />
|
||||||
|
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
||||||
|
<declare-styleable name="PollResultLineView">
|
||||||
|
<attr name="optionName" format="string" localization="suggested" />
|
||||||
|
<attr name="optionCount" format="string" />
|
||||||
|
<attr name="optionSelected" format="boolean" />
|
||||||
|
</declare-styleable>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -6,7 +6,15 @@
|
|||||||
<!-- Sections has been created to avoid merge conflict. Let's see if it's better -->
|
<!-- Sections has been created to avoid merge conflict. Let's see if it's better -->
|
||||||
|
|
||||||
<!-- BEGIN Strings added by Valere -->
|
<!-- BEGIN Strings added by Valere -->
|
||||||
|
<plurals name="poll_info">
|
||||||
|
<item quantity="zero">%d vote</item>
|
||||||
|
<item quantity="other">%d votes</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="poll_info_final">
|
||||||
|
<item quantity="zero">%d vote - Final results</item>
|
||||||
|
<item quantity="other">%d votes - Final results</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="poll_item_selected_aria">Selected Option</string>
|
||||||
<!-- END Strings added by Valere -->
|
<!-- END Strings added by Valere -->
|
||||||
|
|
||||||
|
|
||||||
|
@ -183,6 +183,14 @@
|
|||||||
<item name="colorControlHighlight">@android:color/white</item>
|
<item name="colorControlHighlight">@android:color/white</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<style name="Style.Vector.Poll.Button" parent="Widget.MaterialComponents.Button.OutlinedButton">
|
||||||
|
<item name="android:minHeight">44dp</item>
|
||||||
|
<item name="android:textAllCaps">false</item>
|
||||||
|
<item name="cornerRadius">10dp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<style name="VectorSearchView" parent="Widget.AppCompat.SearchView">
|
<style name="VectorSearchView" parent="Widget.AppCompat.SearchView">
|
||||||
<item name="searchIcon">@drawable/ic_search</item>
|
<item name="searchIcon">@drawable/ic_search</item>
|
||||||
<item name="closeIcon">@drawable/ic_x_green</item>
|
<item name="closeIcon">@drawable/ic_x_green</item>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user