From b7a68edf425451fc6419d735b1df7e9ad3a81de2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artjom=20K=C3=B6nig?= Date: Tue, 27 Dec 2022 12:42:23 +0100 Subject: [PATCH 1/6] refactored location sharing to use the mapserver provided by the wellknown file --- .../BottomSheetMessagePreviewItem.kt | 16 ++-- .../AttachmentTypeSelectorViewModel.kt | 4 +- .../features/home/HomeActivityViewModel.kt | 5 ++ .../composer/MessageComposerFragment.kt | 4 +- .../detail/timeline/action/LocationUiData.kt | 2 + .../action/MessageActionsEpoxyController.kt | 8 +- .../LiveLocationShareMessageItemFactory.kt | 8 +- .../timeline/factory/MessageItemFactory.kt | 8 +- .../timeline/item/AbsMessageLocationItem.kt | 88 ++++++++----------- .../location/LocationSharingFragment.kt | 2 +- .../app/features/location/MapBoxMapExt.kt | 19 ++-- .../app/features/location/UrlMapProvider.kt | 52 +++-------- .../live/map/LiveLocationMapViewFragment.kt | 2 +- .../preview/LocationPreviewFragment.kt | 2 +- .../item_bottom_sheet_message_preview.xml | 5 +- .../item_timeline_event_location_stub.xml | 34 +++---- 16 files changed, 112 insertions(+), 147 deletions(-) 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 bb1b0fbd7b5..566b4624fa3 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 @@ -23,7 +23,7 @@ import android.widget.TextView import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass -import com.bumptech.glide.request.RequestOptions +import com.mapbox.mapboxsdk.maps.MapView import im.vector.app.R import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.VectorEpoxyHolder @@ -36,6 +36,7 @@ import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.action.LocationUiData import im.vector.app.features.home.room.detail.timeline.item.BindingOptions import im.vector.app.features.home.room.detail.timeline.tools.findPillsAndProcess +import im.vector.app.features.location.zoomToLocation import im.vector.app.features.media.ImageContentRenderer import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence import org.matrix.android.sdk.api.util.MatrixItem @@ -98,10 +99,13 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel - GlideApp.with(holder.staticMapImageView) - .load(safeLocationUiData.locationUrl) - .apply(RequestOptions.centerCropTransform()) - .into(holder.staticMapImageView) + holder.staticMapView.getMapAsync { mapbox -> + mapbox.setStyle(safeLocationUiData.locationUrl) + safeLocationUiData.locationData?.let { + mapbox.zoomToLocation(it, animate = false) + } + mapbox.uiSettings.setAllGesturesEnabled(false) + } safeLocationUiData.locationPinProvider.create(safeLocationUiData.locationOwnerId) { pinDrawable -> GlideApp.with(holder.staticMapPinImageView) @@ -124,7 +128,7 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel(R.id.bottom_sheet_message_preview_timestamp) val imagePreview by bind(R.id.bottom_sheet_message_preview_image) val mapViewContainer by bind(R.id.mapViewContainer) - val staticMapImageView by bind(R.id.staticMapImageView) + val staticMapView by bind(R.id.staticMapView) val staticMapPinImageView by bind(R.id.staticMapPinImageView) } } diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModel.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModel.kt index cb74661eba8..b7f0698d648 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModel.kt @@ -27,12 +27,14 @@ import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.features.VectorFeatures +import im.vector.app.features.location.UrlMapProvider import im.vector.app.features.settings.VectorPreferences class AttachmentTypeSelectorViewModel @AssistedInject constructor( @Assisted initialState: AttachmentTypeSelectorViewState, private val vectorFeatures: VectorFeatures, private val vectorPreferences: VectorPreferences, + private val urlMapProvider: UrlMapProvider ) : VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -48,7 +50,7 @@ class AttachmentTypeSelectorViewModel @AssistedInject constructor( init { setState { copy( - isLocationVisible = vectorFeatures.isLocationSharingEnabled(), + isLocationVisible = vectorFeatures.isLocationSharingEnabled() && urlMapProvider.isMapConfigurationAvailable(), isVoiceBroadcastVisible = vectorFeatures.isVoiceBroadcastEnabled(), isTextFormattingEnabled = vectorPreferences.isTextFormattingEnabled(), ) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 8f16121a306..017cfd6bd21 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -36,6 +36,7 @@ import im.vector.app.features.analytics.extensions.toAnalyticsType import im.vector.app.features.analytics.plan.Signup import im.vector.app.features.analytics.store.AnalyticsStore import im.vector.app.features.home.room.list.home.release.ReleaseNotesPreferencesStore +import im.vector.app.features.location.UrlMapProvider import im.vector.app.features.login.ReAuthHelper import im.vector.app.features.onboarding.AuthenticationDescription import im.vector.app.features.raw.wellknown.ElementWellKnown @@ -95,6 +96,7 @@ class HomeActivityViewModel @AssistedInject constructor( private val registerUnifiedPushUseCase: RegisterUnifiedPushUseCase, private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase, private val ensureFcmTokenIsRetrievedUseCase: EnsureFcmTokenIsRetrievedUseCase, + private val mapUrlProvider: UrlMapProvider, ) : VectorViewModel(initialState) { @AssistedFactory @@ -383,6 +385,9 @@ class HomeActivityViewModel @AssistedInject constructor( } val elementWellKnown = rawService.getElementWellknown(session.sessionParams) + + mapUrlProvider.initWithWellknown(elementWellKnown) + val isSecureBackupRequired = elementWellKnown?.isSecureBackupRequired() ?: false // In case of account creation, it is already done before diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 4849e20b6db..5e33e0bb91b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -88,6 +88,7 @@ import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActi import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet import im.vector.app.features.html.PillImageSpan import im.vector.app.features.location.LocationSharingMode +import im.vector.app.features.location.UrlMapProvider import im.vector.app.features.poll.PollMode import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.share.SharedData @@ -121,6 +122,7 @@ class MessageComposerFragment : VectorBaseFragment(), A @Inject lateinit var buildMeta: BuildMeta @Inject lateinit var session: Session @Inject lateinit var errorTracker: ErrorTracker + @Inject lateinit var urlMapProvider: UrlMapProvider private val roomId: String get() = withState(timelineViewModel) { it.roomId } @@ -356,7 +358,7 @@ class MessageComposerFragment : VectorBaseFragment(), A attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@MessageComposerFragment) attachmentTypeSelector.setAttachmentVisibility( AttachmentType.LOCATION, - vectorFeatures.isLocationSharingEnabled(), + vectorFeatures.isLocationSharingEnabled() && urlMapProvider.isMapConfigurationAvailable(), ) attachmentTypeSelector.setAttachmentVisibility( AttachmentType.POLL, !isThreadTimeLine() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/LocationUiData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/LocationUiData.kt index 073dda626fa..8302ba0d7c8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/LocationUiData.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/LocationUiData.kt @@ -17,11 +17,13 @@ package im.vector.app.features.home.room.detail.timeline.action import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider +import im.vector.app.features.location.LocationData /** * Data used to display Location data in the message bottom sheet. */ data class LocationUiData( + val locationData: LocationData?, val locationUrl: String, val locationOwnerId: String?, val locationPinProvider: LocationPinProvider, 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 5daf82fae6f..d499b2bbd9c 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 @@ -39,7 +39,6 @@ import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod import im.vector.app.features.home.room.detail.timeline.tools.linkify import im.vector.app.features.html.SpanUtils -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 im.vector.app.features.media.ImageContentRenderer @@ -228,13 +227,12 @@ class MessageActionsEpoxyController @Inject constructor( val locationContent = state.timelineEvent()?.root?.getClearContent().toModel(catchError = true) ?: return null - val locationUrl = locationContent.toLocationData() - ?.let { urlMapProvider.buildStaticMapUrl(it, INITIAL_MAP_ZOOM_IN_TIMELINE, 1200, 800) } - ?: return null + val locationOwnerId = if (locationContent.isSelfLocation()) state.informationData.matrixItem.id else null return LocationUiData( - locationUrl = locationUrl, + locationData = locationContent.toLocationData(), + locationUrl = urlMapProvider.getMapStyleUrl(), locationOwnerId = locationOwnerId, locationPinProvider = locationPinProvider, ) 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 493602a291a..8b7d3303235 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 @@ -30,7 +30,6 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocation 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.session.Session @@ -105,13 +104,12 @@ class LiveLocationShareMessageItemFactory @Inject constructor( 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) - } + val lastLocationData = runningState.lastGeoUri.toLocationData() return MessageLiveLocationItem_() .attributes(attributes) - .locationUrl(locationUrl) + .locationData(lastLocationData) + .mapStyleUrl(urlMapProvider.getMapStyleUrl()) .mapWidth(width) .mapHeight(height) .locationUserId(attributes.informationData.senderId) 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 42e031a3c47..9f3a0685626 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 @@ -73,7 +73,6 @@ import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.PillsPostProcessor import im.vector.app.features.html.SpanUtils import im.vector.app.features.html.VectorHtmlCompressor -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 im.vector.app.features.media.ImageContentRenderer @@ -222,15 +221,12 @@ class MessageItemFactory @Inject constructor( val width = timelineMediaSizeProvider.getMaxSize().first val height = dimensionConverter.dpToPx(MESSAGE_LOCATION_ITEM_HEIGHT_IN_DP) - val locationUrl = locationContent.toLocationData()?.let { - urlMapProvider.buildStaticMapUrl(it, INITIAL_MAP_ZOOM_IN_TIMELINE, width, height) - } - val locationUserId = if (locationContent.isSelfLocation()) informationData.senderId else null return MessageLocationItem_() .attributes(attributes) - .locationUrl(locationUrl) + .locationData(locationContent.toLocationData()) + .mapStyleUrl(urlMapProvider.getMapStyleUrl()) .mapWidth(width) .mapHeight(height) .locationUserId(locationUserId) 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 4903b8c8cf8..afadbf81a03 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 @@ -16,35 +16,34 @@ 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.annotation.LayoutRes 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 com.mapbox.mapboxsdk.maps.MapView import im.vector.app.R -import im.vector.app.core.glide.GlideApp +import im.vector.app.core.epoxy.ClickListener 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.LocationData import im.vector.app.features.location.MapLoadingErrorView import im.vector.app.features.location.MapLoadingErrorViewState +import im.vector.app.features.location.zoomToLocation abstract class AbsMessageLocationItem( @LayoutRes layoutId: Int = R.layout.item_timeline_event_base ) : AbsMessageItem(layoutId) { @EpoxyAttribute - var locationUrl: String? = null + var locationData: LocationData? = null + + @EpoxyAttribute + var mapStyleUrl: String? = null @EpoxyAttribute var locationUserId: String? = null @@ -65,7 +64,6 @@ abstract class AbsMessageLocationItem( } private fun bindMap(holder: Holder) { - val location = locationUrl ?: return val messageLayout = attributes.informationData.messageLayout val imageCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) { messageLayout.cornersRadius.granularRoundedCorners() @@ -73,53 +71,45 @@ abstract class AbsMessageLocationItem( val dimensionConverter = DimensionConverter(holder.view.resources) RoundedCorners(dimensionConverter.dpToPx(8)) } - holder.staticMapImageView.updateLayoutParams { + holder.staticMapView.updateLayoutParams { width = mapWidth height = mapHeight } - GlideApp.with(holder.staticMapImageView) - .load(location) - .apply(RequestOptions.centerCropTransform()) - .placeholder(holder.staticMapImageView.drawable) - .listener(object : RequestListener { - override fun onLoadFailed( - e: GlideException?, - model: Any?, - target: Target?, - isFirstResource: Boolean - ): Boolean { - holder.staticMapPinImageView.setImageDrawable(null) - holder.staticMapLoadingErrorView.isVisible = true - val mapErrorViewState = MapLoadingErrorViewState(imageCornerTransformation) - holder.staticMapLoadingErrorView.render(mapErrorViewState) - holder.staticMapCopyrightTextView.isVisible = false - return false - } - override fun onResourceReady( - resource: Drawable?, - model: Any?, - target: Target?, - dataSource: DataSource?, - isFirstResource: Boolean - ): Boolean { - 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) - } - holder.staticMapLoadingErrorView.isVisible = false - holder.staticMapCopyrightTextView.isVisible = true - return false - } - }) - .transform(imageCornerTransformation) - .into(holder.staticMapImageView) + holder.staticMapView.addOnDidFailLoadingMapListener { + holder.staticMapLoadingErrorView.isVisible = true + val mapErrorViewState = MapLoadingErrorViewState(imageCornerTransformation) + holder.staticMapLoadingErrorView.render(mapErrorViewState) + } + + holder.staticMapView.addOnDidFinishLoadingMapListener { + locationPinProvider?.create(locationUserId) { pinDrawable -> + holder.staticMapPinImageView.setImageDrawable(pinDrawable) + } + holder.staticMapLoadingErrorView.isVisible = false + } + + holder.staticMapView.clipToOutline = true + holder.staticMapView.getMapAsync { mapbox -> + mapbox.setStyle(mapStyleUrl) + locationData?.let { + mapbox.zoomToLocation(it, animate = false) + } + mapbox.uiSettings.setAllGesturesEnabled(false) + mapbox.addOnMapClickListener { + attributes.itemClickListener?.invoke(holder.staticMapView) + true + } + mapbox.addOnMapLongClickListener { + attributes.itemLongClickListener?.onLongClick(holder.staticMapView) + true + } + } } abstract class Holder(@IdRes stubId: Int) : AbsMessageItem.Holder(stubId) { - val staticMapImageView by bind(R.id.staticMapImageView) + val staticMapView by bind(R.id.staticMapView) val staticMapPinImageView by bind(R.id.staticMapPinImageView) val staticMapLoadingErrorView by bind(R.id.staticMapLoadingError) - val staticMapCopyrightTextView by bind(R.id.staticMapCopyrightTextView) } } 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 779818b3d6f..2563bef27f1 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 @@ -99,7 +99,7 @@ class LocationSharingFragment : lifecycleScope.launchWhenCreated { views.mapView.initialize( - url = urlMapProvider.getMapUrl(), + url = urlMapProvider.getMapStyleUrl(), locationTargetChangeListener = this@LocationSharingFragment ) } diff --git a/vector/src/main/java/im/vector/app/features/location/MapBoxMapExt.kt b/vector/src/main/java/im/vector/app/features/location/MapBoxMapExt.kt index 8e917c665a9..3cd7dc8e68f 100644 --- a/vector/src/main/java/im/vector/app/features/location/MapBoxMapExt.kt +++ b/vector/src/main/java/im/vector/app/features/location/MapBoxMapExt.kt @@ -22,17 +22,24 @@ import com.mapbox.mapboxsdk.geometry.LatLng import com.mapbox.mapboxsdk.geometry.LatLngBounds import com.mapbox.mapboxsdk.maps.MapboxMap -fun MapboxMap?.zoomToLocation(locationData: LocationData, preserveCurrentZoomLevel: Boolean = false) { +fun MapboxMap?.zoomToLocation(locationData: LocationData, preserveCurrentZoomLevel: Boolean = false, animate: Boolean = true) { val zoomLevel = if (preserveCurrentZoomLevel && this?.cameraPosition != null) { cameraPosition.zoom } else { INITIAL_MAP_ZOOM_IN_PREVIEW } - this?.easeCamera { - CameraPosition.Builder() - .target(LatLng(locationData.latitude, locationData.longitude)) - .zoom(zoomLevel) - .build() + val cameraPosition = CameraPosition.Builder() + .target(LatLng(locationData.latitude, locationData.longitude)) + .zoom(zoomLevel) + .build() + if(animate) { + this?.easeCamera { + cameraPosition + } + } else { + this?.moveCamera { + cameraPosition + } } } diff --git a/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt b/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt index 3748b1b19e0..e7472911f48 100644 --- a/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt +++ b/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt @@ -16,52 +16,22 @@ package im.vector.app.features.location -import im.vector.app.features.raw.wellknown.getElementWellknown -import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.raw.RawService -import org.matrix.android.sdk.api.session.Session +import im.vector.app.features.raw.wellknown.ElementWellKnown import javax.inject.Inject +import javax.inject.Singleton -class UrlMapProvider @Inject constructor( - private val session: Session, - private val rawService: RawService, - locationSharingConfig: LocationSharingConfig, -) { - private val keyParam = "?key=${locationSharingConfig.mapTilerKey}" +@Singleton +class UrlMapProvider @Inject constructor() { - private val fallbackMapUrl = buildString { - append(MAP_BASE_URL) - append(keyParam) - } + private var styleUrl : String = "" - suspend fun getMapUrl(): String { - val upstreamMapUrl = tryOrNull { rawService.getElementWellknown(session.sessionParams) } - ?.getBestMapTileServerConfig() - ?.mapStyleUrl - return upstreamMapUrl ?: fallbackMapUrl + fun initWithWellknown(data: ElementWellKnown?) { + styleUrl = data?.getBestMapTileServerConfig()?.mapStyleUrl ?: "" } - fun buildStaticMapUrl( - locationData: LocationData, - zoom: Double, - width: Int, - height: Int - ): String { - return buildString { - append(STATIC_MAP_BASE_URL) - append(locationData.longitude) - append(",") - append(locationData.latitude) - append(",") - append(zoom) - append("/") - append(width) - append("x") - append(height) - append(".png") - append(keyParam) - // Since the default copyright font is too small we put a custom one on map - append("&attribution=false") - } + fun isMapConfigurationAvailable() = styleUrl.isNotEmpty() + + fun getMapStyleUrl(): String { + return styleUrl } } diff --git a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewFragment.kt b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewFragment.kt index 942021dd649..1460b32619c 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewFragment.kt @@ -129,7 +129,7 @@ class LiveLocationMapViewFragment : listenMapLoadingError(it) } lifecycleScope.launch { - mapboxMap.setStyle(urlMapProvider.getMapUrl()) { style -> + mapboxMap.setStyle(urlMapProvider.getMapStyleUrl()) { style -> mapStyle = style this@LiveLocationMapViewFragment.mapboxMap = WeakReference(mapboxMap) symbolManager = SymbolManager(mapFragment.view as MapView, mapboxMap, style).apply { diff --git a/vector/src/main/java/im/vector/app/features/location/preview/LocationPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/location/preview/LocationPreviewFragment.kt index 082cee02f06..0330d01e86b 100644 --- a/vector/src/main/java/im/vector/app/features/location/preview/LocationPreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/preview/LocationPreviewFragment.kt @@ -75,7 +75,7 @@ class LocationPreviewFragment : views.mapView.onCreate(savedInstanceState) lifecycleScope.launchWhenCreated { - views.mapView.initialize(urlMapProvider.getMapUrl()) + views.mapView.initialize(urlMapProvider.getMapStyleUrl()) loadPinDrawable() } } diff --git a/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml b/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml index b41fda99fbe..872b4f5de5d 100644 --- a/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml +++ b/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml @@ -120,9 +120,10 @@ tools:alpha="0.3" tools:visibility="visible"> - 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 8de98b22604..474363b4405 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 @@ -6,12 +6,12 @@ android:layout_height="wrap_content"> - @@ -24,9 +24,9 @@ android:importantForAccessibility="no" android:src="@drawable/bg_map_user_pin" 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_constraintEnd_toEndOf="@id/staticMapView" + app:layout_constraintStart_toStartOf="@id/staticMapView" + app:layout_constraintTop_toTopOf="@id/staticMapView" app:layout_constraintVertical_bias="1.0" /> - - From 0f3e1cdc0e9a6e46a5218d095ad13be83dc1e5e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artjom=20K=C3=B6nig?= Date: Tue, 27 Dec 2022 16:53:13 +0100 Subject: [PATCH 2/6] removed fixed attribution, added changelog --- changelog.d/7852.feature | 1 + library/ui-strings/src/main/res/values/donottranslate.xml | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) create mode 100644 changelog.d/7852.feature diff --git a/changelog.d/7852.feature b/changelog.d/7852.feature new file mode 100644 index 00000000000..85fbd1fc0f6 --- /dev/null +++ b/changelog.d/7852.feature @@ -0,0 +1 @@ +Refactored location sharing timeline items to use the mapserver configured by the wellknown file diff --git a/library/ui-strings/src/main/res/values/donottranslate.xml b/library/ui-strings/src/main/res/values/donottranslate.xml index bfe751ef5a5..5f20fb24d44 100755 --- a/library/ui-strings/src/main/res/values/donottranslate.xml +++ b/library/ui-strings/src/main/res/values/donottranslate.xml @@ -10,6 +10,4 @@ Cut the slack from teams. - - © MapTiler © OpenStreetMap contributors From 2bb6cc801477377ef0604acef4f3ca4ec5ebcfe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artjom=20K=C3=B6nig?= Date: Wed, 28 Dec 2022 09:26:57 +0100 Subject: [PATCH 3/6] fixed unit tests --- .../AttachmentTypeSelectorViewModelTest.kt | 24 +++++++++++++- .../app/test/fakes/FakeUrlMapProvider.kt | 33 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeUrlMapProvider.kt diff --git a/vector/src/test/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModelTest.kt b/vector/src/test/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModelTest.kt index e20d498a37a..66eee0dc6d1 100644 --- a/vector/src/test/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModelTest.kt @@ -17,9 +17,13 @@ package im.vector.app.features.attachments import com.airbnb.mvrx.test.MavericksTestRule +import im.vector.app.features.location.UrlMapProvider +import im.vector.app.test.fakes.FakeUrlMapProvider import im.vector.app.test.fakes.FakeVectorFeatures import im.vector.app.test.fakes.FakeVectorPreferences import im.vector.app.test.test +import io.mockk.every +import io.mockk.mockk import io.mockk.verifyOrder import org.junit.Before import org.junit.Rule @@ -33,6 +37,7 @@ internal class AttachmentTypeSelectorViewModelTest { private val fakeVectorFeatures = FakeVectorFeatures() private val fakeVectorPreferences = FakeVectorPreferences() private val initialState = AttachmentTypeSelectorViewState() + private val fakeUrlMapProvider = FakeUrlMapProvider() @Before fun setUp() { @@ -57,7 +62,7 @@ internal class AttachmentTypeSelectorViewModelTest { @Test fun `given location sharing is enabled, then location sharing option is visible`() { fakeVectorFeatures.givenLocationSharing(isEnabled = true) - + fakeUrlMapProvider.givenAvailableConfiguration() createViewModel() .test() .assertStates( @@ -70,6 +75,22 @@ internal class AttachmentTypeSelectorViewModelTest { .finish() } + @Test + fun `given location sharing is enabled, but no mapserver configuration available, then location sharing option is not visible`() { + fakeVectorFeatures.givenLocationSharing(isEnabled = true) + fakeUrlMapProvider.givenNotAvailableConfiguration() + createViewModel() + .test() + .assertStates( + listOf( + initialState.copy( + isLocationVisible = false + ), + ) + ) + .finish() + } + @Test fun `given voice broadcast is enabled, then voice broadcast option is visible`() { fakeVectorFeatures.givenVoiceBroadcast(isEnabled = true) @@ -137,6 +158,7 @@ internal class AttachmentTypeSelectorViewModelTest { initialState, vectorFeatures = fakeVectorFeatures, vectorPreferences = fakeVectorPreferences.instance, + urlMapProvider = fakeUrlMapProvider.instance ) } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeUrlMapProvider.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeUrlMapProvider.kt new file mode 100644 index 00000000000..92a97d6d00d --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeUrlMapProvider.kt @@ -0,0 +1,33 @@ +/* + * 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.test.fakes + +import im.vector.app.features.location.UrlMapProvider +import io.mockk.every +import io.mockk.mockk + +class FakeUrlMapProvider { + val instance = mockk() + + fun givenAvailableConfiguration() { + every { instance.isMapConfigurationAvailable() } returns true + } + + fun givenNotAvailableConfiguration() { + every { instance.isMapConfigurationAvailable() } returns false + } +} From dd976f46eba5d0b1fb26eac2e266a77ca9f9fbb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artjom=20K=C3=B6nig?= Date: Wed, 28 Dec 2022 19:51:41 +0100 Subject: [PATCH 4/6] changes, based on feedback --- .../main/java/im/vector/app/config/Config.kt | 1 + .../vector/app/core/di/ConfigurationModule.kt | 1 + .../BottomSheetMessagePreviewItem.kt | 3 +- .../AttachmentTypeSelectorViewModel.kt | 3 +- .../features/home/HomeActivityViewModel.kt | 11 ++- .../composer/MessageComposerFragment.kt | 4 +- .../timeline/item/AbsMessageLocationItem.kt | 69 ++++++++++++++----- .../location/LocationSharingConfig.kt | 1 + .../app/features/location/MapBoxMapExt.kt | 14 ++-- .../app/features/location/UrlMapProvider.kt | 23 ++++--- .../live/map/LiveLocationMapViewFragment.kt | 4 +- .../raw/wellknown/WellknownService.kt | 43 ++++++++++++ .../AttachmentTypeSelectorViewModelTest.kt | 25 +------ .../features/location/UrlMapProviderTest.kt | 56 +++++++++++++++ ...MapProvider.kt => FakeWellknownService.kt} | 16 +++-- 15 files changed, 193 insertions(+), 81 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/raw/wellknown/WellknownService.kt create mode 100644 vector/src/test/java/im/vector/app/features/location/UrlMapProviderTest.kt rename vector/src/test/java/im/vector/app/test/fakes/{FakeUrlMapProvider.kt => FakeWellknownService.kt} (63%) diff --git a/vector-config/src/main/java/im/vector/app/config/Config.kt b/vector-config/src/main/java/im/vector/app/config/Config.kt index fdc8e9f73b7..77ef9d57eb1 100644 --- a/vector-config/src/main/java/im/vector/app/config/Config.kt +++ b/vector-config/src/main/java/im/vector/app/config/Config.kt @@ -41,6 +41,7 @@ object Config { const val ENABLE_LOCATION_SHARING = true const val LOCATION_MAP_TILER_KEY = "fU3vlMsMn4Jb6dnEIFsx" + const val ENABLE_LOCATION_SHARING_MAPSERVER_FALLBACK = true /** * The maximum length of voice messages in milliseconds. diff --git a/vector/src/main/java/im/vector/app/core/di/ConfigurationModule.kt b/vector/src/main/java/im/vector/app/core/di/ConfigurationModule.kt index caa38d20d96..c77205dc292 100644 --- a/vector/src/main/java/im/vector/app/core/di/ConfigurationModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/ConfigurationModule.kt @@ -73,6 +73,7 @@ object ConfigurationModule { @Provides fun providesLocationSharingConfig() = LocationSharingConfig( mapTilerKey = Config.LOCATION_MAP_TILER_KEY, + isMapTilerFallbackEnabled = Config.ENABLE_LOCATION_SHARING_MAPSERVER_FALLBACK ) @Provides 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 566b4624fa3..0f65cd67890 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 @@ -36,6 +36,7 @@ import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.action.LocationUiData import im.vector.app.features.home.room.detail.timeline.item.BindingOptions import im.vector.app.features.home.room.detail.timeline.tools.findPillsAndProcess +import im.vector.app.features.location.INITIAL_MAP_ZOOM_IN_TIMELINE import im.vector.app.features.location.zoomToLocation import im.vector.app.features.media.ImageContentRenderer import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence @@ -102,7 +103,7 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel mapbox.setStyle(safeLocationUiData.locationUrl) safeLocationUiData.locationData?.let { - mapbox.zoomToLocation(it, animate = false) + mapbox.zoomToLocation(it, false, INITIAL_MAP_ZOOM_IN_TIMELINE) } mapbox.uiSettings.setAllGesturesEnabled(false) } diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModel.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModel.kt index b7f0698d648..805380849d6 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModel.kt @@ -34,7 +34,6 @@ class AttachmentTypeSelectorViewModel @AssistedInject constructor( @Assisted initialState: AttachmentTypeSelectorViewState, private val vectorFeatures: VectorFeatures, private val vectorPreferences: VectorPreferences, - private val urlMapProvider: UrlMapProvider ) : VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -50,7 +49,7 @@ class AttachmentTypeSelectorViewModel @AssistedInject constructor( init { setState { copy( - isLocationVisible = vectorFeatures.isLocationSharingEnabled() && urlMapProvider.isMapConfigurationAvailable(), + isLocationVisible = vectorFeatures.isLocationSharingEnabled(), isVoiceBroadcastVisible = vectorFeatures.isVoiceBroadcastEnabled(), isTextFormattingEnabled = vectorPreferences.isTextFormattingEnabled(), ) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 017cfd6bd21..9a584f7759b 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -40,6 +40,7 @@ import im.vector.app.features.location.UrlMapProvider import im.vector.app.features.login.ReAuthHelper import im.vector.app.features.onboarding.AuthenticationDescription import im.vector.app.features.raw.wellknown.ElementWellKnown +import im.vector.app.features.raw.wellknown.WellknownService import im.vector.app.features.raw.wellknown.getElementWellknown import im.vector.app.features.raw.wellknown.isSecureBackupRequired import im.vector.app.features.raw.wellknown.withElementWellKnown @@ -55,6 +56,7 @@ import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixPatterns.getServerName import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserPasswordAuth @@ -83,7 +85,7 @@ import kotlin.coroutines.resumeWithException class HomeActivityViewModel @AssistedInject constructor( @Assisted private val initialState: HomeActivityViewState, private val activeSessionHolder: ActiveSessionHolder, - private val rawService: RawService, + private val wellknownService: WellknownService, private val reAuthHelper: ReAuthHelper, private val analyticsStore: AnalyticsStore, private val lightweightSettingsStorage: LightweightSettingsStorage, @@ -96,7 +98,6 @@ class HomeActivityViewModel @AssistedInject constructor( private val registerUnifiedPushUseCase: RegisterUnifiedPushUseCase, private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase, private val ensureFcmTokenIsRetrievedUseCase: EnsureFcmTokenIsRetrievedUseCase, - private val mapUrlProvider: UrlMapProvider, ) : VectorViewModel(initialState) { @AssistedFactory @@ -233,7 +234,7 @@ class HomeActivityViewModel @AssistedInject constructor( .onEach { info -> val isVerified = info.getOrNull()?.isTrusted() ?: false if (!isVerified && onceTrusted) { - rawService.withElementWellKnown(viewModelScope, safeActiveSession.sessionParams) { + wellknownService.getElementWellknown(safeActiveSession.sessionParams.userId.getServerName())?.let { sessionHasBeenUnverified(it) } } @@ -384,9 +385,7 @@ class HomeActivityViewModel @AssistedInject constructor( Timber.w("## No session to init cross signing or bootstrap") } - val elementWellKnown = rawService.getElementWellknown(session.sessionParams) - - mapUrlProvider.initWithWellknown(elementWellKnown) + val elementWellKnown = wellknownService.getElementWellknown(session.sessionParams.userId.getServerName()) val isSecureBackupRequired = elementWellKnown?.isSecureBackupRequired() ?: false diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 5e33e0bb91b..4849e20b6db 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -88,7 +88,6 @@ import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActi import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet import im.vector.app.features.html.PillImageSpan import im.vector.app.features.location.LocationSharingMode -import im.vector.app.features.location.UrlMapProvider import im.vector.app.features.poll.PollMode import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.share.SharedData @@ -122,7 +121,6 @@ class MessageComposerFragment : VectorBaseFragment(), A @Inject lateinit var buildMeta: BuildMeta @Inject lateinit var session: Session @Inject lateinit var errorTracker: ErrorTracker - @Inject lateinit var urlMapProvider: UrlMapProvider private val roomId: String get() = withState(timelineViewModel) { it.roomId } @@ -358,7 +356,7 @@ class MessageComposerFragment : VectorBaseFragment(), A attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@MessageComposerFragment) attachmentTypeSelector.setAttachmentVisibility( AttachmentType.LOCATION, - vectorFeatures.isLocationSharingEnabled() && urlMapProvider.isMapConfigurationAvailable(), + vectorFeatures.isLocationSharingEnabled(), ) attachmentTypeSelector.setAttachmentVisibility( AttachmentType.POLL, !isThreadTimeLine() 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 afadbf81a03..70b78304952 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 @@ -25,11 +25,11 @@ import com.airbnb.epoxy.EpoxyAttribute import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.mapbox.mapboxsdk.maps.MapView import im.vector.app.R -import im.vector.app.core.epoxy.ClickListener 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.INITIAL_MAP_ZOOM_IN_TIMELINE import im.vector.app.features.location.LocationData import im.vector.app.features.location.MapLoadingErrorView import im.vector.app.features.location.MapLoadingErrorViewState @@ -63,6 +63,29 @@ abstract class AbsMessageLocationItem( bindMap(holder) } + var failedLoadingMapListener : MapView.OnDidFailLoadingMapListener? = null + var finishedLoadingMapListener : MapView.OnDidFinishLoadingMapListener? = null + + override fun unbind(holder: H) { + super.unbind(holder) + failedLoadingMapListener?.let { + holder.staticMapView.removeOnDidFailLoadingMapListener(it) + } + finishedLoadingMapListener?.let { + holder.staticMapView.removeOnDidFinishLoadingMapListener(it) + } + } + + override fun onViewAttachedToWindow(holder: H) { + super.onViewAttachedToWindow(holder) + holder.staticMapView.onStart() + } + + override fun onViewDetachedFromWindow(holder: H) { + super.onViewDetachedFromWindow(holder) + holder.staticMapView.onStop() + } + private fun bindMap(holder: Holder) { val messageLayout = attributes.informationData.messageLayout val imageCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) { @@ -71,38 +94,46 @@ abstract class AbsMessageLocationItem( val dimensionConverter = DimensionConverter(holder.view.resources) RoundedCorners(dimensionConverter.dpToPx(8)) } - holder.staticMapView.updateLayoutParams { - width = mapWidth - height = mapHeight - } - holder.staticMapView.addOnDidFailLoadingMapListener { + failedLoadingMapListener = MapView.OnDidFailLoadingMapListener { holder.staticMapLoadingErrorView.isVisible = true val mapErrorViewState = MapLoadingErrorViewState(imageCornerTransformation) holder.staticMapLoadingErrorView.render(mapErrorViewState) } - holder.staticMapView.addOnDidFinishLoadingMapListener { + finishedLoadingMapListener = MapView.OnDidFinishLoadingMapListener { locationPinProvider?.create(locationUserId) { pinDrawable -> holder.staticMapPinImageView.setImageDrawable(pinDrawable) } holder.staticMapLoadingErrorView.isVisible = false } - holder.staticMapView.clipToOutline = true - holder.staticMapView.getMapAsync { mapbox -> - mapbox.setStyle(mapStyleUrl) - locationData?.let { - mapbox.zoomToLocation(it, animate = false) + holder.staticMapView.apply { + updateLayoutParams { + width = mapWidth + height = mapHeight + } + failedLoadingMapListener?.let { + addOnDidFailLoadingMapListener(it) } - mapbox.uiSettings.setAllGesturesEnabled(false) - mapbox.addOnMapClickListener { - attributes.itemClickListener?.invoke(holder.staticMapView) - true + finishedLoadingMapListener?.let { + addOnDidFinishLoadingMapListener(it) } - mapbox.addOnMapLongClickListener { - attributes.itemLongClickListener?.onLongClick(holder.staticMapView) - true + clipToOutline = true + getMapAsync { mapbox -> + mapbox.setStyle(mapStyleUrl) + locationData?.let { + mapbox.zoomToLocation(it, false, INITIAL_MAP_ZOOM_IN_TIMELINE) + } + mapbox.uiSettings.setAllGesturesEnabled(false) + mapbox.addOnMapClickListener { + attributes.itemClickListener?.invoke(holder.staticMapView) + true + } + mapbox.addOnMapLongClickListener { + attributes.itemLongClickListener?.onLongClick(holder.staticMapView) + true + } } } } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingConfig.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingConfig.kt index 4615564e41d..d19f2dbd584 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingConfig.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingConfig.kt @@ -18,4 +18,5 @@ package im.vector.app.features.location data class LocationSharingConfig( val mapTilerKey: String, + val isMapTilerFallbackEnabled: Boolean ) diff --git a/vector/src/main/java/im/vector/app/features/location/MapBoxMapExt.kt b/vector/src/main/java/im/vector/app/features/location/MapBoxMapExt.kt index 3cd7dc8e68f..b183a2e325a 100644 --- a/vector/src/main/java/im/vector/app/features/location/MapBoxMapExt.kt +++ b/vector/src/main/java/im/vector/app/features/location/MapBoxMapExt.kt @@ -22,16 +22,16 @@ import com.mapbox.mapboxsdk.geometry.LatLng import com.mapbox.mapboxsdk.geometry.LatLngBounds import com.mapbox.mapboxsdk.maps.MapboxMap -fun MapboxMap?.zoomToLocation(locationData: LocationData, preserveCurrentZoomLevel: Boolean = false, animate: Boolean = true) { - val zoomLevel = if (preserveCurrentZoomLevel && this?.cameraPosition != null) { - cameraPosition.zoom - } else { - INITIAL_MAP_ZOOM_IN_PREVIEW - } - val cameraPosition = CameraPosition.Builder() +fun MapboxMap?.zoomToLocation( + locationData: LocationData, + animate: Boolean = true, + zoomLevel: Double = INITIAL_MAP_ZOOM_IN_PREVIEW) { + + val cameraPosition = CameraPosition.Builder() .target(LatLng(locationData.latitude, locationData.longitude)) .zoom(zoomLevel) .build() + if(animate) { this?.easeCamera { cameraPosition diff --git a/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt b/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt index e7472911f48..38f79e5d5a9 100644 --- a/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt +++ b/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt @@ -16,22 +16,25 @@ package im.vector.app.features.location -import im.vector.app.features.raw.wellknown.ElementWellKnown +import androidx.annotation.VisibleForTesting +import im.vector.app.features.raw.wellknown.WellknownService import javax.inject.Inject import javax.inject.Singleton @Singleton -class UrlMapProvider @Inject constructor() { +class UrlMapProvider @Inject constructor( + private val wellknownService: WellknownService, + private val locationSharingConfig: LocationSharingConfig, +) { + private val keyParam = "?key=${locationSharingConfig.mapTilerKey}" - private var styleUrl : String = "" - - fun initWithWellknown(data: ElementWellKnown?) { - styleUrl = data?.getBestMapTileServerConfig()?.mapStyleUrl ?: "" + @VisibleForTesting + val fallbackMapUrl = buildString { + append(MAP_BASE_URL) + append(keyParam) } - fun isMapConfigurationAvailable() = styleUrl.isNotEmpty() - - fun getMapStyleUrl(): String { - return styleUrl + fun getMapStyleUrl() : String { + return wellknownService.getMapStyleUrl() ?: if (locationSharingConfig.isMapTilerFallbackEnabled) fallbackMapUrl else "" } } diff --git a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewFragment.kt b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewFragment.kt index 1460b32619c..e4dce356740 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewFragment.kt @@ -157,7 +157,7 @@ class LiveLocationMapViewFragment : symbol?.let { mapboxMap ?.get() - ?.zoomToLocation(LocationData(it.latLng.latitude, it.latLng.longitude, null), preserveCurrentZoomLevel = false) + ?.zoomToLocation(LocationData(it.latLng.latitude, it.latLng.longitude, null)) LiveLocationMapMarkerOptionsDialog(requireContext()) .apply { @@ -332,7 +332,7 @@ class LiveLocationMapViewFragment : .find { it.matrixItem.id == userId } ?.locationData ?.let { locationData -> - mapboxMap?.get()?.zoomToLocation(locationData, preserveCurrentZoomLevel = false) + mapboxMap?.get()?.zoomToLocation(locationData, true) } } diff --git a/vector/src/main/java/im/vector/app/features/raw/wellknown/WellknownService.kt b/vector/src/main/java/im/vector/app/features/raw/wellknown/WellknownService.kt new file mode 100644 index 00000000000..6c07a0f14c4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/raw/wellknown/WellknownService.kt @@ -0,0 +1,43 @@ +/* + * 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.raw.wellknown + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.raw.RawService +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class WellknownService @Inject constructor( + private val rawService: RawService +) { + private var elementWellKnown : ElementWellKnown? = null + + suspend fun getElementWellknown(domain: String): ElementWellKnown? { + elementWellKnown = withContext(Dispatchers.IO) { + tryOrNull { rawService.getWellknown(domain) } + ?.let { ElementWellKnownMapper.from(it) } + } + return elementWellKnown + } + + fun getMapStyleUrl() : String? { + return elementWellKnown?.mapTileServerConfig?.mapStyleUrl + } +} diff --git a/vector/src/test/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModelTest.kt b/vector/src/test/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModelTest.kt index 66eee0dc6d1..960213548f8 100644 --- a/vector/src/test/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModelTest.kt @@ -17,13 +17,9 @@ package im.vector.app.features.attachments import com.airbnb.mvrx.test.MavericksTestRule -import im.vector.app.features.location.UrlMapProvider -import im.vector.app.test.fakes.FakeUrlMapProvider import im.vector.app.test.fakes.FakeVectorFeatures import im.vector.app.test.fakes.FakeVectorPreferences import im.vector.app.test.test -import io.mockk.every -import io.mockk.mockk import io.mockk.verifyOrder import org.junit.Before import org.junit.Rule @@ -37,7 +33,6 @@ internal class AttachmentTypeSelectorViewModelTest { private val fakeVectorFeatures = FakeVectorFeatures() private val fakeVectorPreferences = FakeVectorPreferences() private val initialState = AttachmentTypeSelectorViewState() - private val fakeUrlMapProvider = FakeUrlMapProvider() @Before fun setUp() { @@ -62,7 +57,6 @@ internal class AttachmentTypeSelectorViewModelTest { @Test fun `given location sharing is enabled, then location sharing option is visible`() { fakeVectorFeatures.givenLocationSharing(isEnabled = true) - fakeUrlMapProvider.givenAvailableConfiguration() createViewModel() .test() .assertStates( @@ -75,22 +69,6 @@ internal class AttachmentTypeSelectorViewModelTest { .finish() } - @Test - fun `given location sharing is enabled, but no mapserver configuration available, then location sharing option is not visible`() { - fakeVectorFeatures.givenLocationSharing(isEnabled = true) - fakeUrlMapProvider.givenNotAvailableConfiguration() - createViewModel() - .test() - .assertStates( - listOf( - initialState.copy( - isLocationVisible = false - ), - ) - ) - .finish() - } - @Test fun `given voice broadcast is enabled, then voice broadcast option is visible`() { fakeVectorFeatures.givenVoiceBroadcast(isEnabled = true) @@ -157,8 +135,7 @@ internal class AttachmentTypeSelectorViewModelTest { return AttachmentTypeSelectorViewModel( initialState, vectorFeatures = fakeVectorFeatures, - vectorPreferences = fakeVectorPreferences.instance, - urlMapProvider = fakeUrlMapProvider.instance + vectorPreferences = fakeVectorPreferences.instance ) } } diff --git a/vector/src/test/java/im/vector/app/features/location/UrlMapProviderTest.kt b/vector/src/test/java/im/vector/app/features/location/UrlMapProviderTest.kt new file mode 100644 index 00000000000..3d89fa439f9 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/location/UrlMapProviderTest.kt @@ -0,0 +1,56 @@ +/* + * 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 + +import im.vector.app.test.fakes.FakeWellknownService +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test + +class UrlMapProviderTest { + + @Test + fun `given enabled fallback to maptiler API, when map configuration is not set, then the fallback url should be returned`() { + val wellknownService = FakeWellknownService() + wellknownService.givenMissingMapConfiguration() + val urlMapProvider = UrlMapProvider(wellknownService.instance, LocationSharingConfig("", true)) + urlMapProvider.getMapStyleUrl() shouldBeEqualTo urlMapProvider.fallbackMapUrl + } + + @Test + fun `given enabled fallback to maptiler API, when map configuration is set, then the configurated url should be returned`() { + val wellknownService = FakeWellknownService() + wellknownService.givenValidMapConfiguration() + val urlMapProvider = UrlMapProvider(wellknownService.instance, LocationSharingConfig("", true)) + urlMapProvider.getMapStyleUrl() shouldBeEqualTo wellknownService.A_MAPSTYLE_URL + } + + @Test + fun `given disabled fallback to maptiler API, when map configuration is set, then the configurated url should be returned`() { + val wellknownService = FakeWellknownService() + wellknownService.givenValidMapConfiguration() + val urlMapProvider = UrlMapProvider(wellknownService.instance, LocationSharingConfig("", false)) + urlMapProvider.getMapStyleUrl() shouldBeEqualTo wellknownService.A_MAPSTYLE_URL + } + + @Test + fun `given disabled fallback to maptiler API, when map configuration is not set, then empty string should be returned`() { + val wellknownService = FakeWellknownService() + wellknownService.givenMissingMapConfiguration() + val urlMapProvider = UrlMapProvider(wellknownService.instance, LocationSharingConfig("", false)) + urlMapProvider.getMapStyleUrl() shouldBeEqualTo "" + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeUrlMapProvider.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeWellknownService.kt similarity index 63% rename from vector/src/test/java/im/vector/app/test/fakes/FakeUrlMapProvider.kt rename to vector/src/test/java/im/vector/app/test/fakes/FakeWellknownService.kt index 92a97d6d00d..2d865159676 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeUrlMapProvider.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeWellknownService.kt @@ -16,18 +16,20 @@ package im.vector.app.test.fakes -import im.vector.app.features.location.UrlMapProvider +import im.vector.app.features.raw.wellknown.WellknownService import io.mockk.every import io.mockk.mockk -class FakeUrlMapProvider { - val instance = mockk() +class FakeWellknownService { + val A_MAPSTYLE_URL = "https://example.com/style.json" - fun givenAvailableConfiguration() { - every { instance.isMapConfigurationAvailable() } returns true + val instance = mockk() + + fun givenMissingMapConfiguration() { + every { instance.getMapStyleUrl() } returns null } - fun givenNotAvailableConfiguration() { - every { instance.isMapConfigurationAvailable() } returns false + fun givenValidMapConfiguration() { + every { instance.getMapStyleUrl() } returns A_MAPSTYLE_URL } } From 8f2aa1e0e44c300d157b3406df35c22c273f849a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artjom=20K=C3=B6nig?= Date: Wed, 28 Dec 2022 21:34:55 +0100 Subject: [PATCH 5/6] refactored to KISS --- .../timeline/item/AbsMessageLocationItem.kt | 41 +++++-------------- 1 file changed, 11 insertions(+), 30 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 70b78304952..bee2d77fe58 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 @@ -63,19 +63,6 @@ abstract class AbsMessageLocationItem( bindMap(holder) } - var failedLoadingMapListener : MapView.OnDidFailLoadingMapListener? = null - var finishedLoadingMapListener : MapView.OnDidFinishLoadingMapListener? = null - - override fun unbind(holder: H) { - super.unbind(holder) - failedLoadingMapListener?.let { - holder.staticMapView.removeOnDidFailLoadingMapListener(it) - } - finishedLoadingMapListener?.let { - holder.staticMapView.removeOnDidFinishLoadingMapListener(it) - } - } - override fun onViewAttachedToWindow(holder: H) { super.onViewAttachedToWindow(holder) holder.staticMapView.onStart() @@ -95,30 +82,24 @@ abstract class AbsMessageLocationItem( RoundedCorners(dimensionConverter.dpToPx(8)) } - failedLoadingMapListener = MapView.OnDidFailLoadingMapListener { - holder.staticMapLoadingErrorView.isVisible = true - val mapErrorViewState = MapLoadingErrorViewState(imageCornerTransformation) - holder.staticMapLoadingErrorView.render(mapErrorViewState) - } - - finishedLoadingMapListener = MapView.OnDidFinishLoadingMapListener { - locationPinProvider?.create(locationUserId) { pinDrawable -> - holder.staticMapPinImageView.setImageDrawable(pinDrawable) - } - holder.staticMapLoadingErrorView.isVisible = false - } - holder.staticMapView.apply { updateLayoutParams { width = mapWidth height = mapHeight } - failedLoadingMapListener?.let { - addOnDidFailLoadingMapListener(it) + addOnDidFailLoadingMapListener { + holder.staticMapLoadingErrorView.isVisible = true + val mapErrorViewState = MapLoadingErrorViewState(imageCornerTransformation) + holder.staticMapLoadingErrorView.render(mapErrorViewState) } - finishedLoadingMapListener?.let { - addOnDidFinishLoadingMapListener(it) + + addOnDidFinishLoadingMapListener { + locationPinProvider?.create(locationUserId) { pinDrawable -> + holder.staticMapPinImageView.setImageDrawable(pinDrawable) + } + holder.staticMapLoadingErrorView.isVisible = false } + clipToOutline = true getMapAsync { mapbox -> mapbox.setStyle(mapStyleUrl) From 96780ec5b73bbfcec497c992ed330ef6c212fee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artjom=20K=C3=B6nig?= Date: Thu, 29 Dec 2022 09:34:55 +0100 Subject: [PATCH 6/6] fixed copyright info --- .../home/room/detail/timeline/item/AbsMessageLocationItem.kt | 1 + .../main/java/im/vector/app/features/location/UrlMapProvider.kt | 1 + .../im/vector/app/features/raw/wellknown/WellknownService.kt | 2 +- .../java/im/vector/app/features/location/UrlMapProviderTest.kt | 2 +- .../test/java/im/vector/app/test/fakes/FakeWellknownService.kt | 2 +- 5 files changed, 5 insertions(+), 3 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 bee2d77fe58..c631c99a778 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 @@ -1,5 +1,6 @@ /* * Copyright (c) 2021 New Vector Ltd + * Copyright (c) 2022 BWI GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt b/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt index 38f79e5d5a9..3f6ac95b107 100644 --- a/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt +++ b/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt @@ -1,5 +1,6 @@ /* * Copyright (c) 2022 New Vector Ltd + * Copyright (c) 2022 BWI GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vector/src/main/java/im/vector/app/features/raw/wellknown/WellknownService.kt b/vector/src/main/java/im/vector/app/features/raw/wellknown/WellknownService.kt index 6c07a0f14c4..9d7571fe02b 100644 --- a/vector/src/main/java/im/vector/app/features/raw/wellknown/WellknownService.kt +++ b/vector/src/main/java/im/vector/app/features/raw/wellknown/WellknownService.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright (c) 2022 BWI GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vector/src/test/java/im/vector/app/features/location/UrlMapProviderTest.kt b/vector/src/test/java/im/vector/app/features/location/UrlMapProviderTest.kt index 3d89fa439f9..48c8b1c1e0a 100644 --- a/vector/src/test/java/im/vector/app/features/location/UrlMapProviderTest.kt +++ b/vector/src/test/java/im/vector/app/features/location/UrlMapProviderTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright (c) 2022 BWI GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeWellknownService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeWellknownService.kt index 2d865159676..beed579612c 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeWellknownService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeWellknownService.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright (c) 2022 BWI GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License.