Adding button to get user current location in static location sharing preview

This commit is contained in:
Maxime NATUREL 2023-02-16 15:32:02 +01:00
parent a3a616d8df
commit d23636900f
9 changed files with 175 additions and 13 deletions

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2023 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
import androidx.fragment.app.Fragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.R
fun Fragment.showUserLocationNotAvailableErrorDialog(onConfirmListener: () -> Unit) {
MaterialAlertDialogBuilder(requireActivity())
.setTitle(R.string.location_not_available_dialog_title)
.setMessage(R.string.location_not_available_dialog_content)
.setPositiveButton(R.string.ok) { _, _ ->
onConfirmListener()
}
.setCancelable(false)
.show()
}

View file

@ -176,14 +176,7 @@ class LocationSharingFragment :
}
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) { _, _ ->
locationSharingNavigator.quit()
}
.setCancelable(false)
.show()
showUserLocationNotAvailableErrorDialog { locationSharingNavigator.quit() }
}
private fun handleLiveLocationSharingNotEnoughPermission() {

View file

@ -90,6 +90,7 @@ class LocationTracker @Inject constructor(
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION])
fun start() {
// TODO start only if not already started
Timber.d("start()")
if (locationManager == null) {

View file

@ -20,4 +20,5 @@ import im.vector.app.core.platform.VectorViewModelAction
sealed class LocationPreviewAction : VectorViewModelAction {
object ShowMapLoadingError : LocationPreviewAction()
object ZoomToUserLocation : LocationPreviewAction()
}

View file

@ -31,13 +31,18 @@ import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.platform.VectorMenuProvider
import im.vector.app.core.utils.PERMISSIONS_FOR_FOREGROUND_LOCATION_SHARING
import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.onPermissionDeniedDialog
import im.vector.app.core.utils.openLocation
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.databinding.FragmentLocationPreviewBinding
import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
import im.vector.app.features.location.DEFAULT_PIN_ID
import im.vector.app.features.location.LocationSharingArgs
import im.vector.app.features.location.MapState
import im.vector.app.features.location.UrlMapProvider
import im.vector.app.features.location.showUserLocationNotAvailableErrorDialog
import java.lang.ref.WeakReference
import javax.inject.Inject
@ -78,6 +83,28 @@ class LocationPreviewFragment :
views.mapView.initialize(urlMapProvider.getMapUrl())
loadPinDrawable()
}
observeViewEvents()
initLocateButton()
}
private fun observeViewEvents() {
viewModel.observeViewEvents {
when (it) {
LocationPreviewViewEvents.UserLocationNotAvailableError -> handleUserLocationNotAvailableError()
is LocationPreviewViewEvents.ZoomToUserLocation -> handleZoomToUserLocationEvent(it)
}
}
}
private fun handleUserLocationNotAvailableError() {
showUserLocationNotAvailableErrorDialog {
// do nothing
}
}
private fun handleZoomToUserLocationEvent(event: LocationPreviewViewEvents.ZoomToUserLocation) {
views.mapView.zoomToLocation(event.userLocation)
}
override fun onDestroyView() {
@ -124,6 +151,12 @@ class LocationPreviewFragment :
override fun invalidate() = withState(viewModel) { state ->
views.mapPreviewLoadingError.isVisible = state.loadingMapHasFailed
// TODO render pin for user location
if(state.isLoadingUserLocation) {
showLoadingDialog()
} else {
dismissLoadingDialog()
}
}
override fun getMenuRes() = R.menu.menu_location_preview
@ -154,10 +187,30 @@ class LocationPreviewFragment :
zoomOnlyOnce = true,
userLocationData = location,
pinId = args.locationOwnerId ?: DEFAULT_PIN_ID,
pinDrawable = pinDrawable
pinDrawable = pinDrawable,
)
)
}
}
}
private fun initLocateButton() {
views.mapView.locateButton.setOnClickListener {
if (checkPermissions(PERMISSIONS_FOR_FOREGROUND_LOCATION_SHARING, requireActivity(), foregroundLocationResultLauncher)) {
zoomToUserLocation()
}
}
}
private fun zoomToUserLocation() {
viewModel.handle(LocationPreviewAction.ZoomToUserLocation)
}
private val foregroundLocationResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) {
zoomToUserLocation()
} else if (deniedPermanently) {
activity?.onPermissionDeniedDialog(R.string.denied_permission_generic)
}
}
}

View file

@ -0,0 +1,25 @@
/*
* 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.preview
import im.vector.app.core.platform.VectorViewEvents
import im.vector.app.features.location.LocationData
sealed class LocationPreviewViewEvents : VectorViewEvents {
data class ZoomToUserLocation(val userLocation: LocationData) : LocationPreviewViewEvents()
object UserLocationNotAvailableError : LocationPreviewViewEvents()
}

View file

@ -22,12 +22,18 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.location.LocationData
import im.vector.app.features.location.LocationTracker
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
class LocationPreviewViewModel @AssistedInject constructor(
@Assisted private val initialState: LocationPreviewViewState,
) : VectorViewModel<LocationPreviewViewState, LocationPreviewAction, EmptyViewEvents>(initialState) {
private val locationTracker: LocationTracker,
) : VectorViewModel<LocationPreviewViewState, LocationPreviewAction, LocationPreviewViewEvents>(initialState), LocationTracker.Callback {
@AssistedFactory
interface Factory : MavericksAssistedViewModelFactory<LocationPreviewViewModel, LocationPreviewViewState> {
@ -36,13 +42,61 @@ class LocationPreviewViewModel @AssistedInject constructor(
companion object : MavericksViewModelFactory<LocationPreviewViewModel, LocationPreviewViewState> by hiltMavericksViewModelFactory()
init {
initLocationTracking()
}
private fun initLocationTracking() {
locationTracker.addCallback(this)
locationTracker.locations
.onEach(::onLocationUpdate)
.launchIn(viewModelScope)
}
override fun onCleared() {
super.onCleared()
locationTracker.removeCallback(this)
}
override fun handle(action: LocationPreviewAction) {
when (action) {
LocationPreviewAction.ShowMapLoadingError -> handleShowMapLoadingError()
LocationPreviewAction.ZoomToUserLocation -> handleZoomToUserLocationAction()
}
}
private fun handleShowMapLoadingError() {
setState { copy(loadingMapHasFailed = true) }
}
private fun handleZoomToUserLocationAction() = withState { state ->
if (!state.isLoadingUserLocation) {
setState {
copy(isLoadingUserLocation = true)
}
viewModelScope.launch(Dispatchers.Main) {
locationTracker.start()
locationTracker.requestLastKnownLocation()
}
}
}
override fun onNoLocationProviderAvailable() {
_viewEvents.post(LocationPreviewViewEvents.UserLocationNotAvailableError)
}
private fun onLocationUpdate(locationData: LocationData) {
withState { state ->
if (state.isLoadingUserLocation) {
_viewEvents.post(LocationPreviewViewEvents.ZoomToUserLocation(locationData))
}
}
setState {
copy(
lastKnownUserLocation = locationData,
isLoadingUserLocation = false,
)
}
}
}

View file

@ -17,7 +17,10 @@
package im.vector.app.features.location.preview
import com.airbnb.mvrx.MavericksState
import im.vector.app.features.location.LocationData
data class LocationPreviewViewState(
val loadingMapHasFailed: Boolean = false
val loadingMapHasFailed: Boolean = false,
val isLoadingUserLocation: Boolean = false,
val lastKnownUserLocation: LocationData? = null,
) : MavericksState

View file

@ -13,7 +13,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:mapbox_renderTextureMode="true"
app:showLocateButton="false" />
app:showLocateButton="true" />
<im.vector.app.features.location.MapLoadingErrorView
android:id="@+id/mapPreviewLoadingError"