From 90aad44edfc6beafe151c66dbeb2ca2db1bcc5f6 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Fri, 29 Apr 2022 16:28:34 +0200 Subject: [PATCH 01/45] Adding live location summary data into MessageInformationData --- .../helper/MessageInformationDataFactory.kt | 17 ++++++++++++++--- .../timeline/item/MessageInformationData.kt | 12 +++++++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index f882840eee..8fb7aea7e4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -21,6 +21,7 @@ import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.extensions.localDateTime import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration +import im.vector.app.features.home.room.detail.timeline.item.LiveLocationShareSummaryData import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData import im.vector.app.features.home.room.detail.timeline.item.PollResponseData import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData @@ -44,8 +45,7 @@ import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited import javax.inject.Inject /** - * TODO Update this comment - * This class compute if data of an event (such has avatar, display name, ...) should be displayed, depending on the previous event in the timeline + * This class is responsible of building extra information data associated to a given event. */ class MessageInformationDataFactory @Inject constructor(private val session: Session, private val dateFormatter: VectorDateFormatter, @@ -119,7 +119,8 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses isFirstFromThisSender = isFirstFromThisSender, isLastFromThisSender = isLastFromThisSender, e2eDecoration = e2eDecoration, - sendStateDecoration = sendStateDecoration + sendStateDecoration = sendStateDecoration, + liveLocationShareSummaryData = getLiveLocationShareSummaryData(event) ) } @@ -188,6 +189,16 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses } } + private fun getLiveLocationShareSummaryData(event: TimelineEvent): LiveLocationShareSummaryData? { + return event.annotations?.liveLocationShareAggregatedSummary?.let { summary -> + LiveLocationShareSummaryData( + isActive = summary.isActive, + endOfLiveTimestampAsMilliseconds = summary.endOfLiveTimestampAsMilliseconds, + lastGeoUri = summary.lastLocationDataContent?.getBestLocationInfo()?.geoUri + ) + } + } + /** * Tiles type message never show the sender information (like verification request), so we should repeat it for next message * even if same sender diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt index 9620077fd8..8ad4034a32 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt @@ -20,6 +20,8 @@ import android.os.Parcelable import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.session.crypto.verification.VerificationState +import org.matrix.android.sdk.api.session.room.model.message.LocationInfo +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.util.MatrixItem @@ -42,7 +44,8 @@ data class MessageInformationData( val e2eDecoration: E2EDecoration = E2EDecoration.NONE, val sendStateDecoration: SendStateDecoration = SendStateDecoration.NONE, val isFirstFromThisSender: Boolean = false, - val isLastFromThisSender: Boolean = false + val isLastFromThisSender: Boolean = false, + val liveLocationShareSummaryData: LiveLocationShareSummaryData? = null, ) : Parcelable { val matrixItem: MatrixItem @@ -98,6 +101,13 @@ data class PollVoteSummaryData( val percentage: Double = 0.0 ) : Parcelable +@Parcelize +data class LiveLocationShareSummaryData( + val isActive: Boolean?, + val endOfLiveTimestampAsMilliseconds: Long?, + val lastGeoUri: String?, +) : Parcelable + enum class E2EDecoration { NONE, WARN_IN_CLEAR, From 0561fe5b08fd52baee71b3ad3fd3c81d1289cfd6 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Fri, 29 Apr 2022 17:00:32 +0200 Subject: [PATCH 02/45] Using information data in message factory --- .../factory/LiveLocationMessageItemFactory.kt | 16 ++++++++++------ .../timeline/factory/MessageItemFactory.kt | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt index d233deffb8..3656b86b79 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt @@ -21,9 +21,11 @@ import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem +import im.vector.app.features.home.room.detail.timeline.item.LiveLocationShareSummaryData import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem_ import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent import javax.inject.Inject @@ -34,20 +36,22 @@ class LiveLocationMessageItemFactory @Inject constructor( ) { fun create( - beaconInfoContent: MessageBeaconInfoContent, + liveLocationShareSummaryData: LiveLocationShareSummaryData?, highlight: Boolean, attributes: AbsMessageItem.Attributes, ): VectorEpoxyModel<*>? { // TODO handle location received and stopped states + // TODO create a dedicated ViewState return when { - isLiveRunning(beaconInfoContent) -> buildStartLiveItem(highlight, attributes) - else -> null + liveLocationShareSummaryData == null -> null + isLiveRunning(liveLocationShareSummaryData) -> buildStartLiveItem(highlight, attributes) + else -> null } } - private fun isLiveRunning(beaconInfoContent: MessageBeaconInfoContent): Boolean { - // TODO when we will use aggregatedSummary, check if the live has timed out as well - return beaconInfoContent.isLive.orFalse() + private fun isLiveRunning(liveLocationShareSummaryData: LiveLocationShareSummaryData): Boolean { + // TODO check if the live has timed out as well + return liveLocationShareSummaryData.isActive.orFalse() } private fun buildStartLiveItem( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index b960e2c6a9..953c45f07e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -216,7 +216,7 @@ class MessageItemFactory @Inject constructor( buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes) } } - is MessageBeaconInfoContent -> liveLocationMessageItemFactory.create(messageContent, highlight, attributes) + is MessageBeaconInfoContent -> liveLocationMessageItemFactory.create(informationData.liveLocationShareSummaryData, highlight, attributes) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } return messageItem?.apply { From d89d6bc1623f8df0d0f5943ec2bfd2c748d77997 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 2 May 2022 09:13:05 +0200 Subject: [PATCH 03/45] Adding view state class --- .../factory/LiveLocationMessageItemFactory.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt index 3656b86b79..86a5847605 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt @@ -25,8 +25,8 @@ import im.vector.app.features.home.room.detail.timeline.item.LiveLocationShareSu import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem_ import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.extensions.orTrue -import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.api.session.room.model.message.LocationInfo +import java.time.LocalDateTime import javax.inject.Inject class LiveLocationMessageItemFactory @Inject constructor( @@ -68,4 +68,11 @@ class LiveLocationMessageItemFactory @Inject constructor( .highlighted(highlight) .leftGuideline(avatarSizeProvider.leftGuideline) } + + private sealed class LiveLocationShareViewState { + object Loading : LiveLocationShareViewState() + data class Running(val locationInfo: LocationInfo, val endOfLiveDateTime: LocalDateTime?) : LiveLocationShareViewState() + object Inactive : LiveLocationShareViewState() + object Unkwown : LiveLocationShareViewState() + } } From 68a44c4cc77e85bbfd983b4aee3043a2dd7fe40d Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 2 May 2022 09:13:59 +0200 Subject: [PATCH 04/45] Renaming message item factory --- ...eItemFactory.kt => LiveLocationShareMessageItemFactory.kt} | 2 +- .../home/room/detail/timeline/factory/MessageItemFactory.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/{LiveLocationMessageItemFactory.kt => LiveLocationShareMessageItemFactory.kt} (98%) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt similarity index 98% rename from vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt rename to vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt index 86a5847605..f243da7c81 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt @@ -29,7 +29,7 @@ import org.matrix.android.sdk.api.session.room.model.message.LocationInfo import java.time.LocalDateTime import javax.inject.Inject -class LiveLocationMessageItemFactory @Inject constructor( +class LiveLocationShareMessageItemFactory @Inject constructor( private val dimensionConverter: DimensionConverter, private val timelineMediaSizeProvider: TimelineMediaSizeProvider, private val avatarSizeProvider: AvatarSizeProvider, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 953c45f07e..6f22b3eff4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -148,7 +148,7 @@ class MessageItemFactory @Inject constructor( private val locationPinProvider: LocationPinProvider, private val vectorPreferences: VectorPreferences, private val urlMapProvider: UrlMapProvider, - private val liveLocationMessageItemFactory: LiveLocationMessageItemFactory, + private val liveLocationShareMessageItemFactory: LiveLocationShareMessageItemFactory, ) { // TODO inject this properly? @@ -216,7 +216,7 @@ class MessageItemFactory @Inject constructor( buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes) } } - is MessageBeaconInfoContent -> liveLocationMessageItemFactory.create(informationData.liveLocationShareSummaryData, highlight, attributes) + is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(informationData.liveLocationShareSummaryData, highlight, attributes) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } return messageItem?.apply { From d44a6c50f1f4b5623c4688103a8621eaf139150c Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 2 May 2022 09:47:07 +0200 Subject: [PATCH 05/45] Fixes in DateProvider --- .../vector/app/core/resources/DateProvider.kt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/resources/DateProvider.kt b/vector/src/main/java/im/vector/app/core/resources/DateProvider.kt index 30cb1dcae4..6762bd68da 100644 --- a/vector/src/main/java/im/vector/app/core/resources/DateProvider.kt +++ b/vector/src/main/java/im/vector/app/core/resources/DateProvider.kt @@ -19,27 +19,30 @@ package im.vector.app.core.resources import org.threeten.bp.Instant import org.threeten.bp.LocalDateTime import org.threeten.bp.ZoneId +import org.threeten.bp.ZoneOffset object DateProvider { - private val zoneId = ZoneId.systemDefault() - private val zoneOffset by lazy { - val now = currentLocalDateTime() - zoneId.rules.getOffset(now) - } + // recompute the zoneId each time we access it to handle change of timezones + private val defaultZoneId: ZoneId + get() = ZoneId.systemDefault() + + // recompute the zoneOffset each time we access it to handle change of timezones + private val defaultZoneOffset: ZoneOffset + get() = defaultZoneId.rules.getOffset(currentLocalDateTime()) fun toLocalDateTime(timestamp: Long?): LocalDateTime { val instant = Instant.ofEpochMilli(timestamp ?: 0) - return LocalDateTime.ofInstant(instant, zoneId) + return LocalDateTime.ofInstant(instant, defaultZoneId) } fun currentLocalDateTime(): LocalDateTime { val instant = Instant.now() - return LocalDateTime.ofInstant(instant, zoneId) + return LocalDateTime.ofInstant(instant, defaultZoneId) } fun toTimestamp(localDateTime: LocalDateTime): Long { - return localDateTime.toInstant(zoneOffset).toEpochMilli() + return localDateTime.toInstant(defaultZoneOffset).toEpochMilli() } } From 431d86166ff0ffa93fbe73d645856fd098e7c185 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 2 May 2022 09:47:48 +0200 Subject: [PATCH 06/45] Building correct item depending on the state of the live --- .../LiveLocationShareMessageItemFactory.kt | 56 ++++++++++++++----- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt index f243da7c81..760ec92cd6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt @@ -17,6 +17,7 @@ package im.vector.app.features.home.room.detail.timeline.factory import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.resources.DateProvider import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider @@ -25,8 +26,8 @@ import im.vector.app.features.home.room.detail.timeline.item.LiveLocationShareSu import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem_ import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.session.room.model.message.LocationInfo -import java.time.LocalDateTime +import org.threeten.bp.LocalDateTime +import timber.log.Timber import javax.inject.Inject class LiveLocationShareMessageItemFactory @Inject constructor( @@ -40,21 +41,15 @@ class LiveLocationShareMessageItemFactory @Inject constructor( highlight: Boolean, attributes: AbsMessageItem.Attributes, ): VectorEpoxyModel<*>? { - // TODO handle location received and stopped states - // TODO create a dedicated ViewState - return when { - liveLocationShareSummaryData == null -> null - isLiveRunning(liveLocationShareSummaryData) -> buildStartLiveItem(highlight, attributes) - else -> null + return when (getViewState(liveLocationShareSummaryData)) { + LiveLocationShareViewState.Loading -> buildLoadingItem(highlight, attributes) + LiveLocationShareViewState.Inactive -> buildInactiveItem() + is LiveLocationShareViewState.Running -> buildRunningItem() + LiveLocationShareViewState.Unkwown -> null } } - private fun isLiveRunning(liveLocationShareSummaryData: LiveLocationShareSummaryData): Boolean { - // TODO check if the live has timed out as well - return liveLocationShareSummaryData.isActive.orFalse() - } - - private fun buildStartLiveItem( + private fun buildLoadingItem( highlight: Boolean, attributes: AbsMessageItem.Attributes, ): MessageLiveLocationStartItem { @@ -69,9 +64,40 @@ class LiveLocationShareMessageItemFactory @Inject constructor( .leftGuideline(avatarSizeProvider.leftGuideline) } + private fun buildRunningItem() = null + + private fun buildInactiveItem() = null + + private fun getViewState(liveLocationShareSummaryData: LiveLocationShareSummaryData?): LiveLocationShareViewState { + return when { + liveLocationShareSummaryData?.isActive == null -> LiveLocationShareViewState.Unkwown + liveLocationShareSummaryData.isActive && liveLocationShareSummaryData.lastGeoUri.isNullOrEmpty() -> LiveLocationShareViewState.Loading + liveLocationShareSummaryData.isActive.not() || isLiveTimedOut(liveLocationShareSummaryData) -> LiveLocationShareViewState.Inactive + else -> + LiveLocationShareViewState.Running( + liveLocationShareSummaryData.lastGeoUri.orEmpty(), + getEndOfLiveDateTime(liveLocationShareSummaryData) + ) + }.also { viewState -> Timber.d("computed viewState: $viewState") } + } + + private fun isLiveTimedOut(liveLocationShareSummaryData: LiveLocationShareSummaryData): Boolean { + return getEndOfLiveDateTime(liveLocationShareSummaryData) + ?.let { endOfLive -> + // this will only cover users with different timezones but not users with manually time set + val now = LocalDateTime.now() + now.isAfter(endOfLive) + } + .orFalse() + } + + private fun getEndOfLiveDateTime(liveLocationShareSummaryData: LiveLocationShareSummaryData): LocalDateTime? { + return liveLocationShareSummaryData.endOfLiveTimestampAsMilliseconds?.let { DateProvider.toLocalDateTime(timestamp = it) } + } + private sealed class LiveLocationShareViewState { object Loading : LiveLocationShareViewState() - data class Running(val locationInfo: LocationInfo, val endOfLiveDateTime: LocalDateTime?) : LiveLocationShareViewState() + data class Running(val lastGeoUri: String, val endOfLiveDateTime: LocalDateTime?) : LiveLocationShareViewState() object Inactive : LiveLocationShareViewState() object Unkwown : LiveLocationShareViewState() } From df1ba8ec88f6295b6df663071a113ccf8ee52a10 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 4 Apr 2022 18:14:51 +0200 Subject: [PATCH 07/45] Custom view for the banner --- .../src/main/res/values/styles_location.xml | 8 +++ .../live/LocationLiveMessageBannerView.kt | 40 +++++++++++ .../view_location_live_message_banner.xml | 67 +++++++++++++++++++ vector/src/main/res/values/strings.xml | 1 + 4 files changed, 116 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt create mode 100644 vector/src/main/res/layout/view_location_live_message_banner.xml diff --git a/library/ui-styles/src/main/res/values/styles_location.xml b/library/ui-styles/src/main/res/values/styles_location.xml index 5563d28342..8f76ed469e 100644 --- a/library/ui-styles/src/main/res/values/styles_location.xml +++ b/library/ui-styles/src/main/res/values/styles_location.xml @@ -8,4 +8,12 @@ <item name="android:gravity">center</item> </style> + <style name="Widget.Vector.Button.Text.LocationLive"> + <item name="android:background">?selectableItemBackground</item> + <item name="android:textAppearance">@style/TextAppearance.Vector.Body.Medium</item> + <item name="android:textColor">?colorError</item> + <item name="android:padding">0dp</item> + <item name="android:gravity">center</item> + </style> + </resources> diff --git a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt new file mode 100644 index 0000000000..95805d5b7d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.location.live + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.Button +import androidx.constraintlayout.widget.ConstraintLayout +import im.vector.app.databinding.ViewLocationLiveMessageBannerBinding +import im.vector.app.databinding.ViewLocationLiveStatusBinding + +class LocationLiveMessageBannerView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr) { + + private val binding = ViewLocationLiveMessageBannerBinding.inflate( + LayoutInflater.from(context), + this + ) + + val stopButton: Button + get() = binding.locationLiveMessageBannerStop +} diff --git a/vector/src/main/res/layout/view_location_live_message_banner.xml b/vector/src/main/res/layout/view_location_live_message_banner.xml new file mode 100644 index 0000000000..f3345bac05 --- /dev/null +++ b/vector/src/main/res/layout/view_location_live_message_banner.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"> + + <View + android:id="@+id/locationLiveMessageBannerBackground" + android:layout_width="0dp" + android:layout_height="48dp" + android:alpha="0.85" + android:background="?colorSurface" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <ImageView + android:id="@+id/locationLiveMessageBannerIcon" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_marginHorizontal="8dp" + android:background="@drawable/circle" + android:backgroundTint="?vctr_live_location" + android:padding="3dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="@id/locationLiveMessageBannerBackground" + app:layout_constraintTop_toTopOf="parent" + app:srcCompat="@drawable/ic_attachment_location_live_white" + tools:ignore="ContentDescription" /> + + <TextView + android:id="@+id/locationLiveMessageBannerTitle" + style="@style/Widget.Vector.TextView.Caption" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginHorizontal="8dp" + android:text="@string/location_share_live_enabled" + android:textColor="?colorOnSurface" + app:layout_constraintBottom_toTopOf="@id/locationLiveMessageBannerSubTitle" + app:layout_constraintStart_toEndOf="@id/locationLiveMessageBannerIcon" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_chainStyle="packed" /> + + <TextView + android:id="@+id/locationLiveMessageBannerSubTitle" + style="@style/Widget.Vector.TextView.Caption" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="?vctr_content_secondary" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="@id/locationLiveMessageBannerTitle" + app:layout_constraintTop_toBottomOf="@id/locationLiveMessageBannerTitle" + tools:text="9min left" /> + + <Button + android:id="@+id/locationLiveMessageBannerStop" + style="@style/Widget.Vector.Button.Text.LocationLive" + android:layout_width="wrap_content" + android:layout_height="0dp" + android:layout_marginHorizontal="8dp" + android:text="@string/location_share_live_stop_long_version" + app:layout_constraintBottom_toBottomOf="@id/locationLiveMessageBannerBackground" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="@id/locationLiveMessageBannerBackground" /> +</merge> diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index d5bc322546..f137592c60 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3011,6 +3011,7 @@ <string name="location_share_live_enabled">Live location enabled</string> <string name="location_share_live_started">Loading live location…</string> <string name="location_share_live_stop">Stop</string> + <string name="location_share_live_stop_long_version">Stop sharing</string> <string name="live_location_sharing_notification_title">${app_name} Live Location</string> <string name="live_location_sharing_notification_description">Location sharing is in progress</string> From a412b2128d583f7ec375268f7d598c89583656eb Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Tue, 5 Apr 2022 15:13:01 +0200 Subject: [PATCH 08/45] Rendering method --- .../im/vector/app/core/utils/TextUtils.kt | 55 ++++++++++++++++++- .../live/LocationLiveMessageBannerView.kt | 20 ++++++- vector/src/main/res/values/strings.xml | 9 +++ 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt b/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt index 992a85679c..db2009a620 100644 --- a/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt +++ b/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt @@ -19,11 +19,15 @@ package im.vector.app.core.utils import android.content.Context import android.os.Build import android.text.format.Formatter +import im.vector.app.R import org.threeten.bp.Duration import java.util.TreeMap object TextUtils { + private const val MINUTES_PER_HOUR = 60 + private const val SECONDS_PER_MINUTE = 60 + private val suffixes = TreeMap<Int, String>().also { it[1000] = "k" it[1000000] = "M" @@ -71,13 +75,58 @@ object TextUtils { } fun formatDuration(duration: Duration): String { - val hours = duration.seconds / 3600 - val minutes = (duration.seconds % 3600) / 60 - val seconds = duration.seconds % 60 + val hours = getHours(duration) + val minutes = getMinutes(duration) + val seconds = getSeconds(duration) return if (hours > 0) { String.format("%d:%02d:%02d", hours, minutes, seconds) } else { String.format("%02d:%02d", minutes, seconds) } } + + fun formatDurationWithUnits(context: Context, duration: Duration): String { + val hours = getHours(duration) + val minutes = getMinutes(duration) + val seconds = getSeconds(duration) + val builder = StringBuilder() + // TODO do we need Locale ? test with different language setting + when { + hours > 0 -> { + appendHours(context, builder, hours) + builder.append(" ") + appendMinutes(context, builder, minutes) + builder.append(" ") + appendSeconds(context, builder, seconds) + } + minutes > 0 -> { + appendMinutes(context, builder, minutes) + builder.append(" ") + appendSeconds(context, builder, seconds) + } + else -> { + appendSeconds(context, builder, seconds) + } + } + + return builder.toString() + } + + private fun appendHours(context: Context, builder: StringBuilder, hours: Int) { + builder.append(context.resources.getQuantityString(R.plurals.time_unit_hour_short, hours)) + } + + private fun appendMinutes(context: Context, builder: StringBuilder, minutes: Int) { + builder.append(minutes) + builder.append(context.getString(R.string.time_unit_minute_short)) + } + + private fun appendSeconds(context: Context, builder: StringBuilder, seconds: Int) { + builder.append(seconds) + builder.append(context.getString(R.string.time_unit_second_short)) + } + + private fun getHours(duration: Duration): Int = duration.toHours().toInt() + private fun getMinutes(duration: Duration): Int = duration.toMinutes().toInt() % MINUTES_PER_HOUR + private fun getSeconds(duration: Duration): Int = (duration.seconds % SECONDS_PER_MINUTE).toInt() } diff --git a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt index 95805d5b7d..12fb0db4ba 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt @@ -20,9 +20,18 @@ import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater import android.widget.Button +import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.isVisible +import im.vector.app.R +import im.vector.app.core.utils.TextUtils import im.vector.app.databinding.ViewLocationLiveMessageBannerBinding -import im.vector.app.databinding.ViewLocationLiveStatusBinding +import org.threeten.bp.Duration + +data class LocationLiveMessageBannerViewState( + val isStopButtonVisible: Boolean, + val remainingTimeInMillis: Long +) class LocationLiveMessageBannerView @JvmOverloads constructor( context: Context, @@ -37,4 +46,13 @@ class LocationLiveMessageBannerView @JvmOverloads constructor( val stopButton: Button get() = binding.locationLiveMessageBannerStop + + private val subTitle: TextView + get() = binding.locationLiveMessageBannerSubTitle + + fun render(viewState: LocationLiveMessageBannerViewState) { + stopButton.isVisible = viewState.isStopButtonVisible + val duration = Duration.ofMillis(viewState.remainingTimeInMillis.coerceAtLeast(0L)) + subTitle.text = context.getString(R.string.location_share_live_remaining_time, TextUtils.formatDurationWithUnits(context, duration)) + } } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index f137592c60..b73bb47690 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -322,6 +322,14 @@ <string name="start_chatting">Start Chatting</string> <string name="spaces">Spaces</string> + <!-- Time units --> + <plurals name="time_unit_hour_short"> + <item quantity="one">hour</item> + <item quantity="other">hours</item> + </plurals> + <string name="time_unit_minute_short">min</string> + <string name="time_unit_second_short">sec</string> + <!-- Permissions denied forever --> <string name="denied_permission_generic">Some permissions are missing to perform this action, please grant the permissions from the system settings.</string> <string name="denied_permission_camera">To perform this action, please grant the Camera permission from the system settings.</string> @@ -3012,6 +3020,7 @@ <string name="location_share_live_started">Loading live location…</string> <string name="location_share_live_stop">Stop</string> <string name="location_share_live_stop_long_version">Stop sharing</string> + <string name="location_share_live_remaining_time">%1$s left</string> <string name="live_location_sharing_notification_title">${app_name} Live Location</string> <string name="live_location_sharing_notification_description">Location sharing is in progress</string> From 3acc139307f29597de925649cbd9cb40208da947 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Tue, 5 Apr 2022 16:07:19 +0200 Subject: [PATCH 09/45] Fix format of the duration --- vector/src/main/java/im/vector/app/core/utils/TextUtils.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt b/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt index db2009a620..bb197b2e9d 100644 --- a/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt +++ b/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt @@ -90,7 +90,6 @@ object TextUtils { val minutes = getMinutes(duration) val seconds = getSeconds(duration) val builder = StringBuilder() - // TODO do we need Locale ? test with different language setting when { hours > 0 -> { appendHours(context, builder, hours) @@ -108,11 +107,11 @@ object TextUtils { appendSeconds(context, builder, seconds) } } - return builder.toString() } private fun appendHours(context: Context, builder: StringBuilder, hours: Int) { + builder.append(hours) builder.append(context.resources.getQuantityString(R.plurals.time_unit_hour_short, hours)) } From bbec3a7c2ef540209b4d33b75edb502a468e97d6 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Tue, 5 Apr 2022 16:11:01 +0200 Subject: [PATCH 10/45] Integration into location message item --- .../src/main/res/values/styles_location.xml | 6 +- .../timeline/item/MessageLocationItem.kt | 46 ++++++++++- .../live/LocationLiveMessageBannerView.kt | 76 +++++++++++++++++-- .../item_timeline_event_location_stub.xml | 8 ++ .../view_location_live_message_banner.xml | 20 ++--- vector/src/main/res/values/strings.xml | 3 +- 6 files changed, 137 insertions(+), 22 deletions(-) diff --git a/library/ui-styles/src/main/res/values/styles_location.xml b/library/ui-styles/src/main/res/values/styles_location.xml index 8f76ed469e..7571265241 100644 --- a/library/ui-styles/src/main/res/values/styles_location.xml +++ b/library/ui-styles/src/main/res/values/styles_location.xml @@ -2,14 +2,16 @@ <resources> <style name="Widget.Vector.Button.Text.OnPrimary.LocationLive"> - <item name="android:background">?selectableItemBackground</item> + <item name="android:foreground">?selectableItemBackground</item> + <item name="android:background">@android:color/transparent</item> <item name="android:textSize">12sp</item> <item name="android:padding">0dp</item> <item name="android:gravity">center</item> </style> <style name="Widget.Vector.Button.Text.LocationLive"> - <item name="android:background">?selectableItemBackground</item> + <item name="android:foreground">?selectableItemBackground</item> + <item name="android:background">@android:color/transparent</item> <item name="android:textAppearance">@style/TextAppearance.Vector.Body.Medium</item> <item name="android:textColor">?colorError</item> <item name="android:padding">0dp</item> diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt index 1e2808afd8..cb823aae06 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt @@ -35,6 +35,8 @@ import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners +import im.vector.app.features.location.live.LocationLiveMessageBannerView +import im.vector.app.features.location.live.LocationLiveMessageBannerViewState @EpoxyModelClass(layout = R.layout.item_timeline_event_base) abstract class MessageLocationItem : AbsMessageItem<MessageLocationItem.Holder>() { @@ -57,12 +59,17 @@ abstract class MessageLocationItem : AbsMessageItem<MessageLocationItem.Holder>( override fun bind(holder: Holder) { super.bind(holder) renderSendState(holder.view, null) + bindMap(holder) + bindLocationLiveBanner(holder) + } + + private fun bindMap(holder: Holder) { val location = locationUrl ?: return val messageLayout = attributes.informationData.messageLayout - val dimensionConverter = DimensionConverter(holder.view.resources) val imageCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) { messageLayout.cornersRadius.granularRoundedCorners() } else { + val dimensionConverter = DimensionConverter(holder.view.resources) RoundedCorners(dimensionConverter.dpToPx(8)) } holder.staticMapImageView.updateLayoutParams { @@ -88,9 +95,8 @@ abstract class MessageLocationItem : AbsMessageItem<MessageLocationItem.Holder>( dataSource: DataSource?, isFirstResource: Boolean): Boolean { locationPinProvider?.create(userId) { pinDrawable -> - GlideApp.with(holder.staticMapPinImageView) - .load(pinDrawable) - .into(holder.staticMapPinImageView) + // we are not using Glide since it does not display it correctly when there is no user photo + holder.staticMapPinImageView.setImageDrawable(pinDrawable) } holder.staticMapErrorTextView.isVisible = false return false @@ -100,12 +106,44 @@ abstract class MessageLocationItem : AbsMessageItem<MessageLocationItem.Holder>( .into(holder.staticMapImageView) } + private fun bindLocationLiveBanner(holder: Holder) { + val messageLayout = attributes.informationData.messageLayout + val viewState = if (messageLayout is TimelineMessageLayout.Bubble) { + LocationLiveMessageBannerViewState.Emitter( + remainingTimeInMillis = 4000 * 1000L, + bottomStartCornerRadiusInDp = messageLayout.cornersRadius.bottomStartRadius, + bottomEndCornerRadiusInDp = messageLayout.cornersRadius.bottomEndRadius, + isStopButtonCenteredVertically = false + ) + } else { + val dimensionConverter = DimensionConverter(holder.view.resources) + val cornerRadius = dimensionConverter.dpToPx(8).toFloat() +// LocationLiveMessageBannerViewState.Watcher( +// bottomStartCornerRadiusInDp = cornerRadius, +// bottomEndCornerRadiusInDp = cornerRadius, +// formattedLocalTimeOfEndOfLive = "12:34", +// ) + LocationLiveMessageBannerViewState.Emitter( + remainingTimeInMillis = 4000 * 1000L, + bottomStartCornerRadiusInDp = cornerRadius, + bottomEndCornerRadiusInDp = cornerRadius, + isStopButtonCenteredVertically = true + ) + } + holder.locationLiveMessageBanner.isVisible = true + holder.locationLiveMessageBanner.render(viewState) + + // TODO create a dedicated message Item per state: Start, Location, End? Check if inheritance is possible in Epoxy model + // TODO adjust Copyright map placement + } + override fun getViewStubId() = STUB_ID class Holder : AbsMessageItem.Holder(STUB_ID) { val staticMapImageView by bind<ImageView>(R.id.staticMapImageView) val staticMapPinImageView by bind<ImageView>(R.id.staticMapPinImageView) val staticMapErrorTextView by bind<TextView>(R.id.staticMapErrorTextView) + val locationLiveMessageBanner by bind<LocationLiveMessageBannerView>(R.id.locationLiveMessageBanner) } companion object { diff --git a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt index 12fb0db4ba..989adc27b5 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt @@ -17,21 +17,42 @@ package im.vector.app.features.location.live import android.content.Context +import android.graphics.drawable.ColorDrawable import android.util.AttributeSet import android.view.LayoutInflater import android.widget.Button +import android.widget.ImageView import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet import androidx.core.view.isVisible +import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners import im.vector.app.R +import im.vector.app.core.glide.GlideApp import im.vector.app.core.utils.TextUtils import im.vector.app.databinding.ViewLocationLiveMessageBannerBinding +import im.vector.app.features.themes.ThemeUtils import org.threeten.bp.Duration -data class LocationLiveMessageBannerViewState( - val isStopButtonVisible: Boolean, - val remainingTimeInMillis: Long -) +// TODO should it be moved to timeline.item package? +sealed class LocationLiveMessageBannerViewState( + open val bottomStartCornerRadiusInDp: Float, + open val bottomEndCornerRadiusInDp: Float, +) { + + data class Emitter( + override val bottomStartCornerRadiusInDp: Float, + override val bottomEndCornerRadiusInDp: Float, + val remainingTimeInMillis: Long, + val isStopButtonCenteredVertically: Boolean + ) : LocationLiveMessageBannerViewState(bottomStartCornerRadiusInDp, bottomEndCornerRadiusInDp) + + data class Watcher( + override val bottomStartCornerRadiusInDp: Float, + override val bottomEndCornerRadiusInDp: Float, + val formattedLocalTimeOfEndOfLive: String, + ) : LocationLiveMessageBannerViewState(bottomStartCornerRadiusInDp, bottomEndCornerRadiusInDp) +} class LocationLiveMessageBannerView @JvmOverloads constructor( context: Context, @@ -47,12 +68,57 @@ class LocationLiveMessageBannerView @JvmOverloads constructor( val stopButton: Button get() = binding.locationLiveMessageBannerStop + private val background: ImageView + get() = binding.locationLiveMessageBannerBackground + + private val title: TextView + get() = binding.locationLiveMessageBannerTitle + private val subTitle: TextView get() = binding.locationLiveMessageBannerSubTitle fun render(viewState: LocationLiveMessageBannerViewState) { - stopButton.isVisible = viewState.isStopButtonVisible + when (viewState) { + is LocationLiveMessageBannerViewState.Emitter -> renderEmitter(viewState) + is LocationLiveMessageBannerViewState.Watcher -> renderWatcher(viewState) + } + + GlideApp.with(context) + .load(ColorDrawable(ThemeUtils.getColor(context, R.attr.colorSurface))) + .transform(GranularRoundedCorners(0f, 0f, viewState.bottomEndCornerRadiusInDp, viewState.bottomStartCornerRadiusInDp)) + .into(background) + } + + private fun renderEmitter(viewState: LocationLiveMessageBannerViewState.Emitter) { + stopButton.isVisible = true + title.text = context.getString(R.string.location_share_live_enabled) val duration = Duration.ofMillis(viewState.remainingTimeInMillis.coerceAtLeast(0L)) subTitle.text = context.getString(R.string.location_share_live_remaining_time, TextUtils.formatDurationWithUnits(context, duration)) + + val rootLayout: ConstraintLayout? = (binding.root as? ConstraintLayout) + rootLayout?.let { parentLayout -> + val constraintSet = ConstraintSet() + constraintSet.clone(rootLayout) + + if (viewState.isStopButtonCenteredVertically) { + constraintSet.connect( + R.id.locationLiveMessageBannerStop, + ConstraintSet.BOTTOM, + R.id.locationLiveMessageBannerBackground, + ConstraintSet.BOTTOM, + 0 + ) + } else { + constraintSet.clear(R.id.locationLiveMessageBannerStop, ConstraintSet.BOTTOM) + } + + constraintSet.applyTo(parentLayout) + } + } + + private fun renderWatcher(viewState: LocationLiveMessageBannerViewState.Watcher) { + stopButton.isVisible = false + title.text = context.getString(R.string.location_share_live_view) + subTitle.text = context.getString(R.string.location_share_live_until, viewState.formattedLocalTimeOfEndOfLive) } } diff --git a/vector/src/main/res/layout/item_timeline_event_location_stub.xml b/vector/src/main/res/layout/item_timeline_event_location_stub.xml index f735c2b00e..036d5149b0 100644 --- a/vector/src/main/res/layout/item_timeline_event_location_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_location_stub.xml @@ -45,4 +45,12 @@ app:layout_constraintTop_toBottomOf="@id/staticMapPinImageView" tools:visibility="visible" /> + <im.vector.app.features.location.live.LocationLiveMessageBannerView + android:id="@+id/locationLiveMessageBanner" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintStart_toStartOf="@id/staticMapImageView" + app:layout_constraintEnd_toEndOf="@id/staticMapImageView" + app:layout_constraintBottom_toBottomOf="@id/staticMapImageView" /> + </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/vector/src/main/res/layout/view_location_live_message_banner.xml b/vector/src/main/res/layout/view_location_live_message_banner.xml index f3345bac05..42004a5b81 100644 --- a/vector/src/main/res/layout/view_location_live_message_banner.xml +++ b/vector/src/main/res/layout/view_location_live_message_banner.xml @@ -6,15 +6,16 @@ android:layout_height="wrap_content" tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"> - <View + <ImageView android:id="@+id/locationLiveMessageBannerBackground" android:layout_width="0dp" - android:layout_height="48dp" + android:layout_height="50dp" android:alpha="0.85" - android:background="?colorSurface" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toTopOf="parent" + tools:background="?colorSurface" + tools:ignore="ContentDescription" /> <ImageView android:id="@+id/locationLiveMessageBannerIcon" @@ -36,7 +37,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginHorizontal="8dp" - android:text="@string/location_share_live_enabled" + tools:text="@string/location_share_live_enabled" android:textColor="?colorOnSurface" app:layout_constraintBottom_toTopOf="@id/locationLiveMessageBannerSubTitle" app:layout_constraintStart_toEndOf="@id/locationLiveMessageBannerIcon" @@ -57,11 +58,10 @@ <Button android:id="@+id/locationLiveMessageBannerStop" style="@style/Widget.Vector.Button.Text.LocationLive" - android:layout_width="wrap_content" - android:layout_height="0dp" - android:layout_marginHorizontal="8dp" - android:text="@string/location_share_live_stop_long_version" - app:layout_constraintBottom_toBottomOf="@id/locationLiveMessageBannerBackground" + android:layout_width="45dp" + android:layout_height="30dp" + android:text="@string/location_share_live_stop" app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toBottomOf="@id/locationLiveMessageBannerBackground" app:layout_constraintTop_toTopOf="@id/locationLiveMessageBannerBackground" /> </merge> diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index b73bb47690..00b29e83b3 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3018,8 +3018,9 @@ <string name="location_timeline_failed_to_load_map">Failed to load map</string> <string name="location_share_live_enabled">Live location enabled</string> <string name="location_share_live_started">Loading live location…</string> + <string name="location_share_live_view">View live location</string> + <string name="location_share_live_until">Live until %1$s</string> <string name="location_share_live_stop">Stop</string> - <string name="location_share_live_stop_long_version">Stop sharing</string> <string name="location_share_live_remaining_time">%1$s left</string> <string name="live_location_sharing_notification_title">${app_name} Live Location</string> <string name="live_location_sharing_notification_description">Location sharing is in progress</string> From 8c012145f921a463ed5c14da1a574e7ab06453db Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 11 Apr 2022 15:46:30 +0200 Subject: [PATCH 11/45] Creating a dedicated live location item --- .../timeline/item/AbsMessageLocationItem.kt | 115 +++++++++++++++ .../timeline/item/MessageLiveLocationItem.kt | 70 +++++++++ .../timeline/item/MessageLocationItem.kt | 133 +----------------- .../item_timeline_event_location_stub.xml | 6 +- 4 files changed, 192 insertions(+), 132 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt new file mode 100644 index 0000000000..b95fd0f4e1 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2021 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 im.vector.app.features.home.room.detail.timeline.item + +import android.graphics.drawable.Drawable +import android.widget.ImageView +import android.widget.TextView +import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams +import com.airbnb.epoxy.EpoxyAttribute +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.engine.GlideException +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.Target +import im.vector.app.R +import im.vector.app.core.glide.GlideApp +import im.vector.app.core.utils.DimensionConverter +import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider +import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout +import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners + +abstract class AbsMessageLocationItem<H : AbsMessageLocationItem.Holder> : AbsMessageItem<H>() { + + @EpoxyAttribute + var locationUrl: String? = null + + @EpoxyAttribute + var userId: String? = null + + @EpoxyAttribute + var mapWidth: Int = 0 + + @EpoxyAttribute + var mapHeight: Int = 0 + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var locationPinProvider: LocationPinProvider? = null + + override fun bind(holder: H) { + super.bind(holder) + renderSendState(holder.view, null) + bindMap(holder) + } + + private fun bindMap(holder: Holder) { + val location = locationUrl ?: return + val messageLayout = attributes.informationData.messageLayout + val imageCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) { + messageLayout.cornersRadius.granularRoundedCorners() + } else { + val dimensionConverter = DimensionConverter(holder.view.resources) + RoundedCorners(dimensionConverter.dpToPx(8)) + } + holder.staticMapImageView.updateLayoutParams { + width = mapWidth + height = mapHeight + } + GlideApp.with(holder.staticMapImageView) + .load(location) + .apply(RequestOptions.centerCropTransform()) + .listener(object : RequestListener<Drawable> { + override fun onLoadFailed(e: GlideException?, + model: Any?, + target: Target<Drawable>?, + isFirstResource: Boolean): Boolean { + holder.staticMapPinImageView.setImageResource(R.drawable.ic_location_pin_failed) + holder.staticMapErrorTextView.isVisible = true + return false + } + + override fun onResourceReady(resource: Drawable?, + model: Any?, + target: Target<Drawable>?, + dataSource: DataSource?, + isFirstResource: Boolean): Boolean { + locationPinProvider?.create(userId) { pinDrawable -> + // we are not using Glide since it does not display it correctly when there is no user photo + holder.staticMapPinImageView.setImageDrawable(pinDrawable) + } + holder.staticMapErrorTextView.isVisible = false + return false + } + }) + .transform(imageCornerTransformation) + .into(holder.staticMapImageView) + } + + override fun getViewStubId() = STUB_ID + + open class Holder : AbsMessageItem.Holder(STUB_ID) { + val staticMapImageView by bind<ImageView>(R.id.staticMapImageView) + val staticMapPinImageView by bind<ImageView>(R.id.staticMapPinImageView) + val staticMapErrorTextView by bind<TextView>(R.id.staticMapErrorTextView) + } + + companion object { + private const val STUB_ID = R.id.messageContentLocationStub + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt new file mode 100644 index 0000000000..6163ac5463 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.home.room.detail.timeline.item + +import androidx.core.view.isVisible +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.utils.DimensionConverter +import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout +import im.vector.app.features.location.live.LocationLiveMessageBannerView +import im.vector.app.features.location.live.LocationLiveMessageBannerViewState + +@EpoxyModelClass(layout = R.layout.item_timeline_event_base) +abstract class MessageLiveLocationItem : AbsMessageLocationItem<MessageLiveLocationItem.Holder>() { + + // TODO define the needed attributes + + override fun bind(holder: Holder) { + super.bind(holder) + bindLocationLiveBanner(holder) + } + + private fun bindLocationLiveBanner(holder: Holder) { + val messageLayout = attributes.informationData.messageLayout + val viewState = if (messageLayout is TimelineMessageLayout.Bubble) { + LocationLiveMessageBannerViewState.Emitter( + remainingTimeInMillis = 4000 * 1000L, + bottomStartCornerRadiusInDp = messageLayout.cornersRadius.bottomStartRadius, + bottomEndCornerRadiusInDp = messageLayout.cornersRadius.bottomEndRadius, + isStopButtonCenteredVertically = false + ) + } else { + val dimensionConverter = DimensionConverter(holder.view.resources) + val cornerRadius = dimensionConverter.dpToPx(8).toFloat() +// LocationLiveMessageBannerViewState.Watcher( +// bottomStartCornerRadiusInDp = cornerRadius, +// bottomEndCornerRadiusInDp = cornerRadius, +// formattedLocalTimeOfEndOfLive = "12:34", +// ) + LocationLiveMessageBannerViewState.Emitter( + remainingTimeInMillis = 4000 * 1000L, + bottomStartCornerRadiusInDp = cornerRadius, + bottomEndCornerRadiusInDp = cornerRadius, + isStopButtonCenteredVertically = true + ) + } + holder.locationLiveMessageBanner.isVisible = true + holder.locationLiveMessageBanner.render(viewState) + + // TODO adjust Copyright map placement if needed + } + + class Holder : AbsMessageLocationItem.Holder() { + val locationLiveMessageBanner by bind<LocationLiveMessageBannerView>(R.id.locationLiveMessageBanner) + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt index cb823aae06..7da9149dc4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * Copyright (c) 2022 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. @@ -16,137 +16,10 @@ package im.vector.app.features.home.room.detail.timeline.item -import android.graphics.drawable.Drawable -import android.widget.ImageView -import android.widget.TextView -import androidx.core.view.isVisible -import androidx.core.view.updateLayoutParams -import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass -import com.bumptech.glide.load.DataSource -import com.bumptech.glide.load.engine.GlideException -import com.bumptech.glide.load.resource.bitmap.RoundedCorners -import com.bumptech.glide.request.RequestListener -import com.bumptech.glide.request.RequestOptions -import com.bumptech.glide.request.target.Target import im.vector.app.R -import im.vector.app.core.glide.GlideApp -import im.vector.app.core.utils.DimensionConverter -import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider -import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout -import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners -import im.vector.app.features.location.live.LocationLiveMessageBannerView -import im.vector.app.features.location.live.LocationLiveMessageBannerViewState @EpoxyModelClass(layout = R.layout.item_timeline_event_base) -abstract class MessageLocationItem : AbsMessageItem<MessageLocationItem.Holder>() { - - @EpoxyAttribute - var locationUrl: String? = null - - @EpoxyAttribute - var userId: String? = null - - @EpoxyAttribute - var mapWidth: Int = 0 - - @EpoxyAttribute - var mapHeight: Int = 0 - - @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) - var locationPinProvider: LocationPinProvider? = null - - override fun bind(holder: Holder) { - super.bind(holder) - renderSendState(holder.view, null) - bindMap(holder) - bindLocationLiveBanner(holder) - } - - private fun bindMap(holder: Holder) { - val location = locationUrl ?: return - val messageLayout = attributes.informationData.messageLayout - val imageCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) { - messageLayout.cornersRadius.granularRoundedCorners() - } else { - val dimensionConverter = DimensionConverter(holder.view.resources) - RoundedCorners(dimensionConverter.dpToPx(8)) - } - holder.staticMapImageView.updateLayoutParams { - width = mapWidth - height = mapHeight - } - GlideApp.with(holder.staticMapImageView) - .load(location) - .apply(RequestOptions.centerCropTransform()) - .listener(object : RequestListener<Drawable> { - override fun onLoadFailed(e: GlideException?, - model: Any?, - target: Target<Drawable>?, - isFirstResource: Boolean): Boolean { - holder.staticMapPinImageView.setImageResource(R.drawable.ic_location_pin_failed) - holder.staticMapErrorTextView.isVisible = true - return false - } - - override fun onResourceReady(resource: Drawable?, - model: Any?, - target: Target<Drawable>?, - dataSource: DataSource?, - isFirstResource: Boolean): Boolean { - locationPinProvider?.create(userId) { pinDrawable -> - // we are not using Glide since it does not display it correctly when there is no user photo - holder.staticMapPinImageView.setImageDrawable(pinDrawable) - } - holder.staticMapErrorTextView.isVisible = false - return false - } - }) - .transform(imageCornerTransformation) - .into(holder.staticMapImageView) - } - - private fun bindLocationLiveBanner(holder: Holder) { - val messageLayout = attributes.informationData.messageLayout - val viewState = if (messageLayout is TimelineMessageLayout.Bubble) { - LocationLiveMessageBannerViewState.Emitter( - remainingTimeInMillis = 4000 * 1000L, - bottomStartCornerRadiusInDp = messageLayout.cornersRadius.bottomStartRadius, - bottomEndCornerRadiusInDp = messageLayout.cornersRadius.bottomEndRadius, - isStopButtonCenteredVertically = false - ) - } else { - val dimensionConverter = DimensionConverter(holder.view.resources) - val cornerRadius = dimensionConverter.dpToPx(8).toFloat() -// LocationLiveMessageBannerViewState.Watcher( -// bottomStartCornerRadiusInDp = cornerRadius, -// bottomEndCornerRadiusInDp = cornerRadius, -// formattedLocalTimeOfEndOfLive = "12:34", -// ) - LocationLiveMessageBannerViewState.Emitter( - remainingTimeInMillis = 4000 * 1000L, - bottomStartCornerRadiusInDp = cornerRadius, - bottomEndCornerRadiusInDp = cornerRadius, - isStopButtonCenteredVertically = true - ) - } - holder.locationLiveMessageBanner.isVisible = true - holder.locationLiveMessageBanner.render(viewState) - - // TODO create a dedicated message Item per state: Start, Location, End? Check if inheritance is possible in Epoxy model - // TODO adjust Copyright map placement - } - - override fun getViewStubId() = STUB_ID - - class Holder : AbsMessageItem.Holder(STUB_ID) { - val staticMapImageView by bind<ImageView>(R.id.staticMapImageView) - val staticMapPinImageView by bind<ImageView>(R.id.staticMapPinImageView) - val staticMapErrorTextView by bind<TextView>(R.id.staticMapErrorTextView) - val locationLiveMessageBanner by bind<LocationLiveMessageBannerView>(R.id.locationLiveMessageBanner) - } - - companion object { - private const val STUB_ID = R.id.messageContentLocationStub - } +abstract class MessageLocationItem : AbsMessageLocationItem<MessageLocationItem.Holder>() { + class Holder : AbsMessageLocationItem.Holder() } diff --git a/vector/src/main/res/layout/item_timeline_event_location_stub.xml b/vector/src/main/res/layout/item_timeline_event_location_stub.xml index 036d5149b0..656743f26c 100644 --- a/vector/src/main/res/layout/item_timeline_event_location_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_location_stub.xml @@ -49,8 +49,10 @@ android:id="@+id/locationLiveMessageBanner" android:layout_width="0dp" android:layout_height="wrap_content" - app:layout_constraintStart_toStartOf="@id/staticMapImageView" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="@id/staticMapImageView" app:layout_constraintEnd_toEndOf="@id/staticMapImageView" - app:layout_constraintBottom_toBottomOf="@id/staticMapImageView" /> + app:layout_constraintStart_toStartOf="@id/staticMapImageView" + tools:visibility="visible" /> </androidx.constraintlayout.widget.ConstraintLayout> From 93634cd7af37dc24020ff70b7e971b098e4a216f Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 11 Apr 2022 15:47:52 +0200 Subject: [PATCH 12/45] Removing TODO --- .../app/features/location/live/LocationLiveMessageBannerView.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt index 989adc27b5..bc161c62c6 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt @@ -34,7 +34,6 @@ import im.vector.app.databinding.ViewLocationLiveMessageBannerBinding import im.vector.app.features.themes.ThemeUtils import org.threeten.bp.Duration -// TODO should it be moved to timeline.item package? sealed class LocationLiveMessageBannerViewState( open val bottomStartCornerRadiusInDp: Float, open val bottomEndCornerRadiusInDp: Float, From 11d9579036bbcfe62b160d4c413e4954d3401924 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Tue, 12 Apr 2022 10:32:06 +0200 Subject: [PATCH 13/45] Adding currentUserId attribute --- .../timeline/item/MessageLiveLocationItem.kt | 73 +++++++++++++------ 1 file changed, 50 insertions(+), 23 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt index 6163ac5463..741866784a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt @@ -17,6 +17,7 @@ package im.vector.app.features.home.room.detail.timeline.item import androidx.core.view.isVisible +import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R import im.vector.app.core.utils.DimensionConverter @@ -28,6 +29,8 @@ import im.vector.app.features.location.live.LocationLiveMessageBannerViewState abstract class MessageLiveLocationItem : AbsMessageLocationItem<MessageLiveLocationItem.Holder>() { // TODO define the needed attributes + @EpoxyAttribute + var currentUserId: String? = null override fun bind(holder: Holder) { super.bind(holder) @@ -35,35 +38,59 @@ abstract class MessageLiveLocationItem : AbsMessageLocationItem<MessageLiveLocat } private fun bindLocationLiveBanner(holder: Holder) { + // TODO add check on device id to confirm that is the one that sent the beacon + val isEmitter = currentUserId != null && currentUserId == userId val messageLayout = attributes.informationData.messageLayout - val viewState = if (messageLayout is TimelineMessageLayout.Bubble) { - LocationLiveMessageBannerViewState.Emitter( - remainingTimeInMillis = 4000 * 1000L, - bottomStartCornerRadiusInDp = messageLayout.cornersRadius.bottomStartRadius, - bottomEndCornerRadiusInDp = messageLayout.cornersRadius.bottomEndRadius, - isStopButtonCenteredVertically = false - ) - } else { - val dimensionConverter = DimensionConverter(holder.view.resources) - val cornerRadius = dimensionConverter.dpToPx(8).toFloat() -// LocationLiveMessageBannerViewState.Watcher( -// bottomStartCornerRadiusInDp = cornerRadius, -// bottomEndCornerRadiusInDp = cornerRadius, -// formattedLocalTimeOfEndOfLive = "12:34", -// ) - LocationLiveMessageBannerViewState.Emitter( - remainingTimeInMillis = 4000 * 1000L, - bottomStartCornerRadiusInDp = cornerRadius, - bottomEndCornerRadiusInDp = cornerRadius, - isStopButtonCenteredVertically = true - ) - } + val viewState = buildViewState(holder, messageLayout, isEmitter) holder.locationLiveMessageBanner.isVisible = true holder.locationLiveMessageBanner.render(viewState) - // TODO adjust Copyright map placement if needed } + private fun buildViewState( + holder: Holder, + messageLayout: TimelineMessageLayout, + isEmitter: Boolean + ): LocationLiveMessageBannerViewState { + return when { + messageLayout is TimelineMessageLayout.Bubble && isEmitter -> + LocationLiveMessageBannerViewState.Emitter( + remainingTimeInMillis = 4000 * 1000L, + bottomStartCornerRadiusInDp = messageLayout.cornersRadius.bottomStartRadius, + bottomEndCornerRadiusInDp = messageLayout.cornersRadius.bottomEndRadius, + isStopButtonCenteredVertically = false + ) + messageLayout is TimelineMessageLayout.Bubble -> + LocationLiveMessageBannerViewState.Watcher( + bottomStartCornerRadiusInDp = messageLayout.cornersRadius.bottomStartRadius, + bottomEndCornerRadiusInDp = messageLayout.cornersRadius.bottomEndRadius, + formattedLocalTimeOfEndOfLive = "12:34", + ) + isEmitter -> { + val cornerRadius = getBannerCornerRadiusForDefaultLayout(holder) + LocationLiveMessageBannerViewState.Emitter( + remainingTimeInMillis = 4000 * 1000L, + bottomStartCornerRadiusInDp = cornerRadius, + bottomEndCornerRadiusInDp = cornerRadius, + isStopButtonCenteredVertically = true + ) + } + else -> { + val cornerRadius = getBannerCornerRadiusForDefaultLayout(holder) + LocationLiveMessageBannerViewState.Watcher( + bottomStartCornerRadiusInDp = cornerRadius, + bottomEndCornerRadiusInDp = cornerRadius, + formattedLocalTimeOfEndOfLive = "12:34", + ) + } + } + } + + private fun getBannerCornerRadiusForDefaultLayout(holder: Holder): Float { + val dimensionConverter = DimensionConverter(holder.view.resources) + return dimensionConverter.dpToPx(8).toFloat() + } + class Holder : AbsMessageLocationItem.Holder() { val locationLiveMessageBanner by bind<LocationLiveMessageBannerView>(R.id.locationLiveMessageBanner) } From c432985cb45aa4442316763f358be9c5592a695f Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Tue, 12 Apr 2022 15:02:24 +0200 Subject: [PATCH 14/45] Adding countDownTimer for emitter view --- .../live/LocationLiveMessageBannerView.kt | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt index bc161c62c6..3c2a13655b 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt @@ -18,6 +18,7 @@ package im.vector.app.features.location.live import android.content.Context import android.graphics.drawable.ColorDrawable +import android.os.CountDownTimer import android.util.AttributeSet import android.view.LayoutInflater import android.widget.Button @@ -53,6 +54,8 @@ sealed class LocationLiveMessageBannerViewState( ) : LocationLiveMessageBannerViewState(bottomStartCornerRadiusInDp, bottomEndCornerRadiusInDp) } +private const val REMAINING_TIME_COUNTER_INTERVAL_IN_MS = 1000L + class LocationLiveMessageBannerView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, @@ -76,6 +79,8 @@ class LocationLiveMessageBannerView @JvmOverloads constructor( private val subTitle: TextView get() = binding.locationLiveMessageBannerSubTitle + private var countDownTimer: CountDownTimer? = null + fun render(viewState: LocationLiveMessageBannerViewState) { when (viewState) { is LocationLiveMessageBannerViewState.Emitter -> renderEmitter(viewState) @@ -91,8 +96,19 @@ class LocationLiveMessageBannerView @JvmOverloads constructor( private fun renderEmitter(viewState: LocationLiveMessageBannerViewState.Emitter) { stopButton.isVisible = true title.text = context.getString(R.string.location_share_live_enabled) - val duration = Duration.ofMillis(viewState.remainingTimeInMillis.coerceAtLeast(0L)) - subTitle.text = context.getString(R.string.location_share_live_remaining_time, TextUtils.formatDurationWithUnits(context, duration)) + + countDownTimer?.cancel() + countDownTimer = object : CountDownTimer(viewState.remainingTimeInMillis, REMAINING_TIME_COUNTER_INTERVAL_IN_MS) { + override fun onTick(millisUntilFinished: Long) { + val duration = Duration.ofMillis(millisUntilFinished.coerceAtLeast(0L)) + subTitle.text = context.getString(R.string.location_share_live_remaining_time, TextUtils.formatDurationWithUnits(context, duration)) + } + + override fun onFinish() { + subTitle.text = context.getString(R.string.location_share_live_remaining_time, TextUtils.formatDurationWithUnits(context, Duration.ofMillis(0L))) + } + } + countDownTimer?.start() val rootLayout: ConstraintLayout? = (binding.root as? ConstraintLayout) rootLayout?.let { parentLayout -> From 8b628229ed4ab111959bba9c9af2cbc29178a783 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Tue, 12 Apr 2022 15:21:30 +0200 Subject: [PATCH 15/45] Do not append 0 values when formatting duration --- .../java/im/vector/app/core/utils/TextUtils.kt | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt b/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt index bb197b2e9d..f9c33ffe2a 100644 --- a/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt +++ b/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt @@ -93,15 +93,21 @@ object TextUtils { when { hours > 0 -> { appendHours(context, builder, hours) - builder.append(" ") - appendMinutes(context, builder, minutes) - builder.append(" ") - appendSeconds(context, builder, seconds) + if(minutes > 0) { + builder.append(" ") + appendMinutes(context, builder, minutes) + } + if(seconds > 0) { + builder.append(" ") + appendSeconds(context, builder, seconds) + } } minutes > 0 -> { appendMinutes(context, builder, minutes) - builder.append(" ") - appendSeconds(context, builder, seconds) + if(seconds > 0) { + builder.append(" ") + appendSeconds(context, builder, seconds) + } } else -> { appendSeconds(context, builder, seconds) From adbc430ac8d2e8d619ee296d2b086307ec5e8ecb Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 2 May 2022 10:29:14 +0200 Subject: [PATCH 16/45] Renaming userId into locationUserId in message location item --- .../home/room/detail/timeline/factory/MessageItemFactory.kt | 4 ++-- .../home/room/detail/timeline/item/AbsMessageLocationItem.kt | 4 ++-- .../home/room/detail/timeline/item/MessageLiveLocationItem.kt | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 6f22b3eff4..868018a706 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -237,14 +237,14 @@ class MessageItemFactory @Inject constructor( urlMapProvider.buildStaticMapUrl(it, INITIAL_MAP_ZOOM_IN_TIMELINE, width, height) } - val userId = if (locationContent.isSelfLocation()) informationData.senderId else null + val locationUserId = if (locationContent.isSelfLocation()) informationData.senderId else null return MessageLocationItem_() .attributes(attributes) .locationUrl(locationUrl) .mapWidth(width) .mapHeight(height) - .userId(userId) + .locationUserId(locationUserId) .locationPinProvider(locationPinProvider) .highlighted(highlight) .leftGuideline(avatarSizeProvider.leftGuideline) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt index b95fd0f4e1..ad18759fc6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt @@ -41,7 +41,7 @@ abstract class AbsMessageLocationItem<H : AbsMessageLocationItem.Holder> : AbsMe var locationUrl: String? = null @EpoxyAttribute - var userId: String? = null + var locationUserId: String? = null @EpoxyAttribute var mapWidth: Int = 0 @@ -89,7 +89,7 @@ abstract class AbsMessageLocationItem<H : AbsMessageLocationItem.Holder> : AbsMe target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean { - locationPinProvider?.create(userId) { pinDrawable -> + locationPinProvider?.create(locationUserId) { pinDrawable -> // we are not using Glide since it does not display it correctly when there is no user photo holder.staticMapPinImageView.setImageDrawable(pinDrawable) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt index 741866784a..bbf2ca1348 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt @@ -39,7 +39,7 @@ abstract class MessageLiveLocationItem : AbsMessageLocationItem<MessageLiveLocat private fun bindLocationLiveBanner(holder: Holder) { // TODO add check on device id to confirm that is the one that sent the beacon - val isEmitter = currentUserId != null && currentUserId == userId + val isEmitter = currentUserId != null && currentUserId == locationUserId val messageLayout = attributes.informationData.messageLayout val viewState = buildViewState(holder, messageLayout, isEmitter) holder.locationLiveMessageBanner.isVisible = true From 077977b8bf4e5423114a603b5e1f779deb42d1af Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 2 May 2022 12:18:21 +0200 Subject: [PATCH 17/45] Show running live state item --- .../LiveLocationShareMessageItemFactory.kt | 43 +++++++++++++++++-- .../timeline/item/MessageLiveLocationItem.kt | 30 ++++++++++--- .../app/features/location/LocationData.kt | 11 ++++- .../live/LocationLiveMessageBannerView.kt | 24 ++++++----- 4 files changed, 88 insertions(+), 20 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt index 760ec92cd6..5d9742e1c6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt @@ -16,24 +16,36 @@ package im.vector.app.features.home.room.detail.timeline.factory +import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.resources.DateProvider import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider +import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem import im.vector.app.features.home.room.detail.timeline.item.LiveLocationShareSummaryData +import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationItem +import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem_ +import im.vector.app.features.location.INITIAL_MAP_ZOOM_IN_TIMELINE +import im.vector.app.features.location.UrlMapProvider +import im.vector.app.features.location.toLocationData import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.Session import org.threeten.bp.LocalDateTime import timber.log.Timber import javax.inject.Inject class LiveLocationShareMessageItemFactory @Inject constructor( + private val session: Session, private val dimensionConverter: DimensionConverter, private val timelineMediaSizeProvider: TimelineMediaSizeProvider, private val avatarSizeProvider: AvatarSizeProvider, + private val urlMapProvider: UrlMapProvider, + private val locationPinProvider: LocationPinProvider, + private val vectorDateFormatter: VectorDateFormatter, ) { fun create( @@ -41,10 +53,10 @@ class LiveLocationShareMessageItemFactory @Inject constructor( highlight: Boolean, attributes: AbsMessageItem.Attributes, ): VectorEpoxyModel<*>? { - return when (getViewState(liveLocationShareSummaryData)) { + return when (val currentState = getViewState(liveLocationShareSummaryData)) { LiveLocationShareViewState.Loading -> buildLoadingItem(highlight, attributes) LiveLocationShareViewState.Inactive -> buildInactiveItem() - is LiveLocationShareViewState.Running -> buildRunningItem() + is LiveLocationShareViewState.Running -> buildRunningItem(highlight, attributes, currentState) LiveLocationShareViewState.Unkwown -> null } } @@ -64,7 +76,32 @@ class LiveLocationShareMessageItemFactory @Inject constructor( .leftGuideline(avatarSizeProvider.leftGuideline) } - private fun buildRunningItem() = null + private fun buildRunningItem( + highlight: Boolean, + attributes: AbsMessageItem.Attributes, + runningState: LiveLocationShareViewState.Running, + ): MessageLiveLocationItem { + // TODO only render location if enabled in preferences: to be handled in a next PR + val width = timelineMediaSizeProvider.getMaxSize().first + val height = dimensionConverter.dpToPx(MessageItemFactory.MESSAGE_LOCATION_ITEM_HEIGHT_IN_DP) + + val locationUrl = runningState.lastGeoUri.toLocationData()?.let { + urlMapProvider.buildStaticMapUrl(it, INITIAL_MAP_ZOOM_IN_TIMELINE, width, height) + } + + return MessageLiveLocationItem_() + .attributes(attributes) + .locationUrl(locationUrl) + .mapWidth(width) + .mapHeight(height) + .locationUserId(attributes.informationData.senderId) + .locationPinProvider(locationPinProvider) + .highlighted(highlight) + .leftGuideline(avatarSizeProvider.leftGuideline) + .currentUserId(session.myUserId) + .endOfLiveDateTime(runningState.endOfLiveDateTime) + .vectorDateFormatter(vectorDateFormatter) + } private fun buildInactiveItem() = null diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt index bbf2ca1348..d81e26f29b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt @@ -20,30 +20,42 @@ import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R +import im.vector.app.core.date.DateFormatKind +import im.vector.app.core.date.VectorDateFormatter +import im.vector.app.core.resources.toTimestamp import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout import im.vector.app.features.location.live.LocationLiveMessageBannerView import im.vector.app.features.location.live.LocationLiveMessageBannerViewState +import org.threeten.bp.LocalDateTime @EpoxyModelClass(layout = R.layout.item_timeline_event_base) abstract class MessageLiveLocationItem : AbsMessageLocationItem<MessageLiveLocationItem.Holder>() { - // TODO define the needed attributes @EpoxyAttribute var currentUserId: String? = null + @EpoxyAttribute + var endOfLiveDateTime: LocalDateTime? = null + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + lateinit var vectorDateFormatter: VectorDateFormatter + override fun bind(holder: Holder) { super.bind(holder) bindLocationLiveBanner(holder) } private fun bindLocationLiveBanner(holder: Holder) { - // TODO add check on device id to confirm that is the one that sent the beacon + // TODO in a future PR add check on device id to confirm that is the one that sent the beacon val isEmitter = currentUserId != null && currentUserId == locationUserId val messageLayout = attributes.informationData.messageLayout val viewState = buildViewState(holder, messageLayout, isEmitter) holder.locationLiveMessageBanner.isVisible = true holder.locationLiveMessageBanner.render(viewState) + holder.locationLiveMessageBanner.stopButton.setOnClickListener { + // TODO call stop live location + } // TODO adjust Copyright map placement if needed } @@ -55,7 +67,7 @@ abstract class MessageLiveLocationItem : AbsMessageLocationItem<MessageLiveLocat return when { messageLayout is TimelineMessageLayout.Bubble && isEmitter -> LocationLiveMessageBannerViewState.Emitter( - remainingTimeInMillis = 4000 * 1000L, + remainingTimeInMillis = getRemainingTimeOfLiveInMillis(), bottomStartCornerRadiusInDp = messageLayout.cornersRadius.bottomStartRadius, bottomEndCornerRadiusInDp = messageLayout.cornersRadius.bottomEndRadius, isStopButtonCenteredVertically = false @@ -64,12 +76,12 @@ abstract class MessageLiveLocationItem : AbsMessageLocationItem<MessageLiveLocat LocationLiveMessageBannerViewState.Watcher( bottomStartCornerRadiusInDp = messageLayout.cornersRadius.bottomStartRadius, bottomEndCornerRadiusInDp = messageLayout.cornersRadius.bottomEndRadius, - formattedLocalTimeOfEndOfLive = "12:34", + formattedLocalTimeOfEndOfLive = getFormattedLocalTimeEndOfLive(), ) isEmitter -> { val cornerRadius = getBannerCornerRadiusForDefaultLayout(holder) LocationLiveMessageBannerViewState.Emitter( - remainingTimeInMillis = 4000 * 1000L, + remainingTimeInMillis = getRemainingTimeOfLiveInMillis(), bottomStartCornerRadiusInDp = cornerRadius, bottomEndCornerRadiusInDp = cornerRadius, isStopButtonCenteredVertically = true @@ -80,7 +92,7 @@ abstract class MessageLiveLocationItem : AbsMessageLocationItem<MessageLiveLocat LocationLiveMessageBannerViewState.Watcher( bottomStartCornerRadiusInDp = cornerRadius, bottomEndCornerRadiusInDp = cornerRadius, - formattedLocalTimeOfEndOfLive = "12:34", + formattedLocalTimeOfEndOfLive = getFormattedLocalTimeEndOfLive(), ) } } @@ -91,6 +103,12 @@ abstract class MessageLiveLocationItem : AbsMessageLocationItem<MessageLiveLocat return dimensionConverter.dpToPx(8).toFloat() } + private fun getFormattedLocalTimeEndOfLive() = + endOfLiveDateTime?.toTimestamp()?.let { vectorDateFormatter.format(it, DateFormatKind.MESSAGE_SIMPLE) }.orEmpty() + + private fun getRemainingTimeOfLiveInMillis() = + (endOfLiveDateTime?.toTimestamp() ?: 0) - LocalDateTime.now().toTimestamp() + class Holder : AbsMessageLocationItem.Holder() { val locationLiveMessageBanner by bind<LocationLiveMessageBannerView>(R.id.locationLiveMessageBanner) } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationData.kt b/vector/src/main/java/im/vector/app/features/location/LocationData.kt index a69d8d20e3..96d544ba5a 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationData.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationData.kt @@ -29,7 +29,7 @@ data class LocationData( ) : Parcelable /** - * Creates location data from a LocationContent + * Creates location data from a MessageLocationContent * "geo:40.05,29.24;30" -> LocationData(40.05, 29.24, 30) * @return location data or null if geo uri is not valid */ @@ -37,6 +37,15 @@ fun MessageLocationContent.toLocationData(): LocationData? { return parseGeo(getBestGeoUri()) } +/** + * Creates location data from a geoUri String + * "geo:40.05,29.24;30" -> LocationData(40.05, 29.24, 30) + * @return location data or null if geo uri is null or not valid + */ +fun String?.toLocationData(): LocationData? { + return this?.let { parseGeo(it) } +} + @VisibleForTesting fun parseGeo(geo: String): LocationData? { val geoParts = geo diff --git a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt index 3c2a13655b..7d008e0fc6 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt @@ -98,17 +98,21 @@ class LocationLiveMessageBannerView @JvmOverloads constructor( title.text = context.getString(R.string.location_share_live_enabled) countDownTimer?.cancel() - countDownTimer = object : CountDownTimer(viewState.remainingTimeInMillis, REMAINING_TIME_COUNTER_INTERVAL_IN_MS) { - override fun onTick(millisUntilFinished: Long) { - val duration = Duration.ofMillis(millisUntilFinished.coerceAtLeast(0L)) - subTitle.text = context.getString(R.string.location_share_live_remaining_time, TextUtils.formatDurationWithUnits(context, duration)) - } + viewState.remainingTimeInMillis + .takeIf { it >= 0 } + ?.let { + countDownTimer = object : CountDownTimer(it, REMAINING_TIME_COUNTER_INTERVAL_IN_MS) { + override fun onTick(millisUntilFinished: Long) { + val duration = Duration.ofMillis(millisUntilFinished.coerceAtLeast(0L)) + subTitle.text = context.getString(R.string.location_share_live_remaining_time, TextUtils.formatDurationWithUnits(context, duration)) + } - override fun onFinish() { - subTitle.text = context.getString(R.string.location_share_live_remaining_time, TextUtils.formatDurationWithUnits(context, Duration.ofMillis(0L))) - } - } - countDownTimer?.start() + override fun onFinish() { + subTitle.text = context.getString(R.string.location_share_live_remaining_time, TextUtils.formatDurationWithUnits(context, Duration.ofMillis(0L))) + } + } + countDownTimer?.start() + } val rootLayout: ConstraintLayout? = (binding.root as? ConstraintLayout) rootLayout?.let { parentLayout -> From b577f6ab8eec494d4cf23c696360319c81bd3cd2 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 2 May 2022 14:42:24 +0200 Subject: [PATCH 18/45] Fix display in Bubble mode --- .../android/sdk/api/session/room/timeline/TimelineEvent.kt | 2 ++ .../timeline/factory/LiveLocationShareMessageItemFactory.kt | 5 ++++- .../detail/timeline/style/TimelineMessageLayoutFactory.kt | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt index adbc8ab12a..6793ac62d3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt @@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent @@ -140,6 +141,7 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? { EventType.STICKER -> root.getClearContent().toModel<MessageStickerContent>() in EventType.POLL_START -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessagePollContent>() in EventType.STATE_ROOM_BEACON_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessageBeaconInfoContent>() + in EventType.BEACON_LOCATION_DATA -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessageBeaconLocationDataContent>() else -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt index 5d9742e1c6..8217d1cc33 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt @@ -53,12 +53,15 @@ class LiveLocationShareMessageItemFactory @Inject constructor( highlight: Boolean, attributes: AbsMessageItem.Attributes, ): VectorEpoxyModel<*>? { - return when (val currentState = getViewState(liveLocationShareSummaryData)) { + val item = when (val currentState = getViewState(liveLocationShareSummaryData)) { LiveLocationShareViewState.Loading -> buildLoadingItem(highlight, attributes) LiveLocationShareViewState.Inactive -> buildInactiveItem() is LiveLocationShareViewState.Running -> buildRunningItem(highlight, attributes, currentState) LiveLocationShareViewState.Unkwown -> null } + item?.layout(attributes.informationData.messageLayout.layoutRes) + + return item } private fun buildLoadingItem( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt index f083c70100..fab7dcb49a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt @@ -146,7 +146,7 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess private fun MessageContent?.timestampInsideMessage(): Boolean { if (this == null) return false - if (msgType == MessageType.MSGTYPE_LOCATION) return vectorPreferences.labsRenderLocationsInTimeline() + if (msgType == MessageType.MSGTYPE_LOCATION || msgType == MessageType.MSGTYPE_BEACON_LOCATION_DATA) return vectorPreferences.labsRenderLocationsInTimeline() return this.msgType in MSG_TYPES_WITH_TIMESTAMP_INSIDE_MESSAGE } From 3a36762632738785a4244896e7cdef64be36115e Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 2 May 2022 14:56:36 +0200 Subject: [PATCH 19/45] Fix some namings after rebase --- .../timeline/factory/LiveLocationShareMessageItemFactory.kt | 2 +- .../detail/timeline/helper/MessageInformationDataFactory.kt | 2 +- .../home/room/detail/timeline/item/MessageInformationData.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt index 8217d1cc33..cf8423593d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt @@ -132,7 +132,7 @@ class LiveLocationShareMessageItemFactory @Inject constructor( } private fun getEndOfLiveDateTime(liveLocationShareSummaryData: LiveLocationShareSummaryData): LocalDateTime? { - return liveLocationShareSummaryData.endOfLiveTimestampAsMilliseconds?.let { DateProvider.toLocalDateTime(timestamp = it) } + return liveLocationShareSummaryData.endOfLiveTimestampMillis?.let { DateProvider.toLocalDateTime(timestamp = it) } } private sealed class LiveLocationShareViewState { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index 8fb7aea7e4..4425415563 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -193,7 +193,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses return event.annotations?.liveLocationShareAggregatedSummary?.let { summary -> LiveLocationShareSummaryData( isActive = summary.isActive, - endOfLiveTimestampAsMilliseconds = summary.endOfLiveTimestampAsMilliseconds, + endOfLiveTimestampMillis = summary.endOfLiveTimestampMillis, lastGeoUri = summary.lastLocationDataContent?.getBestLocationInfo()?.geoUri ) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt index 8ad4034a32..dae1c3671d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt @@ -104,7 +104,7 @@ data class PollVoteSummaryData( @Parcelize data class LiveLocationShareSummaryData( val isActive: Boolean?, - val endOfLiveTimestampAsMilliseconds: Long?, + val endOfLiveTimestampMillis: Long?, val lastGeoUri: String?, ) : Parcelable From d341611e544afdcd569314daff7d0cf2c39abfad Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 2 May 2022 15:00:17 +0200 Subject: [PATCH 20/45] Format location event for debugging purpose --- .../detail/timeline/factory/TimelineItemFactory.kt | 13 +++++++------ .../detail/timeline/format/NoticeEventFormatter.kt | 3 ++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index f4bcc1ba65..9b6026031e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -100,7 +100,7 @@ class TimelineItemFactory @Inject constructor( // Message itemsX EventType.STICKER, in EventType.POLL_START, - EventType.MESSAGE -> messageItemFactory.create(params) + EventType.MESSAGE -> messageItemFactory.create(params) EventType.REDACTION, EventType.KEY_VERIFICATION_ACCEPT, EventType.KEY_VERIFICATION_START, @@ -113,14 +113,15 @@ class TimelineItemFactory @Inject constructor( EventType.CALL_NEGOTIATE, EventType.REACTION, in EventType.POLL_RESPONSE, - in EventType.POLL_END -> noticeItemFactory.create(params) + in EventType.POLL_END -> noticeItemFactory.create(params) // Calls EventType.CALL_INVITE, EventType.CALL_HANGUP, EventType.CALL_REJECT, - EventType.CALL_ANSWER -> callItemFactory.create(params) + EventType.CALL_ANSWER -> callItemFactory.create(params) + in EventType.BEACON_LOCATION_DATA -> noticeItemFactory.create(params) // Crypto - EventType.ENCRYPTED -> { + EventType.ENCRYPTED -> { if (event.root.isRedacted()) { // Redacted event, let the MessageItemFactory handle it messageItemFactory.create(params) @@ -129,11 +130,11 @@ class TimelineItemFactory @Inject constructor( } } EventType.KEY_VERIFICATION_CANCEL, - EventType.KEY_VERIFICATION_DONE -> { + EventType.KEY_VERIFICATION_DONE -> { verificationConclusionItemFactory.create(params) } // Unhandled event types - else -> { + else -> { // Should only happen when shouldShowHiddenEvents() settings is ON Timber.v("Type ${event.root.getClearType()} not handled") defaultItemFactory.create(params) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index 7ad0cb27c6..8e06b3ee5d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -107,7 +107,8 @@ class NoticeEventFormatter @Inject constructor( EventType.REDACTION, EventType.STICKER, in EventType.POLL_RESPONSE, - in EventType.POLL_END -> formatDebug(timelineEvent.root) + in EventType.POLL_END, + in EventType.BEACON_LOCATION_DATA -> formatDebug(timelineEvent.root) else -> { Timber.v("Type $type not handled by this formatter") null From 4a4ec47c4901a56f041e138341342c8e27cf79ac Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 2 May 2022 15:16:03 +0200 Subject: [PATCH 21/45] Binding stop button of emitter view --- .../detail/timeline/helper/MessageItemAttributesFactory.kt | 1 + .../features/home/room/detail/timeline/item/AbsMessageItem.kt | 1 + .../home/room/detail/timeline/item/MessageLiveLocationItem.kt | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt index 45c711ff93..737b0dc85d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt @@ -57,6 +57,7 @@ class MessageItemAttributesFactory @Inject constructor( memberClickListener = { callback?.onMemberNameClicked(informationData) }, + callback = callback, reactionPillCallback = callback, avatarCallback = callback, threadCallback = callback, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt index 30c366738d..d52b254ff4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -178,6 +178,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H> override val itemLongClickListener: View.OnLongClickListener? = null, override val itemClickListener: ClickListener? = null, val memberClickListener: ClickListener? = null, + val callback: TimelineEventController.Callback? = null, override val reactionPillCallback: TimelineEventController.ReactionPillCallback? = null, val avatarCallback: TimelineEventController.AvatarCallback? = null, val threadCallback: TimelineEventController.ThreadCallback? = null, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt index d81e26f29b..d68e091564 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt @@ -24,6 +24,7 @@ import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.resources.toTimestamp import im.vector.app.core.utils.DimensionConverter +import im.vector.app.features.home.room.detail.RoomDetailAction import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout import im.vector.app.features.location.live.LocationLiveMessageBannerView import im.vector.app.features.location.live.LocationLiveMessageBannerViewState @@ -54,7 +55,7 @@ abstract class MessageLiveLocationItem : AbsMessageLocationItem<MessageLiveLocat holder.locationLiveMessageBanner.isVisible = true holder.locationLiveMessageBanner.render(viewState) holder.locationLiveMessageBanner.stopButton.setOnClickListener { - // TODO call stop live location + attributes.callback?.onTimelineItemAction(RoomDetailAction.StopLiveLocationSharing) } // TODO adjust Copyright map placement if needed } From b0c1ca87df895afa87e9b621bcb47a27f672d0e3 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 2 May 2022 15:16:42 +0200 Subject: [PATCH 22/45] Reducing period of location update to 2 seconds --- vector/src/main/java/im/vector/app/features/location/Config.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/location/Config.kt b/vector/src/main/java/im/vector/app/features/location/Config.kt index 6f947290e2..c29e2e911a 100644 --- a/vector/src/main/java/im/vector/app/features/location/Config.kt +++ b/vector/src/main/java/im/vector/app/features/location/Config.kt @@ -22,5 +22,5 @@ const val DEFAULT_PIN_ID = "DEFAULT_PIN_ID" const val INITIAL_MAP_ZOOM_IN_PREVIEW = 15.0 const val INITIAL_MAP_ZOOM_IN_TIMELINE = 17.0 -const val MIN_TIME_TO_UPDATE_LOCATION_MILLIS = 5 * 1_000L // every 5 seconds +const val MIN_TIME_TO_UPDATE_LOCATION_MILLIS = 2 * 1_000L // every 2 seconds const val MIN_DISTANCE_TO_UPDATE_LOCATION_METERS = 10f From 370b6a81bcc9c2def40b5232598cc99914775134 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 2 May 2022 15:30:14 +0200 Subject: [PATCH 23/45] Checking inactive state before loading to handle timed out live without location --- .../timeline/factory/LiveLocationShareMessageItemFactory.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt index cf8423593d..753e5116f0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt @@ -110,9 +110,9 @@ class LiveLocationShareMessageItemFactory @Inject constructor( private fun getViewState(liveLocationShareSummaryData: LiveLocationShareSummaryData?): LiveLocationShareViewState { return when { - liveLocationShareSummaryData?.isActive == null -> LiveLocationShareViewState.Unkwown - liveLocationShareSummaryData.isActive && liveLocationShareSummaryData.lastGeoUri.isNullOrEmpty() -> LiveLocationShareViewState.Loading + liveLocationShareSummaryData?.isActive == null -> LiveLocationShareViewState.Unkwown liveLocationShareSummaryData.isActive.not() || isLiveTimedOut(liveLocationShareSummaryData) -> LiveLocationShareViewState.Inactive + liveLocationShareSummaryData.isActive && liveLocationShareSummaryData.lastGeoUri.isNullOrEmpty() -> LiveLocationShareViewState.Loading else -> LiveLocationShareViewState.Running( liveLocationShareSummaryData.lastGeoUri.orEmpty(), From a37edb591b20f8cd0a637bbf9c4cc96299a97980 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 2 May 2022 17:10:27 +0200 Subject: [PATCH 24/45] Adding xml layout for inactive state --- ...line_event_live_location_inactive_stub.xml | 74 +++++++++++++++++++ ...imeline_event_live_location_start_stub.xml | 2 +- vector/src/main/res/values/strings.xml | 1 + 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml diff --git a/vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml b/vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml new file mode 100644 index 0000000000..e26713b484 --- /dev/null +++ b/vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <!-- Size will be overrode --> + <ImageView + android:id="@+id/locationLiveInactiveMap" + android:layout_width="300dp" + android:layout_height="200dp" + android:contentDescription="@string/a11y_static_map_image" + android:src="@drawable/bg_no_location_map" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <ImageView + android:id="@+id/locationLiveInactiveBanner" + android:layout_width="0dp" + android:layout_height="48dp" + android:alpha="0.85" + android:src="@color/element_background_light" + app:layout_constraintBottom_toBottomOf="@id/locationLiveInactiveMap" + app:layout_constraintEnd_toEndOf="@id/locationLiveInactiveMap" + app:layout_constraintStart_toStartOf="@id/locationLiveInactiveMap" + tools:ignore="ContentDescription" /> + + <ImageView + android:id="@+id/locationLiveInactiveIcon" + android:layout_width="0dp" + android:layout_height="65dp" + android:alpha="0.85" + android:src="@drawable/ic_attachment_location_white" + app:layout_constraintBottom_toTopOf="@id/locationLiveInactiveVerticalCenter" + app:layout_constraintEnd_toEndOf="@id/locationLiveInactiveMap" + app:layout_constraintStart_toStartOf="@id/locationLiveInactiveMap" + app:tint="?vctr_content_quaternary" + tools:ignore="ContentDescription" /> + + <ImageView + android:id="@+id/locationLiveInactiveBannerIcon" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_marginHorizontal="8dp" + android:background="@drawable/circle" + android:backgroundTint="?vctr_content_quaternary" + android:padding="3dp" + app:layout_constraintBottom_toBottomOf="@id/locationLiveInactiveBanner" + app:layout_constraintStart_toStartOf="@id/locationLiveInactiveBanner" + app:layout_constraintTop_toTopOf="@id/locationLiveInactiveBanner" + app:srcCompat="@drawable/ic_attachment_location_live_white" + tools:ignore="ContentDescription" /> + + <TextView + android:id="@+id/locationLiveInactiveTitle" + style="@style/Widget.Vector.TextView.Caption" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginHorizontal="8dp" + android:text="@string/location_share_live_ended" + android:textColor="?vctr_content_tertiary" + app:layout_constraintBottom_toBottomOf="@id/locationLiveInactiveBanner" + app:layout_constraintStart_toEndOf="@id/locationLiveInactiveBannerIcon" + app:layout_constraintTop_toTopOf="@id/locationLiveInactiveBanner" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/locationLiveInactiveVerticalCenter" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.5" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/vector/src/main/res/layout/item_timeline_event_live_location_start_stub.xml b/vector/src/main/res/layout/item_timeline_event_live_location_start_stub.xml index b81a6cc0e9..199d36cfde 100644 --- a/vector/src/main/res/layout/item_timeline_event_live_location_start_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_live_location_start_stub.xml @@ -20,7 +20,7 @@ android:layout_width="0dp" android:layout_height="48dp" android:alpha="0.85" - android:src="?colorSurface" + android:src="@color/element_background_light" app:layout_constraintBottom_toBottomOf="@id/locationLiveStartMap" app:layout_constraintEnd_toEndOf="@id/locationLiveStartMap" app:layout_constraintStart_toStartOf="@id/locationLiveStartMap" diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 00b29e83b3..7adc751f8e 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3018,6 +3018,7 @@ <string name="location_timeline_failed_to_load_map">Failed to load map</string> <string name="location_share_live_enabled">Live location enabled</string> <string name="location_share_live_started">Loading live location…</string> + <string name="location_share_live_ended">Live location ended</string> <string name="location_share_live_view">View live location</string> <string name="location_share_live_until">Live until %1$s</string> <string name="location_share_live_stop">Stop</string> From c10b2a405c99715013590abac1af09d9bd0a6254 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Tue, 3 May 2022 09:54:20 +0200 Subject: [PATCH 25/45] Show inactive message item when a live is inactive --- .../LiveLocationShareMessageItemFactory.kt | 23 +++++- .../DefaultLiveLocationShareStatusItem.kt | 80 +++++++++++++++++++ .../item/LiveLocationShareStatusItem.kt | 31 +++++++ .../item/MessageLiveLocationInactiveItem.kt | 52 ++++++++++++ .../item/MessageLiveLocationStartItem.kt | 53 ++---------- ...line_event_live_location_inactive_stub.xml | 10 +-- ...imeline_event_live_location_start_stub.xml | 9 ++- ...em_timeline_event_view_stubs_container.xml | 6 ++ 8 files changed, 203 insertions(+), 61 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultLiveLocationShareStatusItem.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/LiveLocationShareStatusItem.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationInactiveItem.kt diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt index 753e5116f0..f993676958 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt @@ -25,6 +25,8 @@ import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvid import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem import im.vector.app.features.home.room.detail.timeline.item.LiveLocationShareSummaryData +import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationInactiveItem +import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationInactiveItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationItem import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem @@ -54,8 +56,8 @@ class LiveLocationShareMessageItemFactory @Inject constructor( attributes: AbsMessageItem.Attributes, ): VectorEpoxyModel<*>? { val item = when (val currentState = getViewState(liveLocationShareSummaryData)) { + LiveLocationShareViewState.Inactive -> buildInactiveItem(highlight, attributes) LiveLocationShareViewState.Loading -> buildLoadingItem(highlight, attributes) - LiveLocationShareViewState.Inactive -> buildInactiveItem() is LiveLocationShareViewState.Running -> buildRunningItem(highlight, attributes, currentState) LiveLocationShareViewState.Unkwown -> null } @@ -64,6 +66,21 @@ class LiveLocationShareMessageItemFactory @Inject constructor( return item } + private fun buildInactiveItem( + highlight: Boolean, + attributes: AbsMessageItem.Attributes, + ): MessageLiveLocationInactiveItem { + val width = timelineMediaSizeProvider.getMaxSize().first + val height = dimensionConverter.dpToPx(MessageItemFactory.MESSAGE_LOCATION_ITEM_HEIGHT_IN_DP) + + return MessageLiveLocationInactiveItem_() + .attributes(attributes) + .mapWidth(width) + .mapHeight(height) + .highlighted(highlight) + .leftGuideline(avatarSizeProvider.leftGuideline) + } + private fun buildLoadingItem( highlight: Boolean, attributes: AbsMessageItem.Attributes, @@ -106,11 +123,9 @@ class LiveLocationShareMessageItemFactory @Inject constructor( .vectorDateFormatter(vectorDateFormatter) } - private fun buildInactiveItem() = null - private fun getViewState(liveLocationShareSummaryData: LiveLocationShareSummaryData?): LiveLocationShareViewState { return when { - liveLocationShareSummaryData?.isActive == null -> LiveLocationShareViewState.Unkwown + liveLocationShareSummaryData?.isActive == null -> LiveLocationShareViewState.Unkwown liveLocationShareSummaryData.isActive.not() || isLiveTimedOut(liveLocationShareSummaryData) -> LiveLocationShareViewState.Inactive liveLocationShareSummaryData.isActive && liveLocationShareSummaryData.lastGeoUri.isNullOrEmpty() -> LiveLocationShareViewState.Loading else -> diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultLiveLocationShareStatusItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultLiveLocationShareStatusItem.kt new file mode 100644 index 0000000000..e453b01692 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultLiveLocationShareStatusItem.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.home.room.detail.timeline.item + +import android.content.res.Resources +import android.graphics.drawable.ColorDrawable +import android.widget.ImageView +import androidx.core.view.updateLayoutParams +import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import im.vector.app.R +import im.vector.app.core.glide.GlideApp +import im.vector.app.core.utils.DimensionConverter +import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout +import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners +import im.vector.app.features.themes.ThemeUtils + +/** + * Default implementation of common methods for item representing the status of a live location share. + */ +class DefaultLiveLocationShareStatusItem : LiveLocationShareStatusItem { + + override fun bindMap( + mapImageView: ImageView, + mapWidth: Int, + mapHeight: Int, + messageLayout: TimelineMessageLayout + ) { + val mapCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) { + messageLayout.cornersRadius.granularRoundedCorners() + } else { + RoundedCorners(getDefaultLayoutCornerRadiusInDp(mapImageView.resources)) + } + mapImageView.updateLayoutParams { + width = mapWidth + height = mapHeight + } + GlideApp.with(mapImageView) + .load(R.drawable.bg_no_location_map) + .transform(mapCornerTransformation) + .into(mapImageView) + } + + override fun bindBottomBanner(bannerImageView: ImageView, messageLayout: TimelineMessageLayout) { + val imageCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) { + GranularRoundedCorners( + 0f, + 0f, + messageLayout.cornersRadius.bottomEndRadius, + messageLayout.cornersRadius.bottomStartRadius + ) + } else { + val bottomCornerRadius = getDefaultLayoutCornerRadiusInDp(bannerImageView.resources).toFloat() + GranularRoundedCorners(0f, 0f, bottomCornerRadius, bottomCornerRadius) + } + GlideApp.with(bannerImageView) + .load(ColorDrawable(ThemeUtils.getColor(bannerImageView.context, R.attr.colorSurface))) + .transform(imageCornerTransformation) + .into(bannerImageView) + } + + private fun getDefaultLayoutCornerRadiusInDp(resources: Resources): Int { + val dimensionConverter = DimensionConverter(resources) + return dimensionConverter.dpToPx(8) + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/LiveLocationShareStatusItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/LiveLocationShareStatusItem.kt new file mode 100644 index 0000000000..2f79f2fc9e --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/LiveLocationShareStatusItem.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.home.room.detail.timeline.item + +import android.widget.ImageView +import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout + +interface LiveLocationShareStatusItem { + fun bindMap( + mapImageView: ImageView, + mapWidth: Int, + mapHeight: Int, + messageLayout: TimelineMessageLayout + ) + + fun bindBottomBanner(bannerImageView: ImageView, messageLayout: TimelineMessageLayout) +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationInactiveItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationInactiveItem.kt new file mode 100644 index 0000000000..bb85316bf1 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationInactiveItem.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.home.room.detail.timeline.item + +import android.widget.ImageView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R + +@EpoxyModelClass(layout = R.layout.item_timeline_event_base) +abstract class MessageLiveLocationInactiveItem : + AbsMessageItem<MessageLiveLocationInactiveItem.Holder>(), + LiveLocationShareStatusItem by DefaultLiveLocationShareStatusItem() { + + @EpoxyAttribute + var mapWidth: Int = 0 + + @EpoxyAttribute + var mapHeight: Int = 0 + + override fun bind(holder: Holder) { + super.bind(holder) + renderSendState(holder.view, null) + bindMap(holder.noLocationMapImageView, mapWidth, mapHeight, attributes.informationData.messageLayout) + bindBottomBanner(holder.bannerImageView, attributes.informationData.messageLayout) + } + + override fun getViewStubId() = STUB_ID + + class Holder : AbsMessageItem.Holder(STUB_ID) { + val bannerImageView by bind<ImageView>(R.id.locationLiveInactiveBanner) + val noLocationMapImageView by bind<ImageView>(R.id.locationLiveInactiveMap) + } + + companion object { + private const val STUB_ID = R.id.messageContentLiveLocationInactiveStub + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationStartItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationStartItem.kt index 390db0ef50..001774b579 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationStartItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationStartItem.kt @@ -16,22 +16,15 @@ package im.vector.app.features.home.room.detail.timeline.item -import android.graphics.drawable.ColorDrawable import android.widget.ImageView -import androidx.core.view.updateLayoutParams import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass -import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners -import com.bumptech.glide.load.resource.bitmap.RoundedCorners import im.vector.app.R -import im.vector.app.core.glide.GlideApp -import im.vector.app.core.utils.DimensionConverter -import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout -import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners -import im.vector.app.features.themes.ThemeUtils @EpoxyModelClass(layout = R.layout.item_timeline_event_base) -abstract class MessageLiveLocationStartItem : AbsMessageItem<MessageLiveLocationStartItem.Holder>() { +abstract class MessageLiveLocationStartItem : + AbsMessageItem<MessageLiveLocationStartItem.Holder>(), + LiveLocationShareStatusItem by DefaultLiveLocationShareStatusItem() { @EpoxyAttribute var mapWidth: Int = 0 @@ -42,44 +35,8 @@ abstract class MessageLiveLocationStartItem : AbsMessageItem<MessageLiveLocation override fun bind(holder: Holder) { super.bind(holder) renderSendState(holder.view, null) - bindMap(holder) - bindBottomBanner(holder) - } - - private fun bindMap(holder: Holder) { - val messageLayout = attributes.informationData.messageLayout - val mapCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) { - messageLayout.cornersRadius.granularRoundedCorners() - } else { - RoundedCorners(getDefaultLayoutCornerRadiusInDp(holder)) - } - holder.noLocationMapImageView.updateLayoutParams { - width = mapWidth - height = mapHeight - } - GlideApp.with(holder.noLocationMapImageView) - .load(R.drawable.bg_no_location_map) - .transform(mapCornerTransformation) - .into(holder.noLocationMapImageView) - } - - private fun bindBottomBanner(holder: Holder) { - val messageLayout = attributes.informationData.messageLayout - val imageCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) { - GranularRoundedCorners(0f, 0f, messageLayout.cornersRadius.bottomEndRadius, messageLayout.cornersRadius.bottomStartRadius) - } else { - val bottomCornerRadius = getDefaultLayoutCornerRadiusInDp(holder).toFloat() - GranularRoundedCorners(0f, 0f, bottomCornerRadius, bottomCornerRadius) - } - GlideApp.with(holder.bannerImageView) - .load(ColorDrawable(ThemeUtils.getColor(holder.bannerImageView.context, R.attr.colorSurface))) - .transform(imageCornerTransformation) - .into(holder.bannerImageView) - } - - private fun getDefaultLayoutCornerRadiusInDp(holder: Holder): Int { - val dimensionConverter = DimensionConverter(holder.view.resources) - return dimensionConverter.dpToPx(8) + bindMap(holder.noLocationMapImageView, mapWidth, mapHeight, attributes.informationData.messageLayout) + bindBottomBanner(holder.bannerImageView, attributes.informationData.messageLayout) } override fun getViewStubId() = STUB_ID diff --git a/vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml b/vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml index e26713b484..e5d2f91404 100644 --- a/vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml @@ -20,7 +20,7 @@ android:layout_width="0dp" android:layout_height="48dp" android:alpha="0.85" - android:src="@color/element_background_light" + android:src="?colorSurface" app:layout_constraintBottom_toBottomOf="@id/locationLiveInactiveMap" app:layout_constraintEnd_toEndOf="@id/locationLiveInactiveMap" app:layout_constraintStart_toStartOf="@id/locationLiveInactiveMap" @@ -30,7 +30,6 @@ android:id="@+id/locationLiveInactiveIcon" android:layout_width="0dp" android:layout_height="65dp" - android:alpha="0.85" android:src="@drawable/ic_attachment_location_white" app:layout_constraintBottom_toTopOf="@id/locationLiveInactiveVerticalCenter" app:layout_constraintEnd_toEndOf="@id/locationLiveInactiveMap" @@ -40,9 +39,10 @@ <ImageView android:id="@+id/locationLiveInactiveBannerIcon" - android:layout_width="32dp" - android:layout_height="32dp" - android:layout_marginHorizontal="8dp" + android:layout_width="26dp" + android:layout_height="26dp" + android:layout_marginVertical="8dp" + android:layout_marginStart="8dp" android:background="@drawable/circle" android:backgroundTint="?vctr_content_quaternary" android:padding="3dp" diff --git a/vector/src/main/res/layout/item_timeline_event_live_location_start_stub.xml b/vector/src/main/res/layout/item_timeline_event_live_location_start_stub.xml index 199d36cfde..104063782b 100644 --- a/vector/src/main/res/layout/item_timeline_event_live_location_start_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_live_location_start_stub.xml @@ -20,7 +20,7 @@ android:layout_width="0dp" android:layout_height="48dp" android:alpha="0.85" - android:src="@color/element_background_light" + android:src="?colorSurface" app:layout_constraintBottom_toBottomOf="@id/locationLiveStartMap" app:layout_constraintEnd_toEndOf="@id/locationLiveStartMap" app:layout_constraintStart_toStartOf="@id/locationLiveStartMap" @@ -28,9 +28,10 @@ <ImageView android:id="@+id/locationLiveStartIcon" - android:layout_width="32dp" - android:layout_height="32dp" - android:layout_marginHorizontal="8dp" + android:layout_width="26dp" + android:layout_height="26dp" + android:layout_marginVertical="8dp" + android:layout_marginStart="8dp" android:background="@drawable/circle" android:backgroundTint="?vctr_content_quaternary" android:padding="3dp" diff --git a/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml b/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml index 355d5fa7fe..3f08fae131 100644 --- a/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml +++ b/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml @@ -65,6 +65,12 @@ android:layout_height="wrap_content" android:layout="@layout/item_timeline_event_live_location_start_stub" /> + <ViewStub + android:id="@+id/messageContentLiveLocationInactiveStub" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout="@layout/item_timeline_event_live_location_inactive_stub" /> + </FrameLayout> From 889e09a891511350d4bd1d2f16e23aa6dc9ae9ef Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Tue, 3 May 2022 11:16:11 +0200 Subject: [PATCH 26/45] Adding changelog entry --- changelog.d/5689.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5689.wip diff --git a/changelog.d/5689.wip b/changelog.d/5689.wip new file mode 100644 index 0000000000..ccea1ec541 --- /dev/null +++ b/changelog.d/5689.wip @@ -0,0 +1 @@ +[Live location sharing] Update message in timeline during the live From f2b66c5fb5224e08e5b0b972bab927eabf283d86 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Tue, 3 May 2022 11:46:51 +0200 Subject: [PATCH 27/45] Fix pin position in static map --- .../layout/item_timeline_event_location_stub.xml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/layout/item_timeline_event_location_stub.xml b/vector/src/main/res/layout/item_timeline_event_location_stub.xml index 656743f26c..d99ac9fa26 100644 --- a/vector/src/main/res/layout/item_timeline_event_location_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_location_stub.xml @@ -21,13 +21,13 @@ android:layout_width="51dp" android:layout_height="55dp" android:layout_gravity="center" - android:layout_marginBottom="28dp" android:importantForAccessibility="no" android:src="@drawable/bg_map_user_pin" - app:layout_constraintBottom_toBottomOf="@id/staticMapImageView" + app:layout_constraintBottom_toTopOf="@id/staticMapVerticalCenter" app:layout_constraintEnd_toEndOf="@id/staticMapImageView" app:layout_constraintStart_toStartOf="@id/staticMapImageView" - app:layout_constraintTop_toTopOf="@id/staticMapImageView" /> + app:layout_constraintTop_toTopOf="@id/staticMapImageView" + app:layout_constraintVertical_bias="1.0" /> <TextView android:id="@+id/staticMapErrorTextView" @@ -55,4 +55,11 @@ app:layout_constraintStart_toStartOf="@id/staticMapImageView" tools:visibility="visible" /> + <androidx.constraintlayout.widget.Guideline + android:id="@+id/staticMapVerticalCenter" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.5" /> + </androidx.constraintlayout.widget.ConstraintLayout> From 0a21bd4b78b00c10a130f619ff92887a673d39e3 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Tue, 3 May 2022 12:02:05 +0200 Subject: [PATCH 28/45] Fix crash when mixing static and live location --- .../room/detail/timeline/item/AbsMessageLocationItem.kt | 8 ++------ .../room/detail/timeline/item/MessageLiveLocationItem.kt | 8 +++++++- .../room/detail/timeline/item/MessageLocationItem.kt | 9 ++++++++- .../layout/item_timeline_event_view_stubs_container.xml | 6 ++++++ 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt index ad18759fc6..b502d82829 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt @@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.item import android.graphics.drawable.Drawable import android.widget.ImageView import android.widget.TextView +import androidx.annotation.IdRes import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import com.airbnb.epoxy.EpoxyAttribute @@ -101,15 +102,10 @@ abstract class AbsMessageLocationItem<H : AbsMessageLocationItem.Holder> : AbsMe .into(holder.staticMapImageView) } - override fun getViewStubId() = STUB_ID - - open class Holder : AbsMessageItem.Holder(STUB_ID) { + abstract class Holder(@IdRes stubId: Int) : AbsMessageItem.Holder(stubId) { val staticMapImageView by bind<ImageView>(R.id.staticMapImageView) val staticMapPinImageView by bind<ImageView>(R.id.staticMapPinImageView) val staticMapErrorTextView by bind<TextView>(R.id.staticMapErrorTextView) } - companion object { - private const val STUB_ID = R.id.messageContentLocationStub - } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt index d68e091564..daca48e7aa 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt @@ -110,7 +110,13 @@ abstract class MessageLiveLocationItem : AbsMessageLocationItem<MessageLiveLocat private fun getRemainingTimeOfLiveInMillis() = (endOfLiveDateTime?.toTimestamp() ?: 0) - LocalDateTime.now().toTimestamp() - class Holder : AbsMessageLocationItem.Holder() { + override fun getViewStubId() = STUB_ID + + class Holder : AbsMessageLocationItem.Holder(STUB_ID) { val locationLiveMessageBanner by bind<LocationLiveMessageBannerView>(R.id.locationLiveMessageBanner) } + + companion object { + private const val STUB_ID = R.id.messageContentLiveLocationStub + } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt index 7da9149dc4..37f728407b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt @@ -21,5 +21,12 @@ import im.vector.app.R @EpoxyModelClass(layout = R.layout.item_timeline_event_base) abstract class MessageLocationItem : AbsMessageLocationItem<MessageLocationItem.Holder>() { - class Holder : AbsMessageLocationItem.Holder() + + override fun getViewStubId() = STUB_ID + + class Holder : AbsMessageLocationItem.Holder(STUB_ID) + + companion object { + private const val STUB_ID = R.id.messageContentLocationStub + } } diff --git a/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml b/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml index 3f08fae131..0d45a48b9b 100644 --- a/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml +++ b/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml @@ -59,6 +59,12 @@ android:layout_height="wrap_content" android:layout="@layout/item_timeline_event_location_stub" /> + <ViewStub + android:id="@+id/messageContentLiveLocationStub" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout="@layout/item_timeline_event_location_stub" /> + <ViewStub android:id="@+id/messageContentLiveLocationStartStub" android:layout_width="match_parent" From 6622651a90fa80ff7bdbe2c9baf15630741c7316 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Wed, 4 May 2022 16:02:23 +0200 Subject: [PATCH 29/45] Fix send of the first location after start --- .../location/LocationSharingService.kt | 64 +++++++++++-------- .../app/features/location/LocationTracker.kt | 16 ++++- 2 files changed, 51 insertions(+), 29 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt index 362b82ccf5..8b9a1c75ae 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt @@ -55,7 +55,10 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { private val binder = LocalBinder() - private var roomArgsList = mutableListOf<RoomArgs>() + /** + * Keep track of a map between beacon event Id starting the live and RoomArgs. + */ + private var roomArgsMap = mutableMapOf<String, RoomArgs>() private var timers = mutableListOf<Timer>() override fun onCreate() { @@ -73,8 +76,6 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { Timber.i("### LocationSharingService.onStartCommand. sessionId - roomId ${roomArgs?.sessionId} - ${roomArgs?.roomId}") if (roomArgs != null) { - roomArgsList.add(roomArgs) - // Show a sticky notification val notification = notificationUtils.buildLiveLocationSharingNotification() startForeground(roomArgs.roomId.hashCode(), notification) @@ -87,7 +88,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { .getSafeActiveSession() ?.let { session -> session.coroutineScope.launch(session.coroutineDispatchers.io) { - sendLiveBeaconInfo(session, roomArgs) + sendStartingLiveBeaconInfo(session, roomArgs) } } } @@ -95,7 +96,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { return START_STICKY } - private suspend fun sendLiveBeaconInfo(session: Session, roomArgs: RoomArgs) { + private suspend fun sendStartingLiveBeaconInfo(session: Session, roomArgs: RoomArgs) { val beaconContent = MessageBeaconInfoContent( timeout = roomArgs.durationMillis, isLive = true, @@ -103,7 +104,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { ).toContent() val stateKey = session.myUserId - session + val beaconEventId = session .getRoom(roomArgs.roomId) ?.stateService() ?.sendStateEvent( @@ -111,6 +112,16 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { stateKey = stateKey, body = beaconContent ) + + beaconEventId + ?.takeUnless { it.isEmpty() } + ?.let { + roomArgsMap[it] = roomArgs + locationTracker.requestLastKnownLocation() + } + ?: run { + Timber.w("### LocationSharingService.sendStartingLiveBeaconInfo error, no received beacon info id") + } } private fun scheduleTimer(roomId: String, durationMillis: Long) { @@ -134,9 +145,13 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { // Send a new beacon info state by setting live field as false sendStoppedBeaconInfo(roomId) - synchronized(roomArgsList) { - roomArgsList.removeAll { it.roomId == roomId } - if (roomArgsList.isEmpty()) { + synchronized(roomArgsMap) { + val beaconIds = roomArgsMap + .filter { it.value.roomId == roomId } + .map { it.key } + beaconIds.forEach { roomArgsMap.remove(it) } + + if (roomArgsMap.isEmpty()) { Timber.i("### LocationSharingService. Destroying self, time is up for all rooms") destroyMe() } @@ -156,16 +171,17 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { override fun onLocationUpdate(locationData: LocationData) { Timber.i("### LocationSharingService.onLocationUpdate. Uncertainty: ${locationData.uncertainty}") - val session = activeSessionHolder.getSafeActiveSession() // Emit location update to all rooms in which live location sharing is active - session?.coroutineScope?.launch(session.coroutineDispatchers.io) { - roomArgsList.toList().forEach { roomArg -> - sendLiveLocation(roomArg.roomId, locationData) - } + roomArgsMap.toMap().forEach { item -> + sendLiveLocation(item.value.roomId, item.key, locationData) } } - private suspend fun sendLiveLocation(roomId: String, locationData: LocationData) { + private fun sendLiveLocation( + roomId: String, + beaconInfoEventId: String, + locationData: LocationData + ) { val session = activeSessionHolder.getSafeActiveSession() val room = session?.getRoom(roomId) val userId = session?.myUserId @@ -174,18 +190,12 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { return } - room - .stateService() - .getLiveLocationBeaconInfo(userId, true) - ?.eventId - ?.let { - room.sendService().sendLiveLocation( - beaconInfoEventId = it, - latitude = locationData.latitude, - longitude = locationData.longitude, - uncertainty = locationData.uncertainty - ) - } + room.sendService().sendLiveLocation( + beaconInfoEventId = beaconInfoEventId, + latitude = locationData.latitude, + longitude = locationData.longitude, + uncertainty = locationData.uncertainty + ) } override fun onLocationProviderIsNotAvailable() { diff --git a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt index b7006370a6..4e56e7954c 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt @@ -40,10 +40,12 @@ class LocationTracker @Inject constructor( fun onLocationProviderIsNotAvailable() } - private var callbacks = mutableListOf<Callback>() + private val callbacks = mutableListOf<Callback>() private var hasGpsProviderLiveLocation = false + private var lastLocation: LocationData? = null + @RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION]) fun start() { Timber.d("## LocationTracker. start()") @@ -92,6 +94,14 @@ class LocationTracker @Inject constructor( callbacks.clear() } + /** + * Request the last known location. It will be given async through Callback. + * Please ensure adding a callback to receive the value. + */ + fun requestLastKnownLocation() { + lastLocation?.let { location -> callbacks.forEach { it.onLocationUpdate(location) } } + } + fun addCallback(callback: Callback) { if (!callbacks.contains(callback)) { callbacks.add(callback) @@ -127,7 +137,9 @@ class LocationTracker @Inject constructor( } } } - callbacks.forEach { it.onLocationUpdate(location.toLocationData()) } + val locationData = location.toLocationData() + lastLocation = locationData + callbacks.forEach { it.onLocationUpdate(locationData) } } override fun onProviderDisabled(provider: String) { From 82cbc351e502292a3c21fd68977a41dc1a261c42 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Wed, 4 May 2022 17:17:18 +0200 Subject: [PATCH 30/45] Fix background color of bottom banner --- .../timeline/item/DefaultLiveLocationShareStatusItem.kt | 2 +- .../location/live/LocationLiveMessageBannerView.kt | 7 +++++-- .../item_timeline_event_live_location_inactive_stub.xml | 2 +- .../item_timeline_event_live_location_start_stub.xml | 2 +- .../main/res/layout/view_location_live_message_banner.xml | 2 +- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultLiveLocationShareStatusItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultLiveLocationShareStatusItem.kt index e453b01692..c421efda12 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultLiveLocationShareStatusItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultLiveLocationShareStatusItem.kt @@ -68,7 +68,7 @@ class DefaultLiveLocationShareStatusItem : LiveLocationShareStatusItem { GranularRoundedCorners(0f, 0f, bottomCornerRadius, bottomCornerRadius) } GlideApp.with(bannerImageView) - .load(ColorDrawable(ThemeUtils.getColor(bannerImageView.context, R.attr.colorSurface))) + .load(ColorDrawable(ThemeUtils.getColor(bannerImageView.context, android.R.attr.colorBackground))) .transform(imageCornerTransformation) .into(bannerImageView) } diff --git a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt index 7d008e0fc6..37174c8442 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt @@ -88,7 +88,7 @@ class LocationLiveMessageBannerView @JvmOverloads constructor( } GlideApp.with(context) - .load(ColorDrawable(ThemeUtils.getColor(context, R.attr.colorSurface))) + .load(ColorDrawable(ThemeUtils.getColor(context, android.R.attr.colorBackground))) .transform(GranularRoundedCorners(0f, 0f, viewState.bottomEndCornerRadiusInDp, viewState.bottomStartCornerRadiusInDp)) .into(background) } @@ -108,7 +108,10 @@ class LocationLiveMessageBannerView @JvmOverloads constructor( } override fun onFinish() { - subTitle.text = context.getString(R.string.location_share_live_remaining_time, TextUtils.formatDurationWithUnits(context, Duration.ofMillis(0L))) + subTitle.text = context.getString( + R.string.location_share_live_remaining_time, + TextUtils.formatDurationWithUnits(context, Duration.ofMillis(0L)) + ) } } countDownTimer?.start() diff --git a/vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml b/vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml index e5d2f91404..248a1b9078 100644 --- a/vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml @@ -20,7 +20,7 @@ android:layout_width="0dp" android:layout_height="48dp" android:alpha="0.85" - android:src="?colorSurface" + android:src="?android:colorBackground" app:layout_constraintBottom_toBottomOf="@id/locationLiveInactiveMap" app:layout_constraintEnd_toEndOf="@id/locationLiveInactiveMap" app:layout_constraintStart_toStartOf="@id/locationLiveInactiveMap" diff --git a/vector/src/main/res/layout/item_timeline_event_live_location_start_stub.xml b/vector/src/main/res/layout/item_timeline_event_live_location_start_stub.xml index 104063782b..741853c9a7 100644 --- a/vector/src/main/res/layout/item_timeline_event_live_location_start_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_live_location_start_stub.xml @@ -20,7 +20,7 @@ android:layout_width="0dp" android:layout_height="48dp" android:alpha="0.85" - android:src="?colorSurface" + android:src="?android:colorBackground" app:layout_constraintBottom_toBottomOf="@id/locationLiveStartMap" app:layout_constraintEnd_toEndOf="@id/locationLiveStartMap" app:layout_constraintStart_toStartOf="@id/locationLiveStartMap" diff --git a/vector/src/main/res/layout/view_location_live_message_banner.xml b/vector/src/main/res/layout/view_location_live_message_banner.xml index 42004a5b81..a63ddac767 100644 --- a/vector/src/main/res/layout/view_location_live_message_banner.xml +++ b/vector/src/main/res/layout/view_location_live_message_banner.xml @@ -14,7 +14,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - tools:background="?colorSurface" + tools:background="?android:colorBackground" tools:ignore="ContentDescription" /> <ImageView From 4862f8e7c9da0e06213a0ccd13ba2c030505cc26 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 9 May 2022 14:46:45 +0200 Subject: [PATCH 31/45] Dark mode support for no location map background --- .../drawable-night-hdpi/bg_no_location_map.webp | Bin 0 -> 474 bytes .../drawable-night-mdpi/bg_no_location_map.webp | Bin 0 -> 320 bytes .../drawable-night-xhdpi/bg_no_location_map.webp | Bin 0 -> 1062 bytes .../bg_no_location_map.webp | Bin 0 -> 1446 bytes .../bg_no_location_map.webp | Bin 0 -> 1690 bytes .../bg_no_location_map.webp | Bin .../bg_no_location_map.webp | Bin .../bg_no_location_map.webp | Bin .../bg_no_location_map.webp | Bin .../bg_no_location_map.webp | Bin 10 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 vector/src/main/res/drawable-night-hdpi/bg_no_location_map.webp create mode 100644 vector/src/main/res/drawable-night-mdpi/bg_no_location_map.webp create mode 100644 vector/src/main/res/drawable-night-xhdpi/bg_no_location_map.webp create mode 100644 vector/src/main/res/drawable-night-xxhdpi/bg_no_location_map.webp create mode 100644 vector/src/main/res/drawable-night-xxxhdpi/bg_no_location_map.webp rename vector/src/main/res/{drawable-hdpi => drawable-notnight-hdpi}/bg_no_location_map.webp (100%) rename vector/src/main/res/{drawable-mdpi => drawable-notnight-mdpi}/bg_no_location_map.webp (100%) rename vector/src/main/res/{drawable-xhdpi => drawable-notnight-xhdpi}/bg_no_location_map.webp (100%) rename vector/src/main/res/{drawable-xxhdpi => drawable-notnight-xxhdpi}/bg_no_location_map.webp (100%) rename vector/src/main/res/{drawable-xxxhdpi => drawable-notnight-xxxhdpi}/bg_no_location_map.webp (100%) diff --git a/vector/src/main/res/drawable-night-hdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-night-hdpi/bg_no_location_map.webp new file mode 100644 index 0000000000000000000000000000000000000000..5c0d2e39d55ba843721e60c64fac277efacec6eb GIT binary patch literal 474 zcmV<00VV!YNk&G}0RRA3MM6+kP&il$0000G0002h005Ez06|PpNOJ%H00DQPG?FAW zdf?A8ByjFOA_6FIKgv@)W(K3$ww0{k0IOgP7*oU}FxLPSummguXaJ4C%&p0t@+0~` z0gT&!7!#3b$A>Z&q8G-ZtRGJA0#e>U=8yWH`k(s$cjE)3yMW|75IuTfEQ)r(hy})} zKolwg09H^qAUFX401z1fodGJ=0FwYdZ8nxkBqJgrAWEQs4T))P;cWrOpaO?!&}yox zcpq}+xjNssS+9ooEQpARcSOd?!;E^+oXWl;oyI`hW0oSFrIhoN@Q-S5d{mOnX}XPw zjmsh;BW&MFj9=^^`6E!>+WsPrZX?Ay4OG>O*_k+EJkotE3AgfMN4D&n+q`zUHmD#u z;uk|Amstx)&=hCN1aR$9q=V0mx5J+-zh6)W01ir&8mZDOXV83Jf5!~C8zma8*3h{k zH&Pn~A(VB)Ztlj`V(b9^{mW@^G4b>L;6}|@%tM5ei0yc~i-8S(&&ViX3K+1=;;gfQ z(W^vAv7oSi`S)FuTYipA&~1#wzdoQqO+q~aU+kNNfFLx|Xb|jh4X=~R$QM@PBOG1t QFY5{{A{q<IbM!=x0KS#ek^lez literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable-night-mdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-night-mdpi/bg_no_location_map.webp new file mode 100644 index 0000000000000000000000000000000000000000..3c17c090d6b58edd123695e0ae55e9bd9a60792d GIT binary patch literal 320 zcmV-G0l)rINk&FE0RRA3MM6+kP&il$0000G0001v003bC06|PpNJ#(y00BRsAOIOg zDJ=zNw?;$+-b2lUZDiX@zW-RswE&MD`tR$+5Yhh$*!n;?ykZWFgQ*~bO%y6n%HMw< zLL!AO6n=&n%(f!bw*UZEP&gpS0000m4*;D3DvtnS06uLtmPn){A|W7)xPT3bX#f`5 z03L@p(OX6jS5M-rI+DWd#C^(KeK7ti0^PH)No>b&JLV9X{ndr_TD&3!J@^{0CW>YR z=^ASM)j~SxRUtTMCMq^@0t>_#E>&|H>)&;!aLl1?4(-u0(=Yg5#ed}gN*SXJ0092~ zVhUpTiMi=;C=J*-$XDy|3=}-czUo=W&md^U@_65cD`-W$O4llG7n*f>u8UPPPzpc~ SY_ot9xV;7gqGuZL0002jrGMQ3 literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable-night-xhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-night-xhdpi/bg_no_location_map.webp new file mode 100644 index 0000000000000000000000000000000000000000..b79c747ac141234d4ff079f871756c0bc1b8aa8e GIT binary patch literal 1062 zcmV+>1ljviNk&E<1ONb6MM6+kP&il$0000G0000T0RY1Q06|PpNOk}K00Cb>AV8Va z0w#E1iiij$J!ZIYTido0T`cz&JSK8Fu#ghSXycba31m9f<Bevm7t#L-0H*JcMHK?* zeoXqXKU65K{c&j?WE3wl<~JGpPy8?b7ypa@#sA{}zw2Kz)J?{6l9BafSoWKWO~XJ% z0APL7u;&0)P&goh0{{R}B><fPDjosE06qx<fk1&i000n{mhZ>Oo?d;dU+OLQ>z;T1 z`{E9|TgUyz=nI3HIT>r)Mi+W6k+3%rW0;c1F%!!T8rQG#t+#5{2q^uWQTsTfwK%}V z_mpYIIxU=0`!)?hJ{GZ@^UWGgT<PBAaTV@8gr?-l3y$slYqIf5lm&o|BTY6Jn3Bga z93p_;Y^QpfM0TV%_whQg<LPL&aYy7W9j=9!Mp4?K9yt1b7mGVrxb~z72SK=Sxq*`@ zYZ{Q1=8b)`iWTeAQMV!{2SOdx)z{xMcwp0~1pZw<3c65O*g~|>;6>ye3c`Dg_6sgg zJ#EBSxODfy)@NrNtyXa9r91FeEuuwHIv!?OU#2_oW5%KTIG}kk>rXnygHDHXHC(K6 z+fDQKaYyMY=l}rz|Nn%9NP3m><{<Zz7vKMqVBy^Ce?3rt?Qj@vy6iWffVz8-8O;!# zV})6t*ivk%YQ!J|H+ohdHp~;Jr4Z*fVH&jp8k-m<Z+xI&nLakK*_w`Z*irlIeKAT- zd8^7xJqo~y2OiaEOqKv-^}GLsInk2A+2Z!{l)(u}ivz!XhPW+Glzu)ywGv;`wX_G= z?u>u^qE&1pvyTHfNeo#)-iN#@5@+;90H9E>s-MtH5Ywldp5+xrL>_-SWwONMZH!dS zXO?vf1Rf=Wd<@njEqOBtie-9$mNU+zd15}=jlHs-=JDFT`r37x$z{m6ZDFx`3Q_r7 zL|ftC$2(XyDt|ZnS2qJWqZ}iJ406MJR0bNdR`()%^`pUl+)~@3jwHR9TY6Qb8hI_| z9S)}r;Y@_7D(NKZbjC-kuw&Y0G;<OiYg({$6}qFw)z++HG1hStpRD4Tdr8-N?T$i? zc~#;<9@$%Zybq1`h|Eg3R?P$I_nWseKwRpoLDYfSFhQWjxJbx`!Q^k+1CRV;#p}p^ zsT4xrhkNvB?pc5<xPY`DwT!)ip}tabBIRCL-fR`!@A3=8Rb5jZ;EnJwsN$+Wz0rcv zXHp#2tDVNXnqF?<$`1p3SFHQRdQ;uu7O*P<k?xtB;hoc2@H$=F)(x+x8I|$w-+Zbg zvhlXjs1$5(c7V6sSx3SjH>>yYd}r32QCpNG%&~gnsZ0L_{_C(^t{31y<)u$x!w{!p gE5aGbl8zg3uU7XqnVDH^io=tQQy_isIrf+U0FloJP5=M^ literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable-night-xxhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-night-xxhdpi/bg_no_location_map.webp new file mode 100644 index 0000000000000000000000000000000000000000..75aa5cecc6c6feecc7168a8522d1b3f086630c60 GIT binary patch literal 1446 zcma)6`8U)H6#ovg)jVUe6v>P-*3qNPSYDQ4gvf5>*@YH#8jW>Ar6Fw?^ei(;N2FPv zB>UE}#3W0$ESa)IF$S5jW!}7cf57|U-h1x3=X~zxb3f-?S34UU>K*{VVKL60&PHC+ z002OQK?4EmAi&bW*_JQtK*`<_3GNHzOf68JL548fp?6e#O#Z5p^qB`Xr#XqI7!IJR zaDQtD05$$G_lJ&7Bd1Q>KQ+7a%LM`EPUVOxODnwHpkXyibHy!fZL8avsSiA(g{2oZ z9)-y;byW<M5@r`hqxsfxoX$K=vWG@@;>vLsKOS?ndFPmbCHVg(>Fct88lD&8B*#R_ zmT<PhiYEw5wsMy;?jdroyYDz;u6aWtL4<T#)II=mN4CwrE!6e{C(Qi)=MeLg4~%ai z0Y|iFX?tQtQ#%3>UIdmb1^~ZX02Gim9fSax9o%;d0~Z77hyj5}<b>WZtRr$ll*Z(R zY56Zwv%P1>V&y1>EL-H{;I3PXXBDT-l+28VPf!vVBehT+5#RA18NWrVS`&VdO4ms3 z;rT2M5$*z4!$|ZHQ41sW5Y8DkDyEDzYqgXDM^Z*p@r0icHucOLHnX_Acg`zCCVwO1 zJzd#Fa--rh)uR@!@wNvFK~Dxt8FI%<h2W))UeK?`O)l1VDe(#mUh^y65FOS{iC<I9 z@XABOqwY{PdeEx$$PQ-Jx1r2(`#*|B7aMzQ@^TOHgCu$^pJwvBZDjqr&itaQsI*P5 z9;NBIUF#Z7C2p~CUwI_^MERd9jRxF&Bvlgrp7J0u?o0Wu$B`z50u=_02lp@@oxL6l zW5p(zW+_<Kx2E0l|AYOfkR^tVU2?7Y)VM$j0UsJ>L8g`rj@>q`peLDigqdJ<No)S# zT!@*}L~shZpu9b_4$0UKcT{$&;x_-#Z(oS=<MlpOov)qo8;geVfydMX1ke4&PT)EM zr>?&If#_0Ct#zr&r%ls}y)o$viF!J)<Et^&rkR2g(&&|<g2IrOEt^adOGE!PcAh_a zq4G_Pn5&zETTqp3=)Wc`ea~DcpM+Q2V%b<L5t<QQXiMX0svi{qb_FY)Q@gAKCSi-u zi|AKrFYWQ%-~QmrUI9jG*;szlLGm}bOD5chlR9=_`_{M^VqP#_Ixul<`N=e37?3+R zYdzcL^zpQ4Qi57hi@fz4L7&N7xBa@q6h8yRrVtiAVs<Y-c5yI_ZUU=9scMLW_;Hlu z)nSQ}c?>x+)q`J;vshPeIp?TRcQR1YOlqJo$Rex#aX<ifwxGk`<2amm*Zs}bWL{D} znsWcOSoE%|)=YOS(3&j>z!RwIhoKm;p&E4OKwXPSoN0ML#j537mCrNWxw-fh)K1ro zvu_-AVermJc{7C@-O&~zmz1!s)HZR6!$#I8ygV~ERzy}C7$00a=5}A<M$9!7<5%@R zw~|@$bEtm1-e52wCoO88l#|@^j92ZbFm&zJ-1lmJRe1CEVBe9B40LwZmnU1U1e|>; zt^I5`a$TIdciCBPU`I7FHd?H1<R}_=SNdXRyzjIOH}c09<yhj|8nqqG!ho@CS(!YX zx<E-kOgDA&hNVOk_1>$fv1LYbAAdSl`kd|_?cIt@T5@+8-k?7MNXmi0WX=yETXTK% zz~B?Ip#%2X2H@agy>uVtoI?(HnHofsxYu{VOK0h+<)S5pX8Guw!ShhB%9+g;*?jKw zN_u@O6=Arq^8UvDLV~!B;@NM;@qzQ=NmuE(!P8^59lhK6M3Zlfw0r|~TsvB<TdSIs zyFX~?9YPT<;aE8aV`!2&vH0u#ITzKhp`d01W|N#T0WQDuqU!Sp5e1AFXY@;?nv{Or tz-~88Bf{Nb#4=ppVE=7$Z<)xW(o70Z8#P2EDxG?M{^_s|JwW(>{{eA<+CBgP literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable-night-xxxhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-night-xxxhdpi/bg_no_location_map.webp new file mode 100644 index 0000000000000000000000000000000000000000..511d237d4bf8a6637a90a936da6bb88879048023 GIT binary patch literal 1690 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 U5Au9g2c8cqzMloAQ_ukL4>k=#xBvhE literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable-hdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-notnight-hdpi/bg_no_location_map.webp similarity index 100% rename from vector/src/main/res/drawable-hdpi/bg_no_location_map.webp rename to vector/src/main/res/drawable-notnight-hdpi/bg_no_location_map.webp diff --git a/vector/src/main/res/drawable-mdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-notnight-mdpi/bg_no_location_map.webp similarity index 100% rename from vector/src/main/res/drawable-mdpi/bg_no_location_map.webp rename to vector/src/main/res/drawable-notnight-mdpi/bg_no_location_map.webp diff --git a/vector/src/main/res/drawable-xhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-notnight-xhdpi/bg_no_location_map.webp similarity index 100% rename from vector/src/main/res/drawable-xhdpi/bg_no_location_map.webp rename to vector/src/main/res/drawable-notnight-xhdpi/bg_no_location_map.webp diff --git a/vector/src/main/res/drawable-xxhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-notnight-xxhdpi/bg_no_location_map.webp similarity index 100% rename from vector/src/main/res/drawable-xxhdpi/bg_no_location_map.webp rename to vector/src/main/res/drawable-notnight-xxhdpi/bg_no_location_map.webp diff --git a/vector/src/main/res/drawable-xxxhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-notnight-xxxhdpi/bg_no_location_map.webp similarity index 100% rename from vector/src/main/res/drawable-xxxhdpi/bg_no_location_map.webp rename to vector/src/main/res/drawable-notnight-xxxhdpi/bg_no_location_map.webp From 16be69ebaeffd92a4c3b23f944d2bd06707d2aaa Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 9 May 2022 14:56:35 +0200 Subject: [PATCH 32/45] Fix tint of banner icon for start and inactive message --- .../layout/item_timeline_event_live_location_inactive_stub.xml | 1 + .../res/layout/item_timeline_event_live_location_start_stub.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml b/vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml index 248a1b9078..6b06e05b08 100644 --- a/vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml @@ -50,6 +50,7 @@ app:layout_constraintStart_toStartOf="@id/locationLiveInactiveBanner" app:layout_constraintTop_toTopOf="@id/locationLiveInactiveBanner" app:srcCompat="@drawable/ic_attachment_location_live_white" + app:tint="?android:colorBackground" tools:ignore="ContentDescription" /> <TextView diff --git a/vector/src/main/res/layout/item_timeline_event_live_location_start_stub.xml b/vector/src/main/res/layout/item_timeline_event_live_location_start_stub.xml index 741853c9a7..505b73ae68 100644 --- a/vector/src/main/res/layout/item_timeline_event_live_location_start_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_live_location_start_stub.xml @@ -39,6 +39,7 @@ app:layout_constraintStart_toStartOf="@id/locationLiveStartBanner" app:layout_constraintTop_toTopOf="@id/locationLiveStartBanner" app:srcCompat="@drawable/ic_attachment_location_live_white" + app:tint="?android:colorBackground" tools:ignore="ContentDescription" /> <TextView From 59567e39b402fefa60964f14abee2759e53523a8 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 9 May 2022 15:11:47 +0200 Subject: [PATCH 33/45] Fix code quality issues --- .../main/java/im/vector/app/core/utils/TextUtils.kt | 6 +++--- .../detail/timeline/factory/MessageItemFactory.kt | 7 ++++++- .../detail/timeline/item/AbsMessageLocationItem.kt | 1 - .../detail/timeline/item/MessageInformationData.kt | 2 -- .../timeline/style/TimelineMessageLayoutFactory.kt | 13 ++++++++++--- .../location/live/LocationLiveMessageBannerView.kt | 5 ++++- 6 files changed, 23 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt b/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt index f9c33ffe2a..80dabd5021 100644 --- a/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt +++ b/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt @@ -93,18 +93,18 @@ object TextUtils { when { hours > 0 -> { appendHours(context, builder, hours) - if(minutes > 0) { + if (minutes > 0) { builder.append(" ") appendMinutes(context, builder, minutes) } - if(seconds > 0) { + if (seconds > 0) { builder.append(" ") appendSeconds(context, builder, seconds) } } minutes > 0 -> { appendMinutes(context, builder, minutes) - if(seconds > 0) { + if (seconds > 0) { builder.append(" ") appendSeconds(context, builder, seconds) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 868018a706..ea703a0d11 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -216,7 +216,12 @@ class MessageItemFactory @Inject constructor( buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes) } } - is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(informationData.liveLocationShareSummaryData, highlight, attributes) + is MessageBeaconInfoContent -> + liveLocationShareMessageItemFactory.create( + informationData.liveLocationShareSummaryData, + highlight, + attributes + ) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } return messageItem?.apply { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt index b502d82829..f7146c24e9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt @@ -107,5 +107,4 @@ abstract class AbsMessageLocationItem<H : AbsMessageLocationItem.Holder> : AbsMe val staticMapPinImageView by bind<ImageView>(R.id.staticMapPinImageView) val staticMapErrorTextView by bind<TextView>(R.id.staticMapErrorTextView) } - } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt index dae1c3671d..be0f2d55ca 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt @@ -20,8 +20,6 @@ import android.os.Parcelable import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.session.crypto.verification.VerificationState -import org.matrix.android.sdk.api.session.room.model.message.LocationInfo -import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.util.MatrixItem diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt index fab7dcb49a..5bb4db6e9b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt @@ -66,6 +66,11 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess MessageType.MSGTYPE_VIDEO, MessageType.MSGTYPE_BEACON_INFO, ) + + private val MSG_TYPES_WITH_LOCATION_DATA = setOf( + MessageType.MSGTYPE_LOCATION, + MessageType.MSGTYPE_BEACON_LOCATION_DATA + ) } private val cornerRadius: Float by lazy { @@ -145,9 +150,11 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess } private fun MessageContent?.timestampInsideMessage(): Boolean { - if (this == null) return false - if (msgType == MessageType.MSGTYPE_LOCATION || msgType == MessageType.MSGTYPE_BEACON_LOCATION_DATA) return vectorPreferences.labsRenderLocationsInTimeline() - return this.msgType in MSG_TYPES_WITH_TIMESTAMP_INSIDE_MESSAGE + return when { + this == null -> false + msgType in MSG_TYPES_WITH_LOCATION_DATA -> vectorPreferences.labsRenderLocationsInTimeline() + else -> msgType in MSG_TYPES_WITH_TIMESTAMP_INSIDE_MESSAGE + } } private fun MessageContent?.shouldAddMessageOverlay(): Boolean { diff --git a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt index 37174c8442..bb69cdd1c2 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt @@ -104,7 +104,10 @@ class LocationLiveMessageBannerView @JvmOverloads constructor( countDownTimer = object : CountDownTimer(it, REMAINING_TIME_COUNTER_INTERVAL_IN_MS) { override fun onTick(millisUntilFinished: Long) { val duration = Duration.ofMillis(millisUntilFinished.coerceAtLeast(0L)) - subTitle.text = context.getString(R.string.location_share_live_remaining_time, TextUtils.formatDurationWithUnits(context, duration)) + subTitle.text = context.getString( + R.string.location_share_live_remaining_time, + TextUtils.formatDurationWithUnits(context, duration) + ) } override fun onFinish() { From 7aa958b9ff6df9e3b9dfb62537d963367ef788d0 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 9 May 2022 16:11:36 +0200 Subject: [PATCH 34/45] Fix getting related eventId for location events --- .../EventRelationsAggregationProcessor.kt | 9 ++++++++- ...DefaultLiveLocationAggregationProcessor.kt | 19 ++++++++++++------- .../LiveLocationAggregationProcessor.kt | 1 + 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index 7e0b44a314..0612199502 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -194,7 +194,14 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } in EventType.BEACON_LOCATION_DATA -> { event.getClearContent().toModel<MessageBeaconLocationDataContent>(catchError = true)?.let { - liveLocationAggregationProcessor.handleBeaconLocationData(realm, event, it, roomId, isLocalEcho) + liveLocationAggregationProcessor.handleBeaconLocationData( + realm, + event, + it, + roomId, + event.getRelationContent()?.eventId, + isLocalEcho + ) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt index 997e31a109..3ac47ee8cf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt @@ -60,22 +60,27 @@ internal class DefaultLiveLocationAggregationProcessor @Inject constructor() : L aggregatedSummary.isActive = content.isLive } - override fun handleBeaconLocationData(realm: Realm, event: Event, content: MessageBeaconLocationDataContent, roomId: String, isLocalEcho: Boolean) { + override fun handleBeaconLocationData( + realm: Realm, + event: Event, + content: MessageBeaconLocationDataContent, + roomId: String, + relatedEventId: String?, + isLocalEcho: Boolean + ) { if (event.senderId.isNullOrEmpty() || isLocalEcho) { return } - val targetEventId = content.relatesTo?.eventId - - if (targetEventId.isNullOrEmpty()) { - Timber.w("no target event id found for the live location content") + if (relatedEventId.isNullOrEmpty()) { + Timber.w("no related event id found for the live location content") return } val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate( realm = realm, roomId = roomId, - eventId = targetEventId + eventId = relatedEventId ) val updatedLocationTimestamp = content.getBestTimestampMillis() ?: 0 val currentLocationTimestamp = ContentMapper @@ -85,7 +90,7 @@ internal class DefaultLiveLocationAggregationProcessor @Inject constructor() : L ?: 0 if (updatedLocationTimestamp.isMoreRecentThan(currentLocationTimestamp)) { - Timber.d("updating last location of the summary of id=$targetEventId") + Timber.d("updating last location of the summary of id=$relatedEventId") aggregatedSummary.lastLocationContent = ContentMapper.map(content.toContent()) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt index c0be96f83d..d2450aef9c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt @@ -35,6 +35,7 @@ internal interface LiveLocationAggregationProcessor { event: Event, content: MessageBeaconLocationDataContent, roomId: String, + relatedEventId: String?, isLocalEcho: Boolean, ) } From 738d486f9d9b71a1c5f58ba2a76ba3e503c81fe4 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 9 May 2022 17:31:02 +0200 Subject: [PATCH 35/45] Moving the light "no location" background into default folder --- .../bg_no_location_map.webp | Bin .../bg_no_location_map.webp | Bin .../bg_no_location_map.webp | Bin .../bg_no_location_map.webp | Bin .../bg_no_location_map.webp | Bin 5 files changed, 0 insertions(+), 0 deletions(-) rename vector/src/main/res/{drawable-notnight-hdpi => drawable-hdpi}/bg_no_location_map.webp (100%) rename vector/src/main/res/{drawable-notnight-mdpi => drawable-mdpi}/bg_no_location_map.webp (100%) rename vector/src/main/res/{drawable-notnight-xhdpi => drawable-xhdpi}/bg_no_location_map.webp (100%) rename vector/src/main/res/{drawable-notnight-xxhdpi => drawable-xxhdpi}/bg_no_location_map.webp (100%) rename vector/src/main/res/{drawable-notnight-xxxhdpi => drawable-xxxhdpi}/bg_no_location_map.webp (100%) diff --git a/vector/src/main/res/drawable-notnight-hdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-hdpi/bg_no_location_map.webp similarity index 100% rename from vector/src/main/res/drawable-notnight-hdpi/bg_no_location_map.webp rename to vector/src/main/res/drawable-hdpi/bg_no_location_map.webp diff --git a/vector/src/main/res/drawable-notnight-mdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-mdpi/bg_no_location_map.webp similarity index 100% rename from vector/src/main/res/drawable-notnight-mdpi/bg_no_location_map.webp rename to vector/src/main/res/drawable-mdpi/bg_no_location_map.webp diff --git a/vector/src/main/res/drawable-notnight-xhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-xhdpi/bg_no_location_map.webp similarity index 100% rename from vector/src/main/res/drawable-notnight-xhdpi/bg_no_location_map.webp rename to vector/src/main/res/drawable-xhdpi/bg_no_location_map.webp diff --git a/vector/src/main/res/drawable-notnight-xxhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-xxhdpi/bg_no_location_map.webp similarity index 100% rename from vector/src/main/res/drawable-notnight-xxhdpi/bg_no_location_map.webp rename to vector/src/main/res/drawable-xxhdpi/bg_no_location_map.webp diff --git a/vector/src/main/res/drawable-notnight-xxxhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-xxxhdpi/bg_no_location_map.webp similarity index 100% rename from vector/src/main/res/drawable-notnight-xxxhdpi/bg_no_location_map.webp rename to vector/src/main/res/drawable-xxxhdpi/bg_no_location_map.webp From cb5c6ec35ef12777fc1432573a0021c0263afd12 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Wed, 11 May 2022 09:53:42 +0200 Subject: [PATCH 36/45] Decreasing alpha of bottom banner in message view --- .../layout/item_timeline_event_live_location_inactive_stub.xml | 2 +- .../res/layout/item_timeline_event_live_location_start_stub.xml | 2 +- .../src/main/res/layout/view_location_live_message_banner.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml b/vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml index 6b06e05b08..d5a0cefb28 100644 --- a/vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_live_location_inactive_stub.xml @@ -19,7 +19,7 @@ android:id="@+id/locationLiveInactiveBanner" android:layout_width="0dp" android:layout_height="48dp" - android:alpha="0.85" + android:alpha="0.75" android:src="?android:colorBackground" app:layout_constraintBottom_toBottomOf="@id/locationLiveInactiveMap" app:layout_constraintEnd_toEndOf="@id/locationLiveInactiveMap" diff --git a/vector/src/main/res/layout/item_timeline_event_live_location_start_stub.xml b/vector/src/main/res/layout/item_timeline_event_live_location_start_stub.xml index 505b73ae68..1726928721 100644 --- a/vector/src/main/res/layout/item_timeline_event_live_location_start_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_live_location_start_stub.xml @@ -19,7 +19,7 @@ android:id="@+id/locationLiveStartBanner" android:layout_width="0dp" android:layout_height="48dp" - android:alpha="0.85" + android:alpha="0.75" android:src="?android:colorBackground" app:layout_constraintBottom_toBottomOf="@id/locationLiveStartMap" app:layout_constraintEnd_toEndOf="@id/locationLiveStartMap" diff --git a/vector/src/main/res/layout/view_location_live_message_banner.xml b/vector/src/main/res/layout/view_location_live_message_banner.xml index a63ddac767..35924541d1 100644 --- a/vector/src/main/res/layout/view_location_live_message_banner.xml +++ b/vector/src/main/res/layout/view_location_live_message_banner.xml @@ -10,7 +10,7 @@ android:id="@+id/locationLiveMessageBannerBackground" android:layout_width="0dp" android:layout_height="50dp" - android:alpha="0.85" + android:alpha="0.75" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" From b2099b6b31b769b967b2da22f7a14b47bec29cce Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Wed, 11 May 2022 10:34:54 +0200 Subject: [PATCH 37/45] Updating the no location map background --- .../res/drawable-hdpi/bg_no_location_map.webp | Bin 952 -> 876 bytes .../res/drawable-mdpi/bg_no_location_map.webp | Bin 638 -> 594 bytes .../bg_no_location_map.webp | Bin 474 -> 958 bytes .../bg_no_location_map.webp | Bin 320 -> 640 bytes .../bg_no_location_map.webp | Bin 1062 -> 1500 bytes .../bg_no_location_map.webp | Bin 1446 -> 2022 bytes .../bg_no_location_map.webp | Bin 1690 -> 4010 bytes .../drawable-xhdpi/bg_no_location_map.webp | Bin 1228 -> 1320 bytes .../drawable-xxhdpi/bg_no_location_map.webp | Bin 1846 -> 1822 bytes .../drawable-xxxhdpi/bg_no_location_map.webp | Bin 2706 -> 3522 bytes 10 files changed, 0 insertions(+), 0 deletions(-) diff --git a/vector/src/main/res/drawable-hdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-hdpi/bg_no_location_map.webp index 23a45700f0a59f6caa118e5b7d6a7216c579b3d8..3241b5dc82fbc19906d1b636b0ade4fae1f77900 100644 GIT binary patch literal 876 zcmV-y1C#txNk&Fw0{{S5MM6+kP&iCj0{{RojzAa?C&HkSBt<oQ{_Wl~g9fy1+lF@j zzqD(2Gi=+&5q0|yT)sq6BuPzU0Xxb63n{!as7>2|8PjtI^nbr`fMFak7zb|O_rTx4 z=fi`D!N9=565HmXv5w8pRV%3$g?39CNkWi@dNuv+G(!p;kZx^n+DKKXFyP%X?1U3{ zb_>JIGxvW{i>ma+vp*yH55bKjMN;G*Gh|mzmi7a!_p|lN722G?x861@muT~PzH*Ds zHjZJv`p?m*b`0eA3%Br72Z>`K>MoAm0#P)!Zh<K1S5DyXGPz?Q+K(LrQG4ESI10zM z$?)X&%jUw?eb!WVP&alAL{Bs87KnnObqhqkDck~)caS=SKZAy2Ad05eEfD#`s%c2L zDQg14dW_-ek+eFC@8GxmaJOd~88phiC;dz?KzEZJM<gwiqCYe5h(FVdf2^<-ABaEG zh7TIKk^ko%*Wk}ogHZe#_-4UQ<lTfHG!{L1Hyv5<XA%WBP0*hy<lPkBR^ZRnBXKu* zCWCiVTi#7E_%p{h@^0$Rmn&?gM(mm9^kqdqkPjNgXB#w@Gl8JdxV|iYl#kEwK_itv zk|X$_QQnHX$?t!<Zn7FdW3h5geC|x(<Foubgu@d1{5v3d`FA<;S>yeNJA#HGEp-=| zL8AyrPZ6Y>2Baz<Z$VZ?q%7a$Sd0lkJtQ~TE<va>hv~}}+|wg5UNhsf?J<d0t8$<a zG!SM)%st7hTC<R;bHHTwm!Q#=chfgrGLKmf)Vh7y)GE>^9^-!njXU|iY-F+KQuuEx zRPynez9_}#5%K7=j6aii)1F3ru9-huz#TRU%|d%2&V+ke0MwUZ%q1_`ty%Dqx#9k^ z?P$kp9Ky1EV6qTs7TR*4%q0O`_aHG$rczVH;E^-k5Y>VUvH*+$@fgWA3n3C^z&v^j z>CD){VN5)}f;9^Ob173=kRdSRrbw+-!Z6^Xf87#m_GQW(aTDzlZ9%49-7M4>WO}6B zYeuVIx*A^z!(;-nARC4n6x_K4(t#|ekvs$D(;?v6S9vZ;^)a$RLw7FSp|w^0Kn%+~ zD*Xg;m7rVQVBUR2wYq`4uhE%eH1AnN&ObJaa|!b4N9*I_bi1prUU+)G+`{SP?4t#? C8Lf~2 literal 952 zcmV;p14sN)Nk&Gn0{{S5MM6+kP&il$0000G0002i005By06|PpNNoTB00D3t+qP0Y zZXG-dv5K77YvR3>@7{^KYY8X35itRRWW9L)k-RNZNrF7}$5VME^#_oTw$eYQ<qVX; z9hBW4)HC~!{m1@e|FQqre~h~aD626jZ3c>O=?@?uCCO8OG&Tt`<JoeH<a%D1h5!Io zP&gnQ0{{Rp8vvaFD%Sv#06qx<fk1%*000n{mhKmJ4T-_40_kf*OpDdu$NBpEkA*tj zZ)~rB%&MaRf&XS8Ew}SYIjs(N4OnmpMrI`4)m^Ey{(+rYa9Yb|UCDa74)R#nA=Wzy zudM`d-YIBAH}xb`Nz+C`zIW_6g4boz?8E8wWLnK>!T76ZYEKLQmMX^XeeG{BbKXM` zU0Dx`z3I>xjzi(s?ed(T4x(b{3PBTe`D!*~Ze%{<cCL6>>4iXxwFSN`gdF)VHwFeA zU#g0l;7sN#B_p;Kk#x<Qgv)hr6q@VQxB&kD|I~Zv2MPjCx`Pf1WBN?ndXE~G=tSHH zdR*oTkvz8K4wu|;m`<fvx4A!-LclMO&{ESy((MnB0>um1m9zhl@-{eT{bDCL=(b=c z^CPYnA}k*wL<eGm2K#T`&wU$5?Jvo2Ww~SqORiCV7)m2figJM@+q!VX$uR*I+St`3 z(Z|#Jl~ElX#P>xxS;+_hBrNdCYUR7Jl!M5YafybdB!A_ufOi~AKrLrOInZZNTK6l> zbRZOPLTU38^#wDr(Tz`|c>r=P6UXWB{c6zauhz5tC}k-MJfjD;Qlk@G4Xl=T$u%T1 zWQn=nq-U!l_{1c|%R$YqL?BT2@Su4M8%%jj-Yw|p1`o?hCOQkhc1|xE-vSTDS!_@g zk+}uk?PCxCe6-9D5dJ=(&DvzTDXZN3vNHw5Ak^@0B;-Z}q3-Icj5)=+e1UqoTLCG& zM8kx4mY|Saq|zJ-)lE5|Y(wMF$MmoDw49-M@mW6+8U>l8X?<^<Ml`?Rn;A%lga39e z7VP-b8}UhJ;(%j(YSx1%nOKd*slm8MTn|Ku0F&wn=FY%qfadOB<o2R?{H%Mk$rdv3 zBsUqx^A9CB)$iKsKbZjtVJ_@TI}}q5cHtuDAzYXAqS@1|Y`4@zskx>F952pGoeI(? zeX{lq8{RLy_vULcdxzkxV(OA2?A+i2k4_3U2^wGcdDw^9YPx9HPOj|7{#FQ<)aF|H a^N4~<>s>Kk5d!ip0u+Q_%Gn-15C8!42g6|i diff --git a/vector/src/main/res/drawable-mdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-mdpi/bg_no_location_map.webp index a6130fba782478a78b422e1e36548230d98f400a..03f9ba50626bfd180143d0386ef1fcfa7d8d344c 100644 GIT binary patch literal 594 zcmV-Y0<Ha0Nk&FW0ssJ4MM6+kP&iCI0ssInUH}jfr-3+<WVG{@f2dxn?hZjHZre6u zdUyX-`z@@vZQF=RUfqA&(~KiYYSUGG36wkuf<(NN)TV8~jOn>!zx@jdLP!Wi;`uVX z3n_$zL_+w_P~L+N;o-x=hd+9U{6D9Br+!*@mDwxCoH_tlvTZAkE*8Zl@`yfO+>@UF z;EczW>hFmDI}+HoZNLoOBeHa#@Vp-na&>w<9|zgGpM-NfNLRcT4nU}6^R2<u$Oa%N zjBEhBbSoPGCt8Z6T_YtNfI($s1K{UcDs9`fkwiy@wQvB2)17Ppg3`zaz|Xd_0dV;( zzD_(qYOMCmgU$o&FqjzT!Bl<h5ER@N->XsUAk*Tj$VG40ihJ0kf3{1m#dTrdE^$F_ zrUT0$G00Vjws^W`w|I>F_!2tPHuv${#c%N*+TvxyJ<Nva?c%q19lH1+7jN;A`u{9m zfbbwM0WXi$;=2GS<S=%oDg2etARhvq`YfGE0jd0aOHOE89u+#+?V>(}t{n?GNoNWb zC5bJ5e&wge1v&8{RC^X;%Ffi#kb{Lyn+xYd37yIKwy$|;*CKMT7LS(HIaJdJIXZ{> zwoN&7P=gI};vAYXbSByy3ijQbamYjs7Ajn4ayvqsLk4?1@yJ=b7Bakf${sO^%{`i> zGm+*HS!c39nL}&BL#W#ZX$}#p$$bhc+gmX;W5OI_`0alGK$$}fT1EhLImFa$batf5 gp@Qwd_j_j|YYuVocs{P@PP%=*+Vwi~=yHDo0P{c`fB*mh literal 638 zcmV-^0)hQfNk&F?0ssJ4MM6+kP&il$0000G0001v003bC06|PpNJ0Pr00CeWNs^*b zvta$0R52_)#{s=IA|?R*R*;He^56neGPnV0xddW7NX%0GSN&K0r#X=r`%H|+1yTeD zZny#!-_-zCP&gnC0ssKe5dfV5DvtnS06qx<fk1&6000n{kfG^=iL8tI+*EbpH6e|3 zSf0RTEEL}BF57@eqn==ISPZiY_Al=cF_%GRL5Rrx<P;Bn9o;d0)_H2(Y&o_M^02Cm zx}A?lBB41FP2}aJ`*g+A(i^bA?G($qLjf{6Zx0E^Kn<Z<KT*MJ=(SXi;@#qB$qAyD z9XJ(#Fs4!O3V;Cq|No_g&}2%x#;*byBBhb<Bv1fQ$Si+C$B%TCQ4+D*Rt`YPf^Bll zs7J6PSIn($dW$LEUtnuy&Jm{aHG7c{DUhk;Q(3VjEXecQo`vRU4$cWjvq+8sMqt_( zr`KT$+-iH)UL=eMbJe#S;nG1P=KaS*U1m;Zs}yG$qWm)*gz|H7_hQ3HCIi<2Ak^*n z@bUz#f}}2hp5t@tGVY>nZh@5;E;mwLsR3jPQkp}llNFKR{$w>2RiRK9G9DRy-YEQv zNOaR3t3m!1d^zOxiGag-R><JUo)Ye)2VfNhtNSv!*Bq>@C0GVzG1!d|%8Pzw<6j9h z7O5t`?^(bH|13XF<#R%znSSR7eHAuPa<kcmq;SMwhaRjm8z`M5Iu4!?frU-zmz1Pm z)zBb3yI7RXO@%V3a3MhHw9kTw05TNn#{daxWBHqSaPOG~`4^{R&Atg6BkQeE!2~k4 YEO((?JeOcCc91o4;@Y(|uBt!)0E~DU`2YX_ diff --git a/vector/src/main/res/drawable-night-hdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-night-hdpi/bg_no_location_map.webp index 5c0d2e39d55ba843721e60c64fac277efacec6eb..76e0a75dd66e13c0a0e6f62d535c6c7c1957b76a 100644 GIT binary patch literal 958 zcmV;v13~;!Nk&Gt0{{S5MM6+kP&iDf0{{RojzAa?N5d$RBt_X~*1xs;F)mqP&`6S^ z8Y$;r9>W8pHj*5<v~-p~t$l|JiXusB`UQq@NFgalAwd!~u>(q(p9I3Nh%l@`cu*z$ z0z%{$sftMbRT8Pc>IUEnnV>OnQ1Kq3|Ch-0@hLD8+q!O9>T<mg$d)8YaU_-0V&-LL zW@cvIIn8RB|Nj@TRF%_qj}iTc<hG3<NuHU_b%Cs%q<22xdwaQYZK3MZoA0ga+C<g2 zN7pWTsal2`_diFoj%A?O;DucvGCfCjfyiICZ4Z7Zzg)2kMDej}yFe7`J+%u&rfJXf z{H@}e$Kfb7?RXiUB14zng{xrKu~(@<<!#GAlwGda1)}(bV;6{m{bzQ8$h7Wx3K<nP zJcN`|^Nv@LQ+WK+DtKmEC0UvJ9eCm-t?UZ=YqcO=#Anc(cef!RU4#cNz>CwKkaS_1 z55bGeu_#Hagl@^sVphCJ!&~x3NxXTZW9ZGh*KUZ&DwcfFAC@ls4cq9=JCPu1<<`KP z_usO-B`2oQ7y0c5@gh8Y4qlwdTe81>58aZ-WAc`~j{es)XCF&mE6ZE*gmo-A2ye+} z1LBr!8sRN@Qr?pDD;4abqF~8M_-S=pep-zToq-oez4C)b`#yMaJ}Pg?_3+c`d`5l; zTd<BL$4B9JF!|65^`2nA3M4Pnv<=KA>8Q92OFd~RHRV8ZU`S7q;R`^zNmZup0M3IA zDa&8pz+#X9l%B8Pa1|v8#c(Wnn(H3BE#kcs?mNYpxROlPT=#T9e$Z&x17L|e<hftE zo|GRn3Tx`*nauwjG?sJn)9SRN$`-f(D!)#v_41ZHcmbD7qkk>(o$}4wdZacNGk-zz zw^>|_MCC1cQ->bn(Zico6Y{yfq;ZrpOt_oMMuFrPd5~Ocs9}+B5+9$%_k}Q%-I^Bp z{tz<LTnnu=S|{VMI7ub~W6E+Glx1!%$(VM10FeTdIVq#;FmP<Mb4l*TIT8k=iOGm) z&rc_jIO4{fbnLF@YXl;f#I@IGFgP<zPF)3^_J$D{;hv3L9bJ#b)o~*1TvF3~mWO&S z<#p%T`hX_vy+!-R*>3(sJC_mw4>bp(N)(t(L)<v0)4!sfOAg58el`CK+PMUD<7_gr z90j<!g!ND}{DpQdVd-)-PNcfG_>b@&|IBAqbE(0zUthV7GJ(8LS6@V!<Ni+r<j<$q gs)RwNxVeP+MJ(Uz^WA-|djI6<<KZ4ow=Zu#05ZGa=Kufz literal 474 zcmV<00VV!YNk&G}0RRA3MM6+kP&il$0000G0002h005Ez06|PpNOJ%H00DQPG?FAW zdf?A8ByjFOA_6FIKgv@)W(K3$ww0{k0IOgP7*oU}FxLPSummguXaJ4C%&p0t@+0~` z0gT&!7!#3b$A>Z&q8G-ZtRGJA0#e>U=8yWH`k(s$cjE)3yMW|75IuTfEQ)r(hy})} zKolwg09H^qAUFX401z1fodGJ=0FwYdZ8nxkBqJgrAWEQs4T))P;cWrOpaO?!&}yox zcpq}+xjNssS+9ooEQpARcSOd?!;E^+oXWl;oyI`hW0oSFrIhoN@Q-S5d{mOnX}XPw zjmsh;BW&MFj9=^^`6E!>+WsPrZX?Ay4OG>O*_k+EJkotE3AgfMN4D&n+q`zUHmD#u z;uk|Amstx)&=hCN1aR$9q=V0mx5J+-zh6)W01ir&8mZDOXV83Jf5!~C8zma8*3h{k zH&Pn~A(VB)Ztlj`V(b9^{mW@^G4b>L;6}|@%tM5ei0yc~i-8S(&&ViX3K+1=;;gfQ z(W^vAv7oSi`S)FuTYipA&~1#wzdoQqO+q~aU+kNNfFLx|Xb|jh4X=~R$QM@PBOG1t QFY5{{A{q<IbM!=x0KS#ek^lez diff --git a/vector/src/main/res/drawable-night-mdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-night-mdpi/bg_no_location_map.webp index 3c17c090d6b58edd123695e0ae55e9bd9a60792d..79900cec1b1dca1732e09434b1691d7548be2256 100644 GIT binary patch literal 640 zcmV-`0)PEdNk&F^0ssJ4MM6+kP&iC%0ssInT>uaecfz1;BuCDHdH%GXL+=z2wQVEk zHyYzl>u8*&sF9?onjc>9F9#(Tqezk>CA?9B2IQjwCHJ0c+qPjwr}B<z*H41DQ$d1q z3hp@~0uhNwDnx|JO+v5IZBa-N$xXXpe+e%>9K1WhsrBRXPZ={Z)BJYZ?}IE!woTib z_t>^=+xy*P-P`~6aGr^H-x2+H<hG3@$+P4-fZ7S>36I-huQtc)<GNR!+fg@%z4Bxi zx&d=?q?)gUy8l!I;Hw*|0dSVqssXTN7peg;Cr0|f67Cwxz~ikOssV5oH!AsCthqu| ziUov(JBC_*H^)vj0IuR%m|o0G9PHVd@Gwo;h=VyfdY2uRIR|wRZ;nmH1E5+gqZu?& z#VW0oi)G75H^+o7R$?eTEXZQLg**Q{vDyK$SVd{E`f+k|WTx81iVye7n?qi#DuY-X z<;~%g7R%@}G{;O@EI+6g%WwlZ=8&XVP52S5&G8-prZ@{SV)f0y7JGUsKpZ$@j`!J* z3G6$YSJoUU_#5j+*=ktX5HdekLy#`kl8YkzjcM__IpXBzNKIMTx)9>Xai>761|T3+ zvm}dU3U$ck<3kAGtD!~~i%J&|S}*PXpd=9lxA9s*4dZ4hMsEle!u4*IA&J!&<>6fC z9Re&Y2)R8eySkg59QNQW$zFGYylm_U$vr~`aWf+oKp5{?GQ*mKag9g!Tv-<!JhZUy zF6VVo^>7ftmId!FGCBb=L+rc0=g!aOU|ZuZi92SVvJzNe-vy!TI)7kV<F>Wbzvs7? af35`~y%*y8yq-@lrTqDKKJF)xTy75ye=k=6 literal 320 zcmV-G0l)rINk&FE0RRA3MM6+kP&il$0000G0001v003bC06|PpNJ#(y00BRsAOIOg zDJ=zNw?;$+-b2lUZDiX@zW-RswE&MD`tR$+5Yhh$*!n;?ykZWFgQ*~bO%y6n%HMw< zLL!AO6n=&n%(f!bw*UZEP&gpS0000m4*;D3DvtnS06uLtmPn){A|W7)xPT3bX#f`5 z03L@p(OX6jS5M-rI+DWd#C^(KeK7ti0^PH)No>b&JLV9X{ndr_TD&3!J@^{0CW>YR z=^ASM)j~SxRUtTMCMq^@0t>_#E>&|H>)&;!aLl1?4(-u0(=Yg5#ed}gN*SXJ0092~ zVhUpTiMi=;C=J*-$XDy|3=}-czUo=W&md^U@_65cD`-W$O4llG7n*f>u8UPPPzpc~ SY_ot9xV;7gqGuZL0002jrGMQ3 diff --git a/vector/src/main/res/drawable-night-xhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-night-xhdpi/bg_no_location_map.webp index b79c747ac141234d4ff079f871756c0bc1b8aa8e..14f7e0e44c5ee1c210deb28a128f57f0aee8a7d3 100644 GIT binary patch literal 1500 zcmV<21ta=WNk&H01pok7MM6+kP&iD;1pojqzCa)l|HC$tBqu!tL;k5@VZK1bz@}{^ zK~8rxjz7B7_dkx>NRlK+51rwUYrcCX+eng}^b3pp1JaRbFtIeKZQF(!oyt4ly!TIn z_)SDG9D#WQ5+VY52tf=mfkQCCP+o={K@2285b_azw8~4)aJ<A1=cOMw<e8%#M+Ch1 zQDCENy3syl{mb%m$$zp1>lj;bUgiDPkAJT3F^mnU4FIG#b(pYmZ;6)3<60adZAinz zuB4<5ZU?b;k%Z^&`rrNS&CFIlMf4xGB)M^$*|>-&<XjU55P!hXt7i}H_Vuir4_>?) zdiD5DU*Ed(<kia$51oHJf7I8z9^KMBH~RnYsNT{%&ZybeIso7)?_Fpe0RQ-|)H(o; zQM0FY07Pn~JJ2y-RG_pcwGKG^#=g`uUrL?c=xQAR*AHe}r+k|N3;TKpgnxQpYLai4 z$k)%c4uCVg)YBk}iq))v)&UV5x1`>%LGJA4nbxo+NUnFa4#03kTjm4XBmyN%Y7CoF z8%?Qo06e4huGRq%$=a!Ius;ZuEvXYw<TZAr)&X#*{^)2O0FhW~=>-(?RraL@z?E-q zmwn&@WVthq9`nw&A+Q6HS>k<#%Kbt>U<cp*%KQeh7K4DTK;ejYnod3G#tuB0CU5ig z8hP}<HEMD<pMjoQL~FdwH?S%pz*eYm+~=KoC53+j<++=0JtxqEZ2gS4`F6$w6wEi^ z{p?Zp1Gd6ttIt~9<ru#C#&&p{uT@W@o6no8OV+{JuHnZw-_i;1XOF#EA7Cq5X|WCl z-P#CxYS+XTZ}WA{B>oMQ+myUhZyDZC&8O{-g4`~;(SsOs^YuH`s1rT)_zv$Xre($) z*nw|sYrwn=%X{!sb2s141itx3sy*gqSUD!pUyx5TZ>zPI!hmi*W0$%4w(vU&Fk-fO z2hB<JO5T~^zU<JdMDfj6W^TUI8h%T}KfZs-JN2I#bn`h<zwvIX^_m8H5#vg7H{a<m z5rLk%#C$?@vJk*;j*nZ+0}r4}q#GT^m;wmMoick|X{UgQ0@Zz<Y<xJ$1VxS;+%f}{ zB=Q?QZftvimfUIcZ(gSX^c1QbUGZVxM-Xs?3Wt&l<3Lm{E)$}bAIQp`sxt^=MF3@m zY8Lr33Q8mkLG)#Z@WQbq&5}dyo8IhquM_AkPc{A`JUxkn66ueg>1?F%8=IMpbCyM- zFhB1t|KtU1#cDKLBE5{__uBo^nDl4-jh|@2lJsag)mZyVIb2J8amuKP-A|bZC4yh; z17Ef42{(F>AvWULbE_@vR|enGTP83eimtT@oimEB{J-s{ll#;7H&CcT#(al<H6|6> zYDs+a6`C}Hs^TTFr!wL)N8CEt_~``IFr;E{nLu+qwS>;1Q2V_!)R!Hm{lnPiL+@)O zK#7bU^Gj<XiX33Gu68=sZTiWZF*jST6wichjy`W>T})}HeY;|{Z=cQ&$Ne1XDb^_= zn;WG^<p@7^+-g*8H$ZDC>0-u|U3H3`sq0z6iG8mc1)N&qe0SXI;KD^&?WXYU;jO)u z0!%njoah^vwR3AJ=HftAv5MG2ek!e}1bKQPHL~bMXm9VD&p{hgu}J)#!t1ZyLVz;u zq+*>yW7$l$Y6(G>hEQXb&dSp2=rB!W8kG3HLuz$$%uA7Nw_28pq_+F%QGn(UDok$O zTb^8x2wLp>)F_hd{jVhiB@|DuMRn~h<0V5|@%&Me9RTc%dubpmYR{scEy~lc(`rn7 z1&Twcj3hn)tHtnHRXatK{s6L(_uwL9>O&~{L36_O8T5)or`czrE2|n7Se)k+hfsVL zv8<&q5}QJeMOOb0)<}CWxTd(n)MB>#xrM-iVn=Ubpu0DKmIbxbx6kcy>yiCMqF;@9 zes3QB{5Gv2)B!hczkK!d4y7W|;I?{r=kcqdS1%vlyKST1ynC;rx36!r()|a|Ukw2g C<oI&{ literal 1062 zcmV+>1ljviNk&E<1ONb6MM6+kP&il$0000G0000T0RY1Q06|PpNOk}K00Cb>AV8Va z0w#E1iiij$J!ZIYTido0T`cz&JSK8Fu#ghSXycba31m9f<Bevm7t#L-0H*JcMHK?* zeoXqXKU65K{c&j?WE3wl<~JGpPy8?b7ypa@#sA{}zw2Kz)J?{6l9BafSoWKWO~XJ% z0APL7u;&0)P&goh0{{R}B><fPDjosE06qx<fk1&i000n{mhZ>Oo?d;dU+OLQ>z;T1 z`{E9|TgUyz=nI3HIT>r)Mi+W6k+3%rW0;c1F%!!T8rQG#t+#5{2q^uWQTsTfwK%}V z_mpYIIxU=0`!)?hJ{GZ@^UWGgT<PBAaTV@8gr?-l3y$slYqIf5lm&o|BTY6Jn3Bga z93p_;Y^QpfM0TV%_whQg<LPL&aYy7W9j=9!Mp4?K9yt1b7mGVrxb~z72SK=Sxq*`@ zYZ{Q1=8b)`iWTeAQMV!{2SOdx)z{xMcwp0~1pZw<3c65O*g~|>;6>ye3c`Dg_6sgg zJ#EBSxODfy)@NrNtyXa9r91FeEuuwHIv!?OU#2_oW5%KTIG}kk>rXnygHDHXHC(K6 z+fDQKaYyMY=l}rz|Nn%9NP3m><{<Zz7vKMqVBy^Ce?3rt?Qj@vy6iWffVz8-8O;!# zV})6t*ivk%YQ!J|H+ohdHp~;Jr4Z*fVH&jp8k-m<Z+xI&nLakK*_w`Z*irlIeKAT- zd8^7xJqo~y2OiaEOqKv-^}GLsInk2A+2Z!{l)(u}ivz!XhPW+Glzu)ywGv;`wX_G= z?u>u^qE&1pvyTHfNeo#)-iN#@5@+;90H9E>s-MtH5Ywldp5+xrL>_-SWwONMZH!dS zXO?vf1Rf=Wd<@njEqOBtie-9$mNU+zd15}=jlHs-=JDFT`r37x$z{m6ZDFx`3Q_r7 zL|ftC$2(XyDt|ZnS2qJWqZ}iJ406MJR0bNdR`()%^`pUl+)~@3jwHR9TY6Qb8hI_| z9S)}r;Y@_7D(NKZbjC-kuw&Y0G;<OiYg({$6}qFw)z++HG1hStpRD4Tdr8-N?T$i? zc~#;<9@$%Zybq1`h|Eg3R?P$I_nWseKwRpoLDYfSFhQWjxJbx`!Q^k+1CRV;#p}p^ zsT4xrhkNvB?pc5<xPY`DwT!)ip}tabBIRCL-fR`!@A3=8Rb5jZ;EnJwsN$+Wz0rcv zXHp#2tDVNXnqF?<$`1p3SFHQRdQ;uu7O*P<k?xtB;hoc2@H$=F)(x+x8I|$w-+Zbg zvhlXjs1$5(c7V6sSx3SjH>>yYd}r32QCpNG%&~gnsZ0L_{_C(^t{31y<)u$x!w{!p gE5aGbl8zg3uU7XqnVDH^io=tQQy_isIrf+U0FloJP5=M^ diff --git a/vector/src/main/res/drawable-night-xxhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-night-xxhdpi/bg_no_location_map.webp index 75aa5cecc6c6feecc7168a8522d1b3f086630c60..91cb7c8eb62883a69f58015697ac9ad36d5177a1 100644 GIT binary patch literal 2022 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 E0QbN0p8x;= literal 1446 zcma)6`8U)H6#ovg)jVUe6v>P-*3qNPSYDQ4gvf5>*@YH#8jW>Ar6Fw?^ei(;N2FPv zB>UE}#3W0$ESa)IF$S5jW!}7cf57|U-h1x3=X~zxb3f-?S34UU>K*{VVKL60&PHC+ z002OQK?4EmAi&bW*_JQtK*`<_3GNHzOf68JL548fp?6e#O#Z5p^qB`Xr#XqI7!IJR zaDQtD05$$G_lJ&7Bd1Q>KQ+7a%LM`EPUVOxODnwHpkXyibHy!fZL8avsSiA(g{2oZ z9)-y;byW<M5@r`hqxsfxoX$K=vWG@@;>vLsKOS?ndFPmbCHVg(>Fct88lD&8B*#R_ zmT<PhiYEw5wsMy;?jdroyYDz;u6aWtL4<T#)II=mN4CwrE!6e{C(Qi)=MeLg4~%ai z0Y|iFX?tQtQ#%3>UIdmb1^~ZX02Gim9fSax9o%;d0~Z77hyj5}<b>WZtRr$ll*Z(R zY56Zwv%P1>V&y1>EL-H{;I3PXXBDT-l+28VPf!vVBehT+5#RA18NWrVS`&VdO4ms3 z;rT2M5$*z4!$|ZHQ41sW5Y8DkDyEDzYqgXDM^Z*p@r0icHucOLHnX_Acg`zCCVwO1 zJzd#Fa--rh)uR@!@wNvFK~Dxt8FI%<h2W))UeK?`O)l1VDe(#mUh^y65FOS{iC<I9 z@XABOqwY{PdeEx$$PQ-Jx1r2(`#*|B7aMzQ@^TOHgCu$^pJwvBZDjqr&itaQsI*P5 z9;NBIUF#Z7C2p~CUwI_^MERd9jRxF&Bvlgrp7J0u?o0Wu$B`z50u=_02lp@@oxL6l zW5p(zW+_<Kx2E0l|AYOfkR^tVU2?7Y)VM$j0UsJ>L8g`rj@>q`peLDigqdJ<No)S# zT!@*}L~shZpu9b_4$0UKcT{$&;x_-#Z(oS=<MlpOov)qo8;geVfydMX1ke4&PT)EM zr>?&If#_0Ct#zr&r%ls}y)o$viF!J)<Et^&rkR2g(&&|<g2IrOEt^adOGE!PcAh_a zq4G_Pn5&zETTqp3=)Wc`ea~DcpM+Q2V%b<L5t<QQXiMX0svi{qb_FY)Q@gAKCSi-u zi|AKrFYWQ%-~QmrUI9jG*;szlLGm}bOD5chlR9=_`_{M^VqP#_Ixul<`N=e37?3+R zYdzcL^zpQ4Qi57hi@fz4L7&N7xBa@q6h8yRrVtiAVs<Y-c5yI_ZUU=9scMLW_;Hlu z)nSQ}c?>x+)q`J;vshPeIp?TRcQR1YOlqJo$Rex#aX<ifwxGk`<2amm*Zs}bWL{D} znsWcOSoE%|)=YOS(3&j>z!RwIhoKm;p&E4OKwXPSoN0ML#j537mCrNWxw-fh)K1ro zvu_-AVermJc{7C@-O&~zmz1!s)HZR6!$#I8ygV~ERzy}C7$00a=5}A<M$9!7<5%@R zw~|@$bEtm1-e52wCoO88l#|@^j92ZbFm&zJ-1lmJRe1CEVBe9B40LwZmnU1U1e|>; zt^I5`a$TIdciCBPU`I7FHd?H1<R}_=SNdXRyzjIOH}c09<yhj|8nqqG!ho@CS(!YX zx<E-kOgDA&hNVOk_1>$fv1LYbAAdSl`kd|_?cIt@T5@+8-k?7MNXmi0WX=yETXTK% zz~B?Ip#%2X2H@agy>uVtoI?(HnHofsxYu{VOK0h+<)S5pX8Guw!ShhB%9+g;*?jKw zN_u@O6=Arq^8UvDLV~!B;@NM;@qzQ=NmuE(!P8^59lhK6M3Zlfw0r|~TsvB<TdSIs zyFX~?9YPT<;aE8aV`!2&vH0u#ITzKhp`d01W|N#T0WQDuqU!Sp5e1AFXY@;?nv{Or tz-~88Bf{Nb#4=ppVE=7$Z<)xW(o70Z8#P2EDxG?M{^_s|JwW(>{{eA<+CBgP diff --git a/vector/src/main/res/drawable-night-xxxhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-night-xxxhdpi/bg_no_location_map.webp index 511d237d4bf8a6637a90a936da6bb88879048023..e4864a9eb29bc936ac13ac6305ad8ec4df5cd01c 100644 GIT binary patch literal 4010 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 JVva0&{U7bm;Y0uc literal 1690 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 U5Au9g2c8cqzMloAQ_ukL4>k=#xBvhE diff --git a/vector/src/main/res/drawable-xhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-xhdpi/bg_no_location_map.webp index e908191371edda5adbee97f864fdea5d0f7eb3a8..513089b55bb6775948489034653c83e722d9f9b5 100644 GIT binary patch literal 1320 zcmV+@1=spgNk&E>1pok7MM6+kP&iB!1pojqzCa)l&%!8@Bq`amoc;}=isy2pfl=E? zl3cYHe_D%YpDPu^wrwNPX?y$6`<hWC*-n~lA^SIg0<H*wbIzbPZ3AXZ&mFLT_ir39 zr~}3U%6Rw#Kfd>e=hO!g289VHUhH^G-;wl9g(UFz@6xHiAT#hf_$zbz=}R^|X)#s@ z%?*u&2tx@3iNMe(9D%5iMq(7!_i8qP+O};^+r|$B6o?54sI@RLsH~Uly(gK}+eUc& z|9_JG@QjX65&efPNp9R`HZI}`IU#~e#2>JB{_g4RTRQ8`<2UDPXV325(pz_*pS}J1 z(){D)gIl`m;T`3<ee3@ned&}3+_==8uL?7xJ1_=mt2RGq)|a{i;BA-JRR_QhbE7%{ zPLLU$`DyB;)*Z0CjZ#y7Ph*_jP#pj#%#4yCG$clM!1lK*1wm>yU*1$505|N6ZcHIR zHM#?%|E1QB2~y9OYB6096-IRc>>xF&127IrqccB0OchEoY4MdtbpXaNGm1bI_-UgB zbn^RBEvDQ;TLqxic#z+ejsw(z_&&1hXVihczER5#s!eYMVy))o4begKaj>WZHxhn5 zVV0pwUVO({BRXiih&-@^h480G-FxJzy}l40Bms~YUo5@&vP1eEDA<yn`g#`G<iX*l z@Zu|{E<TOS@jCIyi*NNuDZTjC>5DI18sWkINFQw75MF%q6_6L-q7vRm_T&M0z3{-K z0e$KN@x>PcwVW^$ej5BMrtc&D&%#p|J%gZcgcslDKwo@2o$%sI*XfJzK>GDW8F45+ z;f_MLLmv#%PlN5>K6xwe&Wr1^8{yABP-}J|{d%I?cYs>MHQ|rHJf?qsuYVR^e5R)_ zKBq5k$}YatqqjsN>DLqaA${>3X2OfFi|C6lNUsZb6t;bf+)?PoTOtfjgY!yw;L@PT z^lfGV13AGbv4IJIiTp(=bRB<&#!rv-0658x<W1j}Bk+<V?&%Q$Kl!3Y(5n@2lrPBL z-v*{~Vj;d@vjVnqFL$i~5@4-?|HzUVa0I(EdQT^<c$4BXkJ&A=xI}M>{J9j{rwr|P zr@<roV4ttCnOiv0AG?`7`qLoW>Dv&wY1BXIXO^)}(;A(F`%aTzO>5BGY8SUlS$&FI z{e~|%k{90rvk~;l!O{O#OCMgKcNDUg5k2x(RQ~47Rpc@lJY>^H)*g<?{B~czO<#Pg zMZu&`!DV)G+_VOL@okq3QX3yogvc-X<7M)|zM9eZDS#u#o8t%c<0icOlwgV{Yq3q; z<CFUu$gMJ+$`|xTv9$Y0yK%B@&3p_jp%f#I&y$+tp(BzAdFxkA+_A;3JPH@bHpSqf zEsTDuCUMHt$smemh9f2$SsLY#AUCp(YzNCCM+nBpam5gh510MQnhwUkhLq`B0euQx z>R_VOTzw5I<uQHhaOMVb^?CS-Z4s~d(z&>G!RMNE!)MC%2zi1u%%*QSQaU(xn0cQc z_icV`V|@*F|4oJIBboXd*I7>A&REgoUQWk#c-0@-V32Fb_v-6fU@;*@+9nz28;m0Q zWoq_)3diD$U3#(+w{GztI$8KOEp15UYw+4aNm<%B5-Ed+2M!$(a+Nayk<Z7kuxk>b z7xs_AHp_buQ31tqrb4c<es2K1!1@~E-oE`Ivp(gjk;ao(cX|05xwyA(e{=TYE+<pz eSlsK`+1mM=NB8eesP`wQD1EpqO81_=K3@acV3tJy literal 1228 zcmV;-1T*_mNk&G*1ONb6MM6+kP&il$0000G0000U0RY1Q06|PpNQ3|Y00D4YZQFC) zmC|`@d5j$P`~Tmo%uHg0OzY_A7$FbvRNjb~0D%7R^3>yYS>vbqQ<ZLj?Vh^a8u(M0 zPVK49?noTTRvhc6I3MA^@L%{Z{1^TU|Aqg;f8oFIU-&Ql_pjIeB#u=gjzL`<1zZ{f zu&pq5dB6s;@EkuYlkH88*Ylta09H^qAQ}V!0PrLLodGHz0mJ}42?BvYfdK#j5SEti z7i$h~=Y7OGLM?nqVqUKPKhLM;yaCryfHsKsZ!gHMcUXV@;bH)KAS|GPfF}J}X<zy~ zk~htGD6I&HBLCHvmH(pPA6#y1`XwnE*Pw@wnDcIAW|EV8KqQnV&OF>O$4pKKXWS|% zJlgCQTOJxcZR~_`urFmQLM2*Kzx8F2KCakTRXlS*TAAcRWsB|=6dr0RksGxo4tnoj zl8xUH%lg(b(!bk-O7EBP80~V;K}L1%;n<`i;}`w1(!cXQ-XX}m(V_-&n!)km{dvM$ z+zr5i`yPMlWI3^Dk~!qkvN!^!u(el+fT)hsxI+OHpN}B5%mEFvG=gc)-zF6TC(y3_ zlu&uMDy<FK1yvB4buD^NBQwD)0092~|I?ER{J&h_sP#aUKS#&?N^e&j?Y%o-8h)`( z2DnqtT!Og%c{2-Ge=7{6S6N>guwLsJGz(F&nEFE+pWs)hX#fykYr9_h!wCatz}%50 zqAS)<CX1Rw(@@v>UMOJ%fc$+n#LQ`@n*^8mlBzi5DOU{`^eylJltAkF$E9=hXaaL< zBgjy)QE1+MWPtj}O2+(@Mk-woI~-J|mw$-`^~pK}G;dD~ooYi4_f&C#*<Oqw0O#kF zj3Txp43qj=8DZsm*A)`Ps90{cTK)f;Uj{=ES<)eUI+!l3#=+eQ@ylfju1Fk$sLgHY zSO9>Xo|0b*8l7I!d8JVzr@?0^PRAf7Szl)6Rtv}2#0!M`N_4g@7j#S0?$YX9?^)AJ zx&6X9fB;1o_RAQ1wHzV<E=6k_I=Ggb&ic|kL5cN>)J&ty_Ic#wXaXt&hnJjGB&9>l z`l}Z=C~LLY-azwjs%Pct{-kt5R?pz5Qn*NZ9K598$3>svZ^Q3HyI6d_fJj@b`B9&b znq8<<nMe{-vm9($?v}sHtqhteTO{V)Q?Iixy>j;jraXP9+34GY@|#jUqZo7OM(D*) z?oLTmctLRt=(8VybR_~ko1K`XP$SJOra6wm-~61cE=z!isED0RgCD8kK7PFSMA6_> z(1;L$?WH^1Uf@D6Bm9qP7To|={IGkbtjq7&h%Z3eEq3Aw@}E<<o~J%VT!Z{rR80z5 zqU-p1d*1+T!<6?rwgT`#ogL_f2*OZL-e2vsE)P&&m&NzwGO;}^o*@-$Ggu>7TL}Un zJci)YetXH|5ncBB?hcw1o*4XMB3iWOY=;+p9WYdpi}v535oA!Z8MCpB8E7$E?tuLx zlnQr56I7^dFxqLne6me{-t(o?hLCRzq=^A{$Tp~`p|Fs~$8H0g{l-G;M<wGC)-?tU q2GDL`A-tI6lgz>;{ar6&w9Kf4q$8ewess4!EuIZ^*AI3mL7)J)H&gBa diff --git a/vector/src/main/res/drawable-xxhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-xxhdpi/bg_no_location_map.webp index e062178367e8a1a5733d9a9f6c1bea55474df305..50284965a78ab3ded0bd60f0b5621258acdaed59 100644 GIT binary patch literal 1822 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 Ms5=jyrc$XC0DNU^N&o-= literal 1846 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 ZF}LH;mJ6rzWP)uVYFNp=Vl>JO_zNz(f_VS{ diff --git a/vector/src/main/res/drawable-xxxhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-xxxhdpi/bg_no_location_map.webp index 8b110d33fe17e3323d75c6dda9eb29ee4f0bc8e3..881af0055a73c371b926c46db9540cdf39c1c1c8 100644 GIT binary patch literal 3522 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 literal 2706 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 F_%|y?E0h2L From 4a2310954acf5fa36e429f76c896b1210d48ce0a Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 16 May 2022 10:11:14 +0200 Subject: [PATCH 38/45] Removing non necessary LiveLocationAggregationProcessor interface --- .../sdk/internal/session/SessionModule.kt | 5 - ...DefaultLiveLocationAggregationProcessor.kt | 99 ------------------- .../LiveLocationAggregationProcessor.kt | 78 +++++++++++++-- 3 files changed, 68 insertions(+), 114 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index 0aae9f3105..dab6061042 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -88,8 +88,6 @@ import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationMan import org.matrix.android.sdk.internal.session.openid.DefaultOpenIdService import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService import org.matrix.android.sdk.internal.session.room.EventRelationsAggregationProcessor -import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.DefaultLiveLocationAggregationProcessor -import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.LiveLocationAggregationProcessor import org.matrix.android.sdk.internal.session.room.create.RoomCreateEventProcessor import org.matrix.android.sdk.internal.session.room.prune.RedactionEventProcessor import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor @@ -392,7 +390,4 @@ internal abstract class SessionModule { @Binds abstract fun bindEventSenderProcessor(processor: EventSenderProcessorCoroutine): EventSenderProcessor - - @Binds - abstract fun bindLiveLocationAggregationProcessor(processor: DefaultLiveLocationAggregationProcessor): LiveLocationAggregationProcessor } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt deleted file mode 100644 index 3ac47ee8cf..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2022 The Matrix.org Foundation C.I.C. - * - * 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 org.matrix.android.sdk.internal.session.room.aggregation.livelocation - -import io.realm.Realm -import org.matrix.android.sdk.api.extensions.orTrue -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.toContent -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent -import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent -import org.matrix.android.sdk.internal.database.mapper.ContentMapper -import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity -import org.matrix.android.sdk.internal.database.query.getOrCreate -import timber.log.Timber -import javax.inject.Inject - -internal class DefaultLiveLocationAggregationProcessor @Inject constructor() : LiveLocationAggregationProcessor { - - override fun handleBeaconInfo(realm: Realm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean) { - if (event.senderId.isNullOrEmpty() || isLocalEcho) { - return - } - - val targetEventId = if (content.isLive.orTrue()) { - event.eventId - } else { - // when live is set to false, we use the id of the event that should have been replaced - event.unsignedData?.replacesState - } - - if (targetEventId.isNullOrEmpty()) { - Timber.w("no target event id found for the beacon content") - return - } - - val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate( - realm = realm, - roomId = roomId, - eventId = targetEventId - ) - - Timber.d("updating summary of id=$targetEventId with isLive=${content.isLive}") - - aggregatedSummary.endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) } - aggregatedSummary.isActive = content.isLive - } - - override fun handleBeaconLocationData( - realm: Realm, - event: Event, - content: MessageBeaconLocationDataContent, - roomId: String, - relatedEventId: String?, - isLocalEcho: Boolean - ) { - if (event.senderId.isNullOrEmpty() || isLocalEcho) { - return - } - - if (relatedEventId.isNullOrEmpty()) { - Timber.w("no related event id found for the live location content") - return - } - - val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate( - realm = realm, - roomId = roomId, - eventId = relatedEventId - ) - val updatedLocationTimestamp = content.getBestTimestampMillis() ?: 0 - val currentLocationTimestamp = ContentMapper - .map(aggregatedSummary.lastLocationContent) - .toModel<MessageBeaconLocationDataContent>() - ?.getBestTimestampMillis() - ?: 0 - - if (updatedLocationTimestamp.isMoreRecentThan(currentLocationTimestamp)) { - Timber.d("updating last location of the summary of id=$relatedEventId") - aggregatedSummary.lastLocationContent = ContentMapper.map(content.toContent()) - } - } - - private fun Long.isMoreRecentThan(timestamp: Long) = this > timestamp -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt index d2450aef9c..76b7a4ec8e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt @@ -17,18 +17,48 @@ package org.matrix.android.sdk.internal.session.room.aggregation.livelocation import io.realm.Realm +import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent +import org.matrix.android.sdk.internal.database.mapper.ContentMapper +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.query.getOrCreate +import timber.log.Timber +import javax.inject.Inject -internal interface LiveLocationAggregationProcessor { - fun handleBeaconInfo( - realm: Realm, - event: Event, - content: MessageBeaconInfoContent, - roomId: String, - isLocalEcho: Boolean, - ) +internal class LiveLocationAggregationProcessor @Inject constructor() { + + fun handleBeaconInfo(realm: Realm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean) { + if (event.senderId.isNullOrEmpty() || isLocalEcho) { + return + } + + val targetEventId = if (content.isLive.orTrue()) { + event.eventId + } else { + // when live is set to false, we use the id of the event that should have been replaced + event.unsignedData?.replacesState + } + + if (targetEventId.isNullOrEmpty()) { + Timber.w("no target event id found for the beacon content") + return + } + + val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate( + realm = realm, + roomId = roomId, + eventId = targetEventId + ) + + Timber.d("updating summary of id=$targetEventId with isLive=${content.isLive}") + + aggregatedSummary.endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) } + aggregatedSummary.isActive = content.isLive + } fun handleBeaconLocationData( realm: Realm, @@ -36,6 +66,34 @@ internal interface LiveLocationAggregationProcessor { content: MessageBeaconLocationDataContent, roomId: String, relatedEventId: String?, - isLocalEcho: Boolean, - ) + isLocalEcho: Boolean + ) { + if (event.senderId.isNullOrEmpty() || isLocalEcho) { + return + } + + if (relatedEventId.isNullOrEmpty()) { + Timber.w("no related event id found for the live location content") + return + } + + val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate( + realm = realm, + roomId = roomId, + eventId = relatedEventId + ) + val updatedLocationTimestamp = content.getBestTimestampMillis() ?: 0 + val currentLocationTimestamp = ContentMapper + .map(aggregatedSummary.lastLocationContent) + .toModel<MessageBeaconLocationDataContent>() + ?.getBestTimestampMillis() + ?: 0 + + if (updatedLocationTimestamp.isMoreRecentThan(currentLocationTimestamp)) { + Timber.d("updating last location of the summary of id=$relatedEventId") + aggregatedSummary.lastLocationContent = ContentMapper.map(content.toContent()) + } + } + + private fun Long.isMoreRecentThan(timestamp: Long) = this > timestamp } From 2aeee79c63bdc78421254e8db630ccfbb1fcb873 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 16 May 2022 10:14:39 +0200 Subject: [PATCH 39/45] Using existing common When case for NoticeItem to handle location data --- .../home/room/detail/timeline/factory/TimelineItemFactory.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 9b6026031e..07ae9d66c3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -113,13 +113,13 @@ class TimelineItemFactory @Inject constructor( EventType.CALL_NEGOTIATE, EventType.REACTION, in EventType.POLL_RESPONSE, - in EventType.POLL_END -> noticeItemFactory.create(params) + in EventType.POLL_END, + in EventType.BEACON_LOCATION_DATA -> noticeItemFactory.create(params) // Calls EventType.CALL_INVITE, EventType.CALL_HANGUP, EventType.CALL_REJECT, EventType.CALL_ANSWER -> callItemFactory.create(params) - in EventType.BEACON_LOCATION_DATA -> noticeItemFactory.create(params) // Crypto EventType.ENCRYPTED -> { if (event.root.isRedacted()) { From b0773514eedfb002bc29bcdb9440e749fb507101 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 16 May 2022 10:32:34 +0200 Subject: [PATCH 40/45] Extracting LocationLiveMessageBannerViewState into separated file --- .../live/LocationLiveMessagBannerViewState.kt | 36 +++++++++++++++++++ .../live/LocationLiveMessageBannerView.kt | 19 ---------- 2 files changed, 36 insertions(+), 19 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessagBannerViewState.kt diff --git a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessagBannerViewState.kt b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessagBannerViewState.kt new file mode 100644 index 0000000000..976085386b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessagBannerViewState.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.location.live + +sealed class LocationLiveMessageBannerViewState( + open val bottomStartCornerRadiusInDp: Float, + open val bottomEndCornerRadiusInDp: Float, +) { + + data class Emitter( + override val bottomStartCornerRadiusInDp: Float, + override val bottomEndCornerRadiusInDp: Float, + val remainingTimeInMillis: Long, + val isStopButtonCenteredVertically: Boolean + ) : LocationLiveMessageBannerViewState(bottomStartCornerRadiusInDp, bottomEndCornerRadiusInDp) + + data class Watcher( + override val bottomStartCornerRadiusInDp: Float, + override val bottomEndCornerRadiusInDp: Float, + val formattedLocalTimeOfEndOfLive: String, + ) : LocationLiveMessageBannerViewState(bottomStartCornerRadiusInDp, bottomEndCornerRadiusInDp) +} diff --git a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt index bb69cdd1c2..8cb552e3c4 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt @@ -35,25 +35,6 @@ import im.vector.app.databinding.ViewLocationLiveMessageBannerBinding import im.vector.app.features.themes.ThemeUtils import org.threeten.bp.Duration -sealed class LocationLiveMessageBannerViewState( - open val bottomStartCornerRadiusInDp: Float, - open val bottomEndCornerRadiusInDp: Float, -) { - - data class Emitter( - override val bottomStartCornerRadiusInDp: Float, - override val bottomEndCornerRadiusInDp: Float, - val remainingTimeInMillis: Long, - val isStopButtonCenteredVertically: Boolean - ) : LocationLiveMessageBannerViewState(bottomStartCornerRadiusInDp, bottomEndCornerRadiusInDp) - - data class Watcher( - override val bottomStartCornerRadiusInDp: Float, - override val bottomEndCornerRadiusInDp: Float, - val formattedLocalTimeOfEndOfLive: String, - ) : LocationLiveMessageBannerViewState(bottomStartCornerRadiusInDp, bottomEndCornerRadiusInDp) -} - private const val REMAINING_TIME_COUNTER_INTERVAL_IN_MS = 1000L class LocationLiveMessageBannerView @JvmOverloads constructor( From 3d136112b23ddad07ff0bc9165b852e76b677994 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 16 May 2022 10:39:46 +0200 Subject: [PATCH 41/45] Fix filename --- ...agBannerViewState.kt => LocationLiveMessageBannerViewState.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename vector/src/main/java/im/vector/app/features/location/live/{LocationLiveMessagBannerViewState.kt => LocationLiveMessageBannerViewState.kt} (100%) diff --git a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessagBannerViewState.kt b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerViewState.kt similarity index 100% rename from vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessagBannerViewState.kt rename to vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerViewState.kt From 1d6083b2c7d19ff2c2da739a844fcc926358dda9 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 16 May 2022 18:07:08 +0200 Subject: [PATCH 42/45] Fix missing dot at end of code documentation --- .../main/java/im/vector/app/features/location/LocationData.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationData.kt b/vector/src/main/java/im/vector/app/features/location/LocationData.kt index 2393edb249..b3466ff871 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationData.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationData.kt @@ -38,7 +38,7 @@ fun MessageLocationContent.toLocationData(): LocationData? { } /** - * Creates location data from a geoUri String + * Creates location data from a geoUri String. * "geo:40.05,29.24;30" -> LocationData(40.05, 29.24, 30) * @return location data or null if geo uri is null or not valid */ From cbf8306c6fe90907078278689b518230cfead989 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Tue, 17 May 2022 15:09:27 +0200 Subject: [PATCH 43/45] Removing live location share data from MessageInformationData structure --- .../LiveLocationShareMessageItemFactory.kt | 21 +++++++++++++++++-- .../timeline/factory/MessageItemFactory.kt | 7 +------ .../helper/MessageInformationDataFactory.kt | 12 ----------- .../timeline/item/MessageInformationData.kt | 8 ------- 4 files changed, 20 insertions(+), 28 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt index f993676958..479a742369 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt @@ -24,7 +24,6 @@ import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvide import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem -import im.vector.app.features.home.room.detail.timeline.item.LiveLocationShareSummaryData import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationInactiveItem import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationInactiveItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationItem @@ -36,6 +35,7 @@ import im.vector.app.features.location.UrlMapProvider import im.vector.app.features.location.toLocationData import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.threeten.bp.LocalDateTime import timber.log.Timber import javax.inject.Inject @@ -51,10 +51,11 @@ class LiveLocationShareMessageItemFactory @Inject constructor( ) { fun create( - liveLocationShareSummaryData: LiveLocationShareSummaryData?, + event: TimelineEvent, highlight: Boolean, attributes: AbsMessageItem.Attributes, ): VectorEpoxyModel<*>? { + val liveLocationShareSummaryData = getLiveLocationShareSummaryData(event) val item = when (val currentState = getViewState(liveLocationShareSummaryData)) { LiveLocationShareViewState.Inactive -> buildInactiveItem(highlight, attributes) LiveLocationShareViewState.Loading -> buildLoadingItem(highlight, attributes) @@ -150,6 +151,22 @@ class LiveLocationShareMessageItemFactory @Inject constructor( return liveLocationShareSummaryData.endOfLiveTimestampMillis?.let { DateProvider.toLocalDateTime(timestamp = it) } } + private fun getLiveLocationShareSummaryData(event: TimelineEvent): LiveLocationShareSummaryData? { + return event.annotations?.liveLocationShareAggregatedSummary?.let { summary -> + LiveLocationShareSummaryData( + isActive = summary.isActive, + endOfLiveTimestampMillis = summary.endOfLiveTimestampMillis, + lastGeoUri = summary.lastLocationDataContent?.getBestLocationInfo()?.geoUri + ) + } + } + + private data class LiveLocationShareSummaryData( + val isActive: Boolean?, + val endOfLiveTimestampMillis: Long?, + val lastGeoUri: String?, + ) + private sealed class LiveLocationShareViewState { object Loading : LiveLocationShareViewState() data class Running(val lastGeoUri: String, val endOfLiveDateTime: LocalDateTime?) : LiveLocationShareViewState() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index ea703a0d11..13f783cded 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -216,12 +216,7 @@ class MessageItemFactory @Inject constructor( buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes) } } - is MessageBeaconInfoContent -> - liveLocationShareMessageItemFactory.create( - informationData.liveLocationShareSummaryData, - highlight, - attributes - ) + is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(params.event, highlight, attributes) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } return messageItem?.apply { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index e06a983ab3..7874f843e1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -21,7 +21,6 @@ import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.extensions.localDateTime import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration -import im.vector.app.features.home.room.detail.timeline.item.LiveLocationShareSummaryData import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData import im.vector.app.features.home.room.detail.timeline.item.PollResponseData import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData @@ -120,7 +119,6 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses isLastFromThisSender = isLastFromThisSender, e2eDecoration = e2eDecoration, sendStateDecoration = sendStateDecoration, - liveLocationShareSummaryData = getLiveLocationShareSummaryData(event) ) } @@ -189,16 +187,6 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses } } - private fun getLiveLocationShareSummaryData(event: TimelineEvent): LiveLocationShareSummaryData? { - return event.annotations?.liveLocationShareAggregatedSummary?.let { summary -> - LiveLocationShareSummaryData( - isActive = summary.isActive, - endOfLiveTimestampMillis = summary.endOfLiveTimestampMillis, - lastGeoUri = summary.lastLocationDataContent?.getBestLocationInfo()?.geoUri - ) - } - } - /** * Tiles type message never show the sender information (like verification request), so we should repeat it for next message * even if same sender. diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt index be0f2d55ca..258424c7de 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt @@ -43,7 +43,6 @@ data class MessageInformationData( val sendStateDecoration: SendStateDecoration = SendStateDecoration.NONE, val isFirstFromThisSender: Boolean = false, val isLastFromThisSender: Boolean = false, - val liveLocationShareSummaryData: LiveLocationShareSummaryData? = null, ) : Parcelable { val matrixItem: MatrixItem @@ -99,13 +98,6 @@ data class PollVoteSummaryData( val percentage: Double = 0.0 ) : Parcelable -@Parcelize -data class LiveLocationShareSummaryData( - val isActive: Boolean?, - val endOfLiveTimestampMillis: Long?, - val lastGeoUri: String?, -) : Parcelable - enum class E2EDecoration { NONE, WARN_IN_CLEAR, From e9d93194f15f58c3ce10e132b6fc244de4c8f5bb Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Wed, 18 May 2022 09:48:34 +0200 Subject: [PATCH 44/45] Adding comments on some strings and removing non necessary plural --- .../main/java/im/vector/app/core/utils/TextUtils.kt | 2 +- .../detail/timeline/item/MessageLiveLocationItem.kt | 1 - vector/src/main/res/values/strings.xml | 11 ++++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt b/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt index 80dabd5021..d2f8c4022b 100644 --- a/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt +++ b/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt @@ -118,7 +118,7 @@ object TextUtils { private fun appendHours(context: Context, builder: StringBuilder, hours: Int) { builder.append(hours) - builder.append(context.resources.getQuantityString(R.plurals.time_unit_hour_short, hours)) + builder.append(context.resources.getString(R.string.time_unit_hour_short)) } private fun appendMinutes(context: Context, builder: StringBuilder, minutes: Int) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt index daca48e7aa..838fbd46de 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt @@ -57,7 +57,6 @@ abstract class MessageLiveLocationItem : AbsMessageLocationItem<MessageLiveLocat holder.locationLiveMessageBanner.stopButton.setOnClickListener { attributes.callback?.onTimelineItemAction(RoomDetailAction.StopLiveLocationSharing) } - // TODO adjust Copyright map placement if needed } private fun buildViewState( diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 93955e54b6..1851e33d7f 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -322,12 +322,11 @@ <string name="start_chatting">Start Chatting</string> <string name="spaces">Spaces</string> - <!-- Time units --> - <plurals name="time_unit_hour_short"> - <item quantity="one">hour</item> - <item quantity="other">hours</item> - </plurals> + <!-- Time unit for hour: if a short version exists, it should be used --> + <string name="time_unit_hour_short">h</string> + <!-- Time unit for minute: if a short version exists, it should be used --> <string name="time_unit_minute_short">min</string> + <!-- Time unit for second: if a short version exists, it should be used --> <string name="time_unit_second_short">sec</string> <!-- Permissions denied forever --> @@ -3024,8 +3023,10 @@ <string name="location_share_live_started">Loading live location…</string> <string name="location_share_live_ended">Live location ended</string> <string name="location_share_live_view">View live location</string> + <!-- Examples of usage: Live until 5:42 PM/Live until 17:42--> <string name="location_share_live_until">Live until %1$s</string> <string name="location_share_live_stop">Stop</string> + <!-- Examples of usage: 6h 15min 30sec left/15min 30sec left/30 sec left--> <string name="location_share_live_remaining_time">%1$s left</string> <string name="live_location_sharing_notification_title">${app_name} Live Location</string> <string name="live_location_sharing_notification_description">Location sharing is in progress</string> From 4ebd1ea138af6e0208b46439c3c873596aa2ba16 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Wed, 18 May 2022 13:58:42 +0200 Subject: [PATCH 45/45] Fix parsing of location data in non encrypted room --- .../sdk/api/session/events/model/Event.kt | 11 ++++---- .../EventRelationsAggregationProcessor.kt | 27 ++++++++++++------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index 16bdbd3432..7124d8a1a3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent @@ -375,11 +376,11 @@ fun Event.getRelationContent(): RelationDefaultContent? { content.toModel<EncryptedEventContent>()?.relatesTo } else { content.toModel<MessageContent>()?.relatesTo ?: run { - // Special case to handle stickers, while there is only a local msgtype for stickers - if (getClearType() == EventType.STICKER) { - getClearContent().toModel<MessageStickerContent>()?.relatesTo - } else { - null + // Special cases when there is only a local msgtype for some event types + when (getClearType()) { + EventType.STICKER -> getClearContent().toModel<MessageStickerContent>()?.relatesTo + in EventType.BEACON_LOCATION_DATA -> getClearContent().toModel<MessageBeaconLocationDataContent>()?.relatesTo + else -> null } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index 6cb76e9fbb..af9c0071fe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -193,16 +193,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } in EventType.BEACON_LOCATION_DATA -> { - event.getClearContent().toModel<MessageBeaconLocationDataContent>(catchError = true)?.let { - liveLocationAggregationProcessor.handleBeaconLocationData( - realm, - event, - it, - roomId, - event.getRelationContent()?.eventId, - isLocalEcho - ) - } + handleBeaconLocationData(event, realm, roomId, isLocalEcho) } } } else if (encryptedEventContent?.relatesTo?.type == RelationType.ANNOTATION) { @@ -267,6 +258,9 @@ internal class EventRelationsAggregationProcessor @Inject constructor( liveLocationAggregationProcessor.handleBeaconInfo(realm, event, it, roomId, isLocalEcho) } } + in EventType.BEACON_LOCATION_DATA -> { + handleBeaconLocationData(event, realm, roomId, isLocalEcho) + } else -> Timber.v("UnHandled event ${event.eventId}") } } catch (t: Throwable) { @@ -763,4 +757,17 @@ internal class EventRelationsAggregationProcessor @Inject constructor( verifSummary.sourceEvents.add(event.eventId) } } + + private fun handleBeaconLocationData(event: Event, realm: Realm, roomId: String, isLocalEcho: Boolean) { + event.getClearContent().toModel<MessageBeaconLocationDataContent>(catchError = true)?.let { + liveLocationAggregationProcessor.handleBeaconLocationData( + realm, + event, + it, + roomId, + event.getRelationContent()?.eventId, + isLocalEcho + ) + } + } }