Handling map loading error in sharing and preview fragment

This commit is contained in:
Maxime NATUREL 2022-08-01 09:18:55 +02:00
parent 87ca9606b3
commit e0e06c6ac8
10 changed files with 168 additions and 23 deletions

View file

@ -54,6 +54,7 @@ import im.vector.app.features.home.room.list.RoomListViewModel
import im.vector.app.features.home.room.list.home.HomeRoomListViewModel
import im.vector.app.features.homeserver.HomeServerCapabilitiesViewModel
import im.vector.app.features.invite.InviteUsersToRoomViewModel
import im.vector.app.features.location.LocationPreviewViewModel
import im.vector.app.features.location.LocationSharingViewModel
import im.vector.app.features.location.live.map.LiveLocationMapViewModel
import im.vector.app.features.login.LoginViewModel
@ -605,6 +606,11 @@ interface MavericksViewModelModule {
@MavericksViewModelKey(LocationSharingViewModel::class)
fun createLocationSharingViewModelFactory(factory: LocationSharingViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
@Binds
@IntoMap
@MavericksViewModelKey(LocationPreviewViewModel::class)
fun createLocationPreviewViewModelFactory(factory: LocationPreviewViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
@Binds
@IntoMap
@MavericksViewModelKey(VectorAttachmentViewerViewModel::class)

View file

@ -0,0 +1,23 @@
/*
* 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
import im.vector.app.core.platform.VectorViewModelAction
sealed class LocationPreviewAction : VectorViewModelAction {
object ShowMapLoadingError : LocationPreviewAction()
}

View file

@ -21,8 +21,11 @@ import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import com.mapbox.mapboxsdk.maps.MapView
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseFragment
@ -44,9 +47,13 @@ class LocationPreviewFragment @Inject constructor(
private val args: LocationSharingArgs by args()
private val viewModel: LocationPreviewViewModel by fragmentViewModel()
// Keep a ref to handle properly the onDestroy callback
private var mapView: WeakReference<MapView>? = null
private var mapLoadingErrorListener: MapView.OnDidFailLoadingMapListener? = null
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLocationPreviewBinding {
return FragmentLocationPreviewBinding.inflate(layoutInflater, container, false)
}
@ -55,6 +62,9 @@ class LocationPreviewFragment @Inject constructor(
super.onViewCreated(view, savedInstanceState)
mapView = WeakReference(views.mapView)
mapLoadingErrorListener = MapView.OnDidFailLoadingMapListener {
viewModel.handle(LocationPreviewAction.ShowMapLoadingError)
}.also { views.mapView.addOnDidFailLoadingMapListener(it) }
views.mapView.onCreate(savedInstanceState)
lifecycleScope.launchWhenCreated {
@ -63,6 +73,12 @@ class LocationPreviewFragment @Inject constructor(
}
}
override fun onDestroyView() {
mapLoadingErrorListener?.let { mapView?.get()?.removeOnDidFailLoadingMapListener(it) }
mapLoadingErrorListener = null
super.onDestroyView()
}
override fun onResume() {
super.onResume()
views.mapView.onResume()
@ -99,6 +115,10 @@ class LocationPreviewFragment @Inject constructor(
super.onDestroy()
}
override fun invalidate() = withState(viewModel) { state ->
views.mapPreviewLoadingError.isVisible = state.loadingMapHasFailed
}
override fun getMenuRes() = R.menu.menu_location_preview
override fun handleMenuItemSelected(item: MenuItem): Boolean {

View file

@ -0,0 +1,48 @@
/*
* 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
import com.airbnb.mvrx.MavericksViewModelFactory
import dagger.assisted.Assisted
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
class LocationPreviewViewModel @AssistedInject constructor(
@Assisted private val initialState: LocationPreviewViewState,
) : VectorViewModel<LocationPreviewViewState, LocationPreviewAction, EmptyViewEvents>(initialState) {
@AssistedFactory
interface Factory : MavericksAssistedViewModelFactory<LocationPreviewViewModel, LocationPreviewViewState> {
override fun create(initialState: LocationPreviewViewState): LocationPreviewViewModel
}
companion object : MavericksViewModelFactory<LocationPreviewViewModel, LocationPreviewViewState> by hiltMavericksViewModelFactory()
override fun handle(action: LocationPreviewAction) {
when (action) {
LocationPreviewAction.ShowMapLoadingError -> handleShowMapLoadingError()
}
}
private fun handleShowMapLoadingError() {
setState { copy(loadingMapHasFailed = true) }
}
}

View file

@ -0,0 +1,23 @@
/*
* 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
import com.airbnb.mvrx.MavericksState
data class LocationPreviewViewState(
val loadingMapHasFailed: Boolean = false
) : MavericksState

View file

@ -25,4 +25,5 @@ sealed class LocationSharingAction : VectorViewModelAction {
object ZoomToUserLocation : LocationSharingAction()
object LiveLocationSharingRequested : LocationSharingAction()
data class StartLiveLocationSharing(val durationMillis: Long) : LocationSharingAction()
object ShowMapLoadingError : LocationSharingAction()
}

View file

@ -24,6 +24,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.fragment.app.setFragmentResultListener
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.fragmentViewModel
@ -69,6 +70,7 @@ class LocationSharingFragment @Inject constructor(
private var mapView: WeakReference<MapView>? = null
private var hasRenderedUserAvatar = false
private var mapLoadingErrorListener: MapView.OnDidFailLoadingMapListener? = null
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLocationSharingBinding {
return FragmentLocationSharingBinding.inflate(inflater, container, false)
@ -87,6 +89,9 @@ class LocationSharingFragment @Inject constructor(
super.onViewCreated(view, savedInstanceState)
mapView = WeakReference(views.mapView)
mapLoadingErrorListener = MapView.OnDidFailLoadingMapListener {
viewModel.handle(LocationSharingAction.ShowMapLoadingError)
}.also { views.mapView.addOnDidFailLoadingMapListener(it) }
views.mapView.onCreate(savedInstanceState)
lifecycleScope.launchWhenCreated {
@ -112,6 +117,12 @@ class LocationSharingFragment @Inject constructor(
}
}
override fun onDestroyView() {
mapLoadingErrorListener?.let { mapView?.get()?.removeOnDidFailLoadingMapListener(it) }
mapLoadingErrorListener = null
super.onDestroyView()
}
override fun onResume() {
super.onResume()
views.mapView.onResume()
@ -256,20 +267,27 @@ class LocationSharingFragment @Inject constructor(
}
private fun updateMap(state: LocationSharingViewState) {
// first, update the options view
val options: Set<LocationSharingOption> = when (state.areTargetAndUserLocationEqual) {
true -> setOf(LocationSharingOption.USER_CURRENT, LocationSharingOption.USER_LIVE)
false -> setOf(LocationSharingOption.PINNED)
else -> emptySet()
}
views.shareLocationOptionsPicker.render(options)
if (state.loadingMapHasFailed) {
views.shareLocationOptionsPicker.render(emptySet())
views.shareLocationMapLoadingError.isVisible = true
} else {
// first, update the options view
val options: Set<LocationSharingOption> = when (state.areTargetAndUserLocationEqual) {
true -> setOf(LocationSharingOption.USER_CURRENT, LocationSharingOption.USER_LIVE)
false -> setOf(LocationSharingOption.PINNED)
else -> emptySet()
}
views.shareLocationOptionsPicker.render(options)
// then, update the map using the height of the options view after it has been rendered
views.shareLocationOptionsPicker.post {
val mapState = state
.toMapState()
.copy(logoMarginBottom = views.shareLocationOptionsPicker.height)
views.mapView.render(mapState)
// then, update the map using the height of the options view after it has been rendered
views.shareLocationOptionsPicker.post {
val mapState = state
.toMapState()
.copy(logoMarginBottom = views.shareLocationOptionsPicker.height)
views.mapView.render(mapState)
}
views.shareLocationMapLoadingError.isGone = true
}
}

View file

@ -152,6 +152,7 @@ class LocationSharingViewModel @AssistedInject constructor(
LocationSharingAction.ZoomToUserLocation -> handleZoomToUserLocationAction()
LocationSharingAction.LiveLocationSharingRequested -> handleLiveLocationSharingRequestedAction()
is LocationSharingAction.StartLiveLocationSharing -> handleStartLiveLocationSharingAction(action.durationMillis)
LocationSharingAction.ShowMapLoadingError -> handleShowMapLoadingError()
}
}
@ -211,6 +212,10 @@ class LocationSharingViewModel @AssistedInject constructor(
)
}
private fun handleShowMapLoadingError() {
setState { copy(loadingMapHasFailed = true) }
}
private fun onLocationUpdate(locationData: LocationData) {
Timber.d("onLocationUpdate()")
setState {

View file

@ -36,6 +36,7 @@ data class LocationSharingViewState(
val lastKnownUserLocation: LocationData? = null,
val locationTargetDrawable: Drawable? = null,
val canShareLiveLocation: Boolean = false,
val loadingMapHasFailed: Boolean = false
) : MavericksState {
constructor(locationSharingArgs: LocationSharingArgs) : this(

View file

@ -35,16 +35,6 @@
app:layout_constraintStart_toStartOf="@id/mapView"
app:layout_constraintTop_toTopOf="@id/mapView" />
<im.vector.app.features.location.MapLoadingErrorView
android:id="@+id/shareLocationMapLoadingError"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/shareLocationOptionsPicker"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<im.vector.app.features.location.option.LocationSharingOptionPickerView
android:id="@+id/shareLocationOptionsPicker"
android:layout_width="0dp"
@ -62,4 +52,14 @@
app:layout_constraintBottom_toBottomOf="@id/shareLocationOptionsPicker"
app:layout_constraintEnd_toEndOf="@id/shareLocationOptionsPicker" />
<im.vector.app.features.location.MapLoadingErrorView
android:id="@+id/shareLocationMapLoadingError"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/shareLocationOptionsPicker"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>