mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 13:38:49 +03:00
Show user pins with correct zoom when map is first opened
This commit is contained in:
parent
d6029210d0
commit
5410b61ae3
5 changed files with 155 additions and 53 deletions
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* 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.live.map
|
||||
|
||||
import im.vector.app.features.location.LocationData
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetCurrentUserLiveLocationUseCase @Inject constructor() {
|
||||
|
||||
// TODO add unit tests
|
||||
fun execute(): Flow<List<UserLiveLocationViewState>> {
|
||||
// TODO get room and call SDK to get the correct flow
|
||||
return flow {
|
||||
val user1 = UserLiveLocationViewState(
|
||||
userId = "user1",
|
||||
locationData = LocationData(
|
||||
latitude = 48.863447,
|
||||
longitude = 2.328608,
|
||||
uncertainty = null
|
||||
)
|
||||
)
|
||||
val user2 = UserLiveLocationViewState(
|
||||
userId = "user2",
|
||||
locationData = LocationData(
|
||||
latitude = 48.843816,
|
||||
longitude = 2.359235,
|
||||
uncertainty = null
|
||||
)
|
||||
)
|
||||
emit(listOf(user1, user2))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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.live.map
|
||||
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
|
||||
import im.vector.app.features.location.LocationData
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.channels.trySendBlocking
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetListOfUserLiveLocationUseCase @Inject constructor(
|
||||
private val session: Session,
|
||||
private val locationPinProvider: LocationPinProvider
|
||||
) {
|
||||
|
||||
// TODO add unit tests
|
||||
fun execute(): Flow<List<UserLiveLocationViewState>> {
|
||||
// TODO get room and call SDK to get the correct flow of locations
|
||||
|
||||
return callbackFlow {
|
||||
val myUserId = session.myUserId
|
||||
|
||||
locationPinProvider.create(myUserId) { pinDrawable ->
|
||||
val user1 = UserLiveLocationViewState(
|
||||
userId = session.myUserId,
|
||||
pinDrawable = pinDrawable,
|
||||
locationData = LocationData(
|
||||
latitude = 48.863447,
|
||||
longitude = 2.328608,
|
||||
uncertainty = null
|
||||
)
|
||||
)
|
||||
val user2 = UserLiveLocationViewState(
|
||||
userId = session.myUserId,
|
||||
pinDrawable = pinDrawable,
|
||||
locationData = LocationData(
|
||||
latitude = 48.843816,
|
||||
longitude = 2.359235,
|
||||
uncertainty = null
|
||||
)
|
||||
)
|
||||
val userLocations = listOf(user1, user2)
|
||||
trySendBlocking(userLocations)
|
||||
channel.close()
|
||||
}
|
||||
awaitClose()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,20 +16,34 @@
|
|||
|
||||
package im.vector.app.features.location.live.map
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.args
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.mapbox.mapboxsdk.camera.CameraPosition
|
||||
import com.mapbox.mapboxsdk.constants.MapboxConstants
|
||||
import com.mapbox.mapboxsdk.geometry.LatLng
|
||||
import com.mapbox.mapboxsdk.geometry.LatLngBounds
|
||||
import com.mapbox.mapboxsdk.maps.MapView
|
||||
import com.mapbox.mapboxsdk.maps.MapboxMap
|
||||
import com.mapbox.mapboxsdk.maps.MapboxMapOptions
|
||||
import com.mapbox.mapboxsdk.maps.Style
|
||||
import com.mapbox.mapboxsdk.maps.SupportMapFragment
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
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 im.vector.app.core.extensions.addChildFragment
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentSimpleContainerBinding
|
||||
import im.vector.app.features.location.UrlMapProvider
|
||||
import java.lang.ref.WeakReference
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
@ -43,6 +57,14 @@ class LocationLiveMapViewFragment : VectorBaseFragment<FragmentSimpleContainerBi
|
|||
|
||||
private val args: LocationLiveMapViewArgs by args()
|
||||
|
||||
private val viewModel: LocationLiveMapViewModel by fragmentViewModel()
|
||||
|
||||
private var mapboxMap: WeakReference<MapboxMap>? = null
|
||||
private var symbolManager: SymbolManager? = null
|
||||
private var mapStyle: Style? = null
|
||||
private val pendingLiveLocations = mutableListOf<UserLiveLocationViewState>()
|
||||
private var isMapFirstUpdate = true
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSimpleContainerBinding {
|
||||
return FragmentSimpleContainerBinding.inflate(layoutInflater, container, false)
|
||||
}
|
||||
|
@ -55,9 +77,70 @@ class LocationLiveMapViewFragment : VectorBaseFragment<FragmentSimpleContainerBi
|
|||
private fun setupMap() {
|
||||
val mapFragment = getOrCreateSupportMapFragment()
|
||||
|
||||
mapFragment.getMapAsync { mapBoxMap ->
|
||||
mapFragment.getMapAsync { mapboxMap ->
|
||||
lifecycleScope.launchWhenCreated {
|
||||
mapBoxMap.setStyle(urlMapProvider.getMapUrl())
|
||||
mapboxMap.setStyle(urlMapProvider.getMapUrl()) { style ->
|
||||
mapStyle = style
|
||||
this@LocationLiveMapViewFragment.mapboxMap = WeakReference(mapboxMap)
|
||||
symbolManager = SymbolManager(mapFragment.view as MapView, mapboxMap, style)
|
||||
pendingLiveLocations
|
||||
.takeUnless { it.isEmpty() }
|
||||
?.let { updateMap(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { viewState ->
|
||||
updateMap(viewState.userLocations)
|
||||
}
|
||||
|
||||
private fun updateMap(userLiveLocations: List<UserLiveLocationViewState>) {
|
||||
symbolManager?.let {
|
||||
it.deleteAll()
|
||||
|
||||
val latLngBoundsBuilder = LatLngBounds.Builder()
|
||||
userLiveLocations.forEach { userLocation ->
|
||||
addUserPinToMapStyle(userLocation.userId, userLocation.pinDrawable)
|
||||
val symbolOptions = buildSymbolOptions(userLocation)
|
||||
it.create(symbolOptions)
|
||||
|
||||
if (isMapFirstUpdate) {
|
||||
latLngBoundsBuilder.include(LatLng(userLocation.locationData.latitude, userLocation.locationData.longitude))
|
||||
}
|
||||
}
|
||||
|
||||
if (isMapFirstUpdate) {
|
||||
isMapFirstUpdate = false
|
||||
zoomToViewAllUsers(latLngBoundsBuilder.build())
|
||||
}
|
||||
} ?: run {
|
||||
pendingLiveLocations.clear()
|
||||
pendingLiveLocations.addAll(userLiveLocations)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addUserPinToMapStyle(userId: String, userPinDrawable: Drawable) {
|
||||
mapStyle?.let { style ->
|
||||
if (style.getImage(userId) == null) {
|
||||
style.addImage(userId, userPinDrawable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildSymbolOptions(userLiveLocation: UserLiveLocationViewState) =
|
||||
SymbolOptions()
|
||||
.withLatLng(LatLng(userLiveLocation.locationData.latitude, userLiveLocation.locationData.longitude))
|
||||
.withIconImage(userLiveLocation.userId)
|
||||
.withIconAnchor(Property.ICON_ANCHOR_BOTTOM)
|
||||
|
||||
private fun zoomToViewAllUsers(latLngBounds: LatLngBounds) {
|
||||
mapboxMap?.get()?.let { mapboxMap ->
|
||||
mapboxMap.getCameraForLatLngBounds(latLngBounds)?.let { cameraPosition ->
|
||||
// update the zoom a little to avoid having pins exactly at the edges of the map
|
||||
mapboxMap.cameraPosition = CameraPosition.Builder(cameraPosition)
|
||||
.zoom((cameraPosition.zoom - 1).coerceAtLeast(MapboxConstants.MINIMUM_ZOOM.toDouble()))
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ import kotlinx.coroutines.flow.onEach
|
|||
// TODO add unit tests
|
||||
class LocationLiveMapViewModel @AssistedInject constructor(
|
||||
@Assisted private val initialState: LocationLiveMapViewState,
|
||||
getCurrentUserLiveLocationUseCase: GetCurrentUserLiveLocationUseCase
|
||||
getListOfUserLiveLocationUseCase: GetListOfUserLiveLocationUseCase
|
||||
) : VectorViewModel<LocationLiveMapViewState, LocationLiveMapAction, LocationLiveMapViewEvents>(initialState) {
|
||||
|
||||
@AssistedFactory
|
||||
|
@ -40,7 +40,7 @@ class LocationLiveMapViewModel @AssistedInject constructor(
|
|||
companion object : MavericksViewModelFactory<LocationLiveMapViewModel, LocationLiveMapViewState> by hiltMavericksViewModelFactory()
|
||||
|
||||
init {
|
||||
getCurrentUserLiveLocationUseCase.execute()
|
||||
getListOfUserLiveLocationUseCase.execute()
|
||||
.onEach { setState { copy(userLocations = it) } }
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.app.features.location.live.map
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.airbnb.mvrx.MavericksState
|
||||
import im.vector.app.features.location.LocationData
|
||||
|
||||
|
@ -30,5 +31,6 @@ data class LocationLiveMapViewState(
|
|||
|
||||
data class UserLiveLocationViewState(
|
||||
val userId: String,
|
||||
val pinDrawable: Drawable,
|
||||
val locationData: LocationData
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue