diff --git a/changelog.d/5146.feature b/changelog.d/5146.feature new file mode 100644 index 0000000000..a1832864c8 --- /dev/null +++ b/changelog.d/5146.feature @@ -0,0 +1 @@ +Support generic location pin \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt index c090487c58..d07bd2d73a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt @@ -64,4 +64,12 @@ data class MessageLocationContent( ) : MessageContent { fun getBestGeoUri() = locationInfo?.geoUri ?: geoUri + + /** + * @return true if the location asset is a user location, not a generic one. + */ + fun isSelfLocation(): Boolean { + // Should behave like m.self if locationAsset is null + return locationAsset?.type == null || locationAsset.type == LocationAssetType.SELF + } } diff --git a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt index 5295cbaec3..14ba34cc52 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt @@ -76,6 +76,9 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel + locationPinProvider?.create(locationOwnerId) { pinDrawable -> GlideApp.with(holder.staticMapPinImageView) .load(pinDrawable) .into(holder.staticMapPinImageView) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index d626d87698..2da69bbe6c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -612,13 +612,14 @@ class TimelineFragment @Inject constructor( } private fun handleShowLocationPreview(locationContent: MessageLocationContent, senderId: String) { + val isSelfLocation = locationContent.isSelfLocation() navigator .openLocationSharing( context = requireContext(), roomId = timelineArgs.roomId, mode = LocationSharingMode.PREVIEW, initialLocationData = locationContent.toLocationData(), - locationOwnerId = senderId + locationOwnerId = if (isSelfLocation) senderId else null ) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt index 086a093068..27937047a5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt @@ -45,6 +45,7 @@ import im.vector.app.features.location.toLocationData import im.vector.app.features.media.ImageContentRenderer import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent @@ -77,10 +78,12 @@ class MessageActionsEpoxyController @Inject constructor( val formattedDate = dateFormatter.format(date, DateFormatKind.MESSAGE_DETAIL) val body = state.messageBody.linkify(host.listener) val bindingOptions = spanUtils.getBindingOptions(body) - val locationUrl = state.timelineEvent()?.root?.getClearContent() + + val locationContent = state.timelineEvent()?.root?.getClearContent() ?.toModel(catchError = true) - ?.toLocationData() + val locationUrl = locationContent?.toLocationData() ?.let { urlMapProvider.buildStaticMapUrl(it, INITIAL_MAP_ZOOM_IN_TIMELINE, 1200, 800) } + val locationOwnerId = if (locationContent?.isSelfLocation().orTrue()) state.informationData.matrixItem.id else null bottomSheetMessagePreviewItem { id("preview") @@ -96,6 +99,7 @@ class MessageActionsEpoxyController @Inject constructor( time(formattedDate) locationUrl(locationUrl) locationPinProvider(host.locationPinProvider) + locationOwnerId(locationOwnerId) } // Send state 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 59b7ba3a8c..77bf5970af 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 @@ -219,10 +219,12 @@ class MessageItemFactory @Inject constructor( urlMapProvider.buildStaticMapUrl(it, INITIAL_MAP_ZOOM_IN_TIMELINE, width, height) } + val userId = if (locationContent.isSelfLocation()) informationData.senderId else null + return MessageLocationItem_() .attributes(attributes) .locationUrl(locationUrl) - .userId(informationData.senderId) + .userId(userId) .locationPinProvider(locationPinProvider) .highlighted(highlight) .leftGuideline(avatarSizeProvider.leftGuideline) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt index e92376c44d..0cf30c8c01 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt @@ -45,7 +45,17 @@ class LocationPinProvider @Inject constructor( GlideApp.with(context) } - fun create(userId: String, callback: (Drawable) -> Unit) { + /** + * 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 callback Pin drawable will be sent through the callback + */ + fun create(userId: String?, callback: (Drawable) -> Unit) { + if (userId == null) { + callback(ContextCompat.getDrawable(context, R.drawable.ic_location_pin)!!) + return + } + if (cache.contains(userId)) { callback(cache[userId]!!) return 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 6f0b6abb72..607458678e 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 @@ -41,14 +41,13 @@ abstract class MessageLocationItem : AbsMessageItem( renderSendState(holder.view, null) val location = locationUrl ?: return - val locationOwnerId = userId ?: return GlideApp.with(holder.staticMapImageView) .load(location) .apply(RequestOptions.centerCropTransform()) .into(holder.staticMapImageView) - locationPinProvider?.create(locationOwnerId) { pinDrawable -> + locationPinProvider?.create(userId) { pinDrawable -> GlideApp.with(holder.staticMapPinImageView) .load(pinDrawable) .into(holder.staticMapPinImageView) 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 29ca6b81a9..6f947290e2 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 @@ -18,6 +18,7 @@ package im.vector.app.features.location const val MAP_BASE_URL = "https://api.maptiler.com/maps/streets/style.json" const val STATIC_MAP_BASE_URL = "https://api.maptiler.com/maps/basic/static/" +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 diff --git a/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt index c4f2f148bf..d993c76b0e 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt @@ -121,7 +121,7 @@ class LocationPreviewFragment @Inject constructor( MapState( zoomOnlyOnce = true, pinLocationData = location, - pinId = args.locationOwnerId, + pinId = args.locationOwnerId ?: DEFAULT_PIN_ID, pinDrawable = pinDrawable ) ) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt index 67b36b8442..10c271727b 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt @@ -30,7 +30,7 @@ data class LocationSharingArgs( val roomId: String, val mode: LocationSharingMode, val initialLocationData: LocationData?, - val locationOwnerId: String + val locationOwnerId: String? ) : Parcelable @AndroidEntryPoint diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt index f6bad2826b..7099bec9f0 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt @@ -118,8 +118,4 @@ class LocationSharingFragment @Inject constructor( views.mapView.render(state.toMapState()) views.shareLocationGpsLoading.isGone = state.lastKnownLocation != null } - - companion object { - const val USER_PIN_NAME = "USER_PIN_NAME" - } } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt index f3b937855a..a9a24094eb 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt @@ -42,6 +42,6 @@ data class LocationSharingViewState( fun LocationSharingViewState.toMapState() = MapState( zoomOnlyOnce = true, pinLocationData = lastKnownLocation, - pinId = LocationSharingFragment.USER_PIN_NAME, + pinId = DEFAULT_PIN_ID, pinDrawable = pinDrawable ) diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 37d459edc2..b521710c1e 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -556,7 +556,7 @@ class DefaultNavigator @Inject constructor( roomId: String, mode: LocationSharingMode, initialLocationData: LocationData?, - locationOwnerId: String) { + locationOwnerId: String?) { val intent = LocationSharingActivity.getIntent( context, LocationSharingArgs(roomId = roomId, mode = mode, initialLocationData = initialLocationData, locationOwnerId = locationOwnerId) diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index 0ba7625aa6..b5e94241ce 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -162,7 +162,8 @@ interface Navigator { roomId: String, mode: LocationSharingMode, initialLocationData: LocationData?, - locationOwnerId: String) + locationOwnerId: String?) + fun openThread(context: Context, threadTimelineArgs: ThreadTimelineArgs, eventIdToNavigate: String? = null) fun openThreadList(context: Context, threadTimelineArgs: ThreadTimelineArgs) diff --git a/vector/src/main/res/drawable/ic_location_pin.xml b/vector/src/main/res/drawable/ic_location_pin.xml new file mode 100644 index 0000000000..8227ea4e05 --- /dev/null +++ b/vector/src/main/res/drawable/ic_location_pin.xml @@ -0,0 +1,13 @@ + + + + diff --git a/vector/src/test/java/im/vector/app/features/location/LocationDataTest.kt b/vector/src/test/java/im/vector/app/features/location/LocationDataTest.kt index fcfff0096f..e273c0b3c9 100644 --- a/vector/src/test/java/im/vector/app/features/location/LocationDataTest.kt +++ b/vector/src/test/java/im/vector/app/features/location/LocationDataTest.kt @@ -18,7 +18,11 @@ package im.vector.app.features.location import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeNull +import org.amshove.kluent.shouldBeTrue import org.junit.Test +import org.matrix.android.sdk.api.session.room.model.message.LocationAsset +import org.matrix.android.sdk.api.session.room.model.message.LocationAssetType +import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent class LocationDataTest { @Test @@ -57,4 +61,16 @@ class LocationDataTest { parseGeo("ge o:12.34,56.78;13.56").shouldBeNull() parseGeo("geo :12.34,56.78;13.56").shouldBeNull() } + + @Test + fun selfLocationTest() { + val contentWithNullAsset = MessageLocationContent(body = "", geoUri = "", locationAsset = null) + contentWithNullAsset.isSelfLocation().shouldBeTrue() + + val contentWithNullAssetType = MessageLocationContent(body = "", geoUri = "", locationAsset = LocationAsset(type = null)) + contentWithNullAssetType.isSelfLocation().shouldBeTrue() + + val contentWithSelfAssetType = MessageLocationContent(body = "", geoUri = "", locationAsset = LocationAsset(type = LocationAssetType.SELF)) + contentWithSelfAssetType.isSelfLocation().shouldBeTrue() + } }