diff --git a/changelog.d/6711.feature b/changelog.d/6711.feature
new file mode 100644
index 0000000000..cff718affd
--- /dev/null
+++ b/changelog.d/6711.feature
@@ -0,0 +1 @@
+[Location Share] Render fallback UI when map fails to load
diff --git a/library/ui-styles/src/main/res/values/stylable_map_loading_error_view.xml b/library/ui-styles/src/main/res/values/stylable_map_loading_error_view.xml
new file mode 100644
index 0000000000..911167e52a
--- /dev/null
+++ b/library/ui-styles/src/main/res/values/stylable_map_loading_error_view.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
index 3cdc8a1afe..e86b350534 100644
--- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
+++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
@@ -64,8 +64,8 @@ import im.vector.app.features.home.room.detail.search.SearchFragment
import im.vector.app.features.home.room.list.RoomListFragment
import im.vector.app.features.home.room.list.home.HomeRoomListFragment
import im.vector.app.features.home.room.threads.list.views.ThreadListFragment
-import im.vector.app.features.location.LocationPreviewFragment
import im.vector.app.features.location.LocationSharingFragment
+import im.vector.app.features.location.preview.LocationPreviewFragment
import im.vector.app.features.login.LoginCaptchaFragment
import im.vector.app.features.login.LoginFragment
import im.vector.app.features.login.LoginGenericTextInputFormFragment
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..331b4afa18 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
@@ -56,6 +56,7 @@ import im.vector.app.features.homeserver.HomeServerCapabilitiesViewModel
import im.vector.app.features.invite.InviteUsersToRoomViewModel
import im.vector.app.features.location.LocationSharingViewModel
import im.vector.app.features.location.live.map.LiveLocationMapViewModel
+import im.vector.app.features.location.preview.LocationPreviewViewModel
import im.vector.app.features.login.LoginViewModel
import im.vector.app.features.login2.LoginViewModel2
import im.vector.app.features.login2.created.AccountCreatedViewModel
@@ -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/home/room/detail/timeline/item/AbsMessageLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt
index b7790b8a30..4903b8c8cf 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt
@@ -36,6 +36,8 @@ import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners
+import im.vector.app.features.location.MapLoadingErrorView
+import im.vector.app.features.location.MapLoadingErrorViewState
abstract class AbsMessageLocationItem(
@LayoutRes layoutId: Int = R.layout.item_timeline_event_base
@@ -86,8 +88,10 @@ abstract class AbsMessageLocationItem(
target: Target?,
isFirstResource: Boolean
): Boolean {
- holder.staticMapPinImageView.setImageResource(R.drawable.ic_location_pin_failed)
- holder.staticMapErrorTextView.isVisible = true
+ holder.staticMapPinImageView.setImageDrawable(null)
+ holder.staticMapLoadingErrorView.isVisible = true
+ val mapErrorViewState = MapLoadingErrorViewState(imageCornerTransformation)
+ holder.staticMapLoadingErrorView.render(mapErrorViewState)
holder.staticMapCopyrightTextView.isVisible = false
return false
}
@@ -103,7 +107,7 @@ abstract class AbsMessageLocationItem(
// we are not using Glide since it does not display it correctly when there is no user photo
holder.staticMapPinImageView.setImageDrawable(pinDrawable)
}
- holder.staticMapErrorTextView.isVisible = false
+ holder.staticMapLoadingErrorView.isVisible = false
holder.staticMapCopyrightTextView.isVisible = true
return false
}
@@ -115,7 +119,7 @@ abstract class AbsMessageLocationItem(
abstract class Holder(@IdRes stubId: Int) : AbsMessageItem.Holder(stubId) {
val staticMapImageView by bind(R.id.staticMapImageView)
val staticMapPinImageView by bind(R.id.staticMapPinImageView)
- val staticMapErrorTextView by bind(R.id.staticMapErrorTextView)
+ val staticMapLoadingErrorView by bind(R.id.staticMapLoadingError)
val staticMapCopyrightTextView by bind(R.id.staticMapCopyrightTextView)
}
}
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/LocationSharingActivity.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt
index 169af4a5a2..9eddcad649 100644
--- a/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt
@@ -23,6 +23,7 @@ import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityLocationSharingBinding
+import im.vector.app.features.location.preview.LocationPreviewFragment
import kotlinx.parcelize.Parcelize
@Parcelize
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/java/im/vector/app/features/location/MapLoadingErrorView.kt b/vector/src/main/java/im/vector/app/features/location/MapLoadingErrorView.kt
new file mode 100644
index 0000000000..8ea4fcac54
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/location/MapLoadingErrorView.kt
@@ -0,0 +1,69 @@
+/*
+ * 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 android.content.Context
+import android.content.res.TypedArray
+import android.graphics.drawable.ColorDrawable
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.res.use
+import im.vector.app.R
+import im.vector.app.core.glide.GlideApp
+import im.vector.app.databinding.ViewMapLoadingErrorBinding
+import im.vector.app.features.themes.ThemeUtils
+
+/**
+ * Custom view to display an error when map fails to load.
+ */
+class MapLoadingErrorView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : ConstraintLayout(context, attrs, defStyleAttr) {
+
+ private val binding = ViewMapLoadingErrorBinding.inflate(
+ LayoutInflater.from(context),
+ this
+ )
+
+ init {
+ context.obtainStyledAttributes(
+ attrs,
+ R.styleable.MapLoadingErrorView,
+ 0,
+ 0
+ ).use {
+ setErrorDescription(it)
+ }
+ }
+
+ private fun setErrorDescription(typedArray: TypedArray) {
+ val description = typedArray.getString(R.styleable.MapLoadingErrorView_mapErrorDescription)
+ if (description.isNullOrEmpty()) {
+ binding.mapLoadingErrorDescription.setText(R.string.location_share_loading_map_error)
+ } else {
+ binding.mapLoadingErrorDescription.text = description
+ }
+ }
+
+ fun render(mapLoadingErrorViewState: MapLoadingErrorViewState) {
+ GlideApp.with(binding.mapLoadingErrorBackground)
+ .load(ColorDrawable(ThemeUtils.getColor(context, R.attr.vctr_system)))
+ .transform(mapLoadingErrorViewState.backgroundTransformation)
+ .into(binding.mapLoadingErrorBackground)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/location/MapLoadingErrorViewState.kt b/vector/src/main/java/im/vector/app/features/location/MapLoadingErrorViewState.kt
new file mode 100644
index 0000000000..8098d26a12
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/location/MapLoadingErrorViewState.kt
@@ -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
+
+import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
+
+data class MapLoadingErrorViewState(val backgroundTransformation: BitmapTransformation)
diff --git a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapAction.kt b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapAction.kt
index 2a07934146..295d6b5d41 100644
--- a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapAction.kt
+++ b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapAction.kt
@@ -22,4 +22,5 @@ sealed class LiveLocationMapAction : VectorViewModelAction {
data class AddMapSymbol(val key: String, val value: Long) : LiveLocationMapAction()
data class RemoveMapSymbol(val key: String) : LiveLocationMapAction()
object StopSharing : LiveLocationMapAction()
+ object ShowMapLoadingError : LiveLocationMapAction()
}
diff --git a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewFragment.kt b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewFragment.kt
index 283774dbc6..85095e7c9f 100644
--- a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewFragment.kt
@@ -71,11 +71,13 @@ class LiveLocationMapViewFragment @Inject constructor() : VectorBaseFragment? = null
+ private var mapView: MapView? = null
private var symbolManager: SymbolManager? = null
private var mapStyle: Style? = null
private val pendingLiveLocations = mutableListOf()
private var isMapFirstUpdate = true
private var onSymbolClickListener: OnSymbolClickListener? = null
+ private var mapLoadingErrorListener: MapView.OnDidFailLoadingMapListener? = null
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLiveLocationMapViewBinding {
return FragmentLiveLocationMapViewBinding.inflate(layoutInflater, container, false)
@@ -84,6 +86,7 @@ class LiveLocationMapViewFragment @Inject constructor() : VectorBaseFragment
+ (mapFragment.view as? MapView)?.let {
+ mapView = it
+ listenMapLoadingError(it)
+ }
lifecycleScope.launch {
mapboxMap.setStyle(urlMapProvider.getMapUrl()) { style ->
mapStyle = style
@@ -141,6 +146,12 @@ class LiveLocationMapViewFragment @Inject constructor() : VectorBaseFragment
- updateMap(viewState.userLocations)
+ if (viewState.loadingMapHasFailed) {
+ views.mapPreviewLoadingError.isVisible = true
+ } else {
+ views.mapPreviewLoadingError.isGone = true
+ updateMap(viewState.userLocations)
+ }
updateUserListBottomSheet(viewState.userLocations)
}
diff --git a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewModel.kt b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewModel.kt
index 280789e4c6..33c584ff85 100644
--- a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewModel.kt
@@ -61,6 +61,7 @@ class LiveLocationMapViewModel @AssistedInject constructor(
is LiveLocationMapAction.AddMapSymbol -> handleAddMapSymbol(action)
is LiveLocationMapAction.RemoveMapSymbol -> handleRemoveMapSymbol(action)
LiveLocationMapAction.StopSharing -> handleStopSharing()
+ LiveLocationMapAction.ShowMapLoadingError -> handleShowMapLoadingError()
}
}
@@ -87,6 +88,10 @@ class LiveLocationMapViewModel @AssistedInject constructor(
}
}
+ private fun handleShowMapLoadingError() {
+ setState { copy(loadingMapHasFailed = true) }
+ }
+
override fun onLocationServiceRunning(roomIds: Set) {
// NOOP
}
diff --git a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewState.kt b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewState.kt
index 615b4381e2..ddd1cd2369 100644
--- a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewState.kt
@@ -27,7 +27,8 @@ data class LiveLocationMapViewState(
/**
* Map to keep track of symbol ids associated to each user Id.
*/
- val mapSymbolIds: Map = emptyMap()
+ val mapSymbolIds: Map = emptyMap(),
+ val loadingMapHasFailed: Boolean = false,
) : MavericksState {
constructor(liveLocationMapViewArgs: LiveLocationMapViewArgs) : this(
roomId = liveLocationMapViewArgs.roomId
diff --git a/vector/src/main/java/im/vector/app/features/location/preview/LocationPreviewAction.kt b/vector/src/main/java/im/vector/app/features/location/preview/LocationPreviewAction.kt
new file mode 100644
index 0000000000..38f6952f67
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/location/preview/LocationPreviewAction.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.preview
+
+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/preview/LocationPreviewFragment.kt
similarity index 78%
rename from vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt
rename to vector/src/main/java/im/vector/app/features/location/preview/LocationPreviewFragment.kt
index 131119a7aa..8285d0156b 100644
--- a/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/location/preview/LocationPreviewFragment.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021 New Vector Ltd
+ * 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.
@@ -14,15 +14,18 @@
* limitations under the License.
*/
-package im.vector.app.features.location
+package im.vector.app.features.location.preview
import android.os.Bundle
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
@@ -30,6 +33,10 @@ import im.vector.app.core.platform.VectorMenuProvider
import im.vector.app.core.utils.openLocation
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 java.lang.ref.WeakReference
import javax.inject.Inject
@@ -44,9 +51,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 +66,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 +77,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 +119,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/preview/LocationPreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/location/preview/LocationPreviewViewModel.kt
new file mode 100644
index 0000000000..f0698249ce
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/location/preview/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.preview
+
+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/preview/LocationPreviewViewState.kt b/vector/src/main/java/im/vector/app/features/location/preview/LocationPreviewViewState.kt
new file mode 100644
index 0000000000..96e8316323
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/location/preview/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.preview
+
+import com.airbnb.mvrx.MavericksState
+
+data class LocationPreviewViewState(
+ val loadingMapHasFailed: Boolean = false
+) : MavericksState
diff --git a/vector/src/main/res/drawable/ic_location_pin_failed.xml b/vector/src/main/res/drawable/ic_location_pin_failed.xml
deleted file mode 100644
index 250d048836..0000000000
--- a/vector/src/main/res/drawable/ic_location_pin_failed.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/vector/src/main/res/layout/fragment_live_location_map_view.xml b/vector/src/main/res/layout/fragment_live_location_map_view.xml
index 81db466dab..369404ddbd 100644
--- a/vector/src/main/res/layout/fragment_live_location_map_view.xml
+++ b/vector/src/main/res/layout/fragment_live_location_map_view.xml
@@ -17,6 +17,13 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
+
+
+
+
diff --git a/vector/src/main/res/layout/fragment_location_sharing.xml b/vector/src/main/res/layout/fragment_location_sharing.xml
index cd15f418ea..4f0825b992 100644
--- a/vector/src/main/res/layout/fragment_location_sharing.xml
+++ b/vector/src/main/res/layout/fragment_location_sharing.xml
@@ -52,4 +52,14 @@
app:layout_constraintBottom_toBottomOf="@id/shareLocationOptionsPicker"
app:layout_constraintEnd_toEndOf="@id/shareLocationOptionsPicker" />
+
+
diff --git a/vector/src/main/res/layout/item_timeline_event_location_stub.xml b/vector/src/main/res/layout/item_timeline_event_location_stub.xml
index e4c87dab71..8de98b2260 100644
--- a/vector/src/main/res/layout/item_timeline_event_location_stub.xml
+++ b/vector/src/main/res/layout/item_timeline_event_location_stub.xml
@@ -29,21 +29,16 @@
app:layout_constraintTop_toTopOf="@id/staticMapImageView"
app:layout_constraintVertical_bias="1.0" />
-
+ app:layout_constraintBottom_toTopOf="@id/liveLocationRunningBanner"
+ app:layout_constraintEnd_toEndOf="@id/staticMapImageView"
+ app:layout_constraintStart_toStartOf="@id/staticMapImageView"
+ app:layout_constraintTop_toTopOf="@id/staticMapImageView"
+ app:mapErrorDescription="@string/location_timeline_failed_to_load_map" />
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 6b4eec1c9c..d1b2d237d9 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -3114,6 +3114,7 @@
${app_name} could not access your location. Please try again later.
Open with
Failed to load map
+ Unable to load map\nThis home server may not be configured to display maps.
Live location enabled
Loading live location…
Live location ended