mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-26 11:26:01 +03:00
Add m.buttons support (a.k.a bot buttons)
This commit is contained in:
parent
3ac54c51f6
commit
9a7bd35ddc
18 changed files with 241 additions and 31 deletions
|
@ -68,7 +68,7 @@ interface SendService {
|
|||
* @param optionValue The option value (for compatibility)
|
||||
* @return a [Cancelable]
|
||||
*/
|
||||
fun sendPollReply(pollEventId: String, optionIndex: Int, optionValue: String): Cancelable
|
||||
fun sendOptionsReply(pollEventId: String, optionIndex: Int, optionValue: String): Cancelable
|
||||
|
||||
/**
|
||||
* @param options list of (label, value)
|
||||
|
|
|
@ -84,8 +84,8 @@ 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 {
|
||||
override fun sendOptionsReply(pollEventId: String, optionIndex: Int, optionValue: String): Cancelable {
|
||||
val event = localEchoEventFactory.createOptionsReplyEvent(roomId, pollEventId, optionIndex, optionValue).also {
|
||||
saveLocalEcho(it)
|
||||
}
|
||||
return sendEvent(event)
|
||||
|
|
|
@ -136,7 +136,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
))
|
||||
}
|
||||
|
||||
fun createPollReplyEvent(roomId: String,
|
||||
fun createOptionsReplyEvent(roomId: String,
|
||||
pollEventId: String,
|
||||
optionIndex: Int,
|
||||
optionLabel: String): Event {
|
||||
|
|
|
@ -53,7 +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 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 ReportContent(
|
||||
val eventId: String,
|
||||
|
|
|
@ -199,7 +199,8 @@ 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.ReplyToOptionsPoll -> replyToPoll(action)
|
||||
is RoomDetailAction.ReplyToOptionsButtons -> replyToButtons(action)
|
||||
is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
|
||||
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
|
||||
is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
|
||||
|
@ -861,8 +862,12 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
}
|
||||
}
|
||||
|
||||
private fun replyToPoll(action: RoomDetailAction.ReplyToPoll) {
|
||||
room.sendPollReply(action.eventId, action.optionIndex, action.optionValue)
|
||||
private fun replyToPoll(action: RoomDetailAction.ReplyToOptionsPoll) {
|
||||
room.sendOptionsReply(action.eventId, action.optionIndex, action.optionValue)
|
||||
}
|
||||
|
||||
private fun replyToButtons(action: RoomDetailAction.ReplyToOptionsButtons) {
|
||||
room.sendOptionsReply(action.eventId, action.optionIndex, action.optionValue)
|
||||
}
|
||||
|
||||
private fun observeSyncState() {
|
||||
|
|
|
@ -39,6 +39,7 @@ 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.getFileUrl
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||
|
@ -66,6 +67,7 @@ import im.vector.riotx.features.home.room.detail.timeline.item.MessageFileItem_
|
|||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageImageVideoItem
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageImageVideoItem_
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageOptionsItem_
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessagePollItem_
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem_
|
||||
|
@ -139,17 +141,18 @@ 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)
|
||||
is MessageOptionsContent -> buildOptionsMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessagePollResponseContent -> noticeItemFactory.create(event, highlight, callback)
|
||||
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildPollMessageItem(messageContent: MessageOptionsContent,
|
||||
private fun buildOptionsMessageItem(messageContent: MessageOptionsContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? {
|
||||
if (messageContent.optionType == OptionsType.POLL.value) {
|
||||
return MessagePollItem_()
|
||||
.attributes(attributes)
|
||||
.callback(callback)
|
||||
|
@ -157,6 +160,17 @@ class MessageItemFactory @Inject constructor(
|
|||
.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
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildAudioMessageItem(messageContent: MessageAudioContent,
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import com.google.android.material.button.MaterialButton
|
||||
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.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 MessageOptionsItem : AbsMessageItem<MessageOptionsItem.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)
|
||||
|
||||
renderSendState(holder.view, holder.labelText)
|
||||
|
||||
holder.labelText.setTextOrHide(optionsContent?.label)
|
||||
|
||||
holder.buttonContainer.removeAllViews()
|
||||
|
||||
val relatedEventId = informationData?.eventId ?: return
|
||||
val options = optionsContent?.options?.takeIf { it.isNotEmpty() } ?: return
|
||||
// Now add back the buttons
|
||||
options.forEachIndexed { index, option ->
|
||||
val materialButton = LayoutInflater.from(holder.view.context).inflate(R.layout.option_buttons, holder.buttonContainer, false)
|
||||
as MaterialButton
|
||||
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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Holder : AbsMessageItem.Holder(STUB_ID) {
|
||||
|
||||
val labelText by bind<TextView>(R.id.optionLabelText)
|
||||
|
||||
val buttonContainer by bind<ViewGroup>(R.id.optionsButtonContainer)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val STUB_ID = R.id.messageOptionsStub
|
||||
}
|
||||
}
|
|
@ -145,7 +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.ReplyToPoll(pollId!!, optionIndex, compatValue
|
||||
callback?.onTimelineItemAction(RoomDetailAction.ReplyToOptionsPoll(pollId!!, optionIndex, compatValue
|
||||
?: "$optionIndex"))
|
||||
}
|
||||
})
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/riotx_button_disabled_alpha12" android:state_enabled="false" />
|
||||
<item android:color="@color/riotx_button_primary_accent_alpha12" android:state_enabled="true" />
|
||||
</selector>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/button_bot_disabled_text_color" android:state_enabled="false" />
|
||||
<item android:color="@color/button_bot_enabled_text_color" />
|
||||
</selector>
|
30
vector/src/main/res/drawable/ic_poll.xml
Normal file
30
vector/src/main/res/drawable/ic_poll.xml
Normal file
|
@ -0,0 +1,30 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M3.5,0.5L20.5,0.5A3,3 0,0 1,23.5 3.5L23.5,20.5A3,3 0,0 1,20.5 23.5L3.5,23.5A3,3 0,0 1,0.5 20.5L0.5,3.5A3,3 0,0 1,3.5 0.5z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M5.5,12L6.5,12A1.5,1.5 0,0 1,8 13.5L8,19.5A1.5,1.5 0,0 1,6.5 21L5.5,21A1.5,1.5 0,0 1,4 19.5L4,13.5A1.5,1.5 0,0 1,5.5 12z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="M11.5,9L12.5,9A1.5,1.5 0,0 1,14 10.5L14,19.5A1.5,1.5 0,0 1,12.5 21L11.5,21A1.5,1.5 0,0 1,10 19.5L10,10.5A1.5,1.5 0,0 1,11.5 9z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="M17.5,6L18.5,6A1.5,1.5 0,0 1,20 7.5L20,19.5A1.5,1.5 0,0 1,18.5 21L17.5,21A1.5,1.5 0,0 1,16 19.5L16,7.5A1.5,1.5 0,0 1,17.5 6z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#00000000"/>
|
||||
</vector>
|
|
@ -112,6 +112,13 @@
|
|||
android:layout_marginEnd="56dp"
|
||||
android:layout="@layout/item_timeline_event_poll_stub" />
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/messageOptionsStub"
|
||||
style="@style/TimelineContentStubBaseParams"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="56dp"
|
||||
android:layout="@layout/item_timeline_event_option_buttons_stub" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<?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"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/optionLabelText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:textStyle="normal"
|
||||
tools:text="What would you like to do?" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/optionsButtonContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- Filled at runtime with buttons -->
|
||||
<!--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" /-->
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
|
@ -5,14 +5,29 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="4dp"
|
||||
android:tint="@color/riotx_accent"
|
||||
android:src="@drawable/ic_poll" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pollLabelText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
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
|
||||
android:id="@+id/pollButton1"
|
||||
|
|
6
vector/src/main/res/layout/option_buttons.xml
Normal file
6
vector/src/main/res/layout/option_buttons.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<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" />
|
|
@ -125,6 +125,8 @@
|
|||
<color name="button_disabled_text_color">#FFFFFFFF</color>
|
||||
<color name="button_destructive_enabled_text_color">#FF4B55</color>
|
||||
<color name="button_destructive_disabled_text_color">#FF4B55</color>
|
||||
<color name="button_bot_enabled_text_color">#FF368BD6</color>
|
||||
<color name="button_bot_disabled_text_color">#61708B</color>
|
||||
|
||||
|
||||
<!-- Link color -->
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
<color name="riotx_destructive_accent">#FFFF4B55</color>
|
||||
<color name="riotx_destructive_accent_alpha12">#1EFF4B55</color>
|
||||
<color name="riotx_button_primary_accent_alpha12">#14368BD6</color>
|
||||
|
||||
|
||||
<color name="riotx_positive_accent">#03B381</color>
|
||||
|
|
|
@ -151,6 +151,11 @@
|
|||
<item name="android:textColor">@color/button_positive_text_color_selector</item>
|
||||
</style>
|
||||
|
||||
<style name="VectorButtonStyleInlineBot" parent="VectorButtonStyleDestructive">
|
||||
<item name="backgroundTint">@color/button_bot_background_selector</item>
|
||||
<item name="android:textColor">@color/button_bot_enabled_text_color</item>
|
||||
</style>
|
||||
|
||||
<!--Widget.AppCompat.Button.Borderless.Colored, which sets the text color to colorAccent,
|
||||
using colorControlHighlight as an overlay for focused and pressed states.-->
|
||||
<style name="VectorButtonStyleText" parent="Widget.MaterialComponents.Button.TextButton">
|
||||
|
|
Loading…
Reference in a new issue