Timeline: quick implementation of reactions (without interactions)

This commit is contained in:
ganfra 2022-11-09 19:36:12 +01:00
parent 9f358c43a6
commit 78d60622db
7 changed files with 102 additions and 12 deletions

View file

@ -14,6 +14,7 @@ dependencies {
implementation(libs.mavericks.compose)
implementation(libs.timber)
implementation(libs.datetime)
implementation(libs.accompanist.flowlayout)
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.3")
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")

View file

@ -2,7 +2,9 @@ package io.element.android.x.features.messages
import io.element.android.x.designsystem.components.avatar.AvatarData
import io.element.android.x.designsystem.components.avatar.AvatarSize
import io.element.android.x.features.messages.model.AggregatedReaction
import io.element.android.x.features.messages.model.MessagesItemGroupPosition
import io.element.android.x.features.messages.model.MessagesItemReactionState
import io.element.android.x.features.messages.model.MessagesTimelineItemState
import io.element.android.x.features.messages.model.content.*
import io.element.android.x.matrix.MatrixClient
@ -51,6 +53,7 @@ class MessageTimelineItemStateMapper(
val senderAvatarData =
loadAvatarData(senderDisplayName ?: currentSender, senderAvatarUrl)
return MessagesTimelineItemState.MessageEvent(
id = currentTimelineItem.event.eventId() ?: "",
senderId = currentSender,
@ -58,10 +61,18 @@ class MessageTimelineItemStateMapper(
senderAvatar = senderAvatarData,
content = currentTimelineItem.computeContent(),
isMine = currentTimelineItem.event.isOwn(),
groupPosition = groupPosition
groupPosition = groupPosition,
reactionsState = currentTimelineItem.computeReactionsState()
)
}
private fun MatrixTimelineItem.Event.computeReactionsState(): MessagesItemReactionState {
val aggregatedReactions = event.reactions().map {
AggregatedReaction(key = it.key, count = it.count.toString(), isHighlighted = false)
}
return MessagesItemReactionState(aggregatedReactions)
}
private fun MatrixTimelineItem.Event.computeContent(): MessagesTimelineItemContent {
val content = event.content()
content.asUnableToDecrypt()?.let { encryptedMessage ->

View file

@ -21,6 +21,8 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.End
import androidx.compose.ui.Alignment.Companion.Start
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
@ -36,10 +38,7 @@ import com.airbnb.mvrx.compose.mavericksViewModel
import io.element.android.x.core.data.LogCompositions
import io.element.android.x.core.data.StableCharSequence
import io.element.android.x.designsystem.components.avatar.AvatarData
import io.element.android.x.features.messages.components.MessagesTimelineItemEncryptedView
import io.element.android.x.features.messages.components.MessagesTimelineItemRedactedView
import io.element.android.x.features.messages.components.MessagesTimelineItemTextView
import io.element.android.x.features.messages.components.MessagesTimelineItemUnknownView
import io.element.android.x.features.messages.components.*
import io.element.android.x.features.messages.model.MessagesItemGroupPosition
import io.element.android.x.features.messages.model.MessagesTimelineItemState
import io.element.android.x.features.messages.model.MessagesViewState
@ -213,25 +212,25 @@ fun MessageEventRow(
messageEvent: MessagesTimelineItemState.MessageEvent,
modifier: Modifier = Modifier
) {
val contentAlignment = if (messageEvent.isMine) {
Alignment.CenterEnd
val (parentAlignment, contentAlignment) = if (messageEvent.isMine) {
Pair(Alignment.CenterEnd, End)
} else {
Alignment.CenterStart
Pair(Alignment.CenterStart, Start)
}
Box(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
contentAlignment = contentAlignment
contentAlignment = parentAlignment
) {
Row(
modifier = modifier
.widthIn(max = 300.dp)
.widthIn(max = 300.dp),
) {
if (!messageEvent.isMine) {
Spacer(modifier = Modifier.width(16.dp))
}
Column {
Column(horizontalAlignment = contentAlignment) {
if (messageEvent.showSenderInformation) {
MessageSenderInformation(
messageEvent.safeSenderName,
@ -245,7 +244,7 @@ fun MessageEventRow(
modifier = Modifier
.zIndex(-1f)
) {
val contentModifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp)
val contentModifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp)
when (messageEvent.content) {
is MessagesTimelineItemEncryptedContent -> MessagesTimelineItemEncryptedView(
content = messageEvent.content,
@ -265,6 +264,13 @@ fun MessageEventRow(
)
}
}
MessagesReactionsView(
reactionsState = messageEvent.reactionsState,
modifier = Modifier
.zIndex(1f)
.offset(x = if (messageEvent.isMine) 0.dp else 20.dp, y = -(16.dp))
)
}
if (messageEvent.isMine) {
Spacer(modifier = Modifier.width(16.dp))

View file

@ -0,0 +1,55 @@
package io.element.android.x.features.messages.components
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.google.accompanist.flowlayout.FlowRow
import io.element.android.x.features.messages.model.AggregatedReaction
import io.element.android.x.features.messages.model.MessagesItemReactionState
@Composable
fun MessagesReactionsView(
reactionsState: MessagesItemReactionState,
modifier: Modifier = Modifier,
) {
FlowRow(
modifier = modifier,
mainAxisSpacing = 2.dp,
crossAxisSpacing = 8.dp,
) {
reactionsState.reactions.forEach { reaction ->
MessagesReactionButton(reaction = reaction)
}
}
}
@Composable
fun MessagesReactionButton(reaction: AggregatedReaction, modifier: Modifier = Modifier) {
Surface(
modifier = modifier,
color = MaterialTheme.colorScheme.surfaceVariant,
border = BorderStroke(2.dp, MaterialTheme.colorScheme.background),
shape = RoundedCornerShape(corner = CornerSize(12.dp)),
) {
Row(
modifier = Modifier.padding(vertical = 5.dp, horizontal = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(text = reaction.key, fontSize = 12.sp)
Spacer(modifier = Modifier.width(4.dp))
Text(text = reaction.count, color = MaterialTheme.colorScheme.secondary, fontSize = 12.sp)
}
}
}

View file

@ -0,0 +1,15 @@
package io.element.android.x.features.messages.model
import androidx.compose.runtime.Stable
@Stable
data class MessagesItemReactionState(
val reactions: List<AggregatedReaction>
)
@Stable
data class AggregatedReaction(
val key: String,
val count: String,
val isHighlighted: Boolean = false
)

View file

@ -17,6 +17,7 @@ sealed interface MessagesTimelineItemState {
val sentTime: String = "",
val isMine: Boolean = false,
val groupPosition: MessagesItemGroupPosition = MessagesItemGroupPosition.None,
val reactionsState: MessagesItemReactionState
) : MessagesTimelineItemState {
val showSenderInformation = groupPosition.isNew() && !isMine

View file

@ -65,6 +65,7 @@ accompanist_systemui = { module = "com.google.accompanist:accompanist-systemuico
accompanist_placeholder = { module = "com.google.accompanist:accompanist-placeholder-material", version.ref = "accompanist" }
accompanist_pager = { module = "com.google.accompanist:accompanist-pager", version.ref = "accompanist" }
accompanist_pagerindicator = { module = "com.google.accompanist:accompanist-pager-indicators", version.ref = "accompanist" }
accompanist_flowlayout = { module = "com.google.accompanist:accompanist-flowlayout", version.ref = "accompanist" }
# Test
test_junit = { module = "junit:junit", version.ref = "test_junit" }