Update quick reactions to new design

This commit is contained in:
Valere 2019-06-24 16:13:58 +02:00
parent 92eb7d55dc
commit 43ead66991
13 changed files with 145 additions and 413 deletions

View File

@ -63,19 +63,6 @@ interface RelationService {
fun undoReaction(reaction: String, targetEventId: String, myUserId: String)//: Cancelable


/**
* Update a quick reaction (toggle).
* If you have reacted with agree and then you click on disagree, this call will delete(redact)
* the disagree and add the agree
* If you click on a reaction that you already reacted with, it will undo it
* @param reaction the reaction (preferably emoji)
* @param oppositeReaction the opposite reaction(preferably emoji)
* @param targetEventId the id of the event being reacted
* @param myUserId used to know if a reaction event was made by the user
*/
fun updateQuickReaction(reaction: String, oppositeReaction: String, targetEventId: String, myUserId: String)


/**
* Edit a text message body. Limited to "m.text" contentType
* @param targetEventId The event to edit

View File

@ -29,7 +29,6 @@ import im.vector.matrix.android.internal.session.room.read.DefaultReadService
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
import im.vector.matrix.android.internal.session.room.relation.DefaultRelationService
import im.vector.matrix.android.internal.session.room.relation.FindReactionEventForUndoTask
import im.vector.matrix.android.internal.session.room.relation.UpdateQuickReactionTask
import im.vector.matrix.android.internal.session.room.send.DefaultSendService
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
import im.vector.matrix.android.internal.session.room.state.DefaultStateService
@ -51,7 +50,6 @@ internal class RoomFactory(private val monarchy: Monarchy,
private val setReadMarkersTask: SetReadMarkersTask,
private val cryptoService: CryptoService,
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
private val updateQuickReactionTask: UpdateQuickReactionTask,
private val joinRoomTask: JoinRoomTask,
private val leaveRoomTask: LeaveRoomTask) {

@ -64,7 +62,7 @@ internal class RoomFactory(private val monarchy: Monarchy,
val stateService = DefaultStateService(roomId, taskExecutor, sendStateTask)
val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask)
val readService = DefaultReadService(roomId, monarchy, taskExecutor, setReadMarkersTask)
val reactionService = DefaultRelationService(roomId, eventFactory, findReactionEventForUndoTask, updateQuickReactionTask, monarchy, taskExecutor)
val reactionService = DefaultRelationService(roomId, eventFactory, findReactionEventForUndoTask, monarchy, taskExecutor)

return DefaultRoom(
roomId,

View File

@ -32,9 +32,7 @@ import im.vector.matrix.android.internal.session.room.prune.PruneEventTask
import im.vector.matrix.android.internal.session.room.read.DefaultSetReadMarkersTask
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
import im.vector.matrix.android.internal.session.room.relation.DefaultFindReactionEventForUndoTask
import im.vector.matrix.android.internal.session.room.relation.DefaultUpdateQuickReactionTask
import im.vector.matrix.android.internal.session.room.relation.FindReactionEventForUndoTask
import im.vector.matrix.android.internal.session.room.relation.UpdateQuickReactionTask
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
import im.vector.matrix.android.internal.session.room.send.LocalEchoUpdater
import im.vector.matrix.android.internal.session.room.state.DefaultSendStateTask
@ -82,7 +80,7 @@ class RoomModule {
}

scope(DefaultSession.SCOPE) {
RoomFactory(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get())
RoomFactory(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get())
}

scope(DefaultSession.SCOPE) {
@ -109,10 +107,6 @@ class RoomModule {
DefaultFindReactionEventForUndoTask(get()) as FindReactionEventForUndoTask
}

scope(DefaultSession.SCOPE) {
DefaultUpdateQuickReactionTask(get()) as UpdateQuickReactionTask
}

scope(DefaultSession.SCOPE) {
DefaultPruneEventTask(get()) as PruneEventTask
}

View File

@ -46,7 +46,6 @@ import timber.log.Timber
internal class DefaultRelationService(private val roomId: String,
private val eventFactory: LocalEchoEventFactory,
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
private val updateQuickReactionTask: UpdateQuickReactionTask,
private val monarchy: Monarchy,
private val taskExecutor: TaskExecutor)
: RelationService {
@ -105,31 +104,6 @@ internal class DefaultRelationService(private val roomId: String,
}


override fun updateQuickReaction(reaction: String, oppositeReaction: String, targetEventId: String, myUserId: String) {

val params = UpdateQuickReactionTask.Params(
roomId,
targetEventId,
reaction,
oppositeReaction,
myUserId
)

updateQuickReactionTask.configureWith(params)
.dispatchTo(object : MatrixCallback<UpdateQuickReactionTask.Result> {
override fun onSuccess(data: UpdateQuickReactionTask.Result) {
data.reactionToAdd?.also { sendReaction(it, targetEventId) }
data.reactionToRedact.forEach {
val redactEvent = eventFactory.createRedactEvent(roomId, it, null).also {
saveLocalEcho(it)
}
val redactWork = createRedactEventWork(redactEvent, it, null)
TimelineSendEventWorkCommon.postWork(roomId, redactWork)
}
}
})
.executeBy(taskExecutor)
}

private fun buildWorkIdentifier(identifier: String): String {
return "${roomId}_$identifier"

View File

@ -1,89 +0,0 @@
/*
* Copyright 2019 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.internal.session.room.relation

import arrow.core.Try
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntityFields
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.task.Task
import io.realm.Realm


internal interface UpdateQuickReactionTask : Task<UpdateQuickReactionTask.Params, UpdateQuickReactionTask.Result> {

data class Params(
val roomId: String,
val eventId: String,
val reaction: String,
val oppositeReaction: String,
val myUserId: String
)

data class Result(
val reactionToAdd: String?,
val reactionToRedact: List<String>
)
}

internal class DefaultUpdateQuickReactionTask(private val monarchy: Monarchy) : UpdateQuickReactionTask {
override suspend fun execute(params: UpdateQuickReactionTask.Params): Try<UpdateQuickReactionTask.Result> {
return Try {
var res: Pair<String?, List<String>?>? = null
monarchy.doWithRealm { realm ->
res = updateQuickReaction(realm, params.reaction, params.oppositeReaction, params.eventId, params.myUserId)
}
UpdateQuickReactionTask.Result(res?.first, res?.second ?: emptyList())
}
}


private fun updateQuickReaction(realm: Realm, reaction: String, oppositeReaction: String, eventId: String, myUserId: String): Pair<String?, List<String>?> {
//the emoji reaction has been selected, we need to check if we have reacted it or not
val existingSummary = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
?: return Pair(reaction, null)

//Ok there is already reactions on this event, have we reacted to it
val aggregationForReaction = existingSummary.reactionsSummary.where()
.equalTo(ReactionAggregatedSummaryEntityFields.KEY, reaction)
.findFirst()
val aggregationForOppositeReaction = existingSummary.reactionsSummary.where()
.equalTo(ReactionAggregatedSummaryEntityFields.KEY, oppositeReaction)
.findFirst()

if (aggregationForReaction == null || !aggregationForReaction.addedByMe) {
//i haven't yet reacted to it, so need to add it, but do I need to redact the opposite?
val toRedact = aggregationForOppositeReaction?.sourceEvents?.mapNotNull {
//find source event
val entity = EventEntity.where(realm, it).findFirst()
if (entity?.sender == myUserId) entity.eventId else null
}
return Pair(reaction, toRedact)
} else {
//I already added it, so i need to undo it (like a toggle)
// find all m.redaction coming from me to readact them
val toRedact = aggregationForReaction.sourceEvents.mapNotNull {
//find source event
val entity = EventEntity.where(realm, it).findFirst()
if (entity?.sender == myUserId) entity.eventId else null
}
return Pair(null, toRedact)
}

}
}

View File

@ -30,7 +30,7 @@ sealed class RoomDetailActions {
data class SendReaction(val reaction: String, val targetEventId: String) : RoomDetailActions()
data class RedactAction(val targetEventId: String, val reason: String? = "") : RoomDetailActions()
data class UndoReaction(val targetEventId: String, val key: String, val reason: String? = "") : RoomDetailActions()
data class UpdateQuickReactAction(val targetEventId: String, val selectedReaction: String, val opposite: String) : RoomDetailActions()
data class UpdateQuickReactAction(val targetEventId: String, val selectedReaction: String, val add: Boolean) : RoomDetailActions()
data class ShowEditHistoryAction(val event: String, val editAggregatedSummary: EditAggregatedSummary) : RoomDetailActions()
object AcceptInvite : RoomDetailActions()
object RejectInvite : RoomDetailActions()

View File

@ -681,9 +681,9 @@ class RoomDetailFragment :
.show()
}
MessageMenuViewModel.ACTION_QUICK_REACT -> {
//eventId,ClickedOn,Opposite
(actionData.data as? Triple<String, String, String>)?.let { (eventId, clickedOn, opposite) ->
roomDetailViewModel.process(RoomDetailActions.UpdateQuickReactAction(eventId, clickedOn, opposite))
//eventId,ClickedOn,Add
(actionData.data as? Triple<String, String, Boolean>)?.let { (eventId, clickedOn, add) ->
roomDetailViewModel.process(RoomDetailActions.UpdateQuickReactAction(eventId, clickedOn, add))
}
}
MessageMenuViewModel.ACTION_EDIT -> {

View File

@ -333,7 +333,11 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,


private fun handleUpdateQuickReaction(action: RoomDetailActions.UpdateQuickReactAction) {
room.updateQuickReaction(action.selectedReaction, action.opposite, action.targetEventId, session.sessionParams.credentials.userId)
if (action.add) {
room.sendReaction(action.selectedReaction, action.targetEventId)
} else {
room.undoReaction(action.selectedReaction, action.targetEventId, session.sessionParams.credentials.userId)
}
}

private fun handleSendMedia(action: RoomDetailActions.SendMedia) {

View File

@ -94,8 +94,9 @@ class MessageActionsBottomSheet : BaseMvRxBottomSheetDialog() {
.commit()
}
quickReactionFragment.interactionListener = object : QuickReactionFragment.InteractionListener {
override fun didQuickReactWith(clikedOn: String, opposite: String, reactions: List<String>, eventId: String) {
actionHandlerModel.fireAction(MessageMenuViewModel.ACTION_QUICK_REACT, Triple(eventId, clikedOn, opposite))

override fun didQuickReactWith(clikedOn: String, add: Boolean, eventId: String) {
actionHandlerModel.fireAction(MessageMenuViewModel.ACTION_QUICK_REACT, Triple(eventId, clikedOn, add))
dismiss()
}
}

View File

@ -20,10 +20,6 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.transition.TransitionManager
import butterknife.BindView
import butterknife.ButterKnife
import com.airbnb.mvrx.BaseMvRxFragment
import com.airbnb.mvrx.MvRx
@ -31,6 +27,7 @@ import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.riotredesign.EmojiCompatFontProvider
import im.vector.riotredesign.R
import kotlinx.android.synthetic.main.adapter_item_action_quick_reaction.*
import org.koin.android.ext.android.inject

/**
@ -40,21 +37,6 @@ class QuickReactionFragment : BaseMvRxFragment() {

private val viewModel: QuickReactionViewModel by fragmentViewModel(QuickReactionViewModel::class)

@BindView(R.id.root_layout)
lateinit var rootLayout: ConstraintLayout

@BindView(R.id.quick_react_1_text)
lateinit var quickReact1Text: TextView

@BindView(R.id.quick_react_2_text)
lateinit var quickReact2Text: TextView

@BindView(R.id.quick_react_3_text)
lateinit var quickReact3Text: TextView

@BindView(R.id.quick_react_4_text)
lateinit var quickReact4Text: TextView

var interactionListener: InteractionListener? = null

val fontProvider by inject<EmojiCompatFontProvider>()
@ -65,77 +47,38 @@ class QuickReactionFragment : BaseMvRxFragment() {
return view
}

private val textViews by lazy {
listOf(quickReaction0, quickReaction1, quickReaction2, quickReaction3,
quickReaction4, quickReaction5, quickReaction6, quickReaction7)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

quickReact1Text.text = QuickReactionViewModel.agreePositive
quickReact2Text.text = QuickReactionViewModel.agreeNegative
quickReact3Text.text = QuickReactionViewModel.likePositive
quickReact4Text.text = QuickReactionViewModel.likeNegative

listOf(quickReact1Text, quickReact2Text, quickReact3Text, quickReact4Text).forEach {
it.typeface = fontProvider.typeface ?: Typeface.DEFAULT
}

//configure click listeners
quickReact1Text.setOnClickListener {
viewModel.toggleAgree(true)
}
quickReact2Text.setOnClickListener {
viewModel.toggleAgree(false)
}
quickReact3Text.setOnClickListener {
viewModel.toggleLike(true)
}
quickReact4Text.setOnClickListener {
viewModel.toggleLike(false)
textViews.forEachIndexed { index, textView ->
textView.typeface = fontProvider.typeface ?: Typeface.DEFAULT
textView.setOnClickListener {
viewModel.didSelect(index)
}
}

}

override fun invalidate() = withState(viewModel) {

TransitionManager.beginDelayedTransition(rootLayout)
when (it.agreeTrigleState) {
TriggleState.NONE -> {
quickReact1Text.alpha = 1f
quickReact2Text.alpha = 1f
}
TriggleState.FIRST -> {
quickReact1Text.alpha = 1f
quickReact2Text.alpha = 0.2f

}
TriggleState.SECOND -> {
quickReact1Text.alpha = 0.2f
quickReact2Text.alpha = 1f
}
}
when (it.likeTriggleState) {
TriggleState.NONE -> {
quickReact3Text.alpha = 1f
quickReact4Text.alpha = 1f
}
TriggleState.FIRST -> {
quickReact3Text.alpha = 1f
quickReact4Text.alpha = 0.2f

}
TriggleState.SECOND -> {
quickReact3Text.alpha = 0.2f
quickReact4Text.alpha = 1f
}
it.quickStates.forEachIndexed { index, qs ->
textViews[index].text = qs.reaction
textViews[index].alpha = if (qs.isSelected) 0.2f else 1f
}

if (it.selectionResult != null) {
val clikedOn = it.selectionResult.first
interactionListener?.didQuickReactWith(clikedOn, QuickReactionViewModel.getOpposite(clikedOn)
?: "", it.selectionResult.second, it.eventId)
if (it.result != null) {
interactionListener?.didQuickReactWith(it.result.reaction, it.result.isSelected, it.eventId)
}

}

interface InteractionListener {
fun didQuickReactWith(clikedOn: String, opposite: String, reactions: List<String>, eventId: String)
fun didQuickReactWith(clikedOn: String, add: Boolean, eventId: String)
}

companion object {

View File

@ -25,18 +25,16 @@ import org.koin.android.ext.android.get
/**
* Quick reactions state, it's a toggle with 3rd state
*/
enum class TriggleState {
NONE,
FIRST,
SECOND
}
data class ToggleState(
val reaction: String,
val isSelected: Boolean
)

data class QuickReactionState(
val agreeTrigleState: TriggleState = TriggleState.NONE,
val likeTriggleState: TriggleState = TriggleState.NONE,
/** Pair of 'clickedOn' and current toggles state*/
val selectionResult: Pair<String, List<String>>? = null,
val eventId: String = "") : MvRxState
val quickStates: List<ToggleState>,
val eventId: String = "",
val result: ToggleState? = null
) : MvRxState

/**
* Quick reaction view model
@ -44,107 +42,28 @@ data class QuickReactionState(
class QuickReactionViewModel(initialState: QuickReactionState) : VectorViewModel<QuickReactionState>(initialState) {


fun toggleAgree(isFirst: Boolean) = withState {
if (isFirst) {
setState {
val newTriggle = if (it.agreeTrigleState == TriggleState.FIRST) TriggleState.NONE else TriggleState.FIRST
copy(
agreeTrigleState = newTriggle,
selectionResult = Pair(agreePositive, getReactions(this, newTriggle, null))
)
}
} else {
setState {
val newTriggle = if (it.agreeTrigleState == TriggleState.SECOND) TriggleState.NONE else TriggleState.SECOND
copy(
agreeTrigleState = agreeTrigleState,
selectionResult = Pair(agreeNegative, getReactions(this, newTriggle, null))
)
}
fun didSelect(index: Int) = withState {
val isSelected = it.quickStates[index].isSelected
setState {
copy(result = ToggleState(it.quickStates[index].reaction, !isSelected))
}
}

fun toggleLike(isFirst: Boolean) = withState {
if (isFirst) {
setState {
val newTriggle = if (it.likeTriggleState == TriggleState.FIRST) TriggleState.NONE else TriggleState.FIRST
copy(
likeTriggleState = newTriggle,
selectionResult = Pair(likePositive, getReactions(this, null, newTriggle))
)
}
} else {
setState {
val newTriggle = if (it.likeTriggleState == TriggleState.SECOND) TriggleState.NONE else TriggleState.SECOND
copy(
likeTriggleState = newTriggle,
selectionResult = Pair(likeNegative, getReactions(this, null, newTriggle))
)
}
}
}

private fun getReactions(state: QuickReactionState, newState1: TriggleState?, newState2: TriggleState?): List<String> {
return ArrayList<String>(4).apply {
when (newState2 ?: state.likeTriggleState) {
TriggleState.FIRST -> add(likePositive)
TriggleState.SECOND -> add(likeNegative)
else -> {
}
}
when (newState1 ?: state.agreeTrigleState) {
TriggleState.FIRST -> add(agreePositive)
TriggleState.SECOND -> add(agreeNegative)
else -> {
}
}
}
}


companion object : MvRxViewModelFactory<QuickReactionViewModel, QuickReactionState> {

val agreePositive = "👍"
val agreeNegative = "👎"
val likePositive = "🙂"
val likeNegative = "😔"

fun getOpposite(reaction: String): String? {
return when (reaction) {
agreePositive -> agreeNegative
agreeNegative -> agreePositive
likePositive -> likeNegative
likeNegative -> likePositive
else -> null
}
}
val quickEmojis = listOf("👍", "👎", "😀", "🎉", "😕", "❤️", "🚀", "👀")

override fun initialState(viewModelContext: ViewModelContext): QuickReactionState? {
// Args are accessible from the context.
// val foo = vieWModelContext.args<MyArgs>.foo
val currentSession = viewModelContext.activity.get<Session>()
val parcel = viewModelContext.args as TimelineEventFragmentArgs
val event = currentSession.getRoom(parcel.roomId)?.getTimeLineEvent(parcel.eventId)
?: return null
var agreeTriggle: TriggleState = TriggleState.NONE
var likeTriggle: TriggleState = TriggleState.NONE
event.annotations?.reactionsSummary?.forEach {
//it.addedByMe
if (it.addedByMe) {
if (agreePositive == it.key) {
agreeTriggle = TriggleState.FIRST
} else if (agreeNegative == it.key) {
agreeTriggle = TriggleState.SECOND
}

if (likePositive == it.key) {
likeTriggle = TriggleState.FIRST
} else if (likeNegative == it.key) {
likeTriggle = TriggleState.SECOND
}
}
val summary = event.annotations?.reactionsSummary
val quickReactions = quickEmojis.map { emoji ->
ToggleState(emoji, summary?.firstOrNull { it.key == emoji }?.addedByMe ?: false)
}
return QuickReactionState(agreeTriggle, likeTriggle, null, event.root.eventId ?: "")
return QuickReactionState(quickReactions, event.root.eventId ?: "")
}
}
}

View File

@ -4,116 +4,116 @@
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_layout"
android:layout_width="match_parent"
android:layout_height="96dp">
android:layout_height="wrap_content"
android:padding="8dp">

<TextView
android:id="@+id/quick_react_1_text"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:textColor="@color/black"
android:textSize="30sp"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toTopOf="@id/quick_react_agree_text"
app:layout_constraintEnd_toStartOf="@id/quick_react_2_text"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
android:id="@+id/quickReactionTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:text="@string/quick_reactions"
android:textColor="?riotx_text_secondary"
android:textSize="16sp"
app:layout_constraintTop_toTopOf="parent" />


<TextView
android:id="@+id/quickReaction0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:textColor="?riotx_text_secondary"
android:textSize="24sp"
tools:ignore="MissingConstraints"
tools:text="👍" />

<TextView
android:id="@+id/quick_react_2_text"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:textColor="@color/black"
android:textSize="30sp"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="@id/quick_react_1_text"
app:layout_constraintEnd_toStartOf="@id/center_guideline"
app:layout_constraintStart_toEndOf="@id/quick_react_1_text"
app:layout_constraintTop_toTopOf="@id/quick_react_1_text"
tools:text="👎" />


<TextView
android:id="@+id/quick_react_agree_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/reactions_agree"
android:textAlignment="center"
android:textColor="?riotx_text_secondary"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/center_guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/quick_react_1_text" />

<androidx.constraintlayout.widget.Guideline
android:id="@+id/center_guideline"
android:id="@+id/quickReaction1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
android:padding="4dp"
android:textColor="?riotx_text_secondary"
android:textSize="24sp"
tools:ignore="MissingConstraints"
tools:text="👎" />

<TextView
android:id="@+id/quick_react_3_text"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:textColor="@color/black"
android:textSize="30sp"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="@+id/quick_react_1_text"
app:layout_constraintEnd_toStartOf="@id/quick_react_4_text"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/center_guideline"
app:layout_constraintTop_toTopOf="@id/quick_react_1_text"
android:id="@+id/quickReaction2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:textColor="?riotx_text_secondary"
android:textSize="24sp"
tools:ignore="MissingConstraints"
tools:text="😀" />

<TextView
android:id="@+id/quick_react_4_text"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:textColor="@color/black"
android:textSize="30sp"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="@id/quick_react_3_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/quick_react_3_text"
app:layout_constraintTop_toTopOf="@id/quick_react_3_text"
tools:text="😞" />

<TextView
android:id="@+id/quick_react_like_text"
android:id="@+id/quickReaction3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:textColor="?riotx_text_secondary"
android:textSize="24sp"
tools:ignore="MissingConstraints"
tools:text="🎉" />

<TextView
android:id="@+id/quickReaction4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:textColor="?riotx_text_secondary"
android:textSize="24sp"
tools:ignore="MissingConstraints"
tools:text="😕" />

<TextView
android:id="@+id/quickReaction5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:textColor="?riotx_text_secondary"
android:textSize="24sp"
tools:ignore="MissingConstraints"
tools:text="♥" />

<TextView
android:id="@+id/quickReaction6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:textColor="?riotx_text_secondary"
android:textSize="24sp"
tools:ignore="MissingConstraints"
tools:text="🍆" />

<TextView
android:id="@+id/quickReaction7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:textColor="?riotx_text_secondary"
android:textSize="26sp"
tools:ignore="MissingConstraints"
tools:text="👀" />

<androidx.constraintlayout.helper.widget.Flow
android:id="@+id/reactionsFlowHelper"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/reactions_like"
android:textAlignment="center"
android:textColor="?riotx_text_secondary"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@id/quick_react_agree_text"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
app:constraint_referenced_ids="quickReaction0,quickReaction1,quickReaction2,quickReaction3,quickReaction4,quickReaction5,quickReaction6,quickReaction7"
app:flow_horizontalGap="8dp"
app:flow_horizontalStyle="spread"
app:flow_verticalBias="0"
app:flow_verticalGap="4dp"
app:flow_wrapMode="chain"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/center_guideline"
app:layout_constraintTop_toTopOf="@id/quick_react_agree_text" />
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/quickReactionTitle" />

</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -8,4 +8,5 @@

<string name="settings_sdk_version">Matrix SDK Version</string>

<string name="quick_reactions">Quick Reactions</string>
</resources>