fuzzyworld

haunt / commit

Fix map label overlap, add x/y to points list

54ee6e7f0d014568fdbe1456e9f641fbdd64692e · Pavel Safronov · 2026-05-12 22:06:43 -0700 · parents f4823acea471

Fix map label overlap, add x/y to points list

MapView: move axis legend to top-right corner (was colliding with the
rightmost grid label at the same y coordinate)

PointsAdapter + item_debug_point.xml: show x/y coordinates below each
point name in monospace gray text, e.g. "x: 3.21 m   y: 10.84 m"

DebugViewModel: expose axisBssids StateFlow (sorted union of all BSSIDs
seen in readings + saved points) so the list and map share a consistent
axis definition

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
 .../main/java/net/fuzzyworld/haunt/MainActivity.kt |  7 ++++++
 .../net/fuzzyworld/haunt/debug/DebugViewModel.kt   |  8 ++++++
 .../java/net/fuzzyworld/haunt/debug/MapView.kt     |  9 +++----
 .../net/fuzzyworld/haunt/debug/PointsAdapter.kt    |  9 +++++++
 app/src/main/res/layout/item_debug_point.xml       | 29 ++++++++++++++++------
 5 files changed, 50 insertions(+), 12 deletions(-)

diff --git a/app/src/main/java/net/fuzzyworld/haunt/MainActivity.kt b/app/src/main/java/net/fuzzyworld/haunt/MainActivity.kt
index 3b1ca30..f031591 100644
--- a/app/src/main/java/net/fuzzyworld/haunt/MainActivity.kt
+++ b/app/src/main/java/net/fuzzyworld/haunt/MainActivity.kt
@@ -68,6 +68,13 @@ class MainActivity : AppCompatActivity() {
                 binding.mapView.savedPoints = points
             }
         }
+
+        lifecycleScope.launch {
+            vm.axisBssids.collectLatest { bssids ->
+                adapter.axisBssids = bssids
+                adapter.notifyDataSetChanged()
+            }
+        }
     }
 
     private fun showAddPointDialog() {
diff --git a/app/src/main/java/net/fuzzyworld/haunt/debug/DebugViewModel.kt b/app/src/main/java/net/fuzzyworld/haunt/debug/DebugViewModel.kt
index 3ca000c..2cf3fc5 100644
--- a/app/src/main/java/net/fuzzyworld/haunt/debug/DebugViewModel.kt
+++ b/app/src/main/java/net/fuzzyworld/haunt/debug/DebugViewModel.kt
@@ -6,7 +6,10 @@ import androidx.lifecycle.AndroidViewModel
 import androidx.lifecycle.viewModelScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 import net.fuzzyworld.haunt.ranging.RttRangingManager
 import java.util.UUID
@@ -24,6 +27,11 @@ class DebugViewModel(application: Application) : AndroidViewModel(application) {
 
     val isRttAvailable: Boolean get() = rttManager.isAvailable
 
+    // Sorted union of all BSSIDs ever seen — defines the x/y axes for the map and list
+    val axisBssids: StateFlow<List<String>> = combine(_readings, _points) { r, p ->
+        (r.map { it.bssid } + p.flatMap { it.readings.keys }).distinct().sorted()
+    }.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
+
     // Last known distance per BSSID — used to fill gaps when an AP temporarily drops out
     private val lastKnownReadings: MutableMap<String, Double> = mutableMapOf()
 
diff --git a/app/src/main/java/net/fuzzyworld/haunt/debug/MapView.kt b/app/src/main/java/net/fuzzyworld/haunt/debug/MapView.kt
index 28a019c..290c712 100644
--- a/app/src/main/java/net/fuzzyworld/haunt/debug/MapView.kt
+++ b/app/src/main/java/net/fuzzyworld/haunt/debug/MapView.kt
@@ -139,15 +139,14 @@ class MapView @JvmOverloads constructor(
             labelPaint.color = Color.BLACK
         }
 
-        // Axis labels
+        // Axis legend — top-right corner, away from all grid labels
         val bssids = allBssids
+        dimLabelPaint.textAlign = Paint.Align.RIGHT
         if (bssids.isNotEmpty()) {
-            dimLabelPaint.textAlign = Paint.Align.RIGHT
-            canvas.drawText("→ ${bssids[0].takeLast(5)}", width - 4f, height - 6f, dimLabelPaint)
+            canvas.drawText("x → …${bssids[0].takeLast(5)}", width - 6f, 22f, dimLabelPaint)
         }
         if (bssids.size > 1) {
-            dimLabelPaint.textAlign = Paint.Align.LEFT
-            canvas.drawText("↓ ${bssids[1].takeLast(5)}", 4f, padPx - 10f, dimLabelPaint)
+            canvas.drawText("y ↓ …${bssids[1].takeLast(5)}", width - 6f, 44f, dimLabelPaint)
         }
     }
 }
diff --git a/app/src/main/java/net/fuzzyworld/haunt/debug/PointsAdapter.kt b/app/src/main/java/net/fuzzyworld/haunt/debug/PointsAdapter.kt
index daa3cfc..a9cf5dd 100644
--- a/app/src/main/java/net/fuzzyworld/haunt/debug/PointsAdapter.kt
+++ b/app/src/main/java/net/fuzzyworld/haunt/debug/PointsAdapter.kt
@@ -11,6 +11,7 @@ class PointsAdapter(
 ) : RecyclerView.Adapter<PointsAdapter.ViewHolder>() {
 
     private var points: List<DebugPoint> = emptyList()
+    var axisBssids: List<String> = emptyList()
 
     fun submitList(list: List<DebugPoint>) {
         points = list
@@ -35,6 +36,14 @@ class PointsAdapter(
                 if (!hasFocus) onRename(point.id, b.nameEdit.text.toString().trim().ifEmpty { point.name })
             }
             b.deleteButton.setOnClickListener { onDelete(point.id) }
+
+            val x = axisBssids.getOrNull(0)?.let { point.readings[it] }
+            val y = axisBssids.getOrNull(1)?.let { point.readings[it] }
+            b.xyText.text = when {
+                x != null && y != null -> "x: ${"%.2f".format(x)} m   y: ${"%.2f".format(y)} m"
+                x != null -> "x: ${"%.2f".format(x)} m"
+                else -> ""
+            }
         }
     }
 }
diff --git a/app/src/main/res/layout/item_debug_point.xml b/app/src/main/res/layout/item_debug_point.xml
index 435275a..46c61e4 100644
--- a/app/src/main/res/layout/item_debug_point.xml
+++ b/app/src/main/res/layout/item_debug_point.xml
@@ -5,7 +5,7 @@
     android:orientation="horizontal"
     android:gravity="center_vertical"
     android:paddingHorizontal="12dp"
-    android:paddingVertical="6dp">
+    android:paddingVertical="4dp">
 
     <View
         android:id="@+id/colorSwatch"
@@ -13,15 +13,30 @@
         android:layout_height="20dp"
         android:layout_marginEnd="10dp" />
 
-    <EditText
-        android:id="@+id/nameEdit"
+    <LinearLayout
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_weight="1"
-        android:background="@null"
-        android:singleLine="true"
-        android:textSize="15sp"
-        android:imeOptions="actionDone" />
+        android:orientation="vertical">
+
+        <EditText
+            android:id="@+id/nameEdit"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="@null"
+            android:singleLine="true"
+            android:textSize="15sp"
+            android:imeOptions="actionDone" />
+
+        <TextView
+            android:id="@+id/xyText"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:fontFamily="monospace"
+            android:textSize="11sp"
+            android:textColor="#888888" />
+
+    </LinearLayout>
 
     <ImageButton
         android:id="@+id/deleteButton"