mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 10:25:35 +03:00
Show and track the current location of the user on map.
This commit is contained in:
parent
824e713c51
commit
5904a5955f
7 changed files with 235 additions and 4 deletions
|
@ -488,6 +488,7 @@ dependencies {
|
|||
|
||||
// MapTiler
|
||||
implementation 'org.maplibre.gl:android-sdk:9.5.2'
|
||||
implementation 'org.maplibre.gl:android-plugin-annotation-v9:1.0.0'
|
||||
|
||||
|
||||
// TESTS
|
||||
|
|
|
@ -61,6 +61,7 @@ import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment
|
|||
import im.vector.app.features.home.room.detail.RoomDetailFragment
|
||||
import im.vector.app.features.home.room.detail.search.SearchFragment
|
||||
import im.vector.app.features.home.room.list.RoomListFragment
|
||||
import im.vector.app.features.location.LocationSharingFragment
|
||||
import im.vector.app.features.login.LoginCaptchaFragment
|
||||
import im.vector.app.features.login.LoginFragment
|
||||
import im.vector.app.features.login.LoginGenericTextInputFormFragment
|
||||
|
@ -855,4 +856,9 @@ interface FragmentModule {
|
|||
@IntoMap
|
||||
@FragmentKey(CreatePollFragment::class)
|
||||
fun bindCreatePollFragment(fragment: CreatePollFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(LocationSharingFragment::class)
|
||||
fun bindLocationSharingFragment(fragment: LocationSharingFragment): Fragment
|
||||
}
|
||||
|
|
|
@ -16,22 +16,55 @@
|
|||
|
||||
package im.vector.app.features.location
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.mapbox.mapboxsdk.Mapbox
|
||||
import com.mapbox.mapboxsdk.camera.CameraPosition
|
||||
import com.mapbox.mapboxsdk.geometry.LatLng
|
||||
import com.mapbox.mapboxsdk.maps.MapboxMap
|
||||
import com.mapbox.mapboxsdk.maps.Style
|
||||
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 im.vector.app.R
|
||||
import im.vector.app.core.glide.GlideApp
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentLocationSharingBinding
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import org.billcarsonfr.jsonviewer.Utils
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import javax.inject.Inject
|
||||
|
||||
class LocationSharingFragment @Inject constructor() :
|
||||
VectorBaseFragment<FragmentLocationSharingBinding>() {
|
||||
class LocationSharingFragment @Inject constructor(
|
||||
private val locationTracker: LocationTracker,
|
||||
private val session: Session,
|
||||
private val avatarRenderer: AvatarRenderer
|
||||
) : VectorBaseFragment<FragmentLocationSharingBinding>(), LocationTracker.Callback {
|
||||
|
||||
init {
|
||||
locationTracker.callback = this
|
||||
}
|
||||
|
||||
private val viewModel: LocationSharingViewModel by activityViewModel()
|
||||
|
||||
private val glideRequests by lazy {
|
||||
GlideApp.with(this)
|
||||
}
|
||||
|
||||
private var map: MapboxMap? = null
|
||||
private var symbolManager: SymbolManager? = null
|
||||
private var lastZoomValue: Double = -1.0
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLocationSharingBinding {
|
||||
return FragmentLocationSharingBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
@ -48,12 +81,73 @@ class LocationSharingFragment @Inject constructor() :
|
|||
initMapView(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
locationTracker.stop()
|
||||
}
|
||||
|
||||
private fun initMapView(savedInstanceState: Bundle?) {
|
||||
val key = BuildConfig.mapTilerKey
|
||||
val styleUrl = "https://api.maptiler.com/maps/streets/style.json?key=${key}"
|
||||
val styleUrl = "https://api.maptiler.com/maps/streets/style.json?key=$key"
|
||||
views.mapView.onCreate(savedInstanceState)
|
||||
views.mapView.getMapAsync { map ->
|
||||
map.setStyle(styleUrl)
|
||||
map.setStyle(styleUrl) { style ->
|
||||
addUserPinToMap(style)
|
||||
this.symbolManager = SymbolManager(views.mapView, map, style)
|
||||
this.map = map
|
||||
// All set, start location tracker
|
||||
locationTracker.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addUserPinToMap(style: Style) {
|
||||
session.getUser(session.myUserId)?.toMatrixItem()?.let {
|
||||
val size = Utils.dpToPx(44, requireContext())
|
||||
avatarRenderer.render(glideRequests, it, object : CustomTarget<Drawable>(size, size) {
|
||||
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
||||
val bgUserPin = ContextCompat.getDrawable(requireActivity(), R.drawable.bg_map_user_pin)!!
|
||||
val layerDrawable = LayerDrawable(arrayOf(bgUserPin, resource))
|
||||
val horizontalInset = Utils.dpToPx(4, requireContext())
|
||||
val topInset = Utils.dpToPx(4, requireContext())
|
||||
val bottomInset = Utils.dpToPx(8, requireContext())
|
||||
layerDrawable.setLayerInset(1, horizontalInset, topInset, horizontalInset, bottomInset)
|
||||
|
||||
style.addImage(
|
||||
USER_PIN_NAME,
|
||||
layerDrawable
|
||||
)
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {
|
||||
// Is it possible? Put placeholder instead?
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLocationUpdate(latitude: Double, longitude: Double) {
|
||||
lastZoomValue = if (lastZoomValue == -1.0) INITIAL_ZOOM else map?.cameraPosition?.zoom ?: INITIAL_ZOOM
|
||||
|
||||
val latLng = LatLng(latitude, longitude)
|
||||
|
||||
map?.cameraPosition = CameraPosition.Builder()
|
||||
.target(latLng)
|
||||
.zoom(lastZoomValue)
|
||||
.build()
|
||||
|
||||
symbolManager?.deleteAll()
|
||||
|
||||
symbolManager?.create(
|
||||
SymbolOptions()
|
||||
.withLatLng(latLng)
|
||||
.withIconImage(USER_PIN_NAME)
|
||||
.withIconAnchor(Property.ICON_ANCHOR_BOTTOM)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val INITIAL_ZOOM = 12.0
|
||||
const val USER_PIN_NAME = "USER_PIN_NAME"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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.content.Context
|
||||
import android.location.Location
|
||||
import android.location.LocationListener
|
||||
import android.location.LocationManager
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class LocationTracker @Inject constructor(
|
||||
private val context: Context) : LocationListener {
|
||||
|
||||
interface Callback {
|
||||
fun onLocationUpdate(latitude: Double, longitude: Double)
|
||||
}
|
||||
|
||||
private var locationManager: LocationManager? = null
|
||||
var callback: Callback? = null
|
||||
|
||||
fun start() {
|
||||
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as? LocationManager
|
||||
|
||||
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 -> {
|
||||
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.latitude, lastKnownLocation.longitude)
|
||||
}
|
||||
|
||||
it.requestLocationUpdates(
|
||||
provider,
|
||||
MIN_TIME_MILLIS_TO_UPDATE,
|
||||
MIN_DISTANCE_METERS_TO_UPDATE,
|
||||
this
|
||||
)
|
||||
} ?: run {
|
||||
Timber.v("## LocationTracker. LocationManager is not available")
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
locationManager?.removeUpdates(this)
|
||||
}
|
||||
|
||||
override fun onLocationChanged(location: Location) {
|
||||
callback?.onLocationUpdate(location.latitude, location.longitude)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MIN_TIME_MILLIS_TO_UPDATE = 1 * 60 * 1000L // every 1 minute
|
||||
const val MIN_DISTANCE_METERS_TO_UPDATE = 10f
|
||||
}
|
||||
}
|
10
vector/src/main/res/drawable/bg_map_user_pin.xml
Normal file
10
vector/src/main/res/drawable/bg_map_user_pin.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="51dp"
|
||||
android:height="55dp"
|
||||
android:viewportWidth="51"
|
||||
android:viewportHeight="55">
|
||||
<path
|
||||
android:pathData="M29.1957,50.7341C41.5276,48.9438 51,38.3281 51,25.5C51,11.4167 39.5833,0 25.5,0C11.4167,0 0,11.4167 0,25.5C0,38.3282 9.4725,48.9439 21.8045,50.7342L25.5001,54.2903L29.1957,50.7341Z"
|
||||
android:fillColor="#0DBD8B"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
|
@ -8,4 +9,41 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/shareLocationContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="72dp"
|
||||
android:background="?android:colorBackground"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/shareLocationImageView"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:background="@drawable/circle"
|
||||
android:backgroundTint="?colorPrimary"
|
||||
android:contentDescription="@string/a11y_location_share_icon"
|
||||
android:padding="10dp"
|
||||
android:src="@drawable/ic_attachment_location_white"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.Vector.Subtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="@string/location_share"
|
||||
android:textColor="?colorPrimary"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/shareLocationImageView"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -3706,4 +3706,6 @@
|
|||
<!-- Location -->
|
||||
<string name="location_activity_title_static_sharing">Share location</string>
|
||||
<string name="location_activity_title_preview">Location</string>
|
||||
<string name="a11y_location_share_icon">Share location</string>
|
||||
<string name="location_share">Share location</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue