Skip to content

Commit

Permalink
NativeUserLocation allow to customise images (rnmapbox#3166)
Browse files Browse the repository at this point in the history
* feat: custom images for native location manager

* feat: allow Images/Image component in NativeUserLocation

---------

Co-authored-by: g4rb4g3 <g4rb4g3@gmail.com>
  • Loading branch information
mfazekas and g4rb4g3 authored Nov 12, 2023
1 parent 0a952a8 commit 0078fea
Show file tree
Hide file tree
Showing 47 changed files with 1,419 additions and 172 deletions.
6 changes: 3 additions & 3 deletions android/src/main/java/com/rnmapbox/rnmbx/RNMBXPackage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ class RNMBXPackage : TurboReactPackage() {
fun getViewTagResolver(context: ReactApplicationContext) : ViewTagResolver {
val viewTagResolver = viewTagResolver
if (viewTagResolver == null) {
val viewTagResolver = ViewTagResolver(context)
this.viewTagResolver = viewTagResolver
return viewTagResolver
val result = ViewTagResolver(context)
this.viewTagResolver = result
return result
}
return viewTagResolver
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.rnmapbox.rnmbx.components.RemovalReason
import com.rnmapbox.rnmbx.components.mapview.RNMBXMapView
import com.rnmapbox.rnmbx.utils.Logger
import java.util.Vector
import com.rnmapbox.rnmbx.v11compat.annotation.*

private data class Vec2(val dx: Double, val dy: Double)

Expand Down Expand Up @@ -169,12 +170,11 @@ class RNMBXMarkerView(context: Context?, private val mManager: RNMBXMarkerViewMa
val offset = getOffset()

val options = viewAnnotationOptions {
geometry(coordinate)
width(width)
height(height)
coordinate?.let { geometry(it) }
width(width.toDouble())
height(height.toDouble())
allowOverlap(mAllowOverlap)
offsetX(offset.dx.toInt())
offsetY(offset.dy.toInt())
offsets(offset.dx, offset.dy)
selected(mIsSelected)
}
return options
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import com.facebook.react.uimanager.ViewManagerDelegate
import com.facebook.react.viewmanagers.RNMBXMarkerViewManagerDelegate
import com.facebook.react.viewmanagers.RNMBXMarkerViewManagerInterface
import com.mapbox.maps.ScreenCoordinate
import com.mapbox.maps.viewannotation.OnViewAnnotationUpdatedListener
import com.mapbox.maps.viewannotation.ViewAnnotationManager
import com.rnmapbox.rnmbx.components.mapview.RNMBXMapView
import com.rnmapbox.rnmbx.v11compat.annotation.*

class RNMBXMarkerViewManager(reactApplicationContext: ReactApplicationContext) :
AbstractEventEmitter<RNMBXMarkerView>(reactApplicationContext),
Expand Down Expand Up @@ -69,7 +69,7 @@ class RNMBXMarkerViewManager(reactApplicationContext: ReactApplicationContext) :
fun markerViewContainerSizeFixer(mapView: RNMBXMapView, viewAnnotationManager: ViewAnnotationManager) {
// see https://github.com/rnmapbox/maps/issues/2376
viewAnnotationManager.addOnViewAnnotationUpdatedListener(object :
OnViewAnnotationUpdatedListener {
OnViewAnnotationUpdatedListener() {
override fun onViewAnnotationVisibilityUpdated(view: View, visible: Boolean) {
val parent = view.parent
if (parent is FrameLayout) {
Expand All @@ -79,13 +79,7 @@ class RNMBXMarkerViewManager(reactApplicationContext: ReactApplicationContext) :
}
}

override fun onViewAnnotationPositionUpdated(
view: View,
leftTopCoordinate: ScreenCoordinate,
width: Int,
height: Int
) {
}

})
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,10 +334,10 @@ class RNMBXCamera(private val mContext: Context, private val mManager: RNMBXCame
if (location?.puckBearingEnabled == true) {
when (location.puckBearingSource) {

PuckBearingSource.HEADING -> {
PuckBearing.HEADING -> {
UserTrackingMode.FollowWithHeading
}
PuckBearingSource.COURSE -> {
PuckBearing.COURSE -> {
UserTrackingMode.FollowWithCourse
}
else -> {
Expand Down Expand Up @@ -468,12 +468,12 @@ class RNMBXCamera(private val mContext: Context, private val mManager: RNMBXCame
when (mFollowUserMode ?: "normal") {
"compass" -> {
location.puckBearingEnabled = true
location.puckBearingSource = PuckBearingSource.HEADING
location.puckBearingSource = PuckBearing.HEADING
followOptions.bearing(FollowPuckViewportStateBearing.SyncWithLocationPuck)
}
"course" -> {
location.puckBearingEnabled = true
location.puckBearingSource = PuckBearingSource.COURSE
location.puckBearingSource = PuckBearing.COURSE
followOptions.bearing(FollowPuckViewportStateBearing.SyncWithLocationPuck)
}
"normal" -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.rnmapbox.rnmbx.components.images

import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import com.rnmapbox.rnmbx.v11compat.Cancelable
import com.mapbox.maps.Image
import com.rnmapbox.rnmbx.v11compat.image.toMapboxImage

/**
ImageManager helps to resolve images defined by any of RNMBXImages component.
*/

fun interface Resolver {
fun resolved(name: String, image: Image)
}
class Subscription(val name:String, val resolver: Resolver, val manager: ImageManager): Cancelable {

fun resolved(name: String, image: Image) {
resolver.resolved(name, image)
}
override fun cancel() {
manager.unsubscribe(this)
}
}

class ImageManager {
var subscriptions: MutableMap<String, MutableList<Subscription>> = hashMapOf()

fun subscribe(name: String, resolved: Resolver) : Subscription {
val list = subscriptions.getOrPut(name) { mutableListOf() }
val result = Subscription(name, resolved, this)
list.add(result)
return result
}
fun unsubscribe(subscription: Subscription) {
var list = subscriptions[subscription.name]
list?.removeAll { it === subscription }
}

fun resolve(name: String, image: Image) {
subscriptions[name]?.forEach {
it.resolved(name, image)
}
}

fun resolve(name: String, image: Bitmap) {
resolve(name, image.toMapboxImage())
}

fun resolve(name: String, image: BitmapDrawable) {
resolve(name, image.bitmap)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ class RNMBXImages(context: Context, private val mManager: RNMBXImagesManager) :
val name = nativeImage.info.name
if (!hasImage(name, map)) {
val bitmap = nativeImage.drawable
mMapView!!.imageManager.resolve(name, nativeImage.drawable)
style.addBitmapImage(nativeImage)
mCurrentImages.add(name)
}
Expand Down Expand Up @@ -264,7 +265,7 @@ class RNMBXImages(context: Context, private val mManager: RNMBXImagesManager) :
}
}
if (missingImages.size > 0) {
val task = DownloadMapImageTask(context, map, null)
val task = DownloadMapImageTask(context, map, mMapView!!.imageManager)
val params = missingImages.toTypedArray()
task.execute(*params)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
package com.rnmapbox.rnmbx.components.location

import android.content.Context
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.graphics.drawable.VectorDrawable
import androidx.appcompat.content.res.AppCompatResources
import android.graphics.drawable.BitmapDrawable
import androidx.lifecycle.Lifecycle
import com.mapbox.maps.plugin.locationcomponent.location
import com.rnmapbox.rnmbx.components.mapview.RNMBXMapView
import com.mapbox.maps.extension.style.expressions.dsl.generated.interpolate
import com.mapbox.maps.plugin.LocationPuck2D
import com.mapbox.maps.plugin.lifecycle.lifecycle
import com.mapbox.maps.plugin.locationcomponent.location
import com.rnmapbox.rnmbx.R
import com.rnmapbox.rnmbx.components.mapview.RNMBXMapView
import com.rnmapbox.rnmbx.location.LocationManager

import com.rnmapbox.rnmbx.v11compat.location.PuckBearingSource;
import com.rnmapbox.rnmbx.v11compat.image.*;
import com.rnmapbox.rnmbx.v11compat.image.AppCompatResourcesV11
import com.rnmapbox.rnmbx.v11compat.image.ImageHolder
import com.rnmapbox.rnmbx.v11compat.image.toBitmapImageHolder
import com.rnmapbox.rnmbx.v11compat.image.toByteArray
import com.rnmapbox.rnmbx.v11compat.image.toImageData
import com.rnmapbox.rnmbx.v11compat.location.PuckBearing

/**
* The LocationComponent on android implements display of user's current location.
Expand All @@ -23,9 +25,20 @@ import com.rnmapbox.rnmbx.v11compat.image.*;
* And NativeUserLocation can ask for display of user's current location - independent of Camera's user tracking.
*/
class LocationComponentManager(mapView: RNMBXMapView, context: Context) {
private val MAPBOX_BLUE_COLOR = Color.parseColor("#4A90E2")

var mMapView = mapView
var mContext = context
private var mState = State(showUserLocation=false, followUserLocation=false, tintColor= null, bearingImage = null, puckBearingSource =null)
private var mState = State(
showUserLocation = false,
followUserLocation = false,
tintColor = null,
bearingImage = null,
puckBearingSource = null,
topImage = null,
shadowImage = null,
scale = 1.0,
)

private var mLocationManager: LocationManager = LocationManager.getInstance(context)

Expand All @@ -37,8 +50,13 @@ class LocationComponentManager(mapView: RNMBXMapView, context: Context) {
val showUserLocation: Boolean,
val followUserLocation: Boolean,
val tintColor: Int?, // tint of location puck
var bearingImage: ImageHolder?, // bearing image (background)
var puckBearingSource: PuckBearingSource? // bearing source
var bearingImage: ImageHolder?, // The image used as the middle of the location indicator.
var topImage: ImageHolder?, // The image to use as the top layer for the location indicator.
var shadowImage: ImageHolder?, // The image that acts as a background of the location indicator.
var puckBearingSource: PuckBearing?, // bearing source
var pulsing: Boolean = true,
var scale: Double = 1.0,
var nativeUserLocation: Boolean = false, // LocaitonPuck managed by RNMBXNativeUserLocation
) {
val enabled: Boolean
get() = showUserLocation || followUserLocation
Expand Down Expand Up @@ -78,41 +96,54 @@ class LocationComponentManager(mapView: RNMBXMapView, context: Context) {
mapView.location.updateSettings {
enabled = newState.enabled

if (fullUpdate || (newState.hidden != oldState.hidden) || (newState.tintColor != oldState.tintColor) || (newState.bearingImage != oldState.bearingImage)) {
if (newState.hidden) {
var emptyLocationPuck = LocationPuck2D()
val empty = AppCompatResourcesV11.getDrawableImageHolder(mContext, R.drawable.empty)
emptyLocationPuck.bearingImage = empty
emptyLocationPuck.shadowImage = empty
emptyLocationPuck.topImage = empty
//emptyLocationPuck.opacity = 0.0
locationPuck = emptyLocationPuck
pulsingEnabled = false
} else {
val mapboxBlueColor = Color.parseColor("#4A90E2")
val tintColor = newState.tintColor
val defaultLocationPuck = LocationPuck2D()
var topImage = AppCompatResourcesV11.getDrawableImageHolder(mContext, R.drawable.mapbox_user_icon)
if (tintColor != null) {
val drawable = AppCompatResources.getDrawable(mContext, R.drawable.mapbox_user_icon) as VectorDrawable?
drawable!!.setTint(tintColor)
topImage = drawable.toBitmapImageHolder()
}
defaultLocationPuck.topImage = topImage
val defaultBearingImage = AppCompatResourcesV11.getDrawableImageHolder(
mContext, R.drawable.mapbox_user_stroke_icon
)
defaultLocationPuck.bearingImage = newState.bearingImage ?: defaultBearingImage
val shadowImage = AppCompatResourcesV11.getDrawableImageHolder(
mContext, R.drawable.mapbox_user_icon_shadow
)
defaultLocationPuck.shadowImage = shadowImage
locationPuck = defaultLocationPuck
pulsingEnabled = true
if (tintColor != null) {
pulsingColor = tintColor
if (fullUpdate ||
newState.hidden != oldState.hidden ||
newState.tintColor != oldState.tintColor ||
newState.scale != oldState.scale ||
newState.nativeUserLocation != oldState.nativeUserLocation
) {
if (!newState.nativeUserLocation) {
if (newState.hidden) {
var emptyLocationPuck = LocationPuck2D()
val empty =
AppCompatResourcesV11.getDrawableImageHolder(mContext, R.drawable.empty)
emptyLocationPuck.bearingImage = empty
emptyLocationPuck.shadowImage = empty
emptyLocationPuck.topImage = empty
//emptyLocationPuck.opacity = 0.0
locationPuck = emptyLocationPuck
pulsingEnabled = false
} else {
pulsingColor = mapboxBlueColor
val tintColor = newState.tintColor
var topImage = newState.topImage
if (tintColor != null && topImage != null) {
val imageData = (topImage as ByteArray).toImageData().toByteArray()
val drawable = BitmapDrawable(
mContext.resources,
BitmapFactory.decodeByteArray(imageData, 0, imageData.size)
)
drawable.setTint(tintColor)
topImage = drawable.toBitmapImageHolder()
}
val scaleExpression = if (newState.scale != 1.0) {
interpolate {
linear()
zoom()
stop {
literal(0)
literal(newState.scale)
}
}.toJson()
} else null

locationPuck = LocationPuck2D(
topImage = topImage,
bearingImage = newState.bearingImage,
shadowImage = newState.shadowImage,
scaleExpression = scaleExpression,
)
pulsingEnabled = newState.pulsing
pulsingColor = tintColor ?: MAPBOX_BLUE_COLOR
}
}
}
Expand Down Expand Up @@ -152,7 +183,7 @@ class LocationComponentManager(mapView: RNMBXMapView, context: Context) {

fun showNativeUserLocation(showUserLocation: Boolean) {
update {
it.copy(showUserLocation = showUserLocation)
it.copy(showUserLocation = showUserLocation, nativeUserLocation= showUserLocation)
}
}

Expand Down
Loading

0 comments on commit 0078fea

Please sign in to comment.