mirror of
https://github.com/element-hq/element-android
synced 2024-09-06 17:05:20 +00:00
Merge pull request #8749 from element-hq/feature/bma/locationAvatar
Location avatar
This commit is contained in:
commit
e0c51bd168
1
changelog.d/8749.bugfix
Normal file
1
changelog.d/8749.bugfix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Fix issues about location Event avatar rendering.
|
|
@ -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") }
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue