Merge pull request #8749 from element-hq/feature/bma/locationAvatar

Location avatar
This commit is contained in:
Benoit Marty 2024-02-06 16:02:02 +01:00 committed by GitHub
commit e0c51bd168
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 121 additions and 68 deletions

1
changelog.d/8749.bugfix Normal file
View file

@ -0,0 +1 @@
Fix issues about location Event avatar rendering.

View file

@ -19,6 +19,7 @@ package org.matrix.android.sdk.api.session
import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.session.user.model.User
import timber.log.Timber
/** /**
* Get a room using the RoomService of a Session. * Get a room using the RoomService of a Session.
@ -41,4 +42,5 @@ fun Session.getUser(userId: String): User? = userService().getUser(userId)
/** /**
* Similar to [getUser], but fallback to a User without details if the User is not known by the SDK, or if Session is null. * Similar to [getUser], but fallback to a User without details if the User is not known by the SDK, or if Session is null.
*/ */
fun Session?.getUserOrDefault(userId: String): User = this?.userService()?.getUser(userId) ?: User(userId) fun Session?.getUserOrDefault(userId: String): User = this?.userService()?.getUser(userId)
?: User(userId).also { Timber.w("User $userId not found in local cache, fallback to default") }

View file

@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocati
* Aggregation info concerning a live location share. * Aggregation info concerning a live location share.
*/ */
data class LiveLocationShareAggregatedSummary( data class LiveLocationShareAggregatedSummary(
val roomId: String?,
val userId: String?, val userId: String?,
/** /**
* Indicate whether the live is currently running. * Indicate whether the live is currently running.

View file

@ -28,6 +28,7 @@ internal class LiveLocationShareAggregatedSummaryMapper @Inject constructor() :
override fun map(entity: LiveLocationShareAggregatedSummaryEntity): LiveLocationShareAggregatedSummary { override fun map(entity: LiveLocationShareAggregatedSummaryEntity): LiveLocationShareAggregatedSummary {
return LiveLocationShareAggregatedSummary( return LiveLocationShareAggregatedSummary(
roomId = entity.roomId,
userId = entity.userId, userId = entity.userId,
isActive = entity.isActive, isActive = entity.isActive,
endOfLiveTimestampMillis = entity.endOfLiveTimestampMillis, endOfLiveTimestampMillis = entity.endOfLiveTimestampMillis,

View file

@ -24,6 +24,7 @@ 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.model.message.MessageBeaconLocationDataContent
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
private const val ANY_ROOM_ID = "a-room-id"
private const val ANY_USER_ID = "a-user-id" private const val ANY_USER_ID = "a-user-id"
private const val ANY_ACTIVE_STATE = true private const val ANY_ACTIVE_STATE = true
private const val ANY_TIMEOUT = 123L private const val ANY_TIMEOUT = 123L
@ -40,6 +41,7 @@ class LiveLocationShareAggregatedSummaryMapperTest {
val summary = mapper.map(entity) val summary = mapper.map(entity)
summary shouldBeEqualTo LiveLocationShareAggregatedSummary( summary shouldBeEqualTo LiveLocationShareAggregatedSummary(
roomId = ANY_ROOM_ID,
userId = ANY_USER_ID, userId = ANY_USER_ID,
isActive = ANY_ACTIVE_STATE, isActive = ANY_ACTIVE_STATE,
endOfLiveTimestampMillis = ANY_TIMEOUT, endOfLiveTimestampMillis = ANY_TIMEOUT,
@ -48,6 +50,7 @@ class LiveLocationShareAggregatedSummaryMapperTest {
} }
private fun anEntity(content: MessageBeaconLocationDataContent) = LiveLocationShareAggregatedSummaryEntity( private fun anEntity(content: MessageBeaconLocationDataContent) = LiveLocationShareAggregatedSummaryEntity(
roomId = ANY_ROOM_ID,
userId = ANY_USER_ID, userId = ANY_USER_ID,
isActive = ANY_ACTIVE_STATE, isActive = ANY_ACTIVE_STATE,
endOfLiveTimestampMillis = ANY_TIMEOUT, endOfLiveTimestampMillis = ANY_TIMEOUT,

View file

@ -229,6 +229,7 @@ internal class DefaultLocationSharingServiceTest {
fun `livedata of live summaries is correctly computed`() { fun `livedata of live summaries is correctly computed`() {
val entity = LiveLocationShareAggregatedSummaryEntity() val entity = LiveLocationShareAggregatedSummaryEntity()
val summary = LiveLocationShareAggregatedSummary( val summary = LiveLocationShareAggregatedSummary(
roomId = A_ROOM_ID,
userId = "", userId = "",
isActive = true, isActive = true,
endOfLiveTimestampMillis = 123, endOfLiveTimestampMillis = 123,
@ -255,6 +256,7 @@ internal class DefaultLocationSharingServiceTest {
fun `given an event id when getting livedata on corresponding live summary then it is correctly computed`() { fun `given an event id when getting livedata on corresponding live summary then it is correctly computed`() {
val entity = LiveLocationShareAggregatedSummaryEntity() val entity = LiveLocationShareAggregatedSummaryEntity()
val summary = LiveLocationShareAggregatedSummary( val summary = LiveLocationShareAggregatedSummary(
roomId = A_ROOM_ID,
userId = "", userId = "",
isActive = true, isActive = true,
endOfLiveTimestampMillis = 123, endOfLiveTimestampMillis = 123,

View file

@ -103,10 +103,10 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel<BottomSheetMessa
.apply(RequestOptions.centerCropTransform()) .apply(RequestOptions.centerCropTransform())
.into(holder.staticMapImageView) .into(holder.staticMapImageView)
safeLocationUiData.locationPinProvider.create(safeLocationUiData.locationOwnerId) { pinDrawable -> val pinMatrixItem = matrixItem.takeIf { safeLocationUiData.locationOwnerId != null }
GlideApp.with(holder.staticMapPinImageView) safeLocationUiData.locationPinProvider.create(pinMatrixItem) { pinDrawable ->
.load(pinDrawable) // we are not using Glide since it does not display it correctly when there is no user photo
.into(holder.staticMapPinImageView) holder.staticMapPinImageView.setImageDrawable(pinDrawable)
} }
} }
} }

View file

@ -238,7 +238,7 @@ class MessageActionsEpoxyController @Inject constructor(
val locationUrl = locationContent.toLocationData() val locationUrl = locationContent.toLocationData()
?.let { urlMapProvider.buildStaticMapUrl(it, INITIAL_MAP_ZOOM_IN_TIMELINE, 1200, 800) } ?.let { urlMapProvider.buildStaticMapUrl(it, INITIAL_MAP_ZOOM_IN_TIMELINE, 1200, 800) }
?: return null ?: return null
val locationOwnerId = if (locationContent.isSelfLocation()) state.informationData.matrixItem.id else null val locationOwnerId = if (locationContent.isSelfLocation()) state.informationData.senderId else null
return LocationUiData( return LocationUiData(
locationUrl = locationUrl, locationUrl = locationUrl,

View file

@ -114,7 +114,7 @@ class LiveLocationShareMessageItemFactory @Inject constructor(
.locationUrl(locationUrl) .locationUrl(locationUrl)
.mapWidth(width) .mapWidth(width)
.mapHeight(height) .mapHeight(height)
.locationUserId(attributes.informationData.senderId) .pinMatrixItem(attributes.informationData.matrixItem)
.locationPinProvider(locationPinProvider) .locationPinProvider(locationPinProvider)
.highlighted(highlight) .highlighted(highlight)
.leftGuideline(avatarSizeProvider.leftGuideline) .leftGuideline(avatarSizeProvider.leftGuideline)

View file

@ -233,14 +233,14 @@ class MessageItemFactory @Inject constructor(
urlMapProvider.buildStaticMapUrl(it, INITIAL_MAP_ZOOM_IN_TIMELINE, width, height) urlMapProvider.buildStaticMapUrl(it, INITIAL_MAP_ZOOM_IN_TIMELINE, width, height)
} }
val locationUserId = if (locationContent.isSelfLocation()) informationData.senderId else null val pinMatrixItem = if (locationContent.isSelfLocation()) informationData.matrixItem else null
return MessageLocationItem_() return MessageLocationItem_()
.attributes(attributes) .attributes(attributes)
.locationUrl(locationUrl) .locationUrl(locationUrl)
.mapWidth(width) .mapWidth(width)
.mapHeight(height) .mapHeight(height)
.locationUserId(locationUserId) .pinMatrixItem(pinMatrixItem)
.locationPinProvider(locationPinProvider) .locationPinProvider(locationPinProvider)
.highlighted(highlight) .highlighted(highlight)
.leftGuideline(avatarSizeProvider.leftGuideline) .leftGuideline(avatarSizeProvider.leftGuideline)

View file

@ -19,31 +19,33 @@ package im.vector.app.features.home.room.detail.timeline.helper
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable import android.graphics.drawable.LayerDrawable
import androidx.annotation.ColorInt import android.util.LruCache
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat import androidx.core.graphics.drawable.DrawableCompat
import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition import com.bumptech.glide.request.transition.Transition
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.glide.GlideApp import im.vector.app.core.glide.GlideApp
import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
private data class CachedDrawable(
val drawable: Drawable,
val isError: Boolean,
)
@Singleton @Singleton
class LocationPinProvider @Inject constructor( class LocationPinProvider @Inject constructor(
private val context: Context, private val context: Context,
private val activeSessionHolder: ActiveSessionHolder,
private val dimensionConverter: DimensionConverter, private val dimensionConverter: DimensionConverter,
private val avatarRenderer: AvatarRenderer, private val avatarRenderer: AvatarRenderer,
private val matrixItemColorProvider: MatrixItemColorProvider private val matrixItemColorProvider: MatrixItemColorProvider
) { ) {
private val cache = mutableMapOf<String, Drawable>() private val cache = LruCache<MatrixItem, CachedDrawable>(32)
private val glideRequests by lazy { private val glideRequests by lazy {
GlideApp.with(context) GlideApp.with(context)
@ -51,53 +53,51 @@ class LocationPinProvider @Inject constructor(
/** /**
* Creates a pin drawable. If userId is null then a generic pin drawable will be created. * Creates a pin drawable. If userId is null then a generic pin drawable will be created.
* @param userId userId that will be used to retrieve user avatar * @param matrixUser user that will be used to retrieve user avatar
* @param callback Pin drawable will be sent through the callback * @param callback Pin drawable will be sent through the callback
*/ */
fun create(userId: String?, callback: (Drawable) -> Unit) { fun create(matrixUser: MatrixItem?, callback: (Drawable) -> Unit) {
if (userId == null) { if (matrixUser == null) {
callback(ContextCompat.getDrawable(context, R.drawable.ic_location_pin)!!) callback(ContextCompat.getDrawable(context, R.drawable.ic_location_pin)!!)
return return
} }
val size = dimensionConverter.dpToPx(44)
avatarRenderer.render(glideRequests, matrixUser, object : CustomTarget<Drawable>(size, size) {
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
Timber.d("## Location: onResourceReady")
val pinDrawable = createPinDrawable(matrixUser, resource, isError = false)
callback(pinDrawable)
}
if (cache.contains(userId)) { override fun onLoadCleared(placeholder: Drawable?) {
callback(cache[userId]!!) // Is it possible? Put placeholder instead?
return // FIXME The doc says it has to be implemented and should free resources
} Timber.d("## Location: onLoadCleared")
}
activeSessionHolder override fun onLoadFailed(errorDrawable: Drawable?) {
.getActiveSession() // Note: `onLoadFailed` is also called when the user has no avatarUrl
.getUserOrDefault(userId) // and the errorDrawable is actually the placeholder.
.toMatrixItem() Timber.w("## Location: onLoadFailed")
.let { userItem -> errorDrawable ?: return
val size = dimensionConverter.dpToPx(44) val pinDrawable = createPinDrawable(matrixUser, errorDrawable, isError = true)
val bgTintColor = matrixItemColorProvider.getColor(userItem) callback(pinDrawable)
avatarRenderer.render(glideRequests, userItem, object : CustomTarget<Drawable>(size, size) { }
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) { })
Timber.d("## Location: onResourceReady")
val pinDrawable = createPinDrawable(resource, bgTintColor)
cache[userId] = pinDrawable
callback(pinDrawable)
}
override fun onLoadCleared(placeholder: Drawable?) {
// Is it possible? Put placeholder instead?
// FIXME The doc says it has to be implemented and should free resources
Timber.d("## Location: onLoadCleared")
}
override fun onLoadFailed(errorDrawable: Drawable?) {
Timber.w("## Location: onLoadFailed")
errorDrawable ?: return
val pinDrawable = createPinDrawable(errorDrawable, bgTintColor)
cache[userId] = pinDrawable
callback(pinDrawable)
}
})
}
} }
private fun createPinDrawable(drawable: Drawable, @ColorInt bgTintColor: Int): Drawable { private fun createPinDrawable(
userItem: MatrixItem,
drawable: Drawable,
isError: Boolean,
): Drawable {
val fromCache = cache.get(userItem)
// Return the cached drawable only if it is valid, or the new drawable is again an error
if (fromCache != null && (!fromCache.isError || isError)) {
return fromCache.drawable
}
val bgTintColor = matrixItemColorProvider.getColor(userItem)
val bgUserPin = ContextCompat.getDrawable(context, R.drawable.bg_map_user_pin)!! val bgUserPin = ContextCompat.getDrawable(context, R.drawable.bg_map_user_pin)!!
// use mutate on drawable to avoid sharing the color when we have multiple different user pins // use mutate on drawable to avoid sharing the color when we have multiple different user pins
DrawableCompat.setTint(bgUserPin.mutate(), bgTintColor) DrawableCompat.setTint(bgUserPin.mutate(), bgTintColor)
@ -106,6 +106,7 @@ class LocationPinProvider @Inject constructor(
val topInset = dimensionConverter.dpToPx(4) val topInset = dimensionConverter.dpToPx(4)
val bottomInset = dimensionConverter.dpToPx(8) val bottomInset = dimensionConverter.dpToPx(8)
layerDrawable.setLayerInset(1, horizontalInset, topInset, horizontalInset, bottomInset) layerDrawable.setLayerInset(1, horizontalInset, topInset, horizontalInset, bottomInset)
cache.put(userItem, CachedDrawable(layerDrawable, isError))
return layerDrawable return layerDrawable
} }
} }

View file

@ -38,6 +38,7 @@ import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLay
import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners
import im.vector.app.features.location.MapLoadingErrorView import im.vector.app.features.location.MapLoadingErrorView
import im.vector.app.features.location.MapLoadingErrorViewState import im.vector.app.features.location.MapLoadingErrorViewState
import org.matrix.android.sdk.api.util.MatrixItem
abstract class AbsMessageLocationItem<H : AbsMessageLocationItem.Holder>( abstract class AbsMessageLocationItem<H : AbsMessageLocationItem.Holder>(
@LayoutRes layoutId: Int = R.layout.item_timeline_event_base @LayoutRes layoutId: Int = R.layout.item_timeline_event_base
@ -47,7 +48,7 @@ abstract class AbsMessageLocationItem<H : AbsMessageLocationItem.Holder>(
var locationUrl: String? = null var locationUrl: String? = null
@EpoxyAttribute @EpoxyAttribute
var locationUserId: String? = null var pinMatrixItem: MatrixItem? = null
@EpoxyAttribute @EpoxyAttribute
var mapWidth: Int = 0 var mapWidth: Int = 0
@ -103,7 +104,7 @@ abstract class AbsMessageLocationItem<H : AbsMessageLocationItem.Holder>(
dataSource: DataSource?, dataSource: DataSource?,
isFirstResource: Boolean isFirstResource: Boolean
): Boolean { ): Boolean {
locationPinProvider?.create(locationUserId) { pinDrawable -> locationPinProvider?.create(pinMatrixItem) { pinDrawable ->
// we are not using Glide since it does not display it correctly when there is no user photo // we are not using Glide since it does not display it correctly when there is no user photo
holder.staticMapPinImageView.setImageDrawable(pinDrawable) holder.staticMapPinImageView.setImageDrawable(pinDrawable)
} }

View file

@ -49,7 +49,7 @@ abstract class MessageLiveLocationItem : AbsMessageLocationItem<MessageLiveLocat
private fun bindLiveLocationBanner(holder: Holder) { private fun bindLiveLocationBanner(holder: Holder) {
// TODO in a future PR 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 isEmitter = currentUserId != null && currentUserId == pinMatrixItem?.id
val messageLayout = attributes.informationData.messageLayout val messageLayout = attributes.informationData.messageLayout
val viewState = buildViewState(holder, messageLayout, isEmitter) val viewState = buildViewState(holder, messageLayout, isEmitter)
holder.liveLocationRunningBanner.isVisible = true holder.liveLocationRunningBanner.isVisible = true

View file

@ -106,11 +106,13 @@ class LocationSharingViewModel @AssistedInject constructor(
private fun updatePin(isUserPin: Boolean? = true) { private fun updatePin(isUserPin: Boolean? = true) {
if (isUserPin.orFalse()) { if (isUserPin.orFalse()) {
locationPinProvider.create(userId = session.myUserId) { val matrixItem = room.membershipService().getRoomMember(session.myUserId)?.toMatrixItem()
?: session.getUserOrDefault(session.myUserId).toMatrixItem()
locationPinProvider.create(matrixItem) {
updatePinDrawableInState(it) updatePinDrawableInState(it)
} }
} else { } else {
locationPinProvider.create(userId = null) { locationPinProvider.create(null) {
updatePinDrawableInState(it) updatePinDrawableInState(it)
} }
} }

View file

@ -20,8 +20,10 @@ import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
import im.vector.app.features.location.toLocationData import im.vector.app.features.location.toLocationData
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.session.getUserOrDefault
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject import javax.inject.Inject
@ -43,11 +45,21 @@ class UserLiveLocationViewStateMapper @Inject constructor(
// do nothing on cancellation // do nothing on cancellation
} }
else -> { else -> {
locationPinProvider.create(userId) { pinDrawable -> val session = activeSessionHolder.getActiveSession()
val session = activeSessionHolder.getActiveSession() val roomId = liveLocationShareAggregatedSummary.roomId
val matrixItem = if (roomId != null) {
session.getRoom(roomId)
?.membershipService()
?.getRoomMember(userId)
?.toMatrixItem()
?: MatrixItem.UserItem(userId)
} else {
session.getUserOrDefault(userId).toMatrixItem()
}
locationPinProvider.create(matrixItem) { pinDrawable ->
val locationTimestampMillis = liveLocationShareAggregatedSummary.lastLocationDataContent?.getBestTimestampMillis() val locationTimestampMillis = liveLocationShareAggregatedSummary.lastLocationDataContent?.getBestTimestampMillis()
val viewState = UserLiveLocationViewState( val viewState = UserLiveLocationViewState(
matrixItem = session.getUserOrDefault(userId).toMatrixItem(), matrixItem = matrixItem,
pinDrawable = pinDrawable, pinDrawable = pinDrawable,
locationData = locationData, locationData = locationData,
endOfLiveTimestampMillis = liveLocationShareAggregatedSummary.endOfLiveTimestampMillis, endOfLiveTimestampMillis = liveLocationShareAggregatedSummary.endOfLiveTimestampMillis,

View file

@ -30,6 +30,8 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem
class LocationPreviewViewModel @AssistedInject constructor( class LocationPreviewViewModel @AssistedInject constructor(
@Assisted private val initialState: LocationPreviewViewState, @Assisted private val initialState: LocationPreviewViewState,
@ -46,12 +48,23 @@ class LocationPreviewViewModel @AssistedInject constructor(
companion object : MavericksViewModelFactory<LocationPreviewViewModel, LocationPreviewViewState> by hiltMavericksViewModelFactory() companion object : MavericksViewModelFactory<LocationPreviewViewModel, LocationPreviewViewState> by hiltMavericksViewModelFactory()
init { init {
initPin(initialState.pinUserId) val matrixItem = if (initialState.roomId != null && initialState.pinUserId != null) {
session
.roomService()
.getRoom(initialState.roomId)
?.membershipService()
?.getRoomMember(initialState.pinUserId)
?.toMatrixItem()
?: MatrixItem.UserItem(initialState.pinUserId)
} else {
null
}
initPin(matrixItem)
initLocationTracking() initLocationTracking()
} }
private fun initPin(userId: String?) { private fun initPin(matrixItem: MatrixItem?) {
locationPinProvider.create(userId) { pinDrawable -> locationPinProvider.create(matrixItem) { pinDrawable ->
setState { copy(pinDrawable = pinDrawable) } setState { copy(pinDrawable = pinDrawable) }
} }
} }

View file

@ -23,6 +23,7 @@ import im.vector.app.features.location.LocationSharingArgs
data class LocationPreviewViewState( data class LocationPreviewViewState(
val pinLocationData: LocationData? = null, val pinLocationData: LocationData? = null,
val roomId: String? = null,
val pinUserId: String? = null, val pinUserId: String? = null,
val pinDrawable: Drawable? = null, val pinDrawable: Drawable? = null,
val loadingMapHasFailed: Boolean = false, val loadingMapHasFailed: Boolean = false,
@ -32,6 +33,7 @@ data class LocationPreviewViewState(
constructor(args: LocationSharingArgs) : this( constructor(args: LocationSharingArgs) : this(
pinLocationData = args.initialLocationData, pinLocationData = args.initialLocationData,
roomId = args.roomId,
pinUserId = args.locationOwnerId, pinUserId = args.locationOwnerId,
) )
} }

View file

@ -17,6 +17,7 @@
package im.vector.app.features.location package im.vector.app.features.location
import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldBeFalse
import org.amshove.kluent.shouldBeNull import org.amshove.kluent.shouldBeNull
import org.amshove.kluent.shouldBeTrue import org.amshove.kluent.shouldBeTrue
import org.junit.Test import org.junit.Test
@ -80,6 +81,9 @@ class LocationDataTest {
val contentWithSelfAssetType = MessageLocationContent(body = "", geoUri = "", unstableLocationAsset = LocationAsset(type = LocationAssetType.SELF)) val contentWithSelfAssetType = MessageLocationContent(body = "", geoUri = "", unstableLocationAsset = LocationAsset(type = LocationAssetType.SELF))
contentWithSelfAssetType.isSelfLocation().shouldBeTrue() contentWithSelfAssetType.isSelfLocation().shouldBeTrue()
val contentWithPinAssetType = MessageLocationContent(body = "", geoUri = "", unstableLocationAsset = LocationAsset(type = LocationAssetType.PIN))
contentWithPinAssetType.isSelfLocation().shouldBeFalse()
} }
@Test @Test

View file

@ -54,6 +54,7 @@ class GetLiveLocationShareSummaryUseCaseTest {
@Test @Test
fun `given a room id and event id when calling use case then flow on summary is returned`() = runTest { fun `given a room id and event id when calling use case then flow on summary is returned`() = runTest {
val summary = LiveLocationShareAggregatedSummary( val summary = LiveLocationShareAggregatedSummary(
roomId = A_ROOM_ID,
userId = "userId", userId = "userId",
isActive = true, isActive = true,
endOfLiveTimestampMillis = 123, endOfLiveTimestampMillis = 123,

View file

@ -59,18 +59,21 @@ class GetListOfUserLiveLocationUseCaseTest {
@Test @Test
fun `given a room id then the correct flow of view states list is collected`() = runTest { fun `given a room id then the correct flow of view states list is collected`() = runTest {
val summary1 = LiveLocationShareAggregatedSummary( val summary1 = LiveLocationShareAggregatedSummary(
roomId = A_ROOM_ID,
userId = "userId1", userId = "userId1",
isActive = true, isActive = true,
endOfLiveTimestampMillis = 123, endOfLiveTimestampMillis = 123,
lastLocationDataContent = MessageBeaconLocationDataContent() lastLocationDataContent = MessageBeaconLocationDataContent()
) )
val summary2 = LiveLocationShareAggregatedSummary( val summary2 = LiveLocationShareAggregatedSummary(
roomId = A_ROOM_ID,
userId = "userId2", userId = "userId2",
isActive = true, isActive = true,
endOfLiveTimestampMillis = 1234, endOfLiveTimestampMillis = 1234,
lastLocationDataContent = MessageBeaconLocationDataContent() lastLocationDataContent = MessageBeaconLocationDataContent()
) )
val summary3 = LiveLocationShareAggregatedSummary( val summary3 = LiveLocationShareAggregatedSummary(
roomId = A_ROOM_ID,
userId = "userId3", userId = "userId3",
isActive = true, isActive = true,
endOfLiveTimestampMillis = 1234, endOfLiveTimestampMillis = 1234,

View file

@ -72,6 +72,7 @@ class UserLiveLocationViewStateMapperTest {
@Test @Test
fun `given a summary with invalid data then result is null`() = runTest { fun `given a summary with invalid data then result is null`() = runTest {
val summary1 = LiveLocationShareAggregatedSummary( val summary1 = LiveLocationShareAggregatedSummary(
roomId = null,
userId = null, userId = null,
isActive = true, isActive = true,
endOfLiveTimestampMillis = null, endOfLiveTimestampMillis = null,
@ -98,17 +99,19 @@ class UserLiveLocationViewStateMapperTest {
unstableTimestampMillis = A_LOCATION_TIMESTAMP unstableTimestampMillis = A_LOCATION_TIMESTAMP
) )
val summary = LiveLocationShareAggregatedSummary( val summary = LiveLocationShareAggregatedSummary(
roomId = null,
userId = A_USER_ID, userId = A_USER_ID,
isActive = A_IS_ACTIVE, isActive = A_IS_ACTIVE,
endOfLiveTimestampMillis = A_END_OF_LIVE_TIMESTAMP, endOfLiveTimestampMillis = A_END_OF_LIVE_TIMESTAMP,
lastLocationDataContent = locationDataContent, lastLocationDataContent = locationDataContent,
) )
locationPinProvider.givenCreateForUserId(A_USER_ID, pinDrawable) val matrixItem = MatrixItem.UserItem(id = A_USER_ID, displayName = A_USER_DISPLAY_NAME, avatarUrl = "")
locationPinProvider.givenCreateForMatrixItem(matrixItem, pinDrawable)
val viewState = userLiveLocationViewStateMapper.map(summary) val viewState = userLiveLocationViewStateMapper.map(summary)
val expectedViewState = UserLiveLocationViewState( val expectedViewState = UserLiveLocationViewState(
matrixItem = MatrixItem.UserItem(id = A_USER_ID, displayName = A_USER_DISPLAY_NAME, avatarUrl = ""), matrixItem = matrixItem,
pinDrawable = pinDrawable, pinDrawable = pinDrawable,
locationData = LocationData( locationData = LocationData(
latitude = A_LATITUDE, latitude = A_LATITUDE,

View file

@ -21,12 +21,13 @@ import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvid
import io.mockk.every import io.mockk.every
import io.mockk.invoke import io.mockk.invoke
import io.mockk.mockk import io.mockk.mockk
import org.matrix.android.sdk.api.util.MatrixItem
class FakeLocationPinProvider { class FakeLocationPinProvider {
val instance = mockk<LocationPinProvider>(relaxed = true) val instance = mockk<LocationPinProvider>(relaxed = true)
fun givenCreateForUserId(userId: String, expectedDrawable: Drawable) { fun givenCreateForMatrixItem(matrixItem: MatrixItem, expectedDrawable: Drawable) {
every { instance.create(userId, captureLambda()) } answers { lambda<(Drawable) -> Unit>().invoke(expectedDrawable) } every { instance.create(matrixItem, captureLambda()) } answers { lambda<(Drawable) -> Unit>().invoke(expectedDrawable) }
} }
} }