Send-as-sticker button for sticker-enabled custom emotes
Add some primitive support for sending MSC2545 stickers, at least for
stickers that also support sending as custom emote.
Also, this introduces support to sending stickers as reply this way 🎉
Change-Id: I85b245c2c40b9662342459e50285c081d37f324b
This commit is contained in:
parent
dab8f0b51c
commit
5f787db4f1
|
@ -30,7 +30,7 @@ Here you can find some extra features and changes compared to Element Android (w
|
||||||
- Setting to not alert for new messages if there's still an old notification for that room
|
- Setting to not alert for new messages if there's still an old notification for that room
|
||||||
- Setting to hide start call buttons from the room's toolbar
|
- Setting to hide start call buttons from the room's toolbar
|
||||||
- Render inline images / custom emojis in the timeline
|
- Render inline images / custom emojis in the timeline
|
||||||
- Allow sending custom emotes, if they have been set up with another compatible client ([MSC2545](https://github.com/matrix-org/matrix-spec-proposals/pull/2545))
|
- Allow sending custom emotes (and partly stickers), if they have been set up with another compatible client ([MSC2545](https://github.com/matrix-org/matrix-spec-proposals/pull/2545))
|
||||||
- Render image reactions
|
- Render image reactions
|
||||||
- Send freeform reactions
|
- Send freeform reactions
|
||||||
- Render media captions ([MSC2530](https://github.com/matrix-org/matrix-spec-proposals/pull/2530))
|
- Render media captions ([MSC2530](https://github.com/matrix-org/matrix-spec-proposals/pull/2530))
|
||||||
|
|
|
@ -227,4 +227,6 @@
|
||||||
<!-- Note to translators: the translation MUST contain the string "${app_name_sc_stable}", which will be replaced by the application name -->
|
<!-- Note to translators: the translation MUST contain the string "${app_name_sc_stable}", which will be replaced by the application name -->
|
||||||
<string name="use_latest_app_sc">Use the latest ${app_name_sc_stable} on your other devices:</string>
|
<string name="use_latest_app_sc">Use the latest ${app_name_sc_stable} on your other devices:</string>
|
||||||
|
|
||||||
|
<string name="action_send_as_sticker">Send as sticker</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.util
|
||||||
|
|
||||||
import org.matrix.android.sdk.BuildConfig
|
import org.matrix.android.sdk.BuildConfig
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.EmoteImage
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||||
|
@ -114,7 +115,9 @@ sealed class MatrixItem(
|
||||||
|
|
||||||
data class EmoteItem(override val id: String,
|
data class EmoteItem(override val id: String,
|
||||||
override val displayName: String? = null,
|
override val displayName: String? = null,
|
||||||
override val avatarUrl: String? = null) :
|
val emoteImage: EmoteImage,
|
||||||
|
override val avatarUrl: String? = emoteImage.url,
|
||||||
|
) :
|
||||||
MatrixItem(id, displayName, avatarUrl) {
|
MatrixItem(id, displayName, avatarUrl) {
|
||||||
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
|
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,9 @@
|
||||||
package org.matrix.android.sdk.internal.session.room.send.pills
|
package org.matrix.android.sdk.internal.session.room.send.pills
|
||||||
|
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
|
import org.matrix.android.sdk.api.extensions.orTrue
|
||||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
|
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomEmoteContent.Companion.USAGE_STICKER
|
||||||
import org.matrix.android.sdk.api.session.room.send.MatrixItemSpan
|
import org.matrix.android.sdk.api.session.room.send.MatrixItemSpan
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
import org.matrix.android.sdk.internal.session.displayname.DisplayNameResolver
|
import org.matrix.android.sdk.internal.session.displayname.DisplayNameResolver
|
||||||
|
@ -130,3 +132,23 @@ fun CharSequence.requiresFormattedMessage(): Boolean {
|
||||||
?: return false
|
?: return false
|
||||||
return pills.isNotEmpty()
|
return pills.isNotEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun CharSequence.asSticker(): MatrixItem.EmoteItem? {
|
||||||
|
val spannableString = SpannableString.valueOf(this)
|
||||||
|
val emotes = spannableString
|
||||||
|
?.getSpans(0, length, MatrixItemSpan::class.java)
|
||||||
|
?.map { MentionLinkSpec(it, spannableString.getSpanStart(it), spannableString.getSpanEnd(it)) }
|
||||||
|
?.filter { it.span.matrixItem is MatrixItem.EmoteItem }
|
||||||
|
if (emotes?.size == 1) {
|
||||||
|
val emote = emotes[0]
|
||||||
|
if (emote.start != 0 || emote.end != length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val emoteItem = emote.span.matrixItem as MatrixItem.EmoteItem
|
||||||
|
val emoteImage = emoteItem.emoteImage
|
||||||
|
if (emoteImage.usage?.contains(USAGE_STICKER).orTrue()) {
|
||||||
|
return emoteItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
|
@ -253,7 +253,7 @@ class AutocompleteEmojiPresenter @AssistedInject constructor(
|
||||||
}.filter {
|
}.filter {
|
||||||
query == null || it.key.contains(query, true)
|
query == null || it.key.contains(query, true)
|
||||||
}.map {
|
}.map {
|
||||||
EmojiItem(it.key, "", mxcUrl = it.value.url)
|
EmojiItem(it.key, "", emoteImage = it.value)
|
||||||
}.sortedBy { it.name }.distinctBy { it.mxcUrl }
|
}.sortedBy { it.name }.distinctBy { it.mxcUrl }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,11 +67,16 @@ class AutoCompleter @AssistedInject constructor(
|
||||||
fun create(roomId: String, isInThreadTimeline: Boolean): AutoCompleter
|
fun create(roomId: String, isInThreadTimeline: Boolean): AutoCompleter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun onAutoCompleteCustomEmote() {}
|
||||||
|
}
|
||||||
|
|
||||||
private val autocompleteCommandPresenter: AutocompleteCommandPresenter by lazy {
|
private val autocompleteCommandPresenter: AutocompleteCommandPresenter by lazy {
|
||||||
autocompleteCommandPresenterFactory.create(isInThreadTimeline)
|
autocompleteCommandPresenterFactory.create(isInThreadTimeline)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var editText: EditText? = null
|
private var editText: EditText? = null
|
||||||
|
private var callback: Callback? = null
|
||||||
|
|
||||||
fun enterSpecialMode() {
|
fun enterSpecialMode() {
|
||||||
commandAutocompletePolicy.enabled = false
|
commandAutocompletePolicy.enabled = false
|
||||||
|
@ -83,8 +88,9 @@ class AutoCompleter @AssistedInject constructor(
|
||||||
|
|
||||||
private lateinit var glideRequests: GlideRequests
|
private lateinit var glideRequests: GlideRequests
|
||||||
|
|
||||||
fun setup(editText: EditText) {
|
fun setup(editText: EditText, callback: Callback? = null) {
|
||||||
this.editText = editText
|
this.editText = editText
|
||||||
|
this.callback = callback
|
||||||
glideRequests = GlideApp.with(editText)
|
glideRequests = GlideApp.with(editText)
|
||||||
val backgroundDrawable = ColorDrawable(ThemeUtils.getColor(editText.context, android.R.attr.colorBackground))
|
val backgroundDrawable = ColorDrawable(ThemeUtils.getColor(editText.context, android.R.attr.colorBackground))
|
||||||
setupCommands(backgroundDrawable, editText)
|
setupCommands(backgroundDrawable, editText)
|
||||||
|
@ -95,6 +101,7 @@ class AutoCompleter @AssistedInject constructor(
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
this.editText = null
|
this.editText = null
|
||||||
|
this.callback = null
|
||||||
autocompleteEmojiPresenter.clear()
|
autocompleteEmojiPresenter.clear()
|
||||||
autocompleteRoomPresenter.clear()
|
autocompleteRoomPresenter.clear()
|
||||||
autocompleteCommandPresenter.clear()
|
autocompleteCommandPresenter.clear()
|
||||||
|
@ -194,13 +201,13 @@ class AutoCompleter @AssistedInject constructor(
|
||||||
|
|
||||||
// Replace the word by its completion
|
// Replace the word by its completion
|
||||||
editable.delete(startIndex, endIndex)
|
editable.delete(startIndex, endIndex)
|
||||||
if (item.mxcUrl.isNotEmpty()) {
|
if (item.emoteImage != null) {
|
||||||
// Add emote html
|
// Add emote html
|
||||||
val emote = ":${item.name}:"
|
val emote = ":${item.name}:"
|
||||||
editable.insert(startIndex, emote)
|
editable.insert(startIndex, emote)
|
||||||
|
|
||||||
// Add span to make it look nice
|
// Add span to make it look nice
|
||||||
val matrixItem = MatrixItem.EmoteItem(item.mxcUrl, item.name, item.mxcUrl)
|
val matrixItem = MatrixItem.EmoteItem(item.emoteImage.url, item.name, item.emoteImage)
|
||||||
val span = PillImageSpan(
|
val span = PillImageSpan(
|
||||||
glideRequests,
|
glideRequests,
|
||||||
avatarRenderer,
|
avatarRenderer,
|
||||||
|
@ -210,6 +217,7 @@ class AutoCompleter @AssistedInject constructor(
|
||||||
span.bind(editText)
|
span.bind(editText)
|
||||||
|
|
||||||
editable.setSpan(span, startIndex, startIndex + emote.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
editable.setSpan(span, startIndex, startIndex + emote.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
callback?.onAutoCompleteCustomEmote()
|
||||||
} else {
|
} else {
|
||||||
editable.insert(startIndex, item.emoji)
|
editable.insert(startIndex, item.emoji)
|
||||||
}
|
}
|
||||||
|
|
|
@ -676,13 +676,19 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
private fun handleSendSticker(action: RoomDetailAction.SendSticker) {
|
private fun handleSendSticker(action: RoomDetailAction.SendSticker) {
|
||||||
if (room == null) return
|
if (room == null) return
|
||||||
val content = initialState.rootThreadEventId?.let {
|
val content = initialState.rootThreadEventId?.let {
|
||||||
action.stickerContent.copy(
|
// Some sticker action might already have set this correctly, and maybe also done a real reply
|
||||||
relatesTo = RelationDefaultContent(
|
val actionRelatesTo = action.stickerContent.relatesTo
|
||||||
type = RelationType.THREAD,
|
if (actionRelatesTo?.type != RelationType.THREAD || actionRelatesTo.eventId != it) {
|
||||||
isFallingBack = true,
|
action.stickerContent.copy(
|
||||||
eventId = it
|
relatesTo = RelationDefaultContent(
|
||||||
)
|
type = RelationType.THREAD,
|
||||||
)
|
isFallingBack = true,
|
||||||
|
eventId = it
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
action.stickerContent
|
||||||
|
}
|
||||||
} ?: action.stickerContent
|
} ?: action.stickerContent
|
||||||
|
|
||||||
room.sendService().sendEvent(EventType.STICKER, content.toContent())
|
room.sendService().sendEvent(EventType.STICKER, content.toContent())
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
|
||||||
|
|
||||||
sealed class MessageComposerAction : VectorViewModelAction {
|
sealed class MessageComposerAction : VectorViewModelAction {
|
||||||
data class SendMessage(val text: CharSequence, val formattedText: String?, val autoMarkdown: Boolean) : MessageComposerAction()
|
data class SendMessage(val text: CharSequence, val formattedText: String?, val autoMarkdown: Boolean) : MessageComposerAction()
|
||||||
|
object PopDraft : MessageComposerAction() // SC
|
||||||
data class EnterEditMode(val eventId: String) : MessageComposerAction()
|
data class EnterEditMode(val eventId: String) : MessageComposerAction()
|
||||||
data class EnterQuoteMode(val eventId: String) : MessageComposerAction()
|
data class EnterQuoteMode(val eventId: String) : MessageComposerAction()
|
||||||
data class EnterReplyMode(val eventId: String) : MessageComposerAction()
|
data class EnterReplyMode(val eventId: String) : MessageComposerAction()
|
||||||
|
|
|
@ -103,6 +103,11 @@ import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.isThread
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.relation.ReplyToContent
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
import org.matrix.android.sdk.internal.session.room.send.pills.requiresFormattedMessage
|
import org.matrix.android.sdk.internal.session.room.send.pills.requiresFormattedMessage
|
||||||
import reactivecircus.flowbinding.android.view.focusChanges
|
import reactivecircus.flowbinding.android.view.focusChanges
|
||||||
|
@ -334,7 +339,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
|
||||||
composerEditText.setHint(R.string.room_message_placeholder)
|
composerEditText.setHint(R.string.room_message_placeholder)
|
||||||
|
|
||||||
if (!isRichTextEditorEnabled) {
|
if (!isRichTextEditorEnabled) {
|
||||||
autoCompleter.setup(composerEditText)
|
autoCompleter.setup(composerEditText, composer)
|
||||||
}
|
}
|
||||||
|
|
||||||
observerUserTyping()
|
observerUserTyping()
|
||||||
|
@ -402,6 +407,40 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSendSticker(sticker: MatrixItem.EmoteItem) = withState(messageComposerViewModel) { state ->
|
||||||
|
val image = sticker.emoteImage
|
||||||
|
val sendMode = state.sendMode
|
||||||
|
val relatesTo = if (sendMode is SendMode.Reply) {
|
||||||
|
state.rootThreadEventId?.let {
|
||||||
|
RelationDefaultContent(
|
||||||
|
type = RelationType.THREAD,
|
||||||
|
eventId = it,
|
||||||
|
isFallingBack = false, // sendMode is reply, this reply is intentional and not a thread fallback
|
||||||
|
inReplyTo = ReplyToContent(eventId = sendMode.timelineEvent.eventId)
|
||||||
|
)
|
||||||
|
} ?: RelationDefaultContent(null, null, ReplyToContent(eventId = sendMode.timelineEvent.eventId))
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
val stickerContent = MessageStickerContent(
|
||||||
|
body = image.body ?: sticker.displayName ?: sticker.id,
|
||||||
|
info = image.info,
|
||||||
|
url = image.url,
|
||||||
|
relatesTo = relatesTo,
|
||||||
|
)
|
||||||
|
timelineViewModel.handle(RoomDetailAction.SendSticker(stickerContent))
|
||||||
|
|
||||||
|
if (state.isFullScreen) {
|
||||||
|
messageComposerViewModel.handle(MessageComposerAction.SetFullScreen(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
messageComposerViewModel.handle(MessageComposerAction.PopDraft)
|
||||||
|
emojiPopup.dismiss()
|
||||||
|
if (vectorPreferences.jumpToBottomOnSend()) {
|
||||||
|
timelineViewModel.handle(RoomDetailAction.JumpToBottom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCloseRelatedMessage() {
|
override fun onCloseRelatedMessage() {
|
||||||
messageComposerViewModel.handle(MessageComposerAction.EnterRegularMode(false))
|
messageComposerViewModel.handle(MessageComposerAction.EnterRegularMode(false))
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,11 @@ package im.vector.app.features.home.room.detail.composer
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
|
import im.vector.app.features.home.room.detail.AutoCompleter
|
||||||
import im.vector.app.features.home.room.detail.TimelineViewModel
|
import im.vector.app.features.home.room.detail.TimelineViewModel
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
|
||||||
interface MessageComposerView {
|
interface MessageComposerView : AutoCompleter.Callback {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val MAX_LINES_WHEN_COLLAPSED = 10
|
const val MAX_LINES_WHEN_COLLAPSED = 10
|
||||||
|
@ -43,6 +45,7 @@ interface MessageComposerView {
|
||||||
interface Callback : ComposerEditText.Callback {
|
interface Callback : ComposerEditText.Callback {
|
||||||
fun onCloseRelatedMessage()
|
fun onCloseRelatedMessage()
|
||||||
fun onSendMessage(text: CharSequence)
|
fun onSendMessage(text: CharSequence)
|
||||||
|
fun onSendSticker(sticker: MatrixItem.EmoteItem)
|
||||||
fun onAddAttachment()
|
fun onAddAttachment()
|
||||||
fun onExpandOrCompactChange()
|
fun onExpandOrCompactChange()
|
||||||
fun onFullScreenModeChanged()
|
fun onFullScreenModeChanged()
|
||||||
|
|
|
@ -144,6 +144,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
is MessageComposerAction.SetFullScreen -> handleSetFullScreen(action)
|
is MessageComposerAction.SetFullScreen -> handleSetFullScreen(action)
|
||||||
// SC
|
// SC
|
||||||
MessageComposerAction.ClearFocus -> _viewEvents.post(MessageComposerViewEvents.ClearFocus)
|
MessageComposerAction.ClearFocus -> _viewEvents.post(MessageComposerViewEvents.ClearFocus)
|
||||||
|
MessageComposerAction.PopDraft -> popDraft(room)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,6 @@ import android.util.AttributeSet
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.annotation.ColorInt
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.text.toSpannable
|
import androidx.core.text.toSpannable
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
@ -54,6 +53,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
||||||
import org.matrix.android.sdk.api.util.ContentUtils.extractUsefulTextFromHtmlReply
|
import org.matrix.android.sdk.api.util.ContentUtils.extractUsefulTextFromHtmlReply
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.pills.asSticker
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -76,6 +76,7 @@ class PlainTextComposerLayout @JvmOverloads constructor(
|
||||||
private val views: ComposerLayoutScBinding
|
private val views: ComposerLayoutScBinding
|
||||||
|
|
||||||
override var callback: Callback? = null
|
override var callback: Callback? = null
|
||||||
|
private var modeSupportsSendAsSticker: Boolean = false
|
||||||
|
|
||||||
override val text: Editable?
|
override val text: Editable?
|
||||||
get() = views.composerEditText.text
|
get() = views.composerEditText.text
|
||||||
|
@ -110,6 +111,7 @@ class PlainTextComposerLayout @JvmOverloads constructor(
|
||||||
|
|
||||||
override fun onTextChanged(text: CharSequence) {
|
override fun onTextChanged(text: CharSequence) {
|
||||||
callback?.onTextChanged(text)
|
callback?.onTextChanged(text)
|
||||||
|
updateSendStickerVisibility()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
views.composerRelatedMessageCloseButton.setOnClickListener {
|
views.composerRelatedMessageCloseButton.setOnClickListener {
|
||||||
|
@ -122,6 +124,11 @@ class PlainTextComposerLayout @JvmOverloads constructor(
|
||||||
callback?.onSendMessage(textMessage)
|
callback?.onSendMessage(textMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
views.sendStickerButton.setOnClickListener {
|
||||||
|
val sticker = text?.asSticker() ?: return@setOnClickListener
|
||||||
|
callback?.onSendSticker(sticker)
|
||||||
|
}
|
||||||
|
|
||||||
views.attachmentButton.setOnClickListener {
|
views.attachmentButton.setOnClickListener {
|
||||||
callback?.onAddAttachment()
|
callback?.onAddAttachment()
|
||||||
}
|
}
|
||||||
|
@ -129,6 +136,7 @@ class PlainTextComposerLayout @JvmOverloads constructor(
|
||||||
|
|
||||||
private fun collapse(transitionComplete: (() -> Unit)? = null) {
|
private fun collapse(transitionComplete: (() -> Unit)? = null) {
|
||||||
views.relatedMessageGroup.isVisible = false
|
views.relatedMessageGroup.isVisible = false
|
||||||
|
updateSendStickerVisibility()
|
||||||
transitionComplete?.invoke()
|
transitionComplete?.invoke()
|
||||||
callback?.onExpandOrCompactChange()
|
callback?.onExpandOrCompactChange()
|
||||||
|
|
||||||
|
@ -137,21 +145,33 @@ class PlainTextComposerLayout @JvmOverloads constructor(
|
||||||
|
|
||||||
private fun expand(transitionComplete: (() -> Unit)? = null) {
|
private fun expand(transitionComplete: (() -> Unit)? = null) {
|
||||||
views.relatedMessageGroup.isVisible = true
|
views.relatedMessageGroup.isVisible = true
|
||||||
|
updateSendStickerVisibility()
|
||||||
transitionComplete?.invoke()
|
transitionComplete?.invoke()
|
||||||
callback?.onExpandOrCompactChange()
|
callback?.onExpandOrCompactChange()
|
||||||
|
|
||||||
views.attachmentButton.isVisible = false
|
views.attachmentButton.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateSendStickerVisibility() {
|
||||||
|
val canSendAsSticker = modeSupportsSendAsSticker && views.composerEditText.text?.asSticker() != null
|
||||||
|
views.sendStickerButtonDecor.isVisible = canSendAsSticker
|
||||||
|
views.sendStickerButton.isVisible = canSendAsSticker
|
||||||
|
}
|
||||||
|
|
||||||
override fun setTextIfDifferent(text: CharSequence?): Boolean {
|
override fun setTextIfDifferent(text: CharSequence?): Boolean {
|
||||||
return views.composerEditText.setTextIfDifferent(text)
|
return views.composerEditText.setTextIfDifferent(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onAutoCompleteCustomEmote() {
|
||||||
|
updateSendStickerVisibility()
|
||||||
|
}
|
||||||
|
|
||||||
override fun renderComposerMode(mode: MessageComposerMode, timelineViewModel: TimelineViewModel?) {
|
override fun renderComposerMode(mode: MessageComposerMode, timelineViewModel: TimelineViewModel?) {
|
||||||
val specialMode = mode as? MessageComposerMode.Special
|
val specialMode = mode as? MessageComposerMode.Special
|
||||||
if (specialMode != null) {
|
if (specialMode != null) {
|
||||||
renderSpecialMode(specialMode, timelineViewModel)
|
renderSpecialMode(specialMode, timelineViewModel)
|
||||||
} else if (mode is MessageComposerMode.Normal) {
|
} else if (mode is MessageComposerMode.Normal) {
|
||||||
|
modeSupportsSendAsSticker = true
|
||||||
collapse()
|
collapse()
|
||||||
editText.setTextIfDifferent(mode.content)
|
editText.setTextIfDifferent(mode.content)
|
||||||
}
|
}
|
||||||
|
@ -181,6 +201,7 @@ class PlainTextComposerLayout @JvmOverloads constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun renderSpecialMode(specialMode: MessageComposerMode.Special, timelineViewModel: TimelineViewModel?) {
|
private fun renderSpecialMode(specialMode: MessageComposerMode.Special, timelineViewModel: TimelineViewModel?) {
|
||||||
|
modeSupportsSendAsSticker = specialMode is MessageComposerMode.Reply
|
||||||
val event = specialMode.event
|
val event = specialMode.event
|
||||||
val defaultContent = specialMode.defaultContent
|
val defaultContent = specialMode.defaultContent
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.app.features.reactions.data
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.EmoteImage
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Example:
|
* Example:
|
||||||
|
@ -42,11 +43,13 @@ data class EmojiItem(
|
||||||
@Json(name = "a") val name: String,
|
@Json(name = "a") val name: String,
|
||||||
@Json(name = "b") val unicode: String,
|
@Json(name = "b") val unicode: String,
|
||||||
@Json(name = "j") val keywords: List<String> = emptyList(),
|
@Json(name = "j") val keywords: List<String> = emptyList(),
|
||||||
val mxcUrl: String = ""
|
val emoteImage: EmoteImage? = null,
|
||||||
) {
|
) {
|
||||||
// Cannot be private...
|
// Cannot be private...
|
||||||
var cache: String? = null
|
var cache: String? = null
|
||||||
|
|
||||||
|
val mxcUrl: String = emoteImage?.url ?: ""
|
||||||
|
|
||||||
val emoji: String
|
val emoji: String
|
||||||
get() {
|
get() {
|
||||||
cache?.let { return it }
|
cache?.let { return it }
|
||||||
|
|
|
@ -182,12 +182,39 @@
|
||||||
android:src="@drawable/ic_attachment"
|
android:src="@drawable/ic_attachment"
|
||||||
app:tint="?android:textColorHint"
|
app:tint="?android:textColorHint"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/sendButton"
|
app:layout_constraintBottom_toBottomOf="@id/sendButton"
|
||||||
app:layout_constraintEnd_toStartOf="@id/sendButton"
|
app:layout_constraintEnd_toStartOf="@id/sendStickerButton"
|
||||||
app:layout_constraintStart_toEndOf="@id/composerEditText"
|
app:layout_constraintStart_toEndOf="@id/composerEditText"
|
||||||
app:layout_constraintTop_toTopOf="@id/sendButton"
|
app:layout_constraintTop_toTopOf="@id/sendButton"
|
||||||
app:layout_goneMarginBottom="57dp"
|
app:layout_goneMarginBottom="57dp"
|
||||||
tools:ignore="MissingPrefix" />
|
tools:ignore="MissingPrefix" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/sendStickerButton"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="@dimen/composer_min_height"
|
||||||
|
android:contentDescription="@string/action_send_as_sticker"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:src="@drawable/ic_send"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/sendButton"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/attachmentButton"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/sendButton"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/sendButton"
|
||||||
|
tools:ignore="MissingPrefix"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/sendStickerButtonDecor"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:src="@drawable/ic_attachment_sticker"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:tint="?colorAccent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/sendStickerButton"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/sendStickerButton"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/sendButton"
|
android:id="@+id/sendButton"
|
||||||
android:layout_width="48dp"
|
android:layout_width="48dp"
|
||||||
|
@ -199,7 +226,7 @@
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@id/attachmentButton"
|
app:layout_constraintStart_toEndOf="@id/sendStickerButton"
|
||||||
tools:ignore="MissingPrefix"
|
tools:ignore="MissingPrefix"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue