Meshtastic-Android/app/src/main/java/com/geeksville/mesh/ui/Map.kt
2020-03-30 15:00:18 -07:00

170 lines
5.4 KiB
Kotlin

package com.geeksville.mesh.ui
import android.app.Activity
import android.app.Application
import android.graphics.Color
import android.os.Bundle
import androidx.compose.Composable
import androidx.compose.onCommit
import androidx.ui.core.ContextAmbient
import androidx.ui.fakeandroidview.AndroidView
import androidx.ui.material.MaterialTheme
import androidx.ui.tooling.preview.Preview
import com.geeksville.android.Logging
import com.geeksville.mesh.R
import com.geeksville.mesh.model.NodeDB
import com.geeksville.mesh.model.UIState
import com.mapbox.geojson.Feature
import com.mapbox.geojson.FeatureCollection
import com.mapbox.geojson.Point
import com.mapbox.mapboxsdk.camera.CameraPosition
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory
import com.mapbox.mapboxsdk.geometry.LatLng
import com.mapbox.mapboxsdk.maps.MapView
import com.mapbox.mapboxsdk.maps.Style
import com.mapbox.mapboxsdk.style.expressions.Expression
import com.mapbox.mapboxsdk.style.layers.Property
import com.mapbox.mapboxsdk.style.layers.Property.TEXT_ANCHOR_TOP
import com.mapbox.mapboxsdk.style.layers.Property.TEXT_JUSTIFY_AUTO
import com.mapbox.mapboxsdk.style.layers.PropertyFactory
import com.mapbox.mapboxsdk.style.layers.PropertyFactory.*
import com.mapbox.mapboxsdk.style.layers.SymbolLayer
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource
object mapLog : Logging
/**
* mapbox requires this, until compose has a nicer way of doing it, do it here
*/
private val mapLifecycleCallbacks = object : Application.ActivityLifecycleCallbacks {
var view: MapView? = null
override fun onActivityPaused(activity: Activity) {
view!!.onPause()
}
override fun onActivityStarted(activity: Activity) {
view!!.onStart()
}
override fun onActivityDestroyed(activity: Activity) {
view!!.onDestroy()
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
view!!.onSaveInstanceState(outState)
}
override fun onActivityStopped(activity: Activity) {
view!!.onStop()
}
/**
* Called when the Activity calls [super.onCreate()][Activity.onCreate].
*/
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
}
override fun onActivityResumed(activity: Activity) {
view!!.onResume()
}
}
@Composable
fun MapContent() {
analyticsScreen(name = "map")
val typography = MaterialTheme.typography()
val context = ContextAmbient.current
onCommit(AppStatus.currentScreen) {
onDispose {
// We no longer care about activity lifecycle
(context.applicationContext as Application).unregisterActivityLifecycleCallbacks(
mapLifecycleCallbacks
)
mapLifecycleCallbacks.view = null
}
}
// Find all nodes with valid locations
val locations = NodeDB.nodes.values.mapNotNull { node ->
val p = node.position
if (p != null && (p.latitude != 0.0 || p.longitude != 0.0)) {
val f = Feature.fromGeometry(
Point.fromLngLat(
p.longitude,
p.latitude
)
)
node.user?.let { f.addStringProperty("name", it.longName) }
f
} else
null
}
val nodeSourceId = "node-positions"
val nodeLayerId = "node-layer"
val labelLayerId = "label-layer"
val markerImageId = "my-marker-image"
val nodePositions =
GeoJsonSource(nodeSourceId, FeatureCollection.fromFeatures(locations))
// val markerIcon = BitmapFactory.decodeResource(context.resources, R.drawable.ic_twotone_person_pin_24)
val markerIcon = context.getDrawable(R.drawable.ic_twotone_person_pin_24)!!
val nodeLayer = SymbolLayer(nodeLayerId, nodeSourceId).withProperties(
PropertyFactory.iconImage(markerImageId),
PropertyFactory.iconAnchor(Property.ICON_ANCHOR_BOTTOM)
)
val labelLayer = SymbolLayer(labelLayerId, nodeSourceId).withProperties(
textField(Expression.get("name")),
textSize(12f),
textColor(Color.RED),
textVariableAnchor(arrayOf(TEXT_ANCHOR_TOP)),
textJustify(TEXT_JUSTIFY_AUTO)
)
AndroidView(R.layout.map_view) { view ->
view as MapView
view.onCreate(UIState.savedInstanceState)
mapLifecycleCallbacks.view = view
(context.applicationContext as Application).registerActivityLifecycleCallbacks(
mapLifecycleCallbacks
)
view.getMapAsync { map ->
map.setStyle(Style.OUTDOORS) { style ->
style.addSource(nodePositions)
style.addImage(markerImageId, markerIcon)
style.addLayer(nodeLayer)
style.addLayer(labelLayer)
}
//map.uiSettings.isScrollGesturesEnabled = true
//map.uiSettings.isZoomGesturesEnabled = true
// Center on the user's position (if we have it)
NodeDB.ourNodeInfo?.position?.let {
val cameraPos = CameraPosition.Builder().target(
LatLng(it.latitude, it.longitude)
).zoom(9.0).build()
map.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPos), 1000)
}
}
}
}
@Preview
@Composable
fun previewMap() {
// another bug? It seems modaldrawerlayout not yet supported in preview
MaterialTheme(colors = palette) {
MapContent()
}
}