kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
map fragment kinda works
rodzic
5403b15044
commit
58e6f840ea
|
@ -12,18 +12,16 @@ import android.content.pm.PackageManager
|
|||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import android.widget.FrameLayout
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.MotionEvent
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.Composable
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.ui.core.setContent
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.android.ServiceClient
|
||||
import com.geeksville.mesh.model.MessagesState
|
||||
|
@ -92,50 +90,6 @@ eventually:
|
|||
|
||||
val utf8 = Charset.forName("UTF-8")
|
||||
|
||||
fun androidx.fragment.app.Fragment.setComposable(
|
||||
id: Int,
|
||||
content: @Composable() () -> Unit
|
||||
): View? =
|
||||
context?.let {
|
||||
FrameLayout(it).apply {
|
||||
this.id =
|
||||
id // Compose requires a unique ID for the containing view to make savedInstanceState work
|
||||
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
setContent(content)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A fragment that represents a current 'screen' in our app.
|
||||
*
|
||||
* Useful for tracking analytics
|
||||
*/
|
||||
open class ScreenFragment(private val screenName: String) : Fragment() {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
GeeksvilleApplication.analytics.sendScreenView(screenName)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
GeeksvilleApplication.analytics.endScreenView()
|
||||
super.onPause()
|
||||
}
|
||||
}
|
||||
|
||||
class ComposeFragment(screenName: String, id: Int, private val content: @Composable() () -> Unit) :
|
||||
ScreenFragment(screenName),
|
||||
Logging {
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? =
|
||||
setComposable(id, content)
|
||||
}
|
||||
|
||||
|
||||
class MainActivity : AppCompatActivity(), Logging,
|
||||
ActivityCompat.OnRequestPermissionsResultCallback {
|
||||
|
@ -195,7 +149,8 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
TabInfo(
|
||||
"Map",
|
||||
R.drawable.ic_twotone_map_24,
|
||||
ComposeFragment("Map", 5) { MapContent() })
|
||||
MapFragment()
|
||||
)
|
||||
)
|
||||
|
||||
private
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.Composable
|
||||
import androidx.ui.core.setContent
|
||||
import com.geeksville.android.Logging
|
||||
|
||||
fun androidx.fragment.app.Fragment.setComposable(
|
||||
id: Int,
|
||||
content: @Composable() () -> Unit
|
||||
): View? =
|
||||
context?.let {
|
||||
FrameLayout(it).apply {
|
||||
this.id =
|
||||
id // Compose requires a unique ID for the containing view to make savedInstanceState work
|
||||
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
setContent(content)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ComposeFragment(screenName: String, id: Int, private val content: @Composable() () -> Unit) :
|
||||
ScreenFragment(screenName),
|
||||
Logging {
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? =
|
||||
setComposable(id, content)
|
||||
}
|
|
@ -1,15 +1,10 @@
|
|||
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 android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.model.NodeDB
|
||||
|
@ -32,114 +27,65 @@ import com.mapbox.mapboxsdk.style.layers.SymbolLayer
|
|||
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource
|
||||
|
||||
|
||||
object mapLog : Logging
|
||||
class MapFragment : ScreenFragment("Map"), Logging {
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? = inflater.inflate(R.layout.map_view, container, false)
|
||||
|
||||
lateinit var mapView: MapView
|
||||
|
||||
/**
|
||||
* 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 onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
override fun onActivityPaused(activity: Activity) {
|
||||
view?.onPause()
|
||||
}
|
||||
mapView = view.findViewById(R.id.mapView)
|
||||
mapView.onCreate(UIState.savedInstanceState)
|
||||
|
||||
override fun onActivityStarted(activity: Activity) {
|
||||
view?.onStart()
|
||||
}
|
||||
mapView.getMapAsync { map ->
|
||||
|
||||
override fun onActivityDestroyed(activity: Activity) {
|
||||
view?.onDestroy()
|
||||
}
|
||||
// Find all nodes with valid locations
|
||||
val nodesWithPosition = NodeDB.nodes.values.filter { it.validPosition != null }
|
||||
val locations = nodesWithPosition.map { node ->
|
||||
val p = node.position!!
|
||||
debug("Showing on map: $node")
|
||||
|
||||
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
|
||||
view?.onSaveInstanceState(outState)
|
||||
}
|
||||
val f = Feature.fromGeometry(
|
||||
Point.fromLngLat(
|
||||
p.longitude,
|
||||
p.latitude
|
||||
)
|
||||
)
|
||||
node.user?.let {
|
||||
f.addStringProperty("name", it.longName)
|
||||
}
|
||||
f
|
||||
}
|
||||
val nodeSourceId = "node-positions"
|
||||
val nodeLayerId = "node-layer"
|
||||
val labelLayerId = "label-layer"
|
||||
val markerImageId = "my-marker-image"
|
||||
val nodePositions =
|
||||
GeoJsonSource(nodeSourceId, FeatureCollection.fromFeatures(locations))
|
||||
|
||||
override fun onActivityStopped(activity: Activity) {
|
||||
view?.onStop()
|
||||
}
|
||||
// val markerIcon = BitmapFactory.decodeResource(context.resources, R.drawable.ic_twotone_person_pin_24)
|
||||
val markerIcon = activity!!.getDrawable(R.drawable.ic_twotone_person_pin_24)!!
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
val context = ContextAmbient.current
|
||||
|
||||
// FIXME - remove onCommit
|
||||
onCommit() {
|
||||
onDispose {
|
||||
// We no longer care about activity lifecycle
|
||||
(context.applicationContext as Application).unregisterActivityLifecycleCallbacks(
|
||||
mapLifecycleCallbacks
|
||||
val nodeLayer = SymbolLayer(nodeLayerId, nodeSourceId).withProperties(
|
||||
iconImage(markerImageId),
|
||||
iconAnchor(Property.ICON_ANCHOR_BOTTOM),
|
||||
iconAllowOverlap(true)
|
||||
)
|
||||
mapLifecycleCallbacks.view = null
|
||||
}
|
||||
}
|
||||
|
||||
// Find all nodes with valid locations
|
||||
val nodesWithPosition = NodeDB.nodes.values.filter { it.validPosition != null }
|
||||
val locations = nodesWithPosition.map { node ->
|
||||
val p = node.position!!
|
||||
mapLog.debug("Showing on map: $node")
|
||||
|
||||
val f = Feature.fromGeometry(
|
||||
Point.fromLngLat(
|
||||
p.longitude,
|
||||
p.latitude
|
||||
val labelLayer = SymbolLayer(labelLayerId, nodeSourceId).withProperties(
|
||||
textField(Expression.get("name")),
|
||||
textSize(12f),
|
||||
textColor(Color.RED),
|
||||
textVariableAnchor(arrayOf(TEXT_ANCHOR_TOP)),
|
||||
textJustify(TEXT_JUSTIFY_AUTO),
|
||||
textAllowOverlap(true)
|
||||
)
|
||||
)
|
||||
node.user?.let {
|
||||
f.addStringProperty("name", it.longName)
|
||||
}
|
||||
f
|
||||
}
|
||||
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(
|
||||
iconImage(markerImageId),
|
||||
iconAnchor(Property.ICON_ANCHOR_BOTTOM),
|
||||
iconAllowOverlap(true)
|
||||
)
|
||||
|
||||
val labelLayer = SymbolLayer(labelLayerId, nodeSourceId).withProperties(
|
||||
textField(Expression.get("name")),
|
||||
textSize(12f),
|
||||
textColor(Color.RED),
|
||||
textVariableAnchor(arrayOf(TEXT_ANCHOR_TOP)),
|
||||
textJustify(TEXT_JUSTIFY_AUTO),
|
||||
textAllowOverlap(true)
|
||||
)
|
||||
|
||||
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)
|
||||
|
@ -173,14 +119,38 @@ fun MapContent() {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
mapView.onPause()
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun previewMap() {
|
||||
// another bug? It seems modaldrawerlayout not yet supported in preview
|
||||
MaterialTheme(colors = palette) {
|
||||
MapContent()
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
mapView.onStart()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
mapView.onStop()
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
mapView.onResume()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
mapView.onDestroy()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
mapView.onSaveInstanceState(outState)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
|
||||
/**
|
||||
* A fragment that represents a current 'screen' in our app.
|
||||
*
|
||||
* Useful for tracking analytics
|
||||
*/
|
||||
open class ScreenFragment(private val screenName: String) : Fragment() {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
GeeksvilleApplication.analytics.sendScreenView(screenName)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
GeeksvilleApplication.analytics.endScreenView()
|
||||
super.onPause()
|
||||
}
|
||||
}
|
|
@ -1,10 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<com.mapbox.mapboxsdk.maps.MapView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:mapbox="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/mapView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clickable="true"
|
||||
mapbox:mapbox_uiZoomGestures="true"
|
||||
mapbox:mapbox_uiScrollGestures="true"></com.mapbox.mapboxsdk.maps.MapView>
|
||||
android:id="@+id/mapFrame"> <!-- tab layout requires a unique ID -->
|
||||
|
||||
<com.mapbox.mapboxsdk.maps.MapView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/mapView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clickable="true"
|
||||
mapbox:mapbox_uiZoomGestures="true"
|
||||
mapbox:mapbox_uiScrollGestures="true"></com.mapbox.mapboxsdk.maps.MapView>
|
||||
|
||||
</FrameLayout>
|
Ładowanie…
Reference in New Issue