Skip to content

Commit

Permalink
Implement ContentAvoidingLayout for timeline items (#2113)
Browse files Browse the repository at this point in the history
* Implement `ContentAvoidingLayout` for timeline items

* Truncate long mention pills

---------

Co-authored-by: Benoit Marty <[email protected]>
Co-authored-by: ElementBot <[email protected]>
  • Loading branch information
3 people authored Jan 3, 2024
1 parent 0381027 commit 4f6c742
Show file tree
Hide file tree
Showing 110 changed files with 573 additions and 299 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package io.element.android.features.messages.impl.timeline.components
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
Expand Down Expand Up @@ -69,7 +70,8 @@ fun TimelineEventTimestampView(
Row(
modifier = Modifier
.then(clickModifier)
.padding(start = 16.dp) // Add extra padding for touch target size
// Add extra padding for touch target size
.padding(PaddingValues(start = TimelineEventTimestampViewDefaults.spacing))
.then(modifier),
verticalAlignment = Alignment.CenterVertically,
) {
Expand Down Expand Up @@ -107,3 +109,7 @@ internal fun TimelineEventTimestampViewPreview(@PreviewParameter(TimelineItemEve
onLongClick = {},
)
}

object TimelineEventTimestampViewDefaults {
val spacing = 16.dp
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
Expand All @@ -67,7 +68,8 @@ import io.element.android.features.messages.impl.timeline.TimelineEvents
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
import io.element.android.features.messages.impl.timeline.components.event.toExtraPadding
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayout
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState
import io.element.android.features.messages.impl.timeline.components.receipt.TimelineItemReadReceiptView
import io.element.android.features.messages.impl.timeline.model.InReplyToDetails
Expand All @@ -80,6 +82,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
import io.element.android.features.messages.impl.timeline.model.event.canBeRepliedTo
Expand Down Expand Up @@ -448,12 +451,13 @@ private fun MessageEventBubbleContent(
fun WithTimestampLayout(
timestampPosition: TimestampPosition,
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
canShrinkContent: Boolean = false,
content: @Composable (onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit) -> Unit,
) {
when (timestampPosition) {
TimestampPosition.Overlay ->
Box(modifier, contentAlignment = Alignment.Center) {
content()
content {}
TimelineEventTimestampView(
event = event,
onClick = onTimestampClicked,
Expand All @@ -466,20 +470,26 @@ private fun MessageEventBubbleContent(
)
}
TimestampPosition.Aligned ->
Box(modifier) {
content()
TimelineEventTimestampView(
event = event,
onClick = onTimestampClicked,
onLongClick = ::onTimestampLongClick,
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(horizontal = 8.dp, vertical = 4.dp)
)
}
ContentAvoidingLayout(
modifier = modifier,
// The spacing is negative to make the content overlap the empty space at the start of the timestamp
spacing = (-4).dp,
overlayOffset = DpOffset(0.dp, -1.dp),
shrinkContent = canShrinkContent,
content = { content(this::onContentLayoutChanged) },
overlay = {
TimelineEventTimestampView(
event = event,
onClick = onTimestampClicked,
onLongClick = ::onTimestampLongClick,
modifier = Modifier
.padding(horizontal = 8.dp, vertical = 4.dp)
)
}
)
TimestampPosition.Below ->
Column(modifier) {
content()
content {}
TimelineEventTimestampView(
event = event,
onClick = onTimestampClicked,
Expand All @@ -498,7 +508,8 @@ private fun MessageEventBubbleContent(
timestampPosition: TimestampPosition,
showThreadDecoration: Boolean,
inReplyToDetails: InReplyToDetails?,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
canShrinkContent: Boolean = false,
) {
val context = LocalContext.current
val timestampLayoutModifier: Modifier
Expand All @@ -515,7 +526,8 @@ private fun MessageEventBubbleContent(
}
timestampPosition != TimestampPosition.Overlay -> {
timestampLayoutModifier = Modifier
contentModifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 8.dp, bottom = 8.dp)
contentModifier = Modifier
.padding(start = 12.dp, end = 12.dp, top = 8.dp, bottom = 8.dp)
}
else -> {
timestampLayoutModifier = Modifier
Expand All @@ -530,8 +542,9 @@ private fun MessageEventBubbleContent(
val contentWithTimestamp = @Composable {
WithTimestampLayout(
timestampPosition = timestampPosition,
canShrinkContent = canShrinkContent,
modifier = timestampLayoutModifier,
) {
) { onContentLayoutChanged ->
TimelineItemEventContentView(
content = event.content,
onLinkClicked = { url ->
Expand All @@ -548,9 +561,9 @@ private fun MessageEventBubbleContent(
}
}
},
extraPadding = event.toExtraPadding(),
eventSink = eventSink,
modifier = contentModifier,
onContentLayoutChanged = onContentLayoutChanged,
modifier = contentModifier
)
}
}
Expand Down Expand Up @@ -594,6 +607,7 @@ private fun MessageEventBubbleContent(
showThreadDecoration = event.isThreaded,
timestampPosition = timestampPosition,
inReplyToDetails = event.inReplyTo,
canShrinkContent = event.content is TimelineItemVoiceContent,
modifier = bubbleModifier
)
}
Expand Down Expand Up @@ -655,7 +669,7 @@ internal fun TimelineItemEventRowPreview() = ElementPreview {
isMine = it,
content = aTimelineItemTextContent().copy(
body = "A long text which will be displayed on several lines and" +
" hopefully can be manually adjusted to test different behaviors."
" hopefully can be manually adjusted to test different behaviors."
),
groupPosition = TimelineItemGroupPosition.First,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import androidx.compose.ui.zIndex
import io.element.android.features.messages.impl.timeline.TimelineEvents
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
import io.element.android.features.messages.impl.timeline.components.event.noExtraPadding
import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState
import io.element.android.features.messages.impl.timeline.components.receipt.TimelineItemReadReceiptView
import io.element.android.features.messages.impl.timeline.components.receipt.aReadReceiptData
Expand Down Expand Up @@ -81,7 +80,6 @@ fun TimelineItemStateEventRow(
TimelineItemEventContentView(
content = event.content,
onLinkClicked = {},
extraPadding = noExtraPadding,
eventSink = eventSink,
modifier = Modifier.defaultTimelineContentPadding()
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayout
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContentProvider
import io.element.android.libraries.designsystem.preview.ElementPreview
Expand All @@ -44,15 +46,17 @@ import io.element.android.libraries.designsystem.theme.components.Text
@Composable
fun TimelineItemAudioView(
content: TimelineItemAudioContent,
extraPadding: ExtraPadding,
onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit,
modifier: Modifier = Modifier,
) {
val iconSize = 32.dp
val spacing = 8.dp
Row(
modifier = modifier,
) {
Box(
modifier = Modifier
.size(32.dp)
.size(iconSize)
.clip(CircleShape)
.background(ElementTheme.materialColors.background),
contentAlignment = Alignment.Center,
Expand All @@ -65,7 +69,7 @@ fun TimelineItemAudioView(
.size(16.dp),
)
}
Spacer(Modifier.width(8.dp))
Spacer(Modifier.width(spacing))
Column {
Text(
text = content.body,
Expand All @@ -75,11 +79,15 @@ fun TimelineItemAudioView(
overflow = TextOverflow.Ellipsis
)
Text(
text = content.fileExtensionAndSize + extraPadding.getStr(ElementTheme.typography.fontBodySmRegular),
text = content.fileExtensionAndSize,
color = ElementTheme.materialColors.secondary,
style = ElementTheme.typography.fontBodySmRegular,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
onTextLayout = ContentAvoidingLayout.measureLastTextLine(
onContentLayoutChanged = onContentLayoutChanged,
extraWidth = iconSize + spacing
)
)
}
}
Expand All @@ -91,6 +99,6 @@ internal fun TimelineItemAudioViewPreview(@PreviewParameter(TimelineItemAudioCon
ElementPreview {
TimelineItemAudioView(
content,
extraPadding = noExtraPadding,
onContentLayoutChanged = {},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package io.element.android.features.messages.impl.timeline.components.event
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
Expand All @@ -29,14 +30,14 @@ import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun TimelineItemEncryptedView(
@Suppress("UNUSED_PARAMETER") content: TimelineItemEncryptedContent,
extraPadding: ExtraPadding,
onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit,
modifier: Modifier = Modifier
) {
TimelineItemInformativeView(
text = stringResource(id = CommonStrings.common_waiting_for_decryption_key),
iconDescription = stringResource(id = CommonStrings.dialog_title_warning),
iconResourceId = CommonDrawables.ic_waiting_to_decrypt,
extraPadding = extraPadding,
onContentLayoutChanged = onContentLayoutChanged,
modifier = modifier
)
}
Expand All @@ -48,6 +49,6 @@ internal fun TimelineItemEncryptedViewPreview() = ElementPreview {
content = TimelineItemEncryptedContent(
data = UnableToDecryptContent.Data.Unknown
),
extraPadding = noExtraPadding
onContentLayoutChanged = {},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package io.element.android.features.messages.impl.timeline.components.event
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import io.element.android.features.messages.impl.timeline.TimelineEvents
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
import io.element.android.features.messages.impl.timeline.di.rememberPresenter
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
Expand All @@ -41,32 +42,32 @@ import io.element.android.libraries.architecture.Presenter
@Composable
fun TimelineItemEventContentView(
content: TimelineItemEventContent,
extraPadding: ExtraPadding,
onLinkClicked: (url: String) -> Unit,
eventSink: (TimelineEvents) -> Unit,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit = {},
) {
val presenterFactories = LocalTimelineItemPresenterFactories.current
when (content) {
is TimelineItemEncryptedContent -> TimelineItemEncryptedView(
content = content,
extraPadding = extraPadding,
onContentLayoutChanged = onContentLayoutChanged,
modifier = modifier
)
is TimelineItemRedactedContent -> TimelineItemRedactedView(
content = content,
extraPadding = extraPadding,
onContentLayoutChanged = onContentLayoutChanged,
modifier = modifier
)
is TimelineItemTextBasedContent -> TimelineItemTextView(
content = content,
extraPadding = extraPadding,
modifier = modifier,
onLinkClicked = onLinkClicked,
onContentLayoutChanged = onContentLayoutChanged
)
is TimelineItemUnknownContent -> TimelineItemUnknownView(
content = content,
extraPadding = extraPadding,
onContentLayoutChanged = onContentLayoutChanged,
modifier = modifier
)
is TimelineItemLocationContent -> TimelineItemLocationView(
Expand All @@ -87,12 +88,12 @@ fun TimelineItemEventContentView(
)
is TimelineItemFileContent -> TimelineItemFileView(
content = content,
extraPadding = extraPadding,
onContentLayoutChanged = onContentLayoutChanged,
modifier = modifier
)
is TimelineItemAudioContent -> TimelineItemAudioView(
content = content,
extraPadding = extraPadding,
onContentLayoutChanged = onContentLayoutChanged,
modifier = modifier
)
is TimelineItemStateContent -> TimelineItemStateView(
Expand All @@ -109,7 +110,7 @@ fun TimelineItemEventContentView(
TimelineItemVoiceView(
state = presenter.present(),
content = content,
extraPadding = extraPadding,
onContentLayoutChanged = onContentLayoutChanged,
modifier = modifier
)
}
Expand Down
Loading

0 comments on commit 4f6c742

Please sign in to comment.