From 9256b5671b87d9935c36e852534e6d741f6ecbcb Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Fri, 4 Mar 2022 09:16:12 +0100 Subject: [PATCH 01/19] Show options menu on top of the map --- .../location/LocationSharingFragment.kt | 28 ++++++++++++++++--- .../location/LocationSharingViewState.kt | 1 + .../vector/app/features/location/MapState.kt | 4 ++- .../app/features/location/MapTilerMapView.kt | 2 ++ .../res/layout/fragment_location_sharing.xml | 6 ++-- 5 files changed, 34 insertions(+), 7 deletions(-) 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 b1033f2797..941782b385 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 @@ -114,9 +114,9 @@ class LocationSharingFragment @Inject constructor( } override fun invalidate() = withState(viewModel) { state -> - views.mapView.render(state.toMapState()) - views.shareLocationGpsLoading.isGone = state.lastKnownLocation != null + updateMap(state) updateUserAvatar(state.userItem) + views.shareLocationGpsLoading.isGone = state.lastKnownLocation != null } private fun handleLocationNotAvailableError() { @@ -132,8 +132,10 @@ class LocationSharingFragment @Inject constructor( private fun initOptionsPicker() { // TODO - // change the options dynamically depending on the current chosen location - views.shareLocationOptionsPicker.render(LocationSharingOption.USER_CURRENT) + // change the pin dynamically depending on the current chosen location: cf. LocationPinProvider + // make the pin stay at the center of the map: selected location is the center of the map + // need changes in the event sent when this is a pin drop location? + // need changes in the parsing of events when receiving pin drop location?: should it be shown with user avatar or with pin? views.shareLocationOptionsPicker.optionPinned.debouncedClicks { // TODO } @@ -145,6 +147,24 @@ class LocationSharingFragment @Inject constructor( } } + private fun updateMap(state: LocationSharingViewState) { + // first update the options view + if (state.isUserLocation) { + // TODO activate USER_LIVE option when implemented + views.shareLocationOptionsPicker.render( + LocationSharingOption.USER_CURRENT + ) + } else { + views.shareLocationOptionsPicker.render( + LocationSharingOption.PINNED + ) + } + val mapState = state + .toMapState() + .copy(logoMarginBottom = views.shareLocationOptionsPicker.height) + views.mapView.render(mapState) + } + private fun updateUserAvatar(userItem: MatrixItem.UserItem?) { userItem?.takeUnless { hasRenderedUserAvatar } ?.let { diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt index e63206f515..dc18c028f6 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt @@ -30,6 +30,7 @@ enum class LocationSharingMode(@StringRes val titleRes: Int) { data class LocationSharingViewState( val roomId: String, val mode: LocationSharingMode, + val isUserLocation: Boolean = false, val userItem: MatrixItem.UserItem? = null, val lastKnownLocation: LocationData? = null, val pinDrawable: Drawable? = null diff --git a/vector/src/main/java/im/vector/app/features/location/MapState.kt b/vector/src/main/java/im/vector/app/features/location/MapState.kt index d001457e4f..f4e2ce7f5b 100644 --- a/vector/src/main/java/im/vector/app/features/location/MapState.kt +++ b/vector/src/main/java/im/vector/app/features/location/MapState.kt @@ -17,10 +17,12 @@ package im.vector.app.features.location import android.graphics.drawable.Drawable +import androidx.annotation.Px data class MapState( val zoomOnlyOnce: Boolean, val pinLocationData: LocationData? = null, val pinId: String, - val pinDrawable: Drawable? = null + val pinDrawable: Drawable? = null, + @Px val logoMarginBottom: Int = 0 ) diff --git a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt index dd80f701f6..85cbeda805 100644 --- a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt +++ b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt @@ -68,6 +68,8 @@ class MapTilerMapView @JvmOverloads constructor( pendingState = state } + safeMapRefs.map.uiSettings.setLogoMargins(0, 0, 0, state.logoMarginBottom) + state.pinDrawable?.let { pinDrawable -> if (!safeMapRefs.style.isFullyLoaded || safeMapRefs.style.getImage(state.pinId) == null) { diff --git a/vector/src/main/res/layout/fragment_location_sharing.xml b/vector/src/main/res/layout/fragment_location_sharing.xml index 3d07e4438d..f54cefb4ee 100644 --- a/vector/src/main/res/layout/fragment_location_sharing.xml +++ b/vector/src/main/res/layout/fragment_location_sharing.xml @@ -7,10 +7,12 @@ From a1d155df71b32de553f27f950ff7d079ecdbade6 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Fri, 4 Mar 2022 15:32:36 +0100 Subject: [PATCH 02/19] Adding static pin at the center of the map --- .../location/LocationPreviewFragment.kt | 2 +- .../location/LocationSharingFragment.kt | 35 ++++++++++++++----- .../location/LocationSharingViewModel.kt | 4 +-- .../location/LocationSharingViewState.kt | 6 ++-- .../vector/app/features/location/MapState.kt | 2 +- .../app/features/location/MapTilerMapView.kt | 14 +++++++- .../res/layout/fragment_location_sharing.xml | 20 ++++++++++- vector/src/main/res/values/strings.xml | 1 + 8 files changed, 67 insertions(+), 17 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt index db837f4823..5d823e53a6 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt @@ -123,7 +123,7 @@ class LocationPreviewFragment @Inject constructor( views.mapView.render( MapState( zoomOnlyOnce = true, - pinLocationData = location, + userLocationData = location, pinId = args.locationOwnerId ?: DEFAULT_PIN_ID, pinDrawable = pinDrawable ) 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 941782b385..6d2f70b075 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 @@ -16,6 +16,7 @@ package im.vector.app.features.location +import android.graphics.drawable.Drawable import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -52,6 +53,7 @@ class LocationSharingFragment @Inject constructor( private var mapView: WeakReference? = null private var hasRenderedUserAvatar = false + private var hasUpdatedPin = false override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLocationSharingBinding { return FragmentLocationSharingBinding.inflate(inflater, container, false) @@ -116,7 +118,11 @@ class LocationSharingFragment @Inject constructor( override fun invalidate() = withState(viewModel) { state -> updateMap(state) updateUserAvatar(state.userItem) - views.shareLocationGpsLoading.isGone = state.lastKnownLocation != null + if(!hasUpdatedPin && state.pinDrawable != null) { + hasUpdatedPin = true + updateStaticPin(state.pinDrawable) + } + views.shareLocationGpsLoading.isGone = state.lastKnownUserLocation != null } private fun handleLocationNotAvailableError() { @@ -132,10 +138,14 @@ class LocationSharingFragment @Inject constructor( private fun initOptionsPicker() { // TODO + // move pin creation into the Fragment + // create a useCase to compare pinnedLocation and userLocation // change the pin dynamically depending on the current chosen location: cf. LocationPinProvider - // make the pin stay at the center of the map: selected location is the center of the map + // reset map to user location when clicking on reset icon // need changes in the event sent when this is a pin drop location? // need changes in the parsing of events when receiving pin drop location?: should it be shown with user avatar or with pin? + // set no option at start + views.shareLocationOptionsPicker.render() views.shareLocationOptionsPicker.optionPinned.debouncedClicks { // TODO } @@ -148,8 +158,10 @@ class LocationSharingFragment @Inject constructor( } private fun updateMap(state: LocationSharingViewState) { - // first update the options view - if (state.isUserLocation) { + // first, update the options view + // TODO compute distance between userLocation and location at center of map + val isUserLocation = true + if (isUserLocation) { // TODO activate USER_LIVE option when implemented views.shareLocationOptionsPicker.render( LocationSharingOption.USER_CURRENT @@ -159,10 +171,13 @@ class LocationSharingFragment @Inject constructor( LocationSharingOption.PINNED ) } - val mapState = state - .toMapState() - .copy(logoMarginBottom = views.shareLocationOptionsPicker.height) - views.mapView.render(mapState) + // then, update the map using the height of the options view after it has been rendered + views.shareLocationOptionsPicker.post { + val mapState = state + .toMapState() + .copy(logoMarginBottom = views.shareLocationOptionsPicker.height) + views.mapView.render(mapState) + } } private fun updateUserAvatar(userItem: MatrixItem.UserItem?) { @@ -174,4 +189,8 @@ class LocationSharingFragment @Inject constructor( views.shareLocationOptionsPicker.optionUserCurrent.setIconBackgroundTint(tintColor) } } + + private fun updateStaticPin(drawable: Drawable) { + views.shareLocationPin.setImageDrawable(drawable) + } } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index 989ec255e5..07ad77b277 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -76,7 +76,7 @@ class LocationSharingViewModel @AssistedInject constructor( } private fun handleShareLocation() = withState { state -> - state.lastKnownLocation?.let { location -> + state.lastKnownUserLocation?.let { location -> room.sendLocation( latitude = location.latitude, longitude = location.longitude, @@ -90,7 +90,7 @@ class LocationSharingViewModel @AssistedInject constructor( override fun onLocationUpdate(locationData: LocationData) { setState { - copy(lastKnownLocation = locationData) + copy(lastKnownUserLocation = locationData) } } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt index dc18c028f6..4b8587c40c 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt @@ -30,9 +30,9 @@ enum class LocationSharingMode(@StringRes val titleRes: Int) { data class LocationSharingViewState( val roomId: String, val mode: LocationSharingMode, - val isUserLocation: Boolean = false, val userItem: MatrixItem.UserItem? = null, - val lastKnownLocation: LocationData? = null, + val lastKnownUserLocation: LocationData? = null, + // TODO move pin drawable creation into the view? val pinDrawable: Drawable? = null ) : MavericksState { @@ -44,7 +44,7 @@ data class LocationSharingViewState( fun LocationSharingViewState.toMapState() = MapState( zoomOnlyOnce = true, - pinLocationData = lastKnownLocation, + userLocationData = lastKnownUserLocation, pinId = DEFAULT_PIN_ID, pinDrawable = pinDrawable ) diff --git a/vector/src/main/java/im/vector/app/features/location/MapState.kt b/vector/src/main/java/im/vector/app/features/location/MapState.kt index f4e2ce7f5b..fb6bac3ffe 100644 --- a/vector/src/main/java/im/vector/app/features/location/MapState.kt +++ b/vector/src/main/java/im/vector/app/features/location/MapState.kt @@ -21,7 +21,7 @@ import androidx.annotation.Px data class MapState( val zoomOnlyOnce: Boolean, - val pinLocationData: LocationData? = null, + val userLocationData: LocationData? = null, val pinId: String, val pinDrawable: Drawable? = null, @Px val logoMarginBottom: Int = 0 diff --git a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt index 85cbeda805..dc99b3973a 100644 --- a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt +++ b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt @@ -70,6 +70,9 @@ class MapTilerMapView @JvmOverloads constructor( safeMapRefs.map.uiSettings.setLogoMargins(0, 0, 0, state.logoMarginBottom) + // TODO display a dot pin for userLocation instead of pinDrawable + // TODO add reset to user location button + // TODO check conflict of rendering with preview location in timeline state.pinDrawable?.let { pinDrawable -> if (!safeMapRefs.style.isFullyLoaded || safeMapRefs.style.getImage(state.pinId) == null) { @@ -77,7 +80,7 @@ class MapTilerMapView @JvmOverloads constructor( } } - state.pinLocationData?.let { locationData -> + state.userLocationData?.let { locationData -> if (!initZoomDone || !state.zoomOnlyOnce) { zoomToLocation(locationData.latitude, locationData.longitude) initZoomDone = true @@ -100,4 +103,13 @@ class MapTilerMapView @JvmOverloads constructor( .zoom(INITIAL_MAP_ZOOM_IN_PREVIEW) .build() } + + fun getLocationOfMapCenter(): LocationData? = + mapRefs?.map?.cameraPosition?.target?.let { target -> + LocationData( + latitude = target.latitude, + longitude = target.longitude, + uncertainty = null + ) + } } diff --git a/vector/src/main/res/layout/fragment_location_sharing.xml b/vector/src/main/res/layout/fragment_location_sharing.xml index f54cefb4ee..abc390d1b0 100644 --- a/vector/src/main/res/layout/fragment_location_sharing.xml +++ b/vector/src/main/res/layout/fragment_location_sharing.xml @@ -10,12 +10,30 @@ android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" app:mapbox_renderTextureMode="true" tools:background="#4F00" /> + + + + Map Share location + Pin of selected location on map Share my current location Share my current location Share live location From dec075faf384da2acb1e8edc0c79bd8a96c73132 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Fri, 4 Mar 2022 15:41:27 +0100 Subject: [PATCH 03/19] Handling press on pinned location option --- .../features/location/LocationSharingAction.kt | 3 ++- .../features/location/LocationSharingFragment.kt | 7 ++++--- .../location/LocationSharingViewModel.kt | 16 +++++++++++++--- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt index 01319ef6c7..a5adf4d2af 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt @@ -19,5 +19,6 @@ package im.vector.app.features.location import im.vector.app.core.platform.VectorViewModelAction sealed class LocationSharingAction : VectorViewModelAction { - object OnShareLocation : LocationSharingAction() + object CurrentUserLocationSharingAction : LocationSharingAction() + data class PinnedLocationSharingAction(val locationData: LocationData?) : LocationSharingAction() } 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 6d2f70b075..57550d930d 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt @@ -118,7 +118,7 @@ class LocationSharingFragment @Inject constructor( override fun invalidate() = withState(viewModel) { state -> updateMap(state) updateUserAvatar(state.userItem) - if(!hasUpdatedPin && state.pinDrawable != null) { + if (!hasUpdatedPin && state.pinDrawable != null) { hasUpdatedPin = true updateStaticPin(state.pinDrawable) } @@ -147,10 +147,11 @@ class LocationSharingFragment @Inject constructor( // set no option at start views.shareLocationOptionsPicker.render() views.shareLocationOptionsPicker.optionPinned.debouncedClicks { - // TODO + val selectedLocation = views.mapView.getLocationOfMapCenter() + viewModel.handle(LocationSharingAction.PinnedLocationSharingAction(selectedLocation)) } views.shareLocationOptionsPicker.optionUserCurrent.debouncedClicks { - viewModel.handle(LocationSharingAction.OnShareLocation) + viewModel.handle(LocationSharingAction.CurrentUserLocationSharingAction) } views.shareLocationOptionsPicker.optionUserLive.debouncedClicks { // TODO diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index 07ad77b277..5dd826708d 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -71,12 +71,22 @@ class LocationSharingViewModel @AssistedInject constructor( override fun handle(action: LocationSharingAction) { when (action) { - LocationSharingAction.OnShareLocation -> handleShareLocation() + LocationSharingAction.CurrentUserLocationSharingAction -> handleCurrentUserLocationSharingAction() + is LocationSharingAction.PinnedLocationSharingAction -> handlePinnedLocationSharingAction(action) }.exhaustive } - private fun handleShareLocation() = withState { state -> - state.lastKnownUserLocation?.let { location -> + private fun handleCurrentUserLocationSharingAction() = withState { state -> + shareLocation(state.lastKnownUserLocation) + } + + private fun handlePinnedLocationSharingAction(action: LocationSharingAction.PinnedLocationSharingAction) { + // TODO check if we can use the same api than for user location? + shareLocation(action.locationData) + } + + private fun shareLocation(locationData: LocationData?) { + locationData?.let { location -> room.sendLocation( latitude = location.latitude, longitude = location.longitude, From 01879e252d2c0a4a552d21375060a9d207aef336 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Fri, 4 Mar 2022 18:06:24 +0100 Subject: [PATCH 04/19] Comparing target and user location using Flow in ViewModel --- .../location/LocationSharingAction.kt | 1 + .../location/LocationSharingFragment.kt | 24 +++++++---- .../location/LocationSharingViewModel.kt | 38 ++++++++++++++++- .../location/LocationSharingViewState.kt | 2 + .../location/LocationTargetChangeListener.kt | 21 ++++++++++ .../app/features/location/MapTilerMapView.kt | 38 ++++++++++++----- .../domain/usecase/CompareLocationsUseCase.kt | 41 +++++++++++++++++++ 7 files changed, 145 insertions(+), 20 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationTargetChangeListener.kt create mode 100644 vector/src/main/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCase.kt diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt index a5adf4d2af..3da5641c84 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt @@ -21,4 +21,5 @@ import im.vector.app.core.platform.VectorViewModelAction sealed class LocationSharingAction : VectorViewModelAction { object CurrentUserLocationSharingAction : LocationSharingAction() data class PinnedLocationSharingAction(val locationData: LocationData?) : LocationSharingAction() + data class LocationTargetChangeAction(val locationData: LocationData) : LocationSharingAction() } 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 57550d930d..a6b73d7f36 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 @@ -45,7 +45,7 @@ class LocationSharingFragment @Inject constructor( private val urlMapProvider: UrlMapProvider, private val avatarRenderer: AvatarRenderer, private val matrixItemColorProvider: MatrixItemColorProvider -) : VectorBaseFragment() { +) : VectorBaseFragment(), LocationTargetChangeListener { private val viewModel: LocationSharingViewModel by fragmentViewModel() @@ -66,7 +66,10 @@ class LocationSharingFragment @Inject constructor( views.mapView.onCreate(savedInstanceState) lifecycleScope.launchWhenCreated { - views.mapView.initialize(urlMapProvider.getMapUrl()) + views.mapView.initialize( + url = urlMapProvider.getMapUrl(), + locationTargetChangeListener = this@LocationSharingFragment + ) } initOptionsPicker() @@ -115,6 +118,10 @@ class LocationSharingFragment @Inject constructor( super.onDestroy() } + override fun onLocationTargetChange(target: LocationData) { + viewModel.handle(LocationSharingAction.LocationTargetChangeAction(target)) + } + override fun invalidate() = withState(viewModel) { state -> updateMap(state) updateUserAvatar(state.userItem) @@ -138,17 +145,18 @@ class LocationSharingFragment @Inject constructor( private fun initOptionsPicker() { // TODO - // move pin creation into the Fragment - // create a useCase to compare pinnedLocation and userLocation + // create a useCase to compare 2 locations + // update options menu dynamically // change the pin dynamically depending on the current chosen location: cf. LocationPinProvider + // move pin creation into the Fragment? => need to ask other's opinions // reset map to user location when clicking on reset icon // need changes in the event sent when this is a pin drop location? // need changes in the parsing of events when receiving pin drop location?: should it be shown with user avatar or with pin? // set no option at start views.shareLocationOptionsPicker.render() views.shareLocationOptionsPicker.optionPinned.debouncedClicks { - val selectedLocation = views.mapView.getLocationOfMapCenter() - viewModel.handle(LocationSharingAction.PinnedLocationSharingAction(selectedLocation)) + val targetLocation = views.mapView.getLocationOfMapCenter() + viewModel.handle(LocationSharingAction.PinnedLocationSharingAction(targetLocation)) } views.shareLocationOptionsPicker.optionUserCurrent.debouncedClicks { viewModel.handle(LocationSharingAction.CurrentUserLocationSharingAction) @@ -160,9 +168,7 @@ class LocationSharingFragment @Inject constructor( private fun updateMap(state: LocationSharingViewState) { // first, update the options view - // TODO compute distance between userLocation and location at center of map - val isUserLocation = true - if (isUserLocation) { + if (state.areTargetAndUserLocationEqual) { // TODO activate USER_LIVE option when implemented views.shareLocationOptionsPicker.render( LocationSharingOption.USER_CURRENT diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index 5dd826708d..3fafbd0519 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -25,18 +25,31 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider +import im.vector.app.features.location.domain.usecase.CompareLocationsUseCase +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.sample +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.toMatrixItem +private const val TARGET_LOCATION_CHANGE_SAMPLING_PERIOD_IN_MS = 100L + class LocationSharingViewModel @AssistedInject constructor( @Assisted private val initialState: LocationSharingViewState, private val locationTracker: LocationTracker, private val locationPinProvider: LocationPinProvider, - private val session: Session + private val session: Session, + private val compareLocationsUseCase: CompareLocationsUseCase ) : VectorViewModel(initialState), LocationTracker.Callback { private val room = session.getRoom(initialState.roomId)!! + private val locationTargetFlow = MutableSharedFlow() + @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { override fun create(initialState: LocationSharingViewState): LocationSharingViewModel @@ -48,6 +61,7 @@ class LocationSharingViewModel @AssistedInject constructor( locationTracker.start(this) setUserItem() createPin() + compareTargetAndUserLocation() } private fun setUserItem() { @@ -64,6 +78,21 @@ class LocationSharingViewModel @AssistedInject constructor( } } + private fun compareTargetAndUserLocation() { + locationTargetFlow + .sample(TARGET_LOCATION_CHANGE_SAMPLING_PERIOD_IN_MS) + .map { compareTargetLocation(it) } + .distinctUntilChanged() + .onEach { setState { copy(areTargetAndUserLocationEqual = it) } } + .launchIn(viewModelScope) + } + + private suspend fun compareTargetLocation(targetLocation: LocationData): Boolean { + return awaitState().lastKnownUserLocation + ?.let { userLocation -> compareLocationsUseCase.execute(userLocation, targetLocation) } + ?: false + } + override fun onCleared() { super.onCleared() locationTracker.stop() @@ -73,6 +102,7 @@ class LocationSharingViewModel @AssistedInject constructor( when (action) { LocationSharingAction.CurrentUserLocationSharingAction -> handleCurrentUserLocationSharingAction() is LocationSharingAction.PinnedLocationSharingAction -> handlePinnedLocationSharingAction(action) + is LocationSharingAction.LocationTargetChangeAction -> handleLocationTargetChangeAction(action) }.exhaustive } @@ -98,6 +128,12 @@ class LocationSharingViewModel @AssistedInject constructor( } } + private fun handleLocationTargetChangeAction(action: LocationSharingAction.LocationTargetChangeAction) { + viewModelScope.launch { + locationTargetFlow.emit(action.locationData) + } + } + override fun onLocationUpdate(locationData: LocationData) { setState { copy(lastKnownUserLocation = locationData) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt index 4b8587c40c..8faa392416 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt @@ -31,6 +31,8 @@ data class LocationSharingViewState( val roomId: String, val mode: LocationSharingMode, val userItem: MatrixItem.UserItem? = null, + // TODO declare as nullable when we cannot compare? + val areTargetAndUserLocationEqual: Boolean = true, val lastKnownUserLocation: LocationData? = null, // TODO move pin drawable creation into the view? val pinDrawable: Drawable? = null diff --git a/vector/src/main/java/im/vector/app/features/location/LocationTargetChangeListener.kt b/vector/src/main/java/im/vector/app/features/location/LocationTargetChangeListener.kt new file mode 100644 index 0000000000..07e3afb399 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationTargetChangeListener.kt @@ -0,0 +1,21 @@ +/* + * 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 + +interface LocationTargetChangeListener { + fun onLocationTargetChange(target: LocationData) +} diff --git a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt index dc99b3973a..f8762d4670 100644 --- a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt +++ b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt @@ -48,18 +48,36 @@ class MapTilerMapView @JvmOverloads constructor( /** * For location fragments */ - fun initialize(url: String) { + fun initialize(url: String, locationTargetChangeListener: LocationTargetChangeListener? = null) { Timber.d("## Location: initialize") getMapAsync { map -> - map.setStyle(url) { style -> - mapRefs = MapRefs( - map, - SymbolManager(this, map, style), - style - ) - pendingState?.let { render(it) } - pendingState = null - } + initMapStyle(map, url) + notifyLocationOfMapCenter(locationTargetChangeListener) + listenCameraMove(map, locationTargetChangeListener) + } + } + + private fun initMapStyle(map: MapboxMap, url: String) { + map.setStyle(url) { style -> + mapRefs = MapRefs( + map, + SymbolManager(this, map, style), + style + ) + pendingState?.let { render(it) } + pendingState = null + } + } + + private fun listenCameraMove(map: MapboxMap, locationTargetChangeListener: LocationTargetChangeListener?) { + map.addOnCameraMoveListener { + notifyLocationOfMapCenter(locationTargetChangeListener) + } + } + + private fun notifyLocationOfMapCenter(locationTargetChangeListener: LocationTargetChangeListener?) { + getLocationOfMapCenter()?.let { target -> + locationTargetChangeListener?.onLocationTargetChange(target) } } diff --git a/vector/src/main/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCase.kt b/vector/src/main/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCase.kt new file mode 100644 index 0000000000..a2a5486923 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCase.kt @@ -0,0 +1,41 @@ +/* + * 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.domain.usecase + +import im.vector.app.features.location.LocationData +import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.session.Session +import javax.inject.Inject + +/** + * Use case to check if 2 locations can be considered as equal. + */ +class CompareLocationsUseCase @Inject constructor( + private val session: Session +) { + + // TODO unit test + /** + * Compare the 2 given locations. + * @return true when they are really close and could be considered as the same location, false otherwise + */ + suspend fun execute(location1: LocationData, location2: LocationData): Boolean = + withContext(session.coroutineDispatchers.io) { + // TODO implement real comparison + location1 == location2 + } +} From 6fc6bf1c7d0fb2cd2dac53f1ec1a6b6e2477a73e Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Mon, 7 Mar 2022 14:33:59 +0100 Subject: [PATCH 05/19] Compare locations use case implementation --- .../app/features/location/LocationSharingFragment.kt | 9 ++++----- .../features/location/LocationSharingViewModel.kt | 4 ++-- .../features/location/LocationSharingViewState.kt | 3 +-- .../domain/usecase/CompareLocationsUseCase.kt | 12 ++++++++++-- 4 files changed, 17 insertions(+), 11 deletions(-) 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 a6b73d7f36..4ffe16d738 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 @@ -147,7 +147,6 @@ class LocationSharingFragment @Inject constructor( // TODO // create a useCase to compare 2 locations // update options menu dynamically - // change the pin dynamically depending on the current chosen location: cf. LocationPinProvider // move pin creation into the Fragment? => need to ask other's opinions // reset map to user location when clicking on reset icon // need changes in the event sent when this is a pin drop location? @@ -168,15 +167,15 @@ class LocationSharingFragment @Inject constructor( private fun updateMap(state: LocationSharingViewState) { // first, update the options view - if (state.areTargetAndUserLocationEqual) { + when (state.areTargetAndUserLocationEqual) { // TODO activate USER_LIVE option when implemented - views.shareLocationOptionsPicker.render( + true -> views.shareLocationOptionsPicker.render( LocationSharingOption.USER_CURRENT ) - } else { - views.shareLocationOptionsPicker.render( + false -> views.shareLocationOptionsPicker.render( LocationSharingOption.PINNED ) + else -> views.shareLocationOptionsPicker.render() } // then, update the map using the height of the options view after it has been rendered views.shareLocationOptionsPicker.post { diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index 3fafbd0519..b68c058f13 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -83,14 +83,14 @@ class LocationSharingViewModel @AssistedInject constructor( .sample(TARGET_LOCATION_CHANGE_SAMPLING_PERIOD_IN_MS) .map { compareTargetLocation(it) } .distinctUntilChanged() + // TODO change the pin dynamically depending on the current chosen location: cf. LocationPinProvider .onEach { setState { copy(areTargetAndUserLocationEqual = it) } } .launchIn(viewModelScope) } - private suspend fun compareTargetLocation(targetLocation: LocationData): Boolean { + private suspend fun compareTargetLocation(targetLocation: LocationData): Boolean? { return awaitState().lastKnownUserLocation ?.let { userLocation -> compareLocationsUseCase.execute(userLocation, targetLocation) } - ?: false } override fun onCleared() { diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt index 8faa392416..fb730ce7e8 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt @@ -31,8 +31,7 @@ data class LocationSharingViewState( val roomId: String, val mode: LocationSharingMode, val userItem: MatrixItem.UserItem? = null, - // TODO declare as nullable when we cannot compare? - val areTargetAndUserLocationEqual: Boolean = true, + val areTargetAndUserLocationEqual: Boolean? = null, val lastKnownUserLocation: LocationData? = null, // TODO move pin drawable creation into the view? val pinDrawable: Drawable? = null diff --git a/vector/src/main/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCase.kt b/vector/src/main/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCase.kt index a2a5486923..3c3f1bb8f6 100644 --- a/vector/src/main/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCase.kt @@ -16,11 +16,17 @@ package im.vector.app.features.location.domain.usecase +import com.mapbox.mapboxsdk.geometry.LatLng import im.vector.app.features.location.LocationData import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.session.Session import javax.inject.Inject +/** + * Threshold in meters to consider 2 locations as equal. + */ +private const val SAME_LOCATION_THRESHOLD_IN_METERS = 5 + /** * Use case to check if 2 locations can be considered as equal. */ @@ -35,7 +41,9 @@ class CompareLocationsUseCase @Inject constructor( */ suspend fun execute(location1: LocationData, location2: LocationData): Boolean = withContext(session.coroutineDispatchers.io) { - // TODO implement real comparison - location1 == location2 + val loc1 = LatLng(location1.latitude, location1.longitude) + val loc2 = LatLng(location2.latitude, location2.longitude) + val distance = loc1.distanceTo(loc2) + distance <= SAME_LOCATION_THRESHOLD_IN_METERS } } From 93c397d492a2e45b06b98e3e5028a8645da95fe9 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Mon, 7 Mar 2022 14:55:50 +0100 Subject: [PATCH 06/19] Updating location target drawable dynamically --- .../location/LocationSharingFragment.kt | 12 +++------ .../location/LocationSharingViewModel.kt | 27 +++++++++++++------ .../location/LocationSharingViewState.kt | 5 ++-- 3 files changed, 25 insertions(+), 19 deletions(-) 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 4ffe16d738..fb86033065 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 @@ -53,7 +53,6 @@ class LocationSharingFragment @Inject constructor( private var mapView: WeakReference? = null private var hasRenderedUserAvatar = false - private var hasUpdatedPin = false override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLocationSharingBinding { return FragmentLocationSharingBinding.inflate(inflater, container, false) @@ -125,9 +124,8 @@ class LocationSharingFragment @Inject constructor( override fun invalidate() = withState(viewModel) { state -> updateMap(state) updateUserAvatar(state.userItem) - if (!hasUpdatedPin && state.pinDrawable != null) { - hasUpdatedPin = true - updateStaticPin(state.pinDrawable) + if (state.locationTargetDrawable != null) { + updateLocationTargetPin(state.locationTargetDrawable) } views.shareLocationGpsLoading.isGone = state.lastKnownUserLocation != null } @@ -145,10 +143,8 @@ class LocationSharingFragment @Inject constructor( private fun initOptionsPicker() { // TODO - // create a useCase to compare 2 locations - // update options menu dynamically - // move pin creation into the Fragment? => need to ask other's opinions // reset map to user location when clicking on reset icon + // unit tests // need changes in the event sent when this is a pin drop location? // need changes in the parsing of events when receiving pin drop location?: should it be shown with user avatar or with pin? // set no option at start @@ -196,7 +192,7 @@ class LocationSharingFragment @Inject constructor( } } - private fun updateStaticPin(drawable: Drawable) { + private fun updateLocationTargetPin(drawable: Drawable) { views.shareLocationPin.setImageDrawable(drawable) } } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index b68c058f13..4c2be8694f 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.location +import android.graphics.drawable.Drawable import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -60,7 +61,7 @@ class LocationSharingViewModel @AssistedInject constructor( init { locationTracker.start(this) setUserItem() - createPin() + updatePin() compareTargetAndUserLocation() } @@ -68,13 +69,23 @@ class LocationSharingViewModel @AssistedInject constructor( setState { copy(userItem = session.getUser(session.myUserId)?.toMatrixItem()) } } - private fun createPin() { - locationPinProvider.create(session.myUserId) { - setState { - copy( - pinDrawable = it - ) + private fun updatePin(isUserPin: Boolean? = true) { + if (isUserPin == true) { + locationPinProvider.create(userId = session.myUserId) { + updatePinDrawableInState(it) } + } else { + locationPinProvider.create(userId = null) { + updatePinDrawableInState(it) + } + } + } + + private fun updatePinDrawableInState(drawable: Drawable) { + setState { + copy( + locationTargetDrawable = drawable + ) } } @@ -83,8 +94,8 @@ class LocationSharingViewModel @AssistedInject constructor( .sample(TARGET_LOCATION_CHANGE_SAMPLING_PERIOD_IN_MS) .map { compareTargetLocation(it) } .distinctUntilChanged() - // TODO change the pin dynamically depending on the current chosen location: cf. LocationPinProvider .onEach { setState { copy(areTargetAndUserLocationEqual = it) } } + .onEach { updatePin(isUserPin = it) } .launchIn(viewModelScope) } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt index fb730ce7e8..317bc5a721 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt @@ -33,8 +33,7 @@ data class LocationSharingViewState( val userItem: MatrixItem.UserItem? = null, val areTargetAndUserLocationEqual: Boolean? = null, val lastKnownUserLocation: LocationData? = null, - // TODO move pin drawable creation into the view? - val pinDrawable: Drawable? = null + val locationTargetDrawable: Drawable? = null ) : MavericksState { constructor(locationSharingArgs: LocationSharingArgs) : this( @@ -47,5 +46,5 @@ fun LocationSharingViewState.toMapState() = MapState( zoomOnlyOnce = true, userLocationData = lastKnownUserLocation, pinId = DEFAULT_PIN_ID, - pinDrawable = pinDrawable + pinDrawable = locationTargetDrawable ) From 57fcfeb1c1e7174777195c1955944c513cb9b431 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Mon, 7 Mar 2022 16:13:33 +0100 Subject: [PATCH 07/19] Show dot pin for user location --- .../location/LocationSharingViewModel.kt | 1 + .../location/LocationSharingViewState.kt | 3 ++- .../vector/app/features/location/MapState.kt | 1 + .../app/features/location/MapTilerMapView.kt | 25 ++++++++++++------- .../main/res/drawable/ic_location_user.xml | 14 +++++++++++ 5 files changed, 34 insertions(+), 10 deletions(-) create mode 100644 vector/src/main/res/drawable/ic_location_user.xml diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index 4c2be8694f..b721adb93f 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -146,6 +146,7 @@ class LocationSharingViewModel @AssistedInject constructor( } override fun onLocationUpdate(locationData: LocationData) { + // TODO compare location with lastTargetLocation => need to save info into the ViewState setState { copy(lastKnownUserLocation = locationData) } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt index 317bc5a721..e661ff0cc0 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt @@ -46,5 +46,6 @@ fun LocationSharingViewState.toMapState() = MapState( zoomOnlyOnce = true, userLocationData = lastKnownUserLocation, pinId = DEFAULT_PIN_ID, - pinDrawable = locationTargetDrawable + pinDrawable = null, + showPin = areTargetAndUserLocationEqual == false ) diff --git a/vector/src/main/java/im/vector/app/features/location/MapState.kt b/vector/src/main/java/im/vector/app/features/location/MapState.kt index fb6bac3ffe..c4325291a8 100644 --- a/vector/src/main/java/im/vector/app/features/location/MapState.kt +++ b/vector/src/main/java/im/vector/app/features/location/MapState.kt @@ -24,5 +24,6 @@ data class MapState( val userLocationData: LocationData? = null, val pinId: String, val pinDrawable: Drawable? = null, + val showPin: Boolean = true, @Px val logoMarginBottom: Int = 0 ) diff --git a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt index f8762d4670..903b88423a 100644 --- a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt +++ b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt @@ -18,6 +18,7 @@ package im.vector.app.features.location import android.content.Context import android.util.AttributeSet +import androidx.core.content.ContextCompat import com.mapbox.mapboxsdk.camera.CameraPosition import com.mapbox.mapboxsdk.geometry.LatLng import com.mapbox.mapboxsdk.maps.MapView @@ -26,6 +27,7 @@ import com.mapbox.mapboxsdk.maps.Style import com.mapbox.mapboxsdk.plugins.annotation.SymbolManager import com.mapbox.mapboxsdk.plugins.annotation.SymbolOptions import com.mapbox.mapboxsdk.style.layers.Property +import im.vector.app.R import timber.log.Timber class MapTilerMapView @JvmOverloads constructor( @@ -42,6 +44,9 @@ class MapTilerMapView @JvmOverloads constructor( val style: Style ) + private val userLocationDrawable by lazy { + ContextCompat.getDrawable(context, R.drawable.ic_location_user) + } private var mapRefs: MapRefs? = null private var initZoomDone = false @@ -88,13 +93,13 @@ class MapTilerMapView @JvmOverloads constructor( safeMapRefs.map.uiSettings.setLogoMargins(0, 0, 0, state.logoMarginBottom) - // TODO display a dot pin for userLocation instead of pinDrawable // TODO add reset to user location button // TODO check conflict of rendering with preview location in timeline - state.pinDrawable?.let { pinDrawable -> + val pinDrawable = state.pinDrawable ?: userLocationDrawable + pinDrawable?.let { drawable -> if (!safeMapRefs.style.isFullyLoaded || safeMapRefs.style.getImage(state.pinId) == null) { - safeMapRefs.style.addImage(state.pinId, pinDrawable) + safeMapRefs.style.addImage(state.pinId, drawable) } } @@ -105,12 +110,14 @@ class MapTilerMapView @JvmOverloads constructor( } safeMapRefs.symbolManager.deleteAll() - safeMapRefs.symbolManager.create( - SymbolOptions() - .withLatLng(LatLng(locationData.latitude, locationData.longitude)) - .withIconImage(state.pinId) - .withIconAnchor(Property.ICON_ANCHOR_BOTTOM) - ) + if(pinDrawable != null && state.showPin) { + safeMapRefs.symbolManager.create( + SymbolOptions() + .withLatLng(LatLng(locationData.latitude, locationData.longitude)) + .withIconImage(state.pinId) + .withIconAnchor(Property.ICON_ANCHOR_BOTTOM) + ) + } } } diff --git a/vector/src/main/res/drawable/ic_location_user.xml b/vector/src/main/res/drawable/ic_location_user.xml new file mode 100644 index 0000000000..dc6baca65e --- /dev/null +++ b/vector/src/main/res/drawable/ic_location_user.xml @@ -0,0 +1,14 @@ + + + + + + + + + + From 40e92842ea8f564a9afcfb510a5c3aaa4aa216d3 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Tue, 8 Mar 2022 11:49:59 +0100 Subject: [PATCH 08/19] Show icon to reset map to user location --- .../ui-styles/src/main/res/values/dimens.xml | 3 ++ .../location/LocationSharingFragment.kt | 12 +++++- .../location/LocationSharingViewModel.kt | 5 ++- .../app/features/location/MapTilerMapView.kt | 42 +++++++++++++++++-- vector/src/main/res/drawable/btn_locate.xml | 17 ++++++++ vector/src/main/res/drawable/ic_locate.xml | 9 ++++ vector/src/main/res/values/strings.xml | 1 + 7 files changed, 83 insertions(+), 6 deletions(-) create mode 100644 vector/src/main/res/drawable/btn_locate.xml create mode 100644 vector/src/main/res/drawable/ic_locate.xml diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml index 6737f4faf1..08fe557f7c 100644 --- a/library/ui-styles/src/main/res/values/dimens.xml +++ b/library/ui-styles/src/main/res/values/dimens.xml @@ -64,4 +64,7 @@ 10dp + 16dp + 12dp + 8dp 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 fb86033065..dc28c2dba9 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 @@ -67,10 +67,12 @@ class LocationSharingFragment @Inject constructor( lifecycleScope.launchWhenCreated { views.mapView.initialize( url = urlMapProvider.getMapUrl(), + showLocateBtn = true, locationTargetChangeListener = this@LocationSharingFragment ) } + initLocateBtn() initOptionsPicker() viewModel.observeViewEvents { @@ -141,12 +143,18 @@ class LocationSharingFragment @Inject constructor( .show() } + private fun initLocateBtn() { + views.mapView.locateBtn.setOnClickListener { + // TODO retrieve user location and zoom to this location + } + } + private fun initOptionsPicker() { // TODO // reset map to user location when clicking on reset icon + // changes in the event sent when this is a pinned location + // changes in the parsing of events when receiving pinned location: since we may present a different UI // unit tests - // need changes in the event sent when this is a pin drop location? - // need changes in the parsing of events when receiving pin drop location?: should it be shown with user avatar or with pin? // set no option at start views.shareLocationOptionsPicker.render() views.shareLocationOptionsPicker.optionPinned.debouncedClicks { diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index b721adb93f..5ad762adff 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -37,6 +37,9 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.toMatrixItem +/** + * Sampling period to compare target location and user location. + */ private const val TARGET_LOCATION_CHANGE_SAMPLING_PERIOD_IN_MS = 100L class LocationSharingViewModel @AssistedInject constructor( @@ -122,7 +125,7 @@ class LocationSharingViewModel @AssistedInject constructor( } private fun handlePinnedLocationSharingAction(action: LocationSharingAction.PinnedLocationSharingAction) { - // TODO check if we can use the same api than for user location? + // TODO confirm how to differentiate the user location and pinned location events? shareLocation(action.locationData) } diff --git a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt index 903b88423a..1c6a20aadf 100644 --- a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt +++ b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt @@ -18,7 +18,12 @@ package im.vector.app.features.location import android.content.Context import android.util.AttributeSet +import android.view.Gravity +import android.widget.ImageView import androidx.core.content.ContextCompat +import androidx.core.view.marginBottom +import androidx.core.view.marginTop +import androidx.core.view.updateLayoutParams import com.mapbox.mapboxsdk.camera.CameraPosition import com.mapbox.mapboxsdk.geometry.LatLng import com.mapbox.mapboxsdk.maps.MapView @@ -47,16 +52,24 @@ class MapTilerMapView @JvmOverloads constructor( private val userLocationDrawable by lazy { ContextCompat.getDrawable(context, R.drawable.ic_location_user) } + val locateBtn by lazy { createLocateBtn() } private var mapRefs: MapRefs? = null private var initZoomDone = false /** * For location fragments */ - fun initialize(url: String, locationTargetChangeListener: LocationTargetChangeListener? = null) { + fun initialize( + url: String, + showLocateBtn: Boolean = false, // TODO transform into xml attribute + locationTargetChangeListener: LocationTargetChangeListener? = null + ) { Timber.d("## Location: initialize") getMapAsync { map -> initMapStyle(map, url) + if (showLocateBtn) { + showLocateBtn(map) + } notifyLocationOfMapCenter(locationTargetChangeListener) listenCameraMove(map, locationTargetChangeListener) } @@ -86,6 +99,30 @@ class MapTilerMapView @JvmOverloads constructor( } } + private fun createLocateBtn(): ImageView = + ImageView(context).apply { + setImageDrawable(ContextCompat.getDrawable(context, R.drawable.btn_locate)) + contentDescription = context.getString(R.string.a11y_location_share_locate_btn) + layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) + updateLayoutParams { + val marginHorizontal = context.resources.getDimensionPixelOffset(R.dimen.location_sharing_locate_btn_margin_horizontal) + val marginVertical = context.resources.getDimensionPixelOffset(R.dimen.location_sharing_locate_btn_margin_vertical) + setMargins(marginHorizontal, marginVertical, marginHorizontal, marginVertical) + } + updateLayoutParams { + gravity = Gravity.TOP or Gravity.END + } + } + + private fun showLocateBtn(map: MapboxMap) { + addView(locateBtn) + locateBtn.post { + val marginTop = locateBtn.height + locateBtn.marginTop + locateBtn.marginBottom + val marginRight = context.resources.getDimensionPixelOffset(R.dimen.location_sharing_compass_btn_margin_horizontal) + map.uiSettings.setCompassMargins(0, marginTop, marginRight, 0) + } + } + fun render(state: MapState) { val safeMapRefs = mapRefs ?: return Unit.also { pendingState = state @@ -93,7 +130,6 @@ class MapTilerMapView @JvmOverloads constructor( safeMapRefs.map.uiSettings.setLogoMargins(0, 0, 0, state.logoMarginBottom) - // TODO add reset to user location button // TODO check conflict of rendering with preview location in timeline val pinDrawable = state.pinDrawable ?: userLocationDrawable pinDrawable?.let { drawable -> @@ -110,7 +146,7 @@ class MapTilerMapView @JvmOverloads constructor( } safeMapRefs.symbolManager.deleteAll() - if(pinDrawable != null && state.showPin) { + if (pinDrawable != null && state.showPin) { safeMapRefs.symbolManager.create( SymbolOptions() .withLatLng(LatLng(locationData.latitude, locationData.longitude)) diff --git a/vector/src/main/res/drawable/btn_locate.xml b/vector/src/main/res/drawable/btn_locate.xml new file mode 100644 index 0000000000..583b3a97ea --- /dev/null +++ b/vector/src/main/res/drawable/btn_locate.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/vector/src/main/res/drawable/ic_locate.xml b/vector/src/main/res/drawable/ic_locate.xml new file mode 100644 index 0000000000..784665fcdd --- /dev/null +++ b/vector/src/main/res/drawable/ic_locate.xml @@ -0,0 +1,9 @@ + + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 62f6c37428..bbb2bc7740 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2930,6 +2930,7 @@ Share location Pin of selected location on map + Zoom to current location Share my current location Share my current location Share live location From 01aff36597c955f0e6e555a414aa953d48ce12a0 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Tue, 8 Mar 2022 14:11:11 +0100 Subject: [PATCH 09/19] Using style attibute for locate button visibility --- .../values/stylable_map_tiler_map_view.xml | 8 +++ .../location/LocationSharingFragment.kt | 1 - .../app/features/location/MapTilerMapView.kt | 54 +++++++++++++------ .../res/layout/fragment_location_preview.xml | 5 +- .../res/layout/fragment_location_sharing.xml | 1 + 5 files changed, 51 insertions(+), 18 deletions(-) create mode 100644 library/ui-styles/src/main/res/values/stylable_map_tiler_map_view.xml diff --git a/library/ui-styles/src/main/res/values/stylable_map_tiler_map_view.xml b/library/ui-styles/src/main/res/values/stylable_map_tiler_map_view.xml new file mode 100644 index 0000000000..a7c45918af --- /dev/null +++ b/library/ui-styles/src/main/res/values/stylable_map_tiler_map_view.xml @@ -0,0 +1,8 @@ + + + + + + + + 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 dc28c2dba9..8107966de9 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 @@ -67,7 +67,6 @@ class LocationSharingFragment @Inject constructor( lifecycleScope.launchWhenCreated { views.mapView.initialize( url = urlMapProvider.getMapUrl(), - showLocateBtn = true, locationTargetChangeListener = this@LocationSharingFragment ) } diff --git a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt index 1c6a20aadf..5e2c593240 100644 --- a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt +++ b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt @@ -17,6 +17,7 @@ package im.vector.app.features.location import android.content.Context +import android.content.res.TypedArray import android.util.AttributeSet import android.view.Gravity import android.widget.ImageView @@ -55,21 +56,38 @@ class MapTilerMapView @JvmOverloads constructor( val locateBtn by lazy { createLocateBtn() } private var mapRefs: MapRefs? = null private var initZoomDone = false + private var showLocationBtn = false + + init { + context.theme.obtainStyledAttributes( + attrs, + R.styleable.MapTilerMapView, + 0, + 0 + ).run { + try { + setLocateBtnVisibility(this) + } finally { + recycle() + } + } + } + + private fun setLocateBtnVisibility(typedArray: TypedArray) { + showLocationBtn = typedArray.getBoolean(R.styleable.MapTilerMapView_showLocateButton, false) + } /** * For location fragments */ fun initialize( url: String, - showLocateBtn: Boolean = false, // TODO transform into xml attribute locationTargetChangeListener: LocationTargetChangeListener? = null ) { Timber.d("## Location: initialize") getMapAsync { map -> initMapStyle(map, url) - if (showLocateBtn) { - showLocateBtn(map) - } + initLocateBtn(map) notifyLocationOfMapCenter(locationTargetChangeListener) listenCameraMove(map, locationTargetChangeListener) } @@ -87,15 +105,10 @@ class MapTilerMapView @JvmOverloads constructor( } } - private fun listenCameraMove(map: MapboxMap, locationTargetChangeListener: LocationTargetChangeListener?) { - map.addOnCameraMoveListener { - notifyLocationOfMapCenter(locationTargetChangeListener) - } - } - - private fun notifyLocationOfMapCenter(locationTargetChangeListener: LocationTargetChangeListener?) { - getLocationOfMapCenter()?.let { target -> - locationTargetChangeListener?.onLocationTargetChange(target) + private fun initLocateBtn(map: MapboxMap) { + if (showLocationBtn) { + addView(locateBtn) + adjustCompassBtn(map) } } @@ -114,8 +127,7 @@ class MapTilerMapView @JvmOverloads constructor( } } - private fun showLocateBtn(map: MapboxMap) { - addView(locateBtn) + private fun adjustCompassBtn(map: MapboxMap) { locateBtn.post { val marginTop = locateBtn.height + locateBtn.marginTop + locateBtn.marginBottom val marginRight = context.resources.getDimensionPixelOffset(R.dimen.location_sharing_compass_btn_margin_horizontal) @@ -123,6 +135,18 @@ class MapTilerMapView @JvmOverloads constructor( } } + private fun listenCameraMove(map: MapboxMap, locationTargetChangeListener: LocationTargetChangeListener?) { + map.addOnCameraMoveListener { + notifyLocationOfMapCenter(locationTargetChangeListener) + } + } + + private fun notifyLocationOfMapCenter(locationTargetChangeListener: LocationTargetChangeListener?) { + getLocationOfMapCenter()?.let { target -> + locationTargetChangeListener?.onLocationTargetChange(target) + } + } + fun render(state: MapState) { val safeMapRefs = mapRefs ?: return Unit.also { pendingState = state diff --git a/vector/src/main/res/layout/fragment_location_preview.xml b/vector/src/main/res/layout/fragment_location_preview.xml index c2b3bdd739..5499e5b427 100644 --- a/vector/src/main/res/layout/fragment_location_preview.xml +++ b/vector/src/main/res/layout/fragment_location_preview.xml @@ -8,6 +8,7 @@ android:id="@+id/mapView" android:layout_width="match_parent" android:layout_height="match_parent" - app:mapbox_renderTextureMode="true" /> + app:mapbox_renderTextureMode="true" + app:showLocateButton="false" /> - \ No newline at end of file + diff --git a/vector/src/main/res/layout/fragment_location_sharing.xml b/vector/src/main/res/layout/fragment_location_sharing.xml index abc390d1b0..cd15f418ea 100644 --- a/vector/src/main/res/layout/fragment_location_sharing.xml +++ b/vector/src/main/res/layout/fragment_location_sharing.xml @@ -14,6 +14,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:mapbox_renderTextureMode="true" + app:showLocateButton="true" tools:background="#4F00" /> Date: Tue, 8 Mar 2022 14:29:48 +0100 Subject: [PATCH 10/19] Zoom to user location action --- .../vector/app/features/location/LocationSharingAction.kt | 1 + .../app/features/location/LocationSharingFragment.kt | 8 ++++++-- .../app/features/location/LocationSharingViewEvents.kt | 1 + .../app/features/location/LocationSharingViewModel.kt | 7 +++++++ .../im/vector/app/features/location/MapTilerMapView.kt | 3 +-- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt index 3da5641c84..e575367c66 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt @@ -22,4 +22,5 @@ sealed class LocationSharingAction : VectorViewModelAction { object CurrentUserLocationSharingAction : LocationSharingAction() data class PinnedLocationSharingAction(val locationData: LocationData?) : LocationSharingAction() data class LocationTargetChangeAction(val locationData: LocationData) : LocationSharingAction() + object ZoomToUserLocationAction : LocationSharingAction() } 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 8107966de9..79c9daceb4 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 @@ -78,6 +78,7 @@ class LocationSharingFragment @Inject constructor( when (it) { LocationSharingViewEvents.LocationNotAvailableError -> handleLocationNotAvailableError() LocationSharingViewEvents.Close -> activity?.finish() + is LocationSharingViewEvents.ZoomToUserLocation -> handleZoomToUserLocationEvent(it) }.exhaustive } } @@ -144,13 +145,16 @@ class LocationSharingFragment @Inject constructor( private fun initLocateBtn() { views.mapView.locateBtn.setOnClickListener { - // TODO retrieve user location and zoom to this location + viewModel.handle(LocationSharingAction.ZoomToUserLocationAction) } } + private fun handleZoomToUserLocationEvent(event: LocationSharingViewEvents.ZoomToUserLocation) { + views.mapView.zoomToLocation(event.userLocation.latitude, event.userLocation.longitude) + } + private fun initOptionsPicker() { // TODO - // reset map to user location when clicking on reset icon // changes in the event sent when this is a pinned location // changes in the parsing of events when receiving pinned location: since we may present a different UI // unit tests diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt index 743daaf5e0..8d31db1119 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt @@ -21,4 +21,5 @@ import im.vector.app.core.platform.VectorViewEvents sealed class LocationSharingViewEvents : VectorViewEvents { object Close : LocationSharingViewEvents() object LocationNotAvailableError : LocationSharingViewEvents() + data class ZoomToUserLocation(val userLocation: LocationData) : LocationSharingViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index 5ad762adff..f5bf5f7eb0 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -117,6 +117,7 @@ class LocationSharingViewModel @AssistedInject constructor( LocationSharingAction.CurrentUserLocationSharingAction -> handleCurrentUserLocationSharingAction() is LocationSharingAction.PinnedLocationSharingAction -> handlePinnedLocationSharingAction(action) is LocationSharingAction.LocationTargetChangeAction -> handleLocationTargetChangeAction(action) + LocationSharingAction.ZoomToUserLocationAction -> handleZoomToUserLocationAction() }.exhaustive } @@ -148,6 +149,12 @@ class LocationSharingViewModel @AssistedInject constructor( } } + private fun handleZoomToUserLocationAction() = withState { state -> + state.lastKnownUserLocation?.let { location -> + _viewEvents.post(LocationSharingViewEvents.ZoomToUserLocation(location)) + } + } + override fun onLocationUpdate(locationData: LocationData) { // TODO compare location with lastTargetLocation => need to save info into the ViewState setState { diff --git a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt index 5e2c593240..b4638d6793 100644 --- a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt +++ b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt @@ -154,7 +154,6 @@ class MapTilerMapView @JvmOverloads constructor( safeMapRefs.map.uiSettings.setLogoMargins(0, 0, 0, state.logoMarginBottom) - // TODO check conflict of rendering with preview location in timeline val pinDrawable = state.pinDrawable ?: userLocationDrawable pinDrawable?.let { drawable -> if (!safeMapRefs.style.isFullyLoaded || @@ -181,7 +180,7 @@ class MapTilerMapView @JvmOverloads constructor( } } - private fun zoomToLocation(latitude: Double, longitude: Double) { + fun zoomToLocation(latitude: Double, longitude: Double) { Timber.d("## Location: zoomToLocation") mapRefs?.map?.cameraPosition = CameraPosition.Builder() .target(LatLng(latitude, longitude)) From 8d1822da9685094bb9c7b38761b09ae5de5a6b86 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Tue, 8 Mar 2022 15:02:42 +0100 Subject: [PATCH 11/19] Recompute location comparison on new user location --- .../app/features/location/LocationSharingViewModel.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index f5bf5f7eb0..ead76f1981 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -29,6 +29,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvid import im.vector.app.features.location.domain.usecase.CompareLocationsUseCase import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.lastOrNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -156,10 +157,15 @@ class LocationSharingViewModel @AssistedInject constructor( } override fun onLocationUpdate(locationData: LocationData) { - // TODO compare location with lastTargetLocation => need to save info into the ViewState setState { copy(lastKnownUserLocation = locationData) } + viewModelScope.launch { + // recompute location comparison using last received target location + locationTargetFlow.lastOrNull()?.let { + locationTargetFlow.emit(it) + } + } } override fun onLocationProviderIsNotAvailable() { From 04405c79709795be2a68bcc712e1e783e1694556 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Wed, 9 Mar 2022 09:41:45 +0100 Subject: [PATCH 12/19] Distinguish user location and pinned location sharing --- .../session/room/model/message/LocationAssetType.kt | 11 ++++++++++- .../android/sdk/api/session/room/send/SendService.kt | 3 ++- .../internal/session/room/send/DefaultSendService.kt | 4 ++-- .../session/room/send/LocalEchoEventFactory.kt | 6 ++++-- .../app/features/location/LocationSharingFragment.kt | 2 -- .../app/features/location/LocationSharingViewModel.kt | 10 +++++----- 6 files changed, 23 insertions(+), 13 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAssetType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAssetType.kt index ef40e21c47..a33d2c749f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAssetType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAssetType.kt @@ -21,6 +21,15 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = false) enum class LocationAssetType { + /** + * + **/ @Json(name = "m.self") - SELF + SELF, + + /** + * + **/ + @Json(name = "m.pin") + PIN } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index 913dbfd010..9f8b1d93d7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -142,8 +142,9 @@ interface SendService { * @param latitude required latitude of the location * @param longitude required longitude of the location * @param uncertainty Accuracy of the location in meters + * @param isUserLocation indicates whether the location data corresponds to the user location or not */ - fun sendLocation(latitude: Double, longitude: Double, uncertainty: Double?): Cancelable + fun sendLocation(latitude: Double, longitude: Double, uncertainty: Double?, isUserLocation: Boolean): Cancelable /** * Remove this failed message from the timeline diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index 28c17f38b6..31c7254ed5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -128,8 +128,8 @@ internal class DefaultSendService @AssistedInject constructor( .let { sendEvent(it) } } - override fun sendLocation(latitude: Double, longitude: Double, uncertainty: Double?): Cancelable { - return localEchoEventFactory.createLocationEvent(roomId, latitude, longitude, uncertainty) + override fun sendLocation(latitude: Double, longitude: Double, uncertainty: Double?, isUserLocation: Boolean): Cancelable { + return localEchoEventFactory.createLocationEvent(roomId, latitude, longitude, uncertainty, isUserLocation) .also { createLocalEcho(it) } .let { sendEvent(it) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index bec0ce97dc..0ba95cc1fb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -227,13 +227,15 @@ internal class LocalEchoEventFactory @Inject constructor( fun createLocationEvent(roomId: String, latitude: Double, longitude: Double, - uncertainty: Double?): Event { + uncertainty: Double?, + isUserLocation: Boolean): Event { val geoUri = buildGeoUri(latitude, longitude, uncertainty) + val assetType = if (isUserLocation) LocationAssetType.SELF else LocationAssetType.PIN val content = MessageLocationContent( geoUri = geoUri, body = geoUri, unstableLocationInfo = LocationInfo(geoUri = geoUri, description = geoUri), - unstableLocationAsset = LocationAsset(type = LocationAssetType.SELF), + unstableLocationAsset = LocationAsset(type = assetType), unstableTs = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()), unstableText = geoUri ) 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 79c9daceb4..130fe01c77 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 @@ -155,8 +155,6 @@ class LocationSharingFragment @Inject constructor( private fun initOptionsPicker() { // TODO - // changes in the event sent when this is a pinned location - // changes in the parsing of events when receiving pinned location: since we may present a different UI // unit tests // set no option at start views.shareLocationOptionsPicker.render() diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index ead76f1981..900ae38d84 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -123,20 +123,20 @@ class LocationSharingViewModel @AssistedInject constructor( } private fun handleCurrentUserLocationSharingAction() = withState { state -> - shareLocation(state.lastKnownUserLocation) + shareLocation(state.lastKnownUserLocation, isUserLocation = true) } private fun handlePinnedLocationSharingAction(action: LocationSharingAction.PinnedLocationSharingAction) { - // TODO confirm how to differentiate the user location and pinned location events? - shareLocation(action.locationData) + shareLocation(action.locationData, isUserLocation = false) } - private fun shareLocation(locationData: LocationData?) { + private fun shareLocation(locationData: LocationData?, isUserLocation: Boolean) { locationData?.let { location -> room.sendLocation( latitude = location.latitude, longitude = location.longitude, - uncertainty = location.uncertainty + uncertainty = location.uncertainty, + isUserLocation = isUserLocation ) _viewEvents.post(LocationSharingViewEvents.Close) } ?: run { From 094e62c95bd26b1d87ad296c1fef69c9790c6bd3 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Wed, 9 Mar 2022 14:03:12 +0100 Subject: [PATCH 13/19] Adding unit tests for use case --- .../location/LocationSharingFragment.kt | 2 - .../domain/usecase/CompareLocationsUseCase.kt | 1 - .../usecase/CompareLocationsUseCaseTest.kt | 83 +++++++++++++++++++ 3 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 vector/src/test/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCaseTest.kt 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 130fe01c77..348be7d023 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 @@ -154,8 +154,6 @@ class LocationSharingFragment @Inject constructor( } private fun initOptionsPicker() { - // TODO - // unit tests // set no option at start views.shareLocationOptionsPicker.render() views.shareLocationOptionsPicker.optionPinned.debouncedClicks { diff --git a/vector/src/main/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCase.kt b/vector/src/main/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCase.kt index 3c3f1bb8f6..91738541be 100644 --- a/vector/src/main/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCase.kt @@ -34,7 +34,6 @@ class CompareLocationsUseCase @Inject constructor( private val session: Session ) { - // TODO unit test /** * Compare the 2 given locations. * @return true when they are really close and could be considered as the same location, false otherwise diff --git a/vector/src/test/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCaseTest.kt new file mode 100644 index 0000000000..015a27b0c8 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCaseTest.kt @@ -0,0 +1,83 @@ +/* + * 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.domain.usecase + +import com.airbnb.mvrx.test.MvRxTestRule +import im.vector.app.features.location.LocationData +import im.vector.app.test.fakes.FakeSession +import io.mockk.MockKAnnotations +import io.mockk.impl.annotations.OverrideMockKs +import kotlinx.coroutines.test.runBlockingTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class CompareLocationsUseCaseTest { + + @get:Rule + val mvRxTestRule = MvRxTestRule() + + private val session = FakeSession() + + @OverrideMockKs + lateinit var compareLocationsUseCase: CompareLocationsUseCase + + @Before + fun setUp() { + MockKAnnotations.init(this) + } + + @Test + fun `given 2 very near locations when calling execute then these locations are considered as equal`() = runBlockingTest { + // Given + val location1 = LocationData( + latitude = 48.858269, + longitude = 2.294551, + uncertainty = null + ) + val location2 = LocationData( + latitude = 48.858275, + longitude = 2.294547, + uncertainty = null + ) + // When + val areEqual = compareLocationsUseCase.execute(location1, location2) + + // Then + assert(areEqual) + } + + @Test + fun `given 2 far away locations when calling execute then these locations are considered as not equal`() = runBlockingTest { + // Given + val location1 = LocationData( + latitude = 48.858269, + longitude = 2.294551, + uncertainty = null + ) + val location2 = LocationData( + latitude = 48.861777, + longitude = 2.289348, + uncertainty = null + ) + // When + val areEqual = compareLocationsUseCase.execute(location1, location2) + + // Then + assert(areEqual.not()) + } +} From 9864e519276b4e641882ee19e3caaaf92238ace9 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Wed, 9 Mar 2022 15:01:18 +0100 Subject: [PATCH 14/19] Adding changelog entry --- changelog.d/5417.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5417.feature diff --git a/changelog.d/5417.feature b/changelog.d/5417.feature new file mode 100644 index 0000000000..8b64f9fc7f --- /dev/null +++ b/changelog.d/5417.feature @@ -0,0 +1 @@ +Add ability to pin a location on map for sharing From f495150b4e409e4dbaab7b658a32c03759ea8f08 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Thu, 10 Mar 2022 11:53:59 +0100 Subject: [PATCH 15/19] Fixing asset type representation --- .../room/model/message/LocationAsset.kt | 2 +- .../room/model/message/LocationAssetType.kt | 22 +++++++++---------- .../model/message/MessageLocationContent.kt | 5 +++-- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAsset.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAsset.kt index e8b3cf2488..35fa555a5b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAsset.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAsset.kt @@ -21,5 +21,5 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class LocationAsset( - @Json(name = "type") val type: LocationAssetType? = null + @Json(name = "type") val type: String? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAssetType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAssetType.kt index a33d2c749f..f7d82d4b40 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAssetType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAssetType.kt @@ -16,20 +16,20 @@ package org.matrix.android.sdk.api.session.room.model.message -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -@JsonClass(generateAdapter = false) -enum class LocationAssetType { +/** + * Define what particular asset is being referred to. + * We don't use enum type since it is not limited to a specific set of values. + * The way this type should be interpreted in client side is described in + * [MSC3488](https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md) + */ +object LocationAssetType { /** - * + * Used for user location sharing. **/ - @Json(name = "m.self") - SELF, + const val SELF = "m.self" /** - * + * Used for pin drop location sharing. **/ - @Json(name = "m.pin") - PIN + const val PIN = "m.pin" } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt index 84bf5cf7b7..2052133b06 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt @@ -42,7 +42,7 @@ data class MessageLocationContent( @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.new_content") override val newContent: Content? = null, /** - * See https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md + * See [MSC3488](https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md) */ @Json(name = "org.matrix.msc3488.location") val unstableLocationInfo: LocationInfo? = null, @Json(name = "m.location") val locationInfo: LocationInfo? = null, @@ -54,10 +54,11 @@ data class MessageLocationContent( @Json(name = "org.matrix.msc1767.text") val unstableText: String? = null, @Json(name = "m.text") val text: String? = null, /** - * m.asset defines a generic asset that can be used for location tracking but also in other places like + * Defines a generic asset that can be used for location tracking but also in other places like * inventories, geofencing, checkins/checkouts etc. * It should contain a mandatory namespaced type key defining what particular asset is being referred to. * For the purposes of user location tracking m.self should be used in order to avoid duplicating the mxid. + * See [MSC3488](https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md) */ @Json(name = "org.matrix.msc3488.asset") val unstableLocationAsset: LocationAsset? = null, @Json(name = "m.asset") val locationAsset: LocationAsset? = null From 6779fa1175bcb86bb7c5b5505b054ffa2ca4075e Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Mon, 14 Mar 2022 17:21:55 +0100 Subject: [PATCH 16/19] Change naming for initLocateButton() method --- .../vector/app/features/location/LocationSharingFragment.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 348be7d023..85d0b54098 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 @@ -71,7 +71,7 @@ class LocationSharingFragment @Inject constructor( ) } - initLocateBtn() + initLocateButton() initOptionsPicker() viewModel.observeViewEvents { @@ -143,7 +143,7 @@ class LocationSharingFragment @Inject constructor( .show() } - private fun initLocateBtn() { + private fun initLocateButton() { views.mapView.locateBtn.setOnClickListener { viewModel.handle(LocationSharingAction.ZoomToUserLocationAction) } From abd25c2292d5465908c07980860f5f18c1006181 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Mon, 14 Mar 2022 17:24:28 +0100 Subject: [PATCH 17/19] Removing Action suffix in ViewAction types --- .../app/features/location/LocationSharingAction.kt | 8 ++++---- .../app/features/location/LocationSharingFragment.kt | 8 ++++---- .../features/location/LocationSharingViewModel.kt | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt index e575367c66..ec47c23ea7 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt @@ -19,8 +19,8 @@ package im.vector.app.features.location import im.vector.app.core.platform.VectorViewModelAction sealed class LocationSharingAction : VectorViewModelAction { - object CurrentUserLocationSharingAction : LocationSharingAction() - data class PinnedLocationSharingAction(val locationData: LocationData?) : LocationSharingAction() - data class LocationTargetChangeAction(val locationData: LocationData) : LocationSharingAction() - object ZoomToUserLocationAction : LocationSharingAction() + object CurrentUserLocationSharing : LocationSharingAction() + data class PinnedLocationSharing(val locationData: LocationData?) : LocationSharingAction() + data class LocationTargetChange(val locationData: LocationData) : LocationSharingAction() + object ZoomToUserLocation : LocationSharingAction() } 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 85d0b54098..cdffc95d95 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 @@ -120,7 +120,7 @@ class LocationSharingFragment @Inject constructor( } override fun onLocationTargetChange(target: LocationData) { - viewModel.handle(LocationSharingAction.LocationTargetChangeAction(target)) + viewModel.handle(LocationSharingAction.LocationTargetChange(target)) } override fun invalidate() = withState(viewModel) { state -> @@ -145,7 +145,7 @@ class LocationSharingFragment @Inject constructor( private fun initLocateButton() { views.mapView.locateBtn.setOnClickListener { - viewModel.handle(LocationSharingAction.ZoomToUserLocationAction) + viewModel.handle(LocationSharingAction.ZoomToUserLocation) } } @@ -158,10 +158,10 @@ class LocationSharingFragment @Inject constructor( views.shareLocationOptionsPicker.render() views.shareLocationOptionsPicker.optionPinned.debouncedClicks { val targetLocation = views.mapView.getLocationOfMapCenter() - viewModel.handle(LocationSharingAction.PinnedLocationSharingAction(targetLocation)) + viewModel.handle(LocationSharingAction.PinnedLocationSharing(targetLocation)) } views.shareLocationOptionsPicker.optionUserCurrent.debouncedClicks { - viewModel.handle(LocationSharingAction.CurrentUserLocationSharingAction) + viewModel.handle(LocationSharingAction.CurrentUserLocationSharing) } views.shareLocationOptionsPicker.optionUserLive.debouncedClicks { // TODO diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index 900ae38d84..0356566b16 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -115,10 +115,10 @@ class LocationSharingViewModel @AssistedInject constructor( override fun handle(action: LocationSharingAction) { when (action) { - LocationSharingAction.CurrentUserLocationSharingAction -> handleCurrentUserLocationSharingAction() - is LocationSharingAction.PinnedLocationSharingAction -> handlePinnedLocationSharingAction(action) - is LocationSharingAction.LocationTargetChangeAction -> handleLocationTargetChangeAction(action) - LocationSharingAction.ZoomToUserLocationAction -> handleZoomToUserLocationAction() + LocationSharingAction.CurrentUserLocationSharing -> handleCurrentUserLocationSharingAction() + is LocationSharingAction.PinnedLocationSharing -> handlePinnedLocationSharingAction(action) + is LocationSharingAction.LocationTargetChange -> handleLocationTargetChangeAction(action) + LocationSharingAction.ZoomToUserLocation -> handleZoomToUserLocationAction() }.exhaustive } @@ -126,7 +126,7 @@ class LocationSharingViewModel @AssistedInject constructor( shareLocation(state.lastKnownUserLocation, isUserLocation = true) } - private fun handlePinnedLocationSharingAction(action: LocationSharingAction.PinnedLocationSharingAction) { + private fun handlePinnedLocationSharingAction(action: LocationSharingAction.PinnedLocationSharing) { shareLocation(action.locationData, isUserLocation = false) } @@ -144,7 +144,7 @@ class LocationSharingViewModel @AssistedInject constructor( } } - private fun handleLocationTargetChangeAction(action: LocationSharingAction.LocationTargetChangeAction) { + private fun handleLocationTargetChangeAction(action: LocationSharingAction.LocationTargetChange) { viewModelScope.launch { locationTargetFlow.emit(action.locationData) } From 9ecf12a7ba408b98adfb373deda2ef6fc3b2fb5d Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Tue, 15 Mar 2022 14:59:41 +0100 Subject: [PATCH 18/19] Using extensions for Boolean? type --- .../vector/app/features/location/LocationSharingViewModel.kt | 3 ++- .../vector/app/features/location/LocationSharingViewState.kt | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index 0356566b16..25bc482412 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -35,6 +35,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.sample import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.toMatrixItem @@ -74,7 +75,7 @@ class LocationSharingViewModel @AssistedInject constructor( } private fun updatePin(isUserPin: Boolean? = true) { - if (isUserPin == true) { + if (isUserPin.orFalse()) { locationPinProvider.create(userId = session.myUserId) { updatePinDrawableInState(it) } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt index e661ff0cc0..ee5ba402e2 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt @@ -20,6 +20,7 @@ import android.graphics.drawable.Drawable import androidx.annotation.StringRes import com.airbnb.mvrx.MavericksState import im.vector.app.R +import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.util.MatrixItem enum class LocationSharingMode(@StringRes val titleRes: Int) { @@ -47,5 +48,6 @@ fun LocationSharingViewState.toMapState() = MapState( userLocationData = lastKnownUserLocation, pinId = DEFAULT_PIN_ID, pinDrawable = null, - showPin = areTargetAndUserLocationEqual == false + // show the map pin only when target location and user location are not equal + showPin = areTargetAndUserLocationEqual.orTrue().not() ) From b72c87da444908da7c6c14ca5acfd5a3f5bfbf87 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Tue, 15 Mar 2022 15:08:32 +0100 Subject: [PATCH 19/19] Renaming "btn" into "button" --- .../ui-styles/src/main/res/values/dimens.xml | 6 ++-- .../location/LocationSharingFragment.kt | 2 +- .../app/features/location/MapTilerMapView.kt | 36 +++++++++---------- vector/src/main/res/values/strings.xml | 2 +- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml index 08fe557f7c..600c73c878 100644 --- a/library/ui-styles/src/main/res/values/dimens.xml +++ b/library/ui-styles/src/main/res/values/dimens.xml @@ -64,7 +64,7 @@ 10dp - 16dp - 12dp - 8dp + 16dp + 12dp + 8dp 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 cdffc95d95..e9e96e676c 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 @@ -144,7 +144,7 @@ class LocationSharingFragment @Inject constructor( } private fun initLocateButton() { - views.mapView.locateBtn.setOnClickListener { + views.mapView.locateButton.setOnClickListener { viewModel.handle(LocationSharingAction.ZoomToUserLocation) } } diff --git a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt index b4638d6793..e3206e231d 100644 --- a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt +++ b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt @@ -53,10 +53,10 @@ class MapTilerMapView @JvmOverloads constructor( private val userLocationDrawable by lazy { ContextCompat.getDrawable(context, R.drawable.ic_location_user) } - val locateBtn by lazy { createLocateBtn() } + val locateButton by lazy { createLocateButton() } private var mapRefs: MapRefs? = null private var initZoomDone = false - private var showLocationBtn = false + private var showLocationButton = false init { context.theme.obtainStyledAttributes( @@ -66,15 +66,15 @@ class MapTilerMapView @JvmOverloads constructor( 0 ).run { try { - setLocateBtnVisibility(this) + setLocateButtonVisibility(this) } finally { recycle() } } } - private fun setLocateBtnVisibility(typedArray: TypedArray) { - showLocationBtn = typedArray.getBoolean(R.styleable.MapTilerMapView_showLocateButton, false) + private fun setLocateButtonVisibility(typedArray: TypedArray) { + showLocationButton = typedArray.getBoolean(R.styleable.MapTilerMapView_showLocateButton, false) } /** @@ -87,7 +87,7 @@ class MapTilerMapView @JvmOverloads constructor( Timber.d("## Location: initialize") getMapAsync { map -> initMapStyle(map, url) - initLocateBtn(map) + initLocateButton(map) notifyLocationOfMapCenter(locationTargetChangeListener) listenCameraMove(map, locationTargetChangeListener) } @@ -105,21 +105,21 @@ class MapTilerMapView @JvmOverloads constructor( } } - private fun initLocateBtn(map: MapboxMap) { - if (showLocationBtn) { - addView(locateBtn) - adjustCompassBtn(map) + private fun initLocateButton(map: MapboxMap) { + if (showLocationButton) { + addView(locateButton) + adjustCompassButton(map) } } - private fun createLocateBtn(): ImageView = + private fun createLocateButton(): ImageView = ImageView(context).apply { setImageDrawable(ContextCompat.getDrawable(context, R.drawable.btn_locate)) - contentDescription = context.getString(R.string.a11y_location_share_locate_btn) + contentDescription = context.getString(R.string.a11y_location_share_locate_button) layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) updateLayoutParams { - val marginHorizontal = context.resources.getDimensionPixelOffset(R.dimen.location_sharing_locate_btn_margin_horizontal) - val marginVertical = context.resources.getDimensionPixelOffset(R.dimen.location_sharing_locate_btn_margin_vertical) + val marginHorizontal = context.resources.getDimensionPixelOffset(R.dimen.location_sharing_locate_button_margin_horizontal) + val marginVertical = context.resources.getDimensionPixelOffset(R.dimen.location_sharing_locate_button_margin_vertical) setMargins(marginHorizontal, marginVertical, marginHorizontal, marginVertical) } updateLayoutParams { @@ -127,10 +127,10 @@ class MapTilerMapView @JvmOverloads constructor( } } - private fun adjustCompassBtn(map: MapboxMap) { - locateBtn.post { - val marginTop = locateBtn.height + locateBtn.marginTop + locateBtn.marginBottom - val marginRight = context.resources.getDimensionPixelOffset(R.dimen.location_sharing_compass_btn_margin_horizontal) + private fun adjustCompassButton(map: MapboxMap) { + locateButton.post { + val marginTop = locateButton.height + locateButton.marginTop + locateButton.marginBottom + val marginRight = context.resources.getDimensionPixelOffset(R.dimen.location_sharing_compass_button_margin_horizontal) map.uiSettings.setCompassMargins(0, marginTop, marginRight, 0) } } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index bbb2bc7740..162ab3e119 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2930,7 +2930,7 @@ Share location Pin of selected location on map - Zoom to current location + Zoom to current location Share my current location Share my current location Share live location