From e9b9406bf11fd1069cd79a8a290400859e176861 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Thu, 27 Jan 2022 21:11:20 +0100
Subject: [PATCH] Rework the location code - WIP

---
 .../timeline/helper/LocationPinProvider.kt    | 35 +++++--
 .../im/vector/app/features/location/Config.kt |  4 +-
 .../location/LocationPreviewFragment.kt       | 37 ++++++-
 .../location/LocationSharingAction.kt         |  2 -
 .../location/LocationSharingFragment.kt       | 86 ++++++++--------
 .../location/LocationSharingViewModel.kt      | 36 +++++--
 .../location/LocationSharingViewState.kt      |  4 +-
 .../app/features/location/LocationTracker.kt  | 72 ++++++++------
 .../app/features/location/MapTilerMapView.kt  | 98 ++++++++++++++-----
 .../app/features/location/VectorMapView.kt    | 32 ------
 10 files changed, 247 insertions(+), 159 deletions(-)
 delete mode 100644 vector/src/main/java/im/vector/app/features/location/VectorMapView.kt

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt
index fe3a7d9007..e92376c44d 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt
@@ -28,6 +28,7 @@ import im.vector.app.core.glide.GlideApp
 import im.vector.app.core.utils.DimensionConverter
 import im.vector.app.features.home.AvatarRenderer
 import org.matrix.android.sdk.api.util.toMatrixItem
+import timber.log.Timber
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -54,22 +55,36 @@ class LocationPinProvider @Inject constructor(
             val size = dimensionConverter.dpToPx(44)
             avatarRenderer.render(glideRequests, it, object : CustomTarget<Drawable>(size, size) {
                 override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
-                    val bgUserPin = ContextCompat.getDrawable(context, R.drawable.bg_map_user_pin)!!
-                    val layerDrawable = LayerDrawable(arrayOf(bgUserPin, resource))
-                    val horizontalInset = dimensionConverter.dpToPx(4)
-                    val topInset = dimensionConverter.dpToPx(4)
-                    val bottomInset = dimensionConverter.dpToPx(8)
-                    layerDrawable.setLayerInset(1, horizontalInset, topInset, horizontalInset, bottomInset)
-
-                    cache[userId] = layerDrawable
-
-                    callback(layerDrawable)
+                    Timber.d("## Location: onResourceReady")
+                    val pinDrawable = createPinDrawable(resource)
+                    cache[userId] = pinDrawable
+                    callback(pinDrawable)
                 }
 
                 override fun onLoadCleared(placeholder: Drawable?) {
                     // Is it possible? Put placeholder instead?
+                    // FIXME The doc says it has to be implemented and should free resources
+                    Timber.d("## Location: onLoadCleared")
+                }
+
+                override fun onLoadFailed(errorDrawable: Drawable?) {
+                    Timber.w("## Location: onLoadFailed")
+                    errorDrawable ?: return
+                    val pinDrawable = createPinDrawable(errorDrawable)
+                    cache[userId] = pinDrawable
+                    callback(pinDrawable)
                 }
             })
         }
     }
+
+    private fun createPinDrawable(drawable: Drawable): Drawable {
+        val bgUserPin = ContextCompat.getDrawable(context, R.drawable.bg_map_user_pin)!!
+        val layerDrawable = LayerDrawable(arrayOf(bgUserPin, drawable))
+        val horizontalInset = dimensionConverter.dpToPx(4)
+        val topInset = dimensionConverter.dpToPx(4)
+        val bottomInset = dimensionConverter.dpToPx(8)
+        layerDrawable.setLayerInset(1, horizontalInset, topInset, horizontalInset, bottomInset)
+        return layerDrawable
+    }
 }
diff --git a/vector/src/main/java/im/vector/app/features/location/Config.kt b/vector/src/main/java/im/vector/app/features/location/Config.kt
index 630df16a37..0b3eb015b9 100644
--- a/vector/src/main/java/im/vector/app/features/location/Config.kt
+++ b/vector/src/main/java/im/vector/app/features/location/Config.kt
@@ -17,5 +17,5 @@
 package im.vector.app.features.location
 
 const val INITIAL_MAP_ZOOM = 15.0
-const val MIN_TIME_MILLIS_TO_UPDATE_LOCATION = 1 * 60 * 1000L // every 1 minute
-const val MIN_DISTANCE_METERS_TO_UPDATE_LOCATION = 10f
+const val MIN_TIME_TO_UPDATE_LOCATION_MILLIS = 5 * 1_000L // every 5 seconds
+const val MIN_DISTANCE_TO_UPDATE_LOCATION_METERS = 10f
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 6209bf5a4f..641b5910a6 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
@@ -22,19 +22,27 @@ import android.view.MenuItem
 import android.view.View
 import android.view.ViewGroup
 import com.airbnb.mvrx.args
+import com.mapbox.mapboxsdk.maps.MapView
 import im.vector.app.R
 import im.vector.app.core.platform.VectorBaseFragment
 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 java.lang.ref.WeakReference
 import javax.inject.Inject
 
+/**
+ * TODO Move locationPinProvider to a ViewModel
+ */
 class LocationPreviewFragment @Inject constructor(
         private val locationPinProvider: LocationPinProvider
 ) : VectorBaseFragment<FragmentLocationPreviewBinding>() {
 
     private val args: LocationSharingArgs by args()
 
+    // Keep a ref to handle properly the onDestroy callback
+    private var mapView: WeakReference<MapView>? = null
+
     override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLocationPreviewBinding {
         return FragmentLocationPreviewBinding.inflate(layoutInflater, container, false)
     }
@@ -42,6 +50,8 @@ class LocationPreviewFragment @Inject constructor(
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
 
+        mapView = WeakReference(views.mapView)
+        views.mapView.onCreate(savedInstanceState)
         views.mapView.initialize {
             if (isAdded) {
                 onMapReady()
@@ -49,16 +59,42 @@ class LocationPreviewFragment @Inject constructor(
         }
     }
 
+    override fun onResume() {
+        super.onResume()
+        views.mapView.onResume()
+    }
+
     override fun onPause() {
         views.mapView.onPause()
         super.onPause()
     }
 
+    override fun onLowMemory() {
+        views.mapView.onLowMemory()
+        super.onLowMemory()
+    }
+
+    override fun onStart() {
+        super.onStart()
+        views.mapView.onStart()
+    }
+
     override fun onStop() {
         views.mapView.onStop()
         super.onStop()
     }
 
+    override fun onSaveInstanceState(outState: Bundle) {
+        super.onSaveInstanceState(outState)
+        views.mapView.onSaveInstanceState(outState)
+    }
+
+    override fun onDestroy() {
+        mapView?.get()?.onDestroy()
+        mapView?.clear()
+        super.onDestroy()
+    }
+
     override fun getMenuRes() = R.menu.menu_location_preview
 
     override fun onOptionsItemSelected(item: MenuItem): Boolean {
@@ -85,7 +121,6 @@ class LocationPreviewFragment @Inject constructor(
         locationPinProvider.create(userId) { pinDrawable ->
             views.mapView.apply {
                 zoomToLocation(location.latitude, location.longitude, INITIAL_MAP_ZOOM)
-                deleteAllPins()
                 addPinToMap(userId, pinDrawable)
                 updatePinLocation(userId, location.latitude, location.longitude)
             }
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 71101d0612..01319ef6c7 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
@@ -19,7 +19,5 @@ package im.vector.app.features.location
 import im.vector.app.core.platform.VectorViewModelAction
 
 sealed class LocationSharingAction : VectorViewModelAction {
-    data class OnLocationUpdate(val locationData: LocationData) : LocationSharingAction()
     object OnShareLocation : LocationSharingAction()
-    object OnLocationProviderIsNotAvailable : 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 b35a72029b..2ac8200f60 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
@@ -21,29 +21,29 @@ import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import com.airbnb.mvrx.fragmentViewModel
+import com.airbnb.mvrx.withState
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.mapbox.mapboxsdk.maps.MapView
 import im.vector.app.R
 import im.vector.app.core.extensions.exhaustive
 import im.vector.app.core.platform.VectorBaseFragment
 import im.vector.app.databinding.FragmentLocationSharingBinding
-import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
-import org.matrix.android.sdk.api.session.Session
+import java.lang.ref.WeakReference
 import javax.inject.Inject
 
+/**
+ * We should consider using SupportMapFragment for a out of the box lifecycle handling
+ */
 class LocationSharingFragment @Inject constructor(
-        private val locationTracker: LocationTracker,
-        private val session: Session,
-        private val locationPinProvider: LocationPinProvider
-) : VectorBaseFragment<FragmentLocationSharingBinding>(), LocationTracker.Callback {
-
-    init {
-        locationTracker.callback = this
-    }
+) : VectorBaseFragment<FragmentLocationSharingBinding>() {
 
     private val viewModel: LocationSharingViewModel by fragmentViewModel()
 
     private var lastZoomValue: Double = -1.0
 
+    // Keep a ref to handle properly the onDestroy callback
+    private var mapView: WeakReference<MapView>? = null
+
     override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLocationSharingBinding {
         return FragmentLocationSharingBinding.inflate(inflater, container, false)
     }
@@ -51,11 +51,9 @@ class LocationSharingFragment @Inject constructor(
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
 
-        views.mapView.initialize {
-            if (isAdded) {
-                onMapReady()
-            }
-        }
+        mapView = WeakReference(views.mapView)
+        views.mapView.onCreate(savedInstanceState)
+        views.mapView.initialize()
 
         views.shareLocationContainer.debouncedClicks {
             viewModel.handle(LocationSharingAction.OnShareLocation)
@@ -63,54 +61,48 @@ class LocationSharingFragment @Inject constructor(
 
         viewModel.observeViewEvents {
             when (it) {
-                LocationSharingViewEvents.LocationNotAvailableError    -> handleLocationNotAvailableError()
-                LocationSharingViewEvents.Close                        -> activity?.finish()
+                LocationSharingViewEvents.LocationNotAvailableError -> handleLocationNotAvailableError()
+                LocationSharingViewEvents.Close                     -> activity?.finish()
             }.exhaustive
         }
     }
 
+    override fun onResume() {
+        super.onResume()
+        views.mapView.onResume()
+    }
+
     override fun onPause() {
         views.mapView.onPause()
         super.onPause()
     }
 
+    override fun onSaveInstanceState(outState: Bundle) {
+        super.onSaveInstanceState(outState)
+        views.mapView.onSaveInstanceState(outState)
+    }
+
+    override fun onStart() {
+        super.onStart()
+        views.mapView.onStart()
+    }
+
     override fun onStop() {
         views.mapView.onStop()
         super.onStop()
     }
 
+    override fun onLowMemory() {
+        super.onLowMemory()
+        views.mapView.onLowMemory()
+    }
+
     override fun onDestroy() {
-        locationTracker.stop()
+        mapView?.get()?.onDestroy()
+        mapView?.clear()
         super.onDestroy()
     }
 
-    private fun onMapReady() {
-        if (!isAdded) return
-
-        locationPinProvider.create(session.myUserId) {
-            views.mapView.addPinToMap(
-                    pinId = USER_PIN_NAME,
-                    image = it,
-            )
-            // All set, start location tracker
-            locationTracker.start()
-        }
-    }
-
-    override fun onLocationUpdate(locationData: LocationData) {
-        lastZoomValue = if (lastZoomValue == -1.0) INITIAL_MAP_ZOOM else views.mapView.getCurrentZoom() ?: INITIAL_MAP_ZOOM
-
-        views.mapView.zoomToLocation(locationData.latitude, locationData.longitude, lastZoomValue)
-        views.mapView.deleteAllPins()
-        views.mapView.updatePinLocation(USER_PIN_NAME, locationData.latitude, locationData.longitude)
-
-        viewModel.handle(LocationSharingAction.OnLocationUpdate(locationData))
-    }
-
-    override fun onLocationProviderIsNotAvailable() {
-        viewModel.handle(LocationSharingAction.OnLocationProviderIsNotAvailable)
-    }
-
     private fun handleLocationNotAvailableError() {
         MaterialAlertDialogBuilder(requireActivity())
                 .setTitle(R.string.location_not_available_dialog_title)
@@ -122,6 +114,10 @@ class LocationSharingFragment @Inject constructor(
                 .show()
     }
 
+    override fun invalidate() = withState(viewModel) { state ->
+        views.mapView.render(state)
+    }
+
     companion object {
         const val USER_PIN_NAME = "USER_PIN_NAME"
     }
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 b3c97310e1..f4e1fd0281 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
@@ -24,12 +24,15 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
 import im.vector.app.core.di.hiltMavericksViewModelFactory
 import im.vector.app.core.extensions.exhaustive
 import im.vector.app.core.platform.VectorViewModel
+import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
 import org.matrix.android.sdk.api.session.Session
 
 class LocationSharingViewModel @AssistedInject constructor(
         @Assisted private val initialState: LocationSharingViewState,
-        session: Session
-) : VectorViewModel<LocationSharingViewState, LocationSharingAction, LocationSharingViewEvents>(initialState) {
+        private val locationTracker: LocationTracker,
+        private val locationPinProvider: LocationPinProvider,
+        private val session: Session
+) : VectorViewModel<LocationSharingViewState, LocationSharingAction, LocationSharingViewEvents>(initialState), LocationTracker.Callback {
 
     private val room = session.getRoom(initialState.roomId)!!
 
@@ -38,14 +41,31 @@ class LocationSharingViewModel @AssistedInject constructor(
         override fun create(initialState: LocationSharingViewState): LocationSharingViewModel
     }
 
-    companion object : MavericksViewModelFactory<LocationSharingViewModel, LocationSharingViewState> by hiltMavericksViewModelFactory() {
+    companion object : MavericksViewModelFactory<LocationSharingViewModel, LocationSharingViewState> by hiltMavericksViewModelFactory()
+
+    init {
+        locationTracker.start(this)
+        createPin()
+    }
+
+    private fun createPin() {
+        locationPinProvider.create(session.myUserId) {
+            setState {
+                copy(
+                        pinDrawable = it
+                )
+            }
+        }
+    }
+
+    override fun onCleared() {
+        super.onCleared()
+        locationTracker.stop()
     }
 
     override fun handle(action: LocationSharingAction) {
         when (action) {
-            is LocationSharingAction.OnLocationUpdate              -> handleLocationUpdate(action.locationData)
-            LocationSharingAction.OnShareLocation                  -> handleShareLocation()
-            LocationSharingAction.OnLocationProviderIsNotAvailable -> handleLocationProviderIsNotAvailable()
+            LocationSharingAction.OnShareLocation -> handleShareLocation()
         }.exhaustive
     }
 
@@ -62,13 +82,13 @@ class LocationSharingViewModel @AssistedInject constructor(
         }
     }
 
-    private fun handleLocationUpdate(locationData: LocationData) {
+    override fun onLocationUpdate(locationData: LocationData) {
         setState {
             copy(lastKnownLocation = locationData)
         }
     }
 
-    private fun handleLocationProviderIsNotAvailable() {
+    override fun onLocationProviderIsNotAvailable() {
         _viewEvents.post(LocationSharingViewEvents.LocationNotAvailableError)
     }
 }
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 2869929b12..7ee5ba00fa 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
@@ -16,6 +16,7 @@
 
 package im.vector.app.features.location
 
+import android.graphics.drawable.Drawable
 import androidx.annotation.StringRes
 import com.airbnb.mvrx.MavericksState
 import im.vector.app.R
@@ -28,7 +29,8 @@ enum class LocationSharingMode(@StringRes val titleRes: Int) {
 data class LocationSharingViewState(
         val roomId: String,
         val mode: LocationSharingMode,
-        val lastKnownLocation: LocationData? = null
+        val lastKnownLocation: LocationData? = null,
+        val pinDrawable: Drawable? = null
 ) : MavericksState {
 
     constructor(locationSharingArgs: LocationSharingArgs) : this(
diff --git a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt
index 9ccf79de33..9d3d506a73 100644
--- a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt
+++ b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt
@@ -27,63 +27,73 @@ import timber.log.Timber
 import javax.inject.Inject
 
 class LocationTracker @Inject constructor(
-        private val context: Context
+        context: Context
 ) : LocationListenerCompat {
 
+    private val locationManager = context.getSystemService<LocationManager>()
+
     interface Callback {
         fun onLocationUpdate(locationData: LocationData)
         fun onLocationProviderIsNotAvailable()
     }
 
-    private var locationManager: LocationManager? = null
-    var callback: Callback? = null
+    private var callback: Callback? = null
 
     @RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION])
-    fun start() {
-        val locationManager = context.getSystemService<LocationManager>()
+    fun start(callback: Callback?) {
+        Timber.d("## LocationTracker. start()")
+        this.callback = callback
 
-        locationManager?.let {
-            val isGpsEnabled = it.isProviderEnabled(LocationManager.GPS_PROVIDER)
-            val isNetworkEnabled = it.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
-
-            val provider = when {
-                isGpsEnabled     -> LocationManager.GPS_PROVIDER
-                isNetworkEnabled -> LocationManager.NETWORK_PROVIDER
-                else             -> {
-                    callback?.onLocationProviderIsNotAvailable()
-                    Timber.v("## LocationTracker. There is no location provider available")
-                    return
-                }
-            }
-
-            // Send last known location without waiting location updates
-            it.getLastKnownLocation(provider)?.let { lastKnownLocation ->
-                callback?.onLocationUpdate(lastKnownLocation.toLocationData())
-            }
-
-            it.requestLocationUpdates(
-                    provider,
-                    MIN_TIME_MILLIS_TO_UPDATE_LOCATION,
-                    MIN_DISTANCE_METERS_TO_UPDATE_LOCATION,
-                    this
-            )
-        } ?: run {
+        if (locationManager == null) {
             callback?.onLocationProviderIsNotAvailable()
             Timber.v("## LocationTracker. LocationManager is not available")
+            return
         }
+
+        val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
+        val isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
+
+        Timber.d("## LocationTracker. isGpsEnabled: $isGpsEnabled - isNetworkEnabled: $isNetworkEnabled")
+
+        val provider = when {
+            isGpsEnabled     -> LocationManager.GPS_PROVIDER
+            isNetworkEnabled -> LocationManager.NETWORK_PROVIDER
+            else             -> {
+                callback?.onLocationProviderIsNotAvailable()
+                Timber.v("## LocationTracker. There is no location provider available")
+                return
+            }
+        }
+
+        // Send last known location without waiting location updates
+        locationManager.getLastKnownLocation(provider)?.let { lastKnownLocation ->
+            Timber.d("## LocationTracker. lastKnownLocation")
+            callback?.onLocationUpdate(lastKnownLocation.toLocationData())
+        }
+
+        Timber.d("## LocationTracker. track location using $provider")
+        locationManager.requestLocationUpdates(
+                provider,
+                MIN_TIME_TO_UPDATE_LOCATION_MILLIS,
+                MIN_DISTANCE_TO_UPDATE_LOCATION_METERS,
+                this
+        )
     }
 
     @RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION])
     fun stop() {
+        Timber.d("## LocationTracker. stop()")
         locationManager?.removeUpdates(this)
         callback = null
     }
 
     override fun onLocationChanged(location: Location) {
+        Timber.d("## LocationTracker. onLocationChanged")
         callback?.onLocationUpdate(location.toLocationData())
     }
 
     override fun onProviderDisabled(provider: String) {
+        Timber.d("## LocationTracker. onProviderDisabled: $provider")
         callback?.onLocationProviderIsNotAvailable()
     }
 
diff --git a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt
index c64af1ebaa..e22525485e 100644
--- a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt
+++ b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt
@@ -28,34 +28,47 @@ import com.mapbox.mapboxsdk.plugins.annotation.SymbolManager
 import com.mapbox.mapboxsdk.plugins.annotation.SymbolOptions
 import com.mapbox.mapboxsdk.style.layers.Property
 import im.vector.app.BuildConfig
+import timber.log.Timber
 
 class MapTilerMapView @JvmOverloads constructor(
         context: Context,
         attrs: AttributeSet? = null,
         defStyleAttr: Int = 0
-) : MapView(context, attrs, defStyleAttr), VectorMapView {
+) : MapView(context, attrs, defStyleAttr) {
 
-    private var map: MapboxMap? = null
-    private var symbolManager: SymbolManager? = null
-    private var style: Style? = null
+    private var pendingState: LocationSharingViewState? = null
 
-    override fun initialize(onMapReady: () -> Unit) {
+    data class MapRefs(
+            val map: MapboxMap,
+            val symbolManager: SymbolManager,
+            val style: Style
+    )
+
+    private var mapRefs: MapRefs? = null
+    private var initZoomDone = false
+
+    // TODO Kept only for the bottom sheet usage
+    fun initialize(onMapReady: () -> Unit) {
         getMapAsync { map ->
             map.setStyle(styleUrl) { style ->
-                this.symbolManager = SymbolManager(this, map, style)
-                this.map = map
-                this.style = style
+                mapRefs = MapRefs(
+                        map,
+                        SymbolManager(this, map, style),
+                        style
+                )
                 onMapReady()
             }
         }
     }
 
-    override fun addPinToMap(pinId: String, image: Drawable) {
-        style?.addImage(pinId, image)
+    // TODO Kept only for the bottom sheet usage
+    fun addPinToMap(pinId: String, image: Drawable) {
+        mapRefs?.style?.addImage(pinId, image)
     }
 
-    override fun updatePinLocation(pinId: String, latitude: Double, longitude: Double) {
-        symbolManager?.create(
+    // TODO Kept only for the bottom sheet usage
+    fun updatePinLocation(pinId: String, latitude: Double, longitude: Double) {
+        mapRefs?.symbolManager?.create(
                 SymbolOptions()
                         .withLatLng(LatLng(latitude, longitude))
                         .withIconImage(pinId)
@@ -63,28 +76,59 @@ class MapTilerMapView @JvmOverloads constructor(
         )
     }
 
-    override fun deleteAllPins() {
-        symbolManager?.deleteAll()
+    /**
+     * For location fragments
+     */
+    fun initialize() {
+        Timber.d("## Location: initialize")
+
+        getMapAsync { map ->
+            map.setStyle(styleUrl) { style ->
+                mapRefs = MapRefs(
+                        map,
+                        SymbolManager(this, map, style),
+                        style
+                )
+                pendingState?.let { render(it) }
+                pendingState = null
+            }
+        }
     }
 
-    override fun zoomToLocation(latitude: Double, longitude: Double, zoom: Double) {
-        map?.cameraPosition = CameraPosition.Builder()
+    fun render(data: LocationSharingViewState) {
+        val safeMapRefs = mapRefs ?: return Unit.also {
+            pendingState = data
+        }
+
+        data.pinDrawable?.let { pinDrawable ->
+            if (safeMapRefs.style.getImage(LocationSharingFragment.USER_PIN_NAME) == null) {
+                safeMapRefs.style.addImage(LocationSharingFragment.USER_PIN_NAME, pinDrawable)
+            }
+        }
+
+        data.lastKnownLocation?.let { locationData ->
+            if (!initZoomDone) {
+                zoomToLocation(locationData.latitude, locationData.longitude, INITIAL_MAP_ZOOM)
+                initZoomDone = true
+            }
+
+            safeMapRefs.symbolManager.create(
+                    SymbolOptions()
+                            .withLatLng(LatLng(locationData.latitude, locationData.longitude))
+                            .withIconImage(LocationSharingFragment.USER_PIN_NAME)
+                            .withIconAnchor(Property.ICON_ANCHOR_BOTTOM)
+            )
+        }
+    }
+
+    fun zoomToLocation(latitude: Double, longitude: Double, zoom: Double) {
+        Timber.d("## Location: zoomToLocation")
+        mapRefs?.map?.cameraPosition = CameraPosition.Builder()
                 .target(LatLng(latitude, longitude))
                 .zoom(zoom)
                 .build()
     }
 
-    override fun getCurrentZoom(): Double? {
-        return map?.cameraPosition?.zoom
-    }
-
-    override fun onClick(callback: () -> Unit) {
-        map?.addOnMapClickListener {
-            callback()
-            true
-        }
-    }
-
     companion object {
         private const val styleUrl = "https://api.maptiler.com/maps/streets/style.json?key=${BuildConfig.mapTilerKey}"
     }
diff --git a/vector/src/main/java/im/vector/app/features/location/VectorMapView.kt b/vector/src/main/java/im/vector/app/features/location/VectorMapView.kt
deleted file mode 100644
index 23b59bf99a..0000000000
--- a/vector/src/main/java/im/vector/app/features/location/VectorMapView.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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 android.graphics.drawable.Drawable
-
-interface VectorMapView {
-    fun initialize(onMapReady: () -> Unit)
-
-    fun addPinToMap(pinId: String, image: Drawable)
-    fun updatePinLocation(pinId: String, latitude: Double, longitude: Double)
-    fun deleteAllPins()
-
-    fun zoomToLocation(latitude: Double, longitude: Double, zoom: Double)
-    fun getCurrentZoom(): Double?
-
-    fun onClick(callback: () -> Unit)
-}