From c72ecc4807606e277da5d8d24debb07c0dbd2f67 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 26 May 2023 17:10:34 +0200 Subject: [PATCH] Media: clean a bit Timestamp rendering --- .../messages/impl/timeline/TimelineView.kt | 160 +++++++----------- .../components/TimelineEventTimestampView.kt | 70 ++++++++ .../messages/impl/timeline/util/Modifiers.kt | 23 +++ 3 files changed, 155 insertions(+), 98 deletions(-) create mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt create mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/util/Modifiers.kt diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index c2e60302be..0f31652ec8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -18,7 +18,6 @@ package io.element.android.features.messages.impl.timeline import androidx.compose.animation.animateContentSize import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope @@ -43,7 +42,6 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowDownward -import androidx.compose.material.icons.filled.Error import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -54,10 +52,8 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.LastBaseline import androidx.compose.ui.res.pluralStringResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp @@ -65,6 +61,7 @@ import androidx.compose.ui.zIndex import io.element.android.features.messages.impl.R import io.element.android.features.messages.impl.timeline.components.MessageEventBubble import io.element.android.features.messages.impl.timeline.components.MessageStateEventContainer +import io.element.android.features.messages.impl.timeline.components.TimelineEventTimestampView import io.element.android.features.messages.impl.timeline.components.TimelineItemReactionsView import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView import io.element.android.features.messages.impl.timeline.components.group.GroupHeaderView @@ -74,30 +71,23 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.bubble.BubbleState import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContentProvider -import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent -import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemDaySeparatorModel import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemLoadingModel -import io.element.android.libraries.core.bool.orFalse -import io.element.android.libraries.designsystem.ElementTextStyles +import io.element.android.features.messages.impl.timeline.util.defaultTimelineContentPadding import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight -import io.element.android.libraries.designsystem.theme.ElementColors -import io.element.android.libraries.designsystem.theme.ElementTheme import io.element.android.libraries.designsystem.theme.LocalColors import io.element.android.libraries.designsystem.theme.components.FloatingActionButton import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch -import io.element.android.libraries.ui.strings.R as StringR @Composable fun TimelineView( @@ -281,7 +271,12 @@ fun TimelineItemEventRow( .zIndex(-1f) .widthIn(max = 320.dp) ) { - MessageEventLayout(event = event, onMessageClick = onClick, onMessageLongClick = onLongClick) + MessageEventBubbleContent( + event = event, + interactionSource = interactionSource, + onMessageClick = onClick, + onMessageLongClick = onLongClick + ) } TimelineItemReactionsView( reactionsState = event.reactionsState, @@ -302,91 +297,6 @@ fun TimelineItemEventRow( } } -@Composable -fun MessageEventLayout( - event: TimelineItem.Event, - onMessageClick: () -> Unit, - onMessageLongClick: () -> Unit, - modifier: Modifier = Modifier -) { - val isTextBasedMessage = event.content is TimelineItemTextBasedContent - val isMediaMessage = event.content is TimelineItemImageContent - || event.content is TimelineItemVideoContent - || event.content is TimelineItemFileContent - val isStateMessage = event.content is TimelineItemStateContent - - val interactionSource = remember { MutableInteractionSource() } - - @Composable - fun ContentView( - modifier: Modifier = Modifier - ) { - TimelineItemEventContentView( - content = event.content, - interactionSource = interactionSource, - onClick = onMessageClick, - onLongClick = onMessageLongClick, - modifier = modifier, - ) - } - - @Composable - fun TimestampView( - modifier: Modifier = Modifier - ) { - val formattedTime = event.sentTime - val hasMessageSendingFailed = event.sendState is EventSendState.SendingFailed - val isMessageEdited = (event.content as? TimelineItemTextBasedContent)?.isEdited.orFalse() - val tint = if (hasMessageSendingFailed) ElementTheme.colors.textActionCritical else null - Row(modifier = modifier.clickable(onClick = onMessageClick)) { - if (isMessageEdited) { - Text( - stringResource(StringR.string.common_edited_suffix), - style = ElementTextStyles.Regular.caption2, - color = tint ?: MaterialTheme.colorScheme.secondary, - ) - Spacer(modifier = Modifier.width(4.dp)) - } - Text( - formattedTime, - style = ElementTextStyles.Regular.caption2, - color = tint ?: MaterialTheme.colorScheme.secondary, - ) - if (hasMessageSendingFailed && tint != null) { - Spacer(modifier = Modifier.width(2.dp)) - Icon(imageVector = Icons.Default.Error, contentDescription = "Error sending message", tint = tint, modifier = Modifier.size(15.dp, 18.dp)) - } - } - } - - when { - isMediaMessage -> { - Box(modifier.wrapContentSize()) { - ContentView() - Box( - modifier = Modifier - .padding(horizontal = 4.dp, vertical = 4.dp) - .background(LocalColors.current.gray300, RoundedCornerShape(10.0.dp)) - .align(Alignment.BottomEnd) - ) { - TimestampView(Modifier.padding(horizontal = 4.dp, vertical = 2.dp)) - } - } - } - isTextBasedMessage -> { - Column { - ContentView(modifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 6.dp)) - TimestampView(modifier = Modifier - .align(Alignment.End) - .padding(horizontal = 8.dp, vertical = 4.dp)) - } - } - isStateMessage -> { - ContentView(modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp)) - } - } -} - @Composable fun TimelineItemStateEventRow( event: TimelineItem.Event, @@ -416,6 +326,60 @@ fun TimelineItemStateEventRow( interactionSource = interactionSource, onClick = onClick, onLongClick = onLongClick, + modifier = Modifier.defaultTimelineContentPadding() + ) + } + } +} + +@Composable +fun MessageEventBubbleContent( + event: TimelineItem.Event, + interactionSource: MutableInteractionSource, + onMessageClick: () -> Unit, + onMessageLongClick: () -> Unit, + modifier: Modifier = Modifier +) { + val showTimestampWithOverlay = event.content is TimelineItemImageContent || event.content is TimelineItemVideoContent + + @Composable + fun ContentView( + modifier: Modifier = Modifier + ) { + TimelineItemEventContentView( + content = event.content, + interactionSource = interactionSource, + onClick = onMessageClick, + onLongClick = onMessageLongClick, + modifier = modifier, + ) + } + + if (showTimestampWithOverlay) { + Box(modifier.wrapContentSize()) { + ContentView() + Box( + modifier = Modifier + .padding(horizontal = 4.dp, vertical = 4.dp) + .background(LocalColors.current.gray300, RoundedCornerShape(10.0.dp)) + .align(Alignment.BottomEnd) + ) { + TimelineEventTimestampView( + event = event, + onClick = onMessageClick, + modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp) + ) + } + } + } else { + Column { + ContentView(modifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 8.dp)) + TimelineEventTimestampView( + event = event, + onClick = onMessageClick, + modifier = Modifier + .align(Alignment.End) + .padding(horizontal = 8.dp, vertical = 2.dp) ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt new file mode 100644 index 0000000000..8358d6cc1e --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2023 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 io.element.android.features.messages.impl.timeline.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Error +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import io.element.android.features.messages.impl.timeline.model.TimelineItem +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent +import io.element.android.libraries.core.bool.orFalse +import io.element.android.libraries.designsystem.ElementTextStyles +import io.element.android.libraries.designsystem.theme.ElementTheme +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState +import io.element.android.libraries.ui.strings.R + +@Composable +fun TimelineEventTimestampView( + event: TimelineItem.Event, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + val formattedTime = event.sentTime + val hasMessageSendingFailed = event.sendState is EventSendState.SendingFailed + val isMessageEdited = (event.content as? TimelineItemTextBasedContent)?.isEdited.orFalse() + val tint = if (hasMessageSendingFailed) ElementTheme.colors.textActionCritical else null + Row(modifier = modifier.clickable(onClick = onClick)) { + if (isMessageEdited) { + Text( + stringResource(R.string.common_edited_suffix), + style = ElementTextStyles.Regular.caption2, + color = tint ?: MaterialTheme.colorScheme.secondary, + ) + Spacer(modifier = Modifier.width(4.dp)) + } + Text( + formattedTime, + style = ElementTextStyles.Regular.caption2, + color = tint ?: MaterialTheme.colorScheme.secondary, + ) + if (hasMessageSendingFailed && tint != null) { + Spacer(modifier = Modifier.width(2.dp)) + Icon(imageVector = Icons.Default.Error, contentDescription = "Error sending message", tint = tint, modifier = Modifier.size(15.dp, 18.dp)) + } + } +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/util/Modifiers.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/util/Modifiers.kt new file mode 100644 index 0000000000..611e270742 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/util/Modifiers.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 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 io.element.android.features.messages.impl.timeline.util + +import androidx.compose.foundation.layout.padding +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +fun Modifier.defaultTimelineContentPadding() = padding(horizontal = 12.dp, vertical = 6.dp)