From e0e06c6ac8865bdf974abbc487099e684b6c43b3 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Mon, 1 Aug 2022 09:18:55 +0200 Subject: [PATCH] Handling map loading error in sharing and preview fragment --- .../app/core/di/MavericksViewModelModule.kt | 6 +++ .../location/LocationPreviewAction.kt | 23 +++++++++ .../location/LocationPreviewFragment.kt | 20 ++++++++ .../location/LocationPreviewViewModel.kt | 48 +++++++++++++++++++ .../location/LocationPreviewViewState.kt | 23 +++++++++ .../location/LocationSharingAction.kt | 1 + .../location/LocationSharingFragment.kt | 44 ++++++++++++----- .../location/LocationSharingViewModel.kt | 5 ++ .../location/LocationSharingViewState.kt | 1 + .../res/layout/fragment_location_sharing.xml | 20 ++++---- 10 files changed, 168 insertions(+), 23 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationPreviewAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationPreviewViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationPreviewViewState.kt diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index 9b511a1bfd..3936b9eef7 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -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) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationPreviewAction.kt b/vector/src/main/java/im/vector/app/features/location/LocationPreviewAction.kt new file mode 100644 index 0000000000..75474c3f34 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationPreviewAction.kt @@ -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() +} diff --git a/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt index 131119a7aa..e8443afbc3 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt @@ -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? = 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 { diff --git a/vector/src/main/java/im/vector/app/features/location/LocationPreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationPreviewViewModel.kt new file mode 100644 index 0000000000..5987bd22f8 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationPreviewViewModel.kt @@ -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(initialState) { + + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: LocationPreviewViewState): LocationPreviewViewModel + } + + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + + override fun handle(action: LocationPreviewAction) { + when (action) { + LocationPreviewAction.ShowMapLoadingError -> handleShowMapLoadingError() + } + } + + private fun handleShowMapLoadingError() { + setState { copy(loadingMapHasFailed = true) } + } +} diff --git a/vector/src/main/java/im/vector/app/features/location/LocationPreviewViewState.kt b/vector/src/main/java/im/vector/app/features/location/LocationPreviewViewState.kt new file mode 100644 index 0000000000..c3bc86b704 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationPreviewViewState.kt @@ -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 diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt index 264b1f0e0b..5a1edbf8a1 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt @@ -25,4 +25,5 @@ sealed class LocationSharingAction : VectorViewModelAction { object ZoomToUserLocation : LocationSharingAction() object LiveLocationSharingRequested : LocationSharingAction() data class StartLiveLocationSharing(val durationMillis: Long) : LocationSharingAction() + object ShowMapLoadingError : LocationSharingAction() } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt index ca524a0126..d96410010e 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt @@ -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? = 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 = 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 = 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 } } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index 8056b72d57..28e37a38eb 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -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 { diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt index d5226eacfb..c7a2349afa 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt @@ -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( diff --git a/vector/src/main/res/layout/fragment_location_sharing.xml b/vector/src/main/res/layout/fragment_location_sharing.xml index 7d9241c42b..4f0825b992 100644 --- a/vector/src/main/res/layout/fragment_location_sharing.xml +++ b/vector/src/main/res/layout/fragment_location_sharing.xml @@ -35,16 +35,6 @@ app:layout_constraintStart_toStartOf="@id/mapView" app:layout_constraintTop_toTopOf="@id/mapView" /> - - + +