From bd0e24598aea5db266dd21831e2c4bc87cc0d0be Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Thu, 29 Jun 2023 16:15:15 +0100 Subject: [PATCH] Show location replies per the designs --- features/location/api/build.gradle.kts | 1 + .../features/location/api/StaticMapView.kt | 12 +++- features/location/impl/build.gradle.kts | 1 + .../location/impl/SendLocationView.kt | 13 +++- .../features/location/impl/map/MapView.kt | 7 +- .../messages/impl/MessagesPresenter.kt | 7 +- .../actionlist/ActionListStateProvider.kt | 7 ++ .../impl/actionlist/ActionListView.kt | 16 ++++- .../components/TimelineItemEventRow.kt | 21 +++++- .../MessageSummaryFormatterImpl.kt | 2 +- .../ui/components/AttachmentThumbnail.kt | 8 ++- .../libraries/matrix/ui/components/PinIcon.kt | 66 +++++++++++++++++++ .../matrixui}/src/main/res/drawable/pin.xml | 4 +- .../libraries/textcomposer/TextComposer.kt | 18 +++++ 14 files changed, 167 insertions(+), 16 deletions(-) create mode 100644 libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/PinIcon.kt rename {features/location/api => libraries/matrixui}/src/main/res/drawable/pin.xml (93%) diff --git a/features/location/api/build.gradle.kts b/features/location/api/build.gradle.kts index 8d3e20e479..4d593e1c53 100644 --- a/features/location/api/build.gradle.kts +++ b/features/location/api/build.gradle.kts @@ -27,6 +27,7 @@ dependencies { implementation(projects.libraries.architecture) implementation(projects.libraries.designsystem) implementation(projects.libraries.core) + implementation(projects.libraries.matrixui) implementation(projects.libraries.uiStrings) implementation(libs.coil.compose) ksp(libs.showkase.processor) diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt index 8a3541c22a..20731b5261 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import coil.compose.AsyncImagePainter import coil.compose.rememberAsyncImagePainter @@ -43,6 +44,7 @@ import io.element.android.libraries.designsystem.text.toDp import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.theme.ElementTheme import timber.log.Timber +import io.element.android.libraries.matrix.ui.R as MatrixUIR /** * Shows a static map image downloaded via a third party service's static maps API. @@ -103,9 +105,15 @@ fun StaticMapView( contentScale = ContentScale.Fit, ) Icon( - resourceId = R.drawable.pin, + resourceId = MatrixUIR.drawable.pin, contentDescription = null, - tint = Color.Unspecified + tint = Color.Unspecified, + modifier = Modifier.align { size, space, _ -> + IntOffset( + x = (space.width - size.width) / 2, + y = (space.height / 2) - size.height, + ) + } ) } else { StaticMapPlaceholder( diff --git a/features/location/impl/build.gradle.kts b/features/location/impl/build.gradle.kts index 819c590499..eed060cbf8 100644 --- a/features/location/impl/build.gradle.kts +++ b/features/location/impl/build.gradle.kts @@ -35,6 +35,7 @@ dependencies { implementation(projects.libraries.di) implementation(projects.libraries.designsystem) implementation(projects.libraries.core) + implementation(projects.libraries.matrixui) implementation(libs.maplibre) implementation(libs.maplibre.annotation) implementation(projects.libraries.uiStrings) diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/SendLocationView.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/SendLocationView.kt index b1fd3b88c3..3add47ae42 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/SendLocationView.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/SendLocationView.kt @@ -39,9 +39,9 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import io.element.android.features.location.api.R import io.element.android.features.location.impl.map.MapView import io.element.android.features.location.impl.map.rememberMapState import io.element.android.libraries.designsystem.components.button.BackButton @@ -52,6 +52,7 @@ import io.element.android.libraries.designsystem.theme.components.CenterAlignedT import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.libraries.matrix.ui.R as MatrixUIR @OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) @Composable @@ -115,9 +116,15 @@ fun SendLocationView( mapState = mapState, ) Icon( - resourceId = R.drawable.pin, + resourceId = MatrixUIR.drawable.pin, contentDescription = null, - tint = Color.Unspecified + tint = Color.Unspecified, + modifier = Modifier.align { size, space, _ -> + IntOffset( + x = (space.width - size.width) / 2, + y = (space.height / 2) - size.height, + ) + } ) } } diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/map/MapView.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/map/MapView.kt index 0d2627b33c..0384c4f290 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/map/MapView.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/map/MapView.kt @@ -44,7 +44,6 @@ import com.mapbox.mapboxsdk.maps.MapboxMap import com.mapbox.mapboxsdk.maps.Style import com.mapbox.mapboxsdk.plugins.annotation.SymbolManager import com.mapbox.mapboxsdk.plugins.annotation.SymbolOptions -import io.element.android.features.location.api.R import io.element.android.features.location.api.internal.buildTileServerUrl import io.element.android.features.location.impl.location.Location import io.element.android.libraries.designsystem.preview.ElementPreviewDark @@ -55,6 +54,7 @@ import kotlinx.collections.immutable.toImmutableList import timber.log.Timber import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine +import io.element.android.libraries.matrix.ui.R as MatrixUIR /** * Composable wrapper around MapLibre's [MapView]. @@ -149,12 +149,13 @@ fun MapView( LaunchedEffect(mapRefs, mapState.location) { mapRefs?.let { mapRefs -> mapState.location?.let { location -> - context.getDrawable(R.drawable.pin)?.let { mapRefs.style.addImage("pin", it) } + context.getDrawable(MatrixUIR.drawable.pin)?.let { mapRefs.style.addImage("pin", it) } mapRefs.symbolManager.create( SymbolOptions() .withLatLng(LatLng(location.lat, location.lon)) .withIconImage("pin") .withIconSize(1.3f) + .withIconOffset(arrayOf(0f, 0.5f)) ) Timber.d("Shown pin at location: $location") } @@ -275,7 +276,7 @@ private fun ContentToPreview() { ), markers = listOf( MapState.Marker( - drawable = R.drawable.pin, + drawable = MatrixUIR.drawable.pin, lat = 0.0, lon = 0.0, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index ac64715215..f2c8deca49 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -264,11 +264,16 @@ class MessagesPresenter @AssistedInject constructor( type = AttachmentThumbnailType.File, blurHash = null, ) + is TimelineItemLocationContent -> AttachmentThumbnailInfo( + mediaSource = null, + textContent = null, + type = AttachmentThumbnailType.Location, + blurHash = null, + ) is TimelineItemTextBasedContent, is TimelineItemRedactedContent, is TimelineItemStateContent, is TimelineItemEncryptedContent, - is TimelineItemLocationContent, is TimelineItemUnknownContent -> null } val composerMode = MessageComposerMode.Reply( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt index 1d3f656fc0..ee1b7de309 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt @@ -21,6 +21,7 @@ import io.element.android.features.messages.impl.actionlist.model.TimelineItemAc import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemFileContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent +import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemLocationContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVideoContent import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -54,6 +55,12 @@ open class ActionListStateProvider : PreviewParameterProvider { actions = aTimelineItemActionList(), ) ), + anActionListState().copy( + target = ActionListState.Target.Success( + event = aTimelineItemEvent(content = aTimelineItemLocationContent()), + actions = aTimelineItemActionList(), + ) + ), ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt index 430238ce37..904313120f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt @@ -78,6 +78,7 @@ import io.element.android.libraries.designsystem.theme.components.hide import io.element.android.libraries.matrix.ui.components.AttachmentThumbnail import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType +import io.element.android.libraries.ui.strings.CommonStrings @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -232,8 +233,21 @@ private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modif is TimelineItemStateContent, is TimelineItemEncryptedContent, is TimelineItemRedactedContent, - is TimelineItemLocationContent, is TimelineItemUnknownContent -> content = { ContentForBody(textContent) } + is TimelineItemLocationContent -> { + icon = { + AttachmentThumbnail( + modifier = imageModifier, + info = AttachmentThumbnailInfo( + type = AttachmentThumbnailType.Location, + textContent = stringResource(CommonStrings.common_shared_location), + mediaSource = null, + blurHash = null, + ) + ) + } + content = { ContentForBody(stringResource(CommonStrings.common_shared_location)) } + } is TimelineItemImageContent -> { icon = { AttachmentThumbnail( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index 026d6d4557..f2f7328821 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -41,6 +41,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -54,8 +55,8 @@ import io.element.android.features.messages.impl.timeline.components.event.toExt 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.TimelineItemImageContent -import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent @@ -70,6 +71,7 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo +import io.element.android.libraries.matrix.api.timeline.item.event.LocationMessageType import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType @@ -77,6 +79,7 @@ import io.element.android.libraries.matrix.ui.components.AttachmentThumbnail import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType import io.element.android.libraries.theme.ElementTheme +import io.element.android.libraries.ui.strings.CommonStrings import org.jsoup.Jsoup @Composable @@ -300,9 +303,10 @@ private fun MessageEventBubbleContent( if (inReplyToDetails != null) { val senderName = inReplyToDetails.senderDisplayName ?: inReplyToDetails.senderId.value val attachmentThumbnailInfo = attachmentThumbnailInfoForInReplyTo(inReplyToDetails) + val text = textForInReplyTo(inReplyToDetails) ReplyToContent( senderName = senderName, - text = inReplyToDetails.content.body, + text = text, attachmentThumbnailInfo = attachmentThumbnailInfo, modifier = Modifier .padding(top = 8.dp, start = 8.dp, end = 8.dp) @@ -409,9 +413,22 @@ private fun attachmentThumbnailInfoForInReplyTo(inReplyTo: InReplyTo.Ready) = type = AttachmentThumbnailType.File, blurHash = null, ) + is LocationMessageType -> AttachmentThumbnailInfo( + mediaSource = null, + textContent = inReplyTo.content.body, + type = AttachmentThumbnailType.Location, + blurHash = null, + ) else -> null } +@Composable +private fun textForInReplyTo(inReplyTo: InReplyTo.Ready) = + when (inReplyTo.content.type) { + is LocationMessageType -> stringResource(CommonStrings.common_shared_location) + else -> inReplyTo.content.body + } + @Preview @Composable internal fun TimelineItemEventRowLightPreview() = diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/MessageSummaryFormatterImpl.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/MessageSummaryFormatterImpl.kt index 852c3f1620..e22aed6af9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/MessageSummaryFormatterImpl.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/MessageSummaryFormatterImpl.kt @@ -43,7 +43,7 @@ class MessageSummaryFormatterImpl @Inject constructor( is TimelineItemTextBasedContent -> event.content.body is TimelineItemProfileChangeContent -> event.content.body is TimelineItemStateContent -> event.content.body - is TimelineItemLocationContent -> event.content.body + is TimelineItemLocationContent -> context.getString(CommonStrings.common_shared_location) is TimelineItemEncryptedContent -> context.getString(CommonStrings.common_unable_to_decrypt) is TimelineItemRedactedContent -> context.getString(CommonStrings.common_message_removed) is TimelineItemUnknownContent -> context.getString(CommonStrings.common_unsupported_event) diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AttachmentThumbnail.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AttachmentThumbnail.kt index 5a3bad8988..d2310354c8 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AttachmentThumbnail.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AttachmentThumbnail.kt @@ -19,6 +19,7 @@ package io.element.android.libraries.matrix.ui.components import android.os.Parcelable import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Attachment import androidx.compose.material.icons.outlined.VideoCameraBack @@ -73,6 +74,11 @@ fun AttachmentThumbnail( modifier = Modifier.rotate(-45f) ) } + AttachmentThumbnailType.Location -> { + PinIcon( + modifier = Modifier.fillMaxSize() + ) + } else -> Unit } } @@ -81,7 +87,7 @@ fun AttachmentThumbnail( @Parcelize enum class AttachmentThumbnailType: Parcelable { - Image, Video, File + Image, Video, File, Location } @Parcelize diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/PinIcon.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/PinIcon.kt new file mode 100644 index 0000000000..f38a5ca751 --- /dev/null +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/PinIcon.kt @@ -0,0 +1,66 @@ +/* + * 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.libraries.matrix.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.preview.ElementPreviewDark +import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.matrix.ui.R +import io.element.android.libraries.theme.ElementTheme + +@Composable +fun PinIcon( + modifier: Modifier = Modifier +) { + Box( + modifier = modifier + .background(ElementTheme.colors.bgSubtlePrimary) + ) { + Icon( + modifier = Modifier + .align(Alignment.Center) + .width(22.dp), + resourceId = R.drawable.pin, + contentDescription = null, + tint = Color.Unspecified, + ) + } +} + +@Preview +@Composable +fun PinIconLightPreview() = + ElementPreviewLight { ContentToPreview() } + +@Preview +@Composable +fun PinIconDarkPreview() = + ElementPreviewDark { ContentToPreview() } + +@Composable +private fun ContentToPreview() { + PinIcon() +} diff --git a/features/location/api/src/main/res/drawable/pin.xml b/libraries/matrixui/src/main/res/drawable/pin.xml similarity index 93% rename from features/location/api/src/main/res/drawable/pin.xml rename to libraries/matrixui/src/main/res/drawable/pin.xml index 9f47b9024f..40b9d4030a 100644 --- a/features/location/api/src/main/res/drawable/pin.xml +++ b/libraries/matrixui/src/main/res/drawable/pin.xml @@ -1,8 +1,8 @@ + android:viewportHeight="54"> diff --git a/libraries/textcomposer/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt b/libraries/textcomposer/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt index b56f7c8362..74516cfab0 100644 --- a/libraries/textcomposer/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt +++ b/libraries/textcomposer/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt @@ -552,5 +552,23 @@ private fun ReplyContentToPreview() { composerCanSendMessage = true, composerText = "A message", ) + TextComposer( + onSendMessage = {}, + onComposerTextChange = {}, + composerMode = MessageComposerMode.Reply( + senderName = "Alice", + eventId = EventId("$1234"), + attachmentThumbnailInfo = AttachmentThumbnailInfo( + mediaSource = null, + textContent = null, + type = AttachmentThumbnailType.Location, + blurHash = null, + ), + defaultContent = "Shared location" + ), + onResetComposerMode = {}, + composerCanSendMessage = true, + composerText = "A message", + ) } }