From 58e6f840ea1b3b213298b82cf611cf339cc9f33d Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 7 Apr 2020 11:27:51 -0700 Subject: [PATCH] map fragment kinda works --- .../java/com/geeksville/mesh/MainActivity.kt | 55 +---- .../com/geeksville/mesh/ui/ComposeFragment.kt | 38 ++++ .../main/java/com/geeksville/mesh/ui/Map.kt | 192 ++++++++---------- .../com/geeksville/mesh/ui/ScreenFragment.kt | 21 ++ app/src/main/res/layout/map_view.xml | 17 +- 5 files changed, 157 insertions(+), 166 deletions(-) create mode 100644 app/src/main/java/com/geeksville/mesh/ui/ComposeFragment.kt create mode 100644 app/src/main/java/com/geeksville/mesh/ui/ScreenFragment.kt diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 5653eccf..2dbd0c17 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/ui/ComposeFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ComposeFragment.kt new file mode 100644 index 00000000..1986d4b0 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/ComposeFragment.kt @@ -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) +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/ui/Map.kt b/app/src/main/java/com/geeksville/mesh/ui/Map.kt index 08b4e6f5..3c5934fd 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Map.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Map.kt @@ -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) } } + + + + diff --git a/app/src/main/java/com/geeksville/mesh/ui/ScreenFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ScreenFragment.kt new file mode 100644 index 00000000..d707ce88 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/ScreenFragment.kt @@ -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() + } +} diff --git a/app/src/main/res/layout/map_view.xml b/app/src/main/res/layout/map_view.xml index a77c3ecf..bfdb57e7 100644 --- a/app/src/main/res/layout/map_view.xml +++ b/app/src/main/res/layout/map_view.xml @@ -1,10 +1,17 @@ - + android:id="@+id/mapFrame"> + + + + \ No newline at end of file