mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 13:38:49 +03:00
Message Poll UX, and model
This commit is contained in:
parent
7c5bb4ff5b
commit
a0aebed3f7
21 changed files with 502 additions and 7 deletions
|
@ -26,4 +26,6 @@ object RelationType {
|
|||
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.*/
|
||||
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_LOCATION = "m.location"
|
||||
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"
|
||||
// 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
|
||||
|
|
|
@ -25,5 +25,6 @@ data class ReactionInfo(
|
|||
@Json(name = "event_id") override val eventId: String,
|
||||
val key: String,
|
||||
// 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
|
||||
|
|
|
@ -23,4 +23,5 @@ interface RelationContent {
|
|||
val type: String?
|
||||
val eventId: String?
|
||||
val inReplyTo: ReplyToContent?
|
||||
val option: Int?
|
||||
}
|
||||
|
|
|
@ -22,5 +22,6 @@ import com.squareup.moshi.JsonClass
|
|||
data class RelationDefaultContent(
|
||||
@Json(name = "rel_type") override val type: 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
|
||||
|
|
|
@ -61,6 +61,13 @@ interface SendService {
|
|||
*/
|
||||
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.
|
||||
* @param event The event to redact
|
||||
|
|
|
@ -46,6 +46,7 @@ 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)
|
||||
)
|
||||
.add(SerializeNulls.JSON_ADAPTER_FACTORY)
|
||||
|
|
|
@ -84,6 +84,13 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||
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 {
|
||||
// Encrypted room handling
|
||||
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.MessageFormat
|
||||
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.MessageType
|
||||
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,
|
||||
eventReplaced: TimelineEvent,
|
||||
originalEvent: TimelineEvent,
|
||||
|
|
|
@ -53,6 +53,8 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
|||
data class ResendMessage(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(
|
||||
val eventId: String,
|
||||
val senderId: String?,
|
||||
|
|
|
@ -199,6 +199,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action)
|
||||
is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages()
|
||||
is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages()
|
||||
is RoomDetailAction.ReplyToPoll -> replyToPoll(action)
|
||||
is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
|
||||
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(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() {
|
||||
session.rx()
|
||||
.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.MessageImageInfoContent
|
||||
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.MessageType
|
||||
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.TimelineMediaSizeProvider
|
||||
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.MessageFileItem
|
||||
|
@ -137,10 +137,21 @@ class MessageItemFactory @Inject constructor(
|
|||
is MessageFileContent -> buildFileMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageAudioContent -> buildAudioMessageItem(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)
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
informationData: MessageInformationData,
|
||||
|
@ -228,9 +239,10 @@ class MessageItemFactory @Inject constructor(
|
|||
private fun buildNotHandledMessageItem(messageContent: MessageContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?): DefaultItem? {
|
||||
val text = stringProvider.getString(R.string.rendering_event_error_type_of_message_not_handled, messageContent.msgType)
|
||||
return defaultItemFactory.create(text, informationData, highlight, callback)
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
||||
// For compatibility reason we should display the body
|
||||
return buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes)
|
||||
}
|
||||
|
||||
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="@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>
|
||||
|
||||
<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" />
|
||||
|
||||
</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>
|
||||
|
|
|
@ -6,7 +6,15 @@
|
|||
<!-- Sections has been created to avoid merge conflict. Let's see if it's better -->
|
||||
|
||||
<!-- 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 -->
|
||||
|
||||
|
||||
|
|
|
@ -183,6 +183,14 @@
|
|||
<item name="colorControlHighlight">@android:color/white</item>
|
||||
</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">
|
||||
<item name="searchIcon">@drawable/ic_search</item>
|
||||
<item name="closeIcon">@drawable/ic_x_green</item>
|
||||
|
|
Loading…
Reference in a new issue