From 6495bd9e5ed373c33047a4135ec11d469cac8ecb Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 17 Dec 2021 15:09:02 +0300 Subject: [PATCH] Send location event. --- .../room/model/message/LocationInfo.kt | 22 +++--------- .../model/message/MessageLocationContent.kt | 8 ++--- .../sdk/api/session/room/send/SendService.kt | 8 +++++ .../session/room/send/DefaultSendService.kt | 6 ++++ .../room/send/LocalEchoEventFactory.kt | 35 +++++++++++++++++++ .../app/features/location/LocationData.kt | 24 +++++++++++++ .../location/LocationSharingAction.kt | 3 +- .../location/LocationSharingFragment.kt | 28 +++++++++++++-- .../location/LocationSharingViewEvents.kt | 1 + .../location/LocationSharingViewModel.kt | 29 ++++++++++++++- .../location/LocationSharingViewState.kt | 3 +- .../app/features/location/LocationTracker.kt | 6 ++-- vector/src/main/res/values/strings.xml | 2 ++ 13 files changed, 145 insertions(+), 30 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationData.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt index a76c3c5b64..5110926294 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt @@ -18,29 +18,17 @@ package org.matrix.android.sdk.api.session.room.model.message import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo @JsonClass(generateAdapter = true) data class LocationInfo( /** - * The URL to the thumbnail of the file. Only present if the thumbnail is unencrypted. + * Required. Required. RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' representing this location. */ - @Json(name = "thumbnail_url") val thumbnailUrl: String? = null, + @Json(name = "uri") val geoUri: String? = null, /** - * Metadata about the image referred to in thumbnail_url. + * Required. A description of the location e.g. 'Big Ben, London, UK', or some kind + * of content description for accessibility e.g. 'location attachment'. */ - @Json(name = "thumbnail_info") val thumbnailInfo: ThumbnailInfo? = null, - - /** - * Information on the encrypted thumbnail file, as specified in End-to-end encryption. Only present if the thumbnail is encrypted. - */ - @Json(name = "thumbnail_file") val thumbnailFile: EncryptedFileInfo? = null + @Json(name = "description") val description: String? = null ) - -/** - * Get the url of the encrypted thumbnail or of the thumbnail - */ -fun LocationInfo.getThumbnailUrl(): String? { - return thumbnailFile?.url ?: thumbnailUrl -} 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 6881c09924..9571cc4280 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 @@ -26,7 +26,7 @@ data class MessageLocationContent( /** * Required. Must be 'm.location'. */ - @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String, + @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String = MessageType.MSGTYPE_LOCATION, /** * Required. A description of the location e.g. 'Big Ben, London, UK', or some kind @@ -35,14 +35,14 @@ data class MessageLocationContent( @Json(name = "body") override val body: String, /** - * Required. A geo URI representing this location. + * Required. RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' representing this location. */ @Json(name = "geo_uri") val geoUri: String, /** - * + * See https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md */ - @Json(name = "info") val locationInfo: LocationInfo? = null, + @Json(name = "org.matrix.msc3488.location") val locationInfo: LocationInfo? = null, @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.new_content") override val newContent: Content? = null 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 5b387c3413..7983ad7068 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 @@ -122,6 +122,14 @@ interface SendService { */ fun resendMediaMessage(localEcho: TimelineEvent): Cancelable + /** + * Send a location event to the room + * @param latitude required latitude of the location + * @param longitude required longitude of the location + * @param uncertainty Accuracy of the location in meters + */ + fun sendLocation(latitude: Double, longitude: Double, uncertainty: Double?): Cancelable + /** * Remove this failed message from the timeline * @param localEcho the unsent local echo 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 d3162aef79..dbe4d5eb73 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 @@ -115,6 +115,12 @@ 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) + .also { createLocalEcho(it) } + .let { sendEvent(it) } + } + override fun redactEvent(event: Event, reason: String?): Cancelable { // TODO manage media/attachements? val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason) 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 85b22628d7..179df22326 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 @@ -32,6 +32,7 @@ import org.matrix.android.sdk.api.session.room.model.message.AudioInfo import org.matrix.android.sdk.api.session.room.model.message.AudioWaveformInfo import org.matrix.android.sdk.api.session.room.model.message.FileInfo import org.matrix.android.sdk.api.session.room.model.message.ImageInfo +import org.matrix.android.sdk.api.session.room.model.message.LocationInfo import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody @@ -39,6 +40,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollConte import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent import org.matrix.android.sdk.api.session.room.model.message.MessageFormat import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent +import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent @@ -194,6 +196,22 @@ internal class LocalEchoEventFactory @Inject constructor( unsignedData = UnsignedData(age = null, transactionId = localId)) } + fun createLocationEvent(roomId: String, + latitude: Double, + longitude: Double, + uncertainty: Double?): Event { + val geoUri = buildGeoUri(latitude, longitude, uncertainty) + val content = MessageLocationContent( + geoUri = geoUri, + body = geoUri, + locationInfo = LocationInfo( + geoUri = geoUri, + description = geoUri + ) + ) + return createMessageEvent(roomId, content) + } + fun createReplaceTextOfReply(roomId: String, eventReplaced: TimelineEvent, originalEvent: TimelineEvent, @@ -463,6 +481,23 @@ internal class LocalEchoEventFactory @Inject constructor( } } + /** + * Returns RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' + * Uncertainty of the location is in meters and not required. + */ + private fun buildGeoUri(latitude: Double, longitude: Double, uncertainty: Double?): String { + return buildString { + append("geo:") + append(latitude) + append(",") + append(longitude) + uncertainty?.let { + append(";") + append(it) + } + } + } + /* * { "content": { diff --git a/vector/src/main/java/im/vector/app/features/location/LocationData.kt b/vector/src/main/java/im/vector/app/features/location/LocationData.kt new file mode 100644 index 0000000000..f85b8713d9 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationData.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2021 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 + +data class LocationData( + val latitude: Double, + val longitude: Double, + val uncertainty: Double? +) + 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 5139888b08..0efaefaa5b 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 { - data class OnShareLocation(val latitude: Double, val longitude: Double) : LocationSharingAction() + data class OnLocationUpdate(val locationData: LocationData) : LocationSharingAction() + object OnShareLocation : 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 390d1aafa5..8cd86a2356 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 @@ -26,6 +26,7 @@ import androidx.core.content.ContextCompat import com.airbnb.mvrx.activityViewModel import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.transition.Transition +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.mapbox.mapboxsdk.Mapbox import com.mapbox.mapboxsdk.camera.CameraPosition import com.mapbox.mapboxsdk.geometry.LatLng @@ -79,6 +80,17 @@ class LocationSharingFragment @Inject constructor( super.onViewCreated(view, savedInstanceState) initMapView(savedInstanceState) + + views.shareLocationContainer.debouncedClicks { + viewModel.handle(LocationSharingAction.OnShareLocation) + } + + viewModel.observeViewEvents { + when (it) { + LocationSharingViewEvents.LocationNotAvailableError -> handleLocationNotAvailableError() + LocationSharingViewEvents.Close -> activity?.finish() + } + } } override fun onDestroyView() { @@ -126,10 +138,10 @@ class LocationSharingFragment @Inject constructor( } } - override fun onLocationUpdate(latitude: Double, longitude: Double) { + override fun onLocationUpdate(locationData: LocationData) { lastZoomValue = if (lastZoomValue == -1.0) INITIAL_ZOOM else map?.cameraPosition?.zoom ?: INITIAL_ZOOM - val latLng = LatLng(latitude, longitude) + val latLng = LatLng(locationData.latitude, locationData.longitude) map?.cameraPosition = CameraPosition.Builder() .target(latLng) @@ -144,10 +156,20 @@ class LocationSharingFragment @Inject constructor( .withIconImage(USER_PIN_NAME) .withIconAnchor(Property.ICON_ANCHOR_BOTTOM) ) + + viewModel.handle(LocationSharingAction.OnLocationUpdate(locationData)) + } + + private fun handleLocationNotAvailableError() { + MaterialAlertDialogBuilder(requireActivity()) + .setTitle(R.string.location_not_available_dialog_title) + .setMessage(R.string.location_not_available_dialog_content) + .setPositiveButton(R.string.ok, null) + .show() } companion object { - const val INITIAL_ZOOM = 12.0 + const val INITIAL_ZOOM = 15.0 const val USER_PIN_NAME = "USER_PIN_NAME" } } 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 1032a696ff..743daaf5e0 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 @@ -20,4 +20,5 @@ import im.vector.app.core.platform.VectorViewEvents sealed class LocationSharingViewEvents : VectorViewEvents { object Close : LocationSharingViewEvents() + object LocationNotAvailableError : 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 6da00f176d..4525cac446 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 @@ -23,11 +23,15 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel +import org.matrix.android.sdk.api.session.Session class LocationSharingViewModel @AssistedInject constructor( - @Assisted private val initialState: LocationSharingViewState + @Assisted private val initialState: LocationSharingViewState, + session: Session ) : VectorViewModel(initialState) { + private val room = session.getRoom(initialState.roomId)!! + @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { override fun create(initialState: LocationSharingViewState): LocationSharingViewModel @@ -37,5 +41,28 @@ class LocationSharingViewModel @AssistedInject constructor( } override fun handle(action: LocationSharingAction) { + when (action) { + is LocationSharingAction.OnLocationUpdate -> handleLocationUpdate(action.locationData) + LocationSharingAction.OnShareLocation -> handleShareLocation() + } + } + + private fun handleShareLocation() = withState { state -> + state.lastKnownLocation?.let { location -> + room.sendLocation( + latitude = location.latitude, + longitude = location.longitude, + uncertainty = location.uncertainty + ) + _viewEvents.post(LocationSharingViewEvents.Close) + } ?: run { + _viewEvents.post(LocationSharingViewEvents.LocationNotAvailableError) + } + } + + private fun handleLocationUpdate(locationData: LocationData) { + setState { + copy(lastKnownLocation = 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 d62e568fb2..2869929b12 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 @@ -27,7 +27,8 @@ enum class LocationSharingMode(@StringRes val titleRes: Int) { data class LocationSharingViewState( val roomId: String, - val mode: LocationSharingMode + val mode: LocationSharingMode, + val lastKnownLocation: LocationData? = null ) : MavericksState { constructor(locationSharingArgs: LocationSharingArgs) : this( diff --git a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt index 3cf2689aee..93993245f8 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt @@ -27,7 +27,7 @@ class LocationTracker @Inject constructor( private val context: Context) : LocationListener { interface Callback { - fun onLocationUpdate(latitude: Double, longitude: Double) + fun onLocationUpdate(locationData: LocationData) } private var locationManager: LocationManager? = null @@ -51,7 +51,7 @@ class LocationTracker @Inject constructor( // Send last known location without waiting location updates it.getLastKnownLocation(provider)?.let { lastKnownLocation -> - callback?.onLocationUpdate(lastKnownLocation.latitude, lastKnownLocation.longitude) + callback?.onLocationUpdate(LocationData(lastKnownLocation.latitude, lastKnownLocation.longitude, lastKnownLocation.accuracy.toDouble())) } it.requestLocationUpdates( @@ -70,7 +70,7 @@ class LocationTracker @Inject constructor( } override fun onLocationChanged(location: Location) { - callback?.onLocationUpdate(location.latitude, location.longitude) + callback?.onLocationUpdate(LocationData(location.latitude, location.longitude, location.accuracy.toDouble())) } companion object { diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 166f25c053..086469a1fd 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3708,4 +3708,6 @@ Location Share location Share location + Element could not access your location + Element could not access your location. Please try again later.