Adding button to get user current location in static location sharing preview
This commit is contained in:
parent
a3a616d8df
commit
d23636900f
|
@ -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()
|
||||||
|
}
|
|
@ -176,14 +176,7 @@ class LocationSharingFragment :
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLocationNotAvailableError() {
|
private fun handleLocationNotAvailableError() {
|
||||||
MaterialAlertDialogBuilder(requireActivity())
|
showUserLocationNotAvailableErrorDialog { locationSharingNavigator.quit() }
|
||||||
.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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLiveLocationSharingNotEnoughPermission() {
|
private fun handleLiveLocationSharingNotEnoughPermission() {
|
||||||
|
|
|
@ -90,6 +90,7 @@ class LocationTracker @Inject constructor(
|
||||||
|
|
||||||
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION])
|
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION])
|
||||||
fun start() {
|
fun start() {
|
||||||
|
// TODO start only if not already started
|
||||||
Timber.d("start()")
|
Timber.d("start()")
|
||||||
|
|
||||||
if (locationManager == null) {
|
if (locationManager == null) {
|
||||||
|
|
|
@ -20,4 +20,5 @@ import im.vector.app.core.platform.VectorViewModelAction
|
||||||
|
|
||||||
sealed class LocationPreviewAction : VectorViewModelAction {
|
sealed class LocationPreviewAction : VectorViewModelAction {
|
||||||
object ShowMapLoadingError : LocationPreviewAction()
|
object ShowMapLoadingError : LocationPreviewAction()
|
||||||
|
object ZoomToUserLocation : LocationPreviewAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,13 +31,18 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.platform.VectorMenuProvider
|
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.openLocation
|
||||||
|
import im.vector.app.core.utils.registerForPermissionsResult
|
||||||
import im.vector.app.databinding.FragmentLocationPreviewBinding
|
import im.vector.app.databinding.FragmentLocationPreviewBinding
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
|
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.DEFAULT_PIN_ID
|
||||||
import im.vector.app.features.location.LocationSharingArgs
|
import im.vector.app.features.location.LocationSharingArgs
|
||||||
import im.vector.app.features.location.MapState
|
import im.vector.app.features.location.MapState
|
||||||
import im.vector.app.features.location.UrlMapProvider
|
import im.vector.app.features.location.UrlMapProvider
|
||||||
|
import im.vector.app.features.location.showUserLocationNotAvailableErrorDialog
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -78,6 +83,28 @@ class LocationPreviewFragment :
|
||||||
views.mapView.initialize(urlMapProvider.getMapUrl())
|
views.mapView.initialize(urlMapProvider.getMapUrl())
|
||||||
loadPinDrawable()
|
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() {
|
override fun onDestroyView() {
|
||||||
|
@ -124,6 +151,12 @@ class LocationPreviewFragment :
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) { state ->
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
views.mapPreviewLoadingError.isVisible = state.loadingMapHasFailed
|
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
|
override fun getMenuRes() = R.menu.menu_location_preview
|
||||||
|
@ -154,10 +187,30 @@ class LocationPreviewFragment :
|
||||||
zoomOnlyOnce = true,
|
zoomOnlyOnce = true,
|
||||||
userLocationData = location,
|
userLocationData = location,
|
||||||
pinId = args.locationOwnerId ?: DEFAULT_PIN_ID,
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
|
@ -22,12 +22,18 @@ import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
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.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(
|
class LocationPreviewViewModel @AssistedInject constructor(
|
||||||
@Assisted private val initialState: LocationPreviewViewState,
|
@Assisted private val initialState: LocationPreviewViewState,
|
||||||
) : VectorViewModel<LocationPreviewViewState, LocationPreviewAction, EmptyViewEvents>(initialState) {
|
private val locationTracker: LocationTracker,
|
||||||
|
) : VectorViewModel<LocationPreviewViewState, LocationPreviewAction, LocationPreviewViewEvents>(initialState), LocationTracker.Callback {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory : MavericksAssistedViewModelFactory<LocationPreviewViewModel, LocationPreviewViewState> {
|
interface Factory : MavericksAssistedViewModelFactory<LocationPreviewViewModel, LocationPreviewViewState> {
|
||||||
|
@ -36,13 +42,61 @@ class LocationPreviewViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
companion object : MavericksViewModelFactory<LocationPreviewViewModel, LocationPreviewViewState> by hiltMavericksViewModelFactory()
|
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) {
|
override fun handle(action: LocationPreviewAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
LocationPreviewAction.ShowMapLoadingError -> handleShowMapLoadingError()
|
LocationPreviewAction.ShowMapLoadingError -> handleShowMapLoadingError()
|
||||||
|
LocationPreviewAction.ZoomToUserLocation -> handleZoomToUserLocationAction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleShowMapLoadingError() {
|
private fun handleShowMapLoadingError() {
|
||||||
setState { copy(loadingMapHasFailed = true) }
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,10 @@
|
||||||
package im.vector.app.features.location.preview
|
package im.vector.app.features.location.preview
|
||||||
|
|
||||||
import com.airbnb.mvrx.MavericksState
|
import com.airbnb.mvrx.MavericksState
|
||||||
|
import im.vector.app.features.location.LocationData
|
||||||
|
|
||||||
data class LocationPreviewViewState(
|
data class LocationPreviewViewState(
|
||||||
val loadingMapHasFailed: Boolean = false
|
val loadingMapHasFailed: Boolean = false,
|
||||||
|
val isLoadingUserLocation: Boolean = false,
|
||||||
|
val lastKnownUserLocation: LocationData? = null,
|
||||||
) : MavericksState
|
) : MavericksState
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:mapbox_renderTextureMode="true"
|
app:mapbox_renderTextureMode="true"
|
||||||
app:showLocateButton="false" />
|
app:showLocateButton="true" />
|
||||||
|
|
||||||
<im.vector.app.features.location.MapLoadingErrorView
|
<im.vector.app.features.location.MapLoadingErrorView
|
||||||
android:id="@+id/mapPreviewLoadingError"
|
android:id="@+id/mapPreviewLoadingError"
|
||||||
|
|
Loading…
Reference in New Issue