Timeline: quick implementation of reactions (without interactions)
This commit is contained in:
parent
9f358c43a6
commit
78d60622db
7 changed files with 102 additions and 12 deletions
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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 ->
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue