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 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 bb1b0fbd7b5..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 @@ -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,8 @@ 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 import org.matrix.android.sdk.api.util.MatrixItem @@ -98,10 +100,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, false, INITIAL_MAP_ZOOM_IN_TIMELINE) + } + mapbox.uiSettings.setAllGesturesEnabled(false) + } safeLocationUiData.locationPinProvider.create(safeLocationUiData.locationOwnerId) { pinDrawable -> GlideApp.with(holder.staticMapPinImageView) @@ -124,7 +129,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..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 @@ -27,6 +27,7 @@ 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( 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..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 @@ -36,9 +36,11 @@ 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 +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 @@ -54,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 @@ -82,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, @@ -231,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) } } @@ -382,7 +385,8 @@ class HomeActivityViewModel @AssistedInject constructor( Timber.w("## No session to init cross signing or bootstrap") } - val elementWellKnown = rawService.getElementWellknown(session.sessionParams) + val elementWellKnown = wellknownService.getElementWellknown(session.sessionParams.userId.getServerName()) + 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/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..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. @@ -16,35 +17,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.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 +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 @@ -64,8 +64,17 @@ abstract class AbsMessageLocationItem( bindMap(holder) } + 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 location = locationUrl ?: return val messageLayout = attributes.informationData.messageLayout val imageCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) { messageLayout.cornersRadius.granularRoundedCorners() @@ -73,53 +82,47 @@ abstract class AbsMessageLocationItem( val dimensionConverter = DimensionConverter(holder.view.resources) RoundedCorners(dimensionConverter.dpToPx(8)) } - holder.staticMapImageView.updateLayoutParams { - width = mapWidth - height = mapHeight + + holder.staticMapView.apply { + updateLayoutParams { + width = mapWidth + height = mapHeight + } + addOnDidFailLoadingMapListener { + holder.staticMapLoadingErrorView.isVisible = true + val mapErrorViewState = MapLoadingErrorViewState(imageCornerTransformation) + holder.staticMapLoadingErrorView.render(mapErrorViewState) + } + + addOnDidFinishLoadingMapListener { + locationPinProvider?.create(locationUserId) { pinDrawable -> + holder.staticMapPinImageView.setImageDrawable(pinDrawable) + } + holder.staticMapLoadingErrorView.isVisible = false + } + + 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 + } + } } - 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) } 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/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/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..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,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) { - val zoomLevel = if (preserveCurrentZoomLevel && this?.cameraPosition != null) { - cameraPosition.zoom +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 + } } else { - INITIAL_MAP_ZOOM_IN_PREVIEW - } - this?.easeCamera { - CameraPosition.Builder() - .target(LatLng(locationData.latitude, locationData.longitude)) - .zoom(zoomLevel) - .build() + 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..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. @@ -16,52 +17,25 @@ 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 androidx.annotation.VisibleForTesting +import im.vector.app.features.raw.wellknown.WellknownService import javax.inject.Inject +import javax.inject.Singleton +@Singleton class UrlMapProvider @Inject constructor( - private val session: Session, - private val rawService: RawService, - locationSharingConfig: LocationSharingConfig, + private val wellknownService: WellknownService, + private val locationSharingConfig: LocationSharingConfig, ) { private val keyParam = "?key=${locationSharingConfig.mapTilerKey}" - private val fallbackMapUrl = buildString { + @VisibleForTesting + val fallbackMapUrl = buildString { append(MAP_BASE_URL) append(keyParam) } - suspend fun getMapUrl(): String { - val upstreamMapUrl = tryOrNull { rawService.getElementWellknown(session.sessionParams) } - ?.getBestMapTileServerConfig() - ?.mapStyleUrl - return upstreamMapUrl ?: fallbackMapUrl - } - - 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 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 942021dd649..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 @@ -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 { @@ -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/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/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..9d7571fe02b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/raw/wellknown/WellknownService.kt @@ -0,0 +1,43 @@ +/* + * 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. + * 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/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" /> - - 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..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 @@ -57,7 +57,6 @@ internal class AttachmentTypeSelectorViewModelTest { @Test fun `given location sharing is enabled, then location sharing option is visible`() { fakeVectorFeatures.givenLocationSharing(isEnabled = true) - createViewModel() .test() .assertStates( @@ -136,7 +135,7 @@ internal class AttachmentTypeSelectorViewModelTest { return AttachmentTypeSelectorViewModel( initialState, vectorFeatures = fakeVectorFeatures, - vectorPreferences = fakeVectorPreferences.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..48c8b1c1e0a --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/location/UrlMapProviderTest.kt @@ -0,0 +1,56 @@ +/* + * 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. + * 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/FakeWellknownService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeWellknownService.kt new file mode 100644 index 00000000000..beed579612c --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeWellknownService.kt @@ -0,0 +1,35 @@ +/* + * 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. + * 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.raw.wellknown.WellknownService +import io.mockk.every +import io.mockk.mockk + +class FakeWellknownService { + val A_MAPSTYLE_URL = "https://example.com/style.json" + + val instance = mockk() + + fun givenMissingMapConfiguration() { + every { instance.getMapStyleUrl() } returns null + } + + fun givenValidMapConfiguration() { + every { instance.getMapStyleUrl() } returns A_MAPSTYLE_URL + } +}