mirror of
https://github.com/element-hq/element-android
synced 2024-11-27 11:59:12 +03:00
Comparing target and user location using Flow in ViewModel
This commit is contained in:
parent
dec075faf3
commit
01879e252d
7 changed files with 145 additions and 20 deletions
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ class LocationSharingFragment @Inject constructor(
|
|||
private val urlMapProvider: UrlMapProvider,
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val matrixItemColorProvider: MatrixItemColorProvider
|
||||
) : VectorBaseFragment<FragmentLocationSharingBinding>() {
|
||||
) : VectorBaseFragment<FragmentLocationSharingBinding>(), 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
|
||||
|
|
|
@ -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<LocationSharingViewState, LocationSharingAction, LocationSharingViewEvents>(initialState), LocationTracker.Callback {
|
||||
|
||||
private val room = session.getRoom(initialState.roomId)!!
|
||||
|
||||
private val locationTargetFlow = MutableSharedFlow<LocationData>()
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : MavericksAssistedViewModelFactory<LocationSharingViewModel, LocationSharingViewState> {
|
||||
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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue