Skip to content

Commit 6bd150d

Browse files
authored
Merge pull request #8144 from vector-im/feature/mna/user-location-in-loc-sharing
[Location sharing] Show own location in map views
2 parents 4080f1c + a0bab98 commit 6bd150d

23 files changed

+650
-172
lines changed

changelog.d/8110.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[Location sharing] Show own location in map views
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright (c) 2023 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.features.location
18+
19+
import androidx.fragment.app.Fragment
20+
import com.google.android.material.dialog.MaterialAlertDialogBuilder
21+
import im.vector.app.R
22+
23+
fun Fragment.showUserLocationNotAvailableErrorDialog(onConfirmListener: () -> Unit) {
24+
MaterialAlertDialogBuilder(requireActivity())
25+
.setTitle(R.string.location_not_available_dialog_title)
26+
.setMessage(R.string.location_not_available_dialog_content)
27+
.setPositiveButton(R.string.ok) { _, _ ->
28+
onConfirmListener()
29+
}
30+
.setCancelable(false)
31+
.show()
32+
}

vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -176,14 +176,7 @@ class LocationSharingFragment :
176176
}
177177

178178
private fun handleLocationNotAvailableError() {
179-
MaterialAlertDialogBuilder(requireActivity())
180-
.setTitle(R.string.location_not_available_dialog_title)
181-
.setMessage(R.string.location_not_available_dialog_content)
182-
.setPositiveButton(R.string.ok) { _, _ ->
183-
locationSharingNavigator.quit()
184-
}
185-
.setCancelable(false)
186-
.show()
179+
showUserLocationNotAvailableErrorDialog { locationSharingNavigator.quit() }
187180
}
188181

189182
private fun handleLiveLocationSharingNotEnoughPermission() {

vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ data class LocationSharingViewState(
4747

4848
fun LocationSharingViewState.toMapState() = MapState(
4949
zoomOnlyOnce = true,
50-
userLocationData = lastKnownUserLocation,
50+
pinLocationData = lastKnownUserLocation,
5151
pinId = DEFAULT_PIN_ID,
5252
pinDrawable = null,
5353
// show the map pin only when target location and user location are not equal

vector/src/main/java/im/vector/app/features/location/LocationTracker.kt

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ class LocationTracker @Inject constructor(
6666
@VisibleForTesting
6767
var hasLocationFromGPSProvider = false
6868

69+
private var isStarted = false
70+
private var isStarting = false
6971
private var firstLocationHandled = false
7072
private val _locations = MutableSharedFlow<Location>(replay = 1)
7173

@@ -90,43 +92,48 @@ class LocationTracker @Inject constructor(
9092

9193
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION])
9294
fun start() {
93-
Timber.d("start()")
94-
95-
if (locationManager == null) {
96-
Timber.v("LocationManager is not available")
97-
onNoLocationProviderAvailable()
98-
return
99-
}
95+
if (!isStarting && !isStarted) {
96+
isStarting = true
97+
Timber.d("start()")
98+
99+
if (locationManager == null) {
100+
Timber.v("LocationManager is not available")
101+
onNoLocationProviderAvailable()
102+
return
103+
}
100104

101-
val providers = locationManager.allProviders
105+
val providers = locationManager.allProviders
102106

103-
if (providers.isEmpty()) {
104-
Timber.v("There is no location provider available")
105-
onNoLocationProviderAvailable()
106-
} else {
107-
// Take GPS first
108-
providers.sortedByDescending(::getProviderPriority)
109-
.mapNotNull { provider ->
110-
Timber.d("track location using $provider")
111-
112-
locationManager.requestLocationUpdates(
113-
provider,
114-
minDurationToUpdateLocationMillis,
115-
MIN_DISTANCE_TO_UPDATE_LOCATION_METERS,
116-
this
117-
)
118-
119-
locationManager.getLastKnownLocation(provider)
120-
}
121-
.maxByOrNull { location -> location.time }
122-
?.let { latestKnownLocation ->
123-
if (buildMeta.lowPrivacyLoggingEnabled) {
124-
Timber.d("lastKnownLocation: $latestKnownLocation")
125-
} else {
126-
Timber.d("lastKnownLocation: ${latestKnownLocation.provider}")
107+
if (providers.isEmpty()) {
108+
Timber.v("There is no location provider available")
109+
onNoLocationProviderAvailable()
110+
} else {
111+
// Take GPS first
112+
providers.sortedByDescending(::getProviderPriority)
113+
.mapNotNull { provider ->
114+
Timber.d("track location using $provider")
115+
116+
locationManager.requestLocationUpdates(
117+
provider,
118+
minDurationToUpdateLocationMillis,
119+
MIN_DISTANCE_TO_UPDATE_LOCATION_METERS,
120+
this
121+
)
122+
123+
locationManager.getLastKnownLocation(provider)
124+
}
125+
.maxByOrNull { location -> location.time }
126+
?.let { latestKnownLocation ->
127+
if (buildMeta.lowPrivacyLoggingEnabled) {
128+
Timber.d("lastKnownLocation: $latestKnownLocation")
129+
} else {
130+
Timber.d("lastKnownLocation: ${latestKnownLocation.provider}")
131+
}
132+
notifyLocation(latestKnownLocation)
127133
}
128-
notifyLocation(latestKnownLocation)
129-
}
134+
}
135+
isStarted = true
136+
isStarting = false
130137
}
131138
}
132139

@@ -148,6 +155,8 @@ class LocationTracker @Inject constructor(
148155
callbacks.clear()
149156
hasLocationFromGPSProvider = false
150157
hasLocationFromFusedProvider = false
158+
isStarting = false
159+
isStarted = false
151160
}
152161

153162
/**

vector/src/main/java/im/vector/app/features/location/MapState.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ import androidx.annotation.Px
2121

2222
data class MapState(
2323
val zoomOnlyOnce: Boolean,
24-
val userLocationData: LocationData? = null,
24+
val pinLocationData: LocationData? = null,
2525
val pinId: String,
2626
val pinDrawable: Drawable? = null,
2727
val showPin: Boolean = true,
28-
@Px val logoMarginBottom: Int = 0
28+
val userLocationData: LocationData? = null,
29+
@Px val logoMarginBottom: Int = 0,
2930
)

vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package im.vector.app.features.location
1818

1919
import android.content.Context
2020
import android.content.res.TypedArray
21+
import android.graphics.drawable.Drawable
2122
import android.util.AttributeSet
2223
import android.view.Gravity
2324
import android.widget.ImageView
@@ -38,6 +39,8 @@ import im.vector.app.R
3839
import im.vector.app.core.utils.DimensionConverter
3940
import timber.log.Timber
4041

42+
private const val USER_PIN_ID = "user-pin-id"
43+
4144
class MapTilerMapView @JvmOverloads constructor(
4245
context: Context,
4346
attrs: AttributeSet? = null,
@@ -101,9 +104,11 @@ class MapTilerMapView @JvmOverloads constructor(
101104

102105
private fun initMapStyle(map: MapboxMap, url: String) {
103106
map.setStyle(url) { style ->
107+
val symbolManager = SymbolManager(this, map, style)
108+
symbolManager.iconAllowOverlap = true
104109
mapRefs = MapRefs(
105110
map,
106-
SymbolManager(this, map, style),
111+
symbolManager,
107112
style
108113
)
109114
pendingState?.let { render(it) }
@@ -166,29 +171,43 @@ class MapTilerMapView @JvmOverloads constructor(
166171
}
167172

168173
val pinDrawable = state.pinDrawable ?: userLocationDrawable
169-
pinDrawable?.let { drawable ->
170-
if (!safeMapRefs.style.isFullyLoaded ||
171-
safeMapRefs.style.getImage(state.pinId) == null) {
172-
safeMapRefs.style.addImage(state.pinId, drawable.toBitmap())
173-
}
174-
}
174+
addImageToMapStyle(pinDrawable, state.pinId, safeMapRefs)
175175

176-
state.userLocationData?.let { locationData ->
176+
safeMapRefs.symbolManager.deleteAll()
177+
state.pinLocationData?.let { locationData ->
177178
if (!initZoomDone || !state.zoomOnlyOnce) {
178179
zoomToLocation(locationData)
179180
initZoomDone = true
180181
}
181182

182-
safeMapRefs.symbolManager.deleteAll()
183183
if (pinDrawable != null && state.showPin) {
184-
safeMapRefs.symbolManager.create(
185-
SymbolOptions()
186-
.withLatLng(LatLng(locationData.latitude, locationData.longitude))
187-
.withIconImage(state.pinId)
188-
.withIconAnchor(Property.ICON_ANCHOR_BOTTOM)
189-
)
184+
createSymbol(locationData, state.pinId, safeMapRefs)
190185
}
191186
}
187+
188+
state.userLocationData?.let { locationData ->
189+
addImageToMapStyle(userLocationDrawable, USER_PIN_ID, safeMapRefs)
190+
if (userLocationDrawable != null) {
191+
createSymbol(locationData, USER_PIN_ID, safeMapRefs)
192+
}
193+
}
194+
}
195+
196+
private fun addImageToMapStyle(image: Drawable?, imageId: String, mapRefs: MapRefs) {
197+
image?.let { drawable ->
198+
if (!mapRefs.style.isFullyLoaded || mapRefs.style.getImage(imageId) == null) {
199+
mapRefs.style.addImage(imageId, drawable.toBitmap())
200+
}
201+
}
202+
}
203+
204+
private fun createSymbol(locationData: LocationData, imageId: String, mapRefs: MapRefs) {
205+
mapRefs.symbolManager.create(
206+
SymbolOptions()
207+
.withLatLng(LatLng(locationData.latitude, locationData.longitude))
208+
.withIconImage(imageId)
209+
.withIconAnchor(Property.ICON_ANCHOR_BOTTOM)
210+
)
192211
}
193212

194213
fun zoomToLocation(locationData: LocationData) {

vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapAction.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ sealed class LiveLocationMapAction : VectorViewModelAction {
2323
data class RemoveMapSymbol(val key: String) : LiveLocationMapAction()
2424
object StopSharing : LiveLocationMapAction()
2525
object ShowMapLoadingError : LiveLocationMapAction()
26+
object ZoomToUserLocation : LiveLocationMapAction()
2627
}

vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewEvents.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717
package im.vector.app.features.location.live.map
1818

1919
import im.vector.app.core.platform.VectorViewEvents
20+
import im.vector.app.features.location.LocationData
2021

2122
sealed interface LiveLocationMapViewEvents : VectorViewEvents {
22-
data class Error(val error: Throwable) : LiveLocationMapViewEvents
23+
data class LiveLocationError(val error: Throwable) : LiveLocationMapViewEvents
24+
data class ZoomToUserLocation(val userLocation: LocationData) : LiveLocationMapViewEvents
25+
object UserLocationNotAvailableError : LiveLocationMapViewEvents
2326
}

0 commit comments

Comments
 (0)