fuzzyworld

haunt / commit

Persistent history trail + configurable 'you' color

ee0a854834e139a3f5df613ebd10b721929031d0 · Pavel Safronov · 2026-05-12 22:26:27 -0700 · parents 4f3a4f2359c0

Persistent history trail + configurable 'you' color

History: every ranging pass (2s) records a HistoryPoint{readings, timestamp}.
Rendered as small faded dots behind everything else on the map, using
youColor at 24% alpha. Saved to history.json every 10 entries and on
ViewModel teardown. Loaded on startup so history survives restarts.
Age stored in timestamp field; not visually differentiated yet.

'you' entry: permanent first row in the points list, non-deletable,
non-renamable. Color swatch opens the same color picker as other points.
Chosen color persists in SharedPreferences and propagates to both the
map crosshair fill and the history trail.

MapView: youColor drives current-position circle fill; history drawn
in first pass (below saved points and crosshair).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
 .../main/java/net/fuzzyworld/haunt/MainActivity.kt |  24 +++-
 .../net/fuzzyworld/haunt/debug/DebugViewModel.kt   |  52 +++++--
 .../net/fuzzyworld/haunt/debug/HistoryPoint.kt     |   6 +
 .../net/fuzzyworld/haunt/debug/HistoryStore.kt     |  26 ++++
 .../java/net/fuzzyworld/haunt/debug/MapView.kt     |  29 ++--
 .../net/fuzzyworld/haunt/debug/PointsAdapter.kt    | 156 +++++++++++++--------
 6 files changed, 211 insertions(+), 82 deletions(-)

diff --git a/app/src/main/java/net/fuzzyworld/haunt/MainActivity.kt b/app/src/main/java/net/fuzzyworld/haunt/MainActivity.kt
index df5a0db..f1e81b3 100644
--- a/app/src/main/java/net/fuzzyworld/haunt/MainActivity.kt
+++ b/app/src/main/java/net/fuzzyworld/haunt/MainActivity.kt
@@ -26,14 +26,13 @@ class MainActivity : AppCompatActivity() {
         onRename = { id, name -> vm.renamePoint(id, name) },
         onDelete = { id -> vm.deletePoint(id) },
         onRecolor = { id, color -> vm.recolorPoint(id, color) },
+        onRecolorYou = { color -> vm.setYouColor(color) },
     )
 
     private val requestPermission = registerForActivityResult(
         ActivityResultContracts.RequestPermission()
     ) { granted ->
-        if (!granted) {
-            binding.readingsText.text = "Location permission required for RTT ranging"
-        }
+        if (!granted) binding.readingsText.text = "Location permission required for RTT ranging"
     }
 
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -59,7 +58,10 @@ class MainActivity : AppCompatActivity() {
                 } else {
                     readings.joinToString("\n") { "…${it.bssid.takeLast(8)}  ${String.format("%.2f", it.distanceMeters)} m" }
                 }
-                binding.mapView.currentReadings = readings.associate { it.bssid to it.distanceMeters }
+                val map = readings.associate { it.bssid to it.distanceMeters }
+                binding.mapView.currentReadings = map
+                adapter.youReadings = map
+                adapter.notifyItemChanged(0)
             }
         }
 
@@ -70,6 +72,20 @@ class MainActivity : AppCompatActivity() {
             }
         }
 
+        lifecycleScope.launch {
+            vm.history.collectLatest { history ->
+                binding.mapView.history = history
+            }
+        }
+
+        lifecycleScope.launch {
+            vm.youColor.collectLatest { color ->
+                binding.mapView.youColor = color
+                adapter.youColor = color
+                adapter.notifyItemChanged(0)
+            }
+        }
+
         lifecycleScope.launch {
             vm.axisBssids.collectLatest { bssids ->
                 adapter.axisBssids = bssids
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 680f00f..6d3d842 100644
--- a/app/src/main/java/net/fuzzyworld/haunt/debug/DebugViewModel.kt
+++ b/app/src/main/java/net/fuzzyworld/haunt/debug/DebugViewModel.kt
@@ -1,9 +1,11 @@
 package net.fuzzyworld.haunt.debug
 
 import android.app.Application
+import android.content.Context
 import android.graphics.Color
 import androidx.lifecycle.AndroidViewModel
 import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
@@ -11,6 +13,7 @@ import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 import net.fuzzyworld.haunt.ranging.RttRangingManager
 import java.util.UUID
 
@@ -18,6 +21,8 @@ class DebugViewModel(application: Application) : AndroidViewModel(application) {
 
     private val rttManager = RttRangingManager(application)
     private val store = DebugPointStore(application)
+    private val historyStore = HistoryStore(application)
+    private val prefs = application.getSharedPreferences("haunt_prefs", Context.MODE_PRIVATE)
 
     private val _readings = MutableStateFlow<List<RttRangingManager.ApReading>>(emptyList())
     val readings: StateFlow<List<RttRangingManager.ApReading>> = _readings
@@ -25,25 +30,32 @@ class DebugViewModel(application: Application) : AndroidViewModel(application) {
     private val _points = MutableStateFlow<List<DebugPoint>>(emptyList())
     val points: StateFlow<List<DebugPoint>> = _points
 
+    private val _history = MutableStateFlow<List<HistoryPoint>>(emptyList())
+    val history: StateFlow<List<HistoryPoint>> = _history
+
+    private val defaultYouColor = Color.HSVToColor(floatArrayOf(210f, 0.75f, 0.90f))
+    private val _youColor = MutableStateFlow(prefs.getInt("you_color", defaultYouColor))
+    val youColor: StateFlow<Int> = _youColor
+
     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()
-
-    // BSSIDs already auto-added as router points (persisted via point IDs tagged with the BSSID)
     private val autoAddedBssids: MutableSet<String> = mutableSetOf()
+    private var historyWriteCounter = 0
 
     init {
         _points.value = store.load()
-        // Restore which BSSIDs were already auto-added so we don't duplicate on restart
         _points.value.filter { it.id.startsWith("router:") }.forEach {
             autoAddedBssids.add(it.id.removePrefix("router:"))
         }
+        viewModelScope.launch(Dispatchers.IO) {
+            val loaded = historyStore.load()
+            _history.value = loaded
+        }
         startRanging()
     }
 
@@ -52,16 +64,41 @@ class DebugViewModel(application: Application) : AndroidViewModel(application) {
             while (true) {
                 val fresh = rttManager.rangeAll()
                 fresh.forEach { lastKnownReadings[it.bssid] = it.distanceMeters }
-                val readings = lastKnownReadings.entries
+                val merged = lastKnownReadings.entries
                     .map { RttRangingManager.ApReading(it.key, it.value) }
                     .sortedBy { it.bssid }
-                _readings.value = readings
+                _readings.value = merged
                 autoAddNearbyRouters(fresh)
+
+                if (merged.isNotEmpty()) {
+                    val hp = HistoryPoint(
+                        readings = merged.associate { it.bssid to it.distanceMeters },
+                        timestamp = System.currentTimeMillis(),
+                    )
+                    _history.value = _history.value + hp
+                    historyWriteCounter++
+                    if (historyWriteCounter % 10 == 0) flushHistory()
+                }
+
                 delay(2000)
             }
         }
     }
 
+    fun setYouColor(color: Int) {
+        prefs.edit().putInt("you_color", color).apply()
+        _youColor.value = color
+    }
+
+    private suspend fun flushHistory() = withContext(Dispatchers.IO) {
+        historyStore.save(_history.value)
+    }
+
+    override fun onCleared() {
+        super.onCleared()
+        viewModelScope.launch(Dispatchers.IO) { historyStore.save(_history.value) }
+    }
+
     private fun autoAddNearbyRouters(readings: List<RttRangingManager.ApReading>) {
         val nearby = readings.filter { it.distanceMeters <= 0.5 && it.bssid !in autoAddedBssids }
         if (nearby.isEmpty()) return
@@ -107,7 +144,6 @@ class DebugViewModel(application: Application) : AndroidViewModel(application) {
         store.save(_points.value)
     }
 
-    // Golden-angle stepping guarantees maximum hue separation between successive colors
     private var nextHue = (0..360).random().toFloat()
     private fun randomColor(): Int {
         nextHue = (nextHue + 137.5f) % 360f
diff --git a/app/src/main/java/net/fuzzyworld/haunt/debug/HistoryPoint.kt b/app/src/main/java/net/fuzzyworld/haunt/debug/HistoryPoint.kt
new file mode 100644
index 0000000..45953dd
--- /dev/null
+++ b/app/src/main/java/net/fuzzyworld/haunt/debug/HistoryPoint.kt
@@ -0,0 +1,6 @@
+package net.fuzzyworld.haunt.debug
+
+data class HistoryPoint(
+    val readings: Map<String, Double>,
+    val timestamp: Long,
+)
diff --git a/app/src/main/java/net/fuzzyworld/haunt/debug/HistoryStore.kt b/app/src/main/java/net/fuzzyworld/haunt/debug/HistoryStore.kt
new file mode 100644
index 0000000..25f4972
--- /dev/null
+++ b/app/src/main/java/net/fuzzyworld/haunt/debug/HistoryStore.kt
@@ -0,0 +1,26 @@
+package net.fuzzyworld.haunt.debug
+
+import android.content.Context
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import java.io.File
+
+class HistoryStore(context: Context) {
+
+    private val file = File(context.filesDir, "history.json")
+    private val gson = Gson()
+
+    fun load(): MutableList<HistoryPoint> {
+        if (!file.exists()) return mutableListOf()
+        return try {
+            val type = object : TypeToken<MutableList<HistoryPoint>>() {}.type
+            gson.fromJson<MutableList<HistoryPoint>>(file.readText(), type) ?: mutableListOf()
+        } catch (_: Exception) {
+            mutableListOf()
+        }
+    }
+
+    fun save(history: List<HistoryPoint>) {
+        file.writeText(gson.toJson(history))
+    }
+}
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 290c712..5ba1233 100644
--- a/app/src/main/java/net/fuzzyworld/haunt/debug/MapView.kt
+++ b/app/src/main/java/net/fuzzyworld/haunt/debug/MapView.kt
@@ -18,10 +18,15 @@ class MapView @JvmOverloads constructor(
     var savedPoints: List<DebugPoint> = emptyList()
         set(value) { field = value; invalidate() }
 
-    // Live position — rendered as a moving point on the same map, same visual language
     var currentReadings: Map<String, Double> = emptyMap()
         set(value) { field = value; invalidate() }
 
+    var history: List<HistoryPoint> = emptyList()
+        set(value) { field = value; invalidate() }
+
+    var youColor: Int = Color.HSVToColor(floatArrayOf(210f, 0.75f, 0.90f))
+        set(value) { field = value; invalidate() }
+
     private val fillPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL }
     private val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.STROKE }
     private val gridPaint = Paint().apply {
@@ -60,8 +65,9 @@ class MapView @JvmOverloads constructor(
 
         val currentCoords = if (currentReadings.isNotEmpty()) coords(currentReadings) else null
         val savedCoords = savedPoints.mapNotNull { p -> coords(p.readings)?.let { p to it } }
+        val historyCoords = history.mapNotNull { coords(it.readings) }
 
-        val allCoords = savedCoords.map { it.second } + listOfNotNull(currentCoords)
+        val allCoords = savedCoords.map { it.second } + historyCoords + listOfNotNull(currentCoords)
 
         if (allCoords.isEmpty()) {
             labelPaint.color = Color.parseColor("#AAAAAA")
@@ -96,6 +102,13 @@ class MapView @JvmOverloads constructor(
             canvas.drawText("${gy}m", 4f, sy(gy.toDouble()) - 6f, dimLabelPaint)
         }
 
+        // History trail — drawn first so it appears behind everything else
+        val historyColor = Color.argb(60, Color.red(youColor), Color.green(youColor), Color.blue(youColor))
+        fillPaint.color = historyColor
+        historyCoords.forEach { (x, y) ->
+            canvas.drawCircle(sx(x), sy(y), 7f, fillPaint)
+        }
+
         // Saved points
         labelPaint.color = Color.BLACK
         savedCoords.forEach { (point, xy) ->
@@ -112,25 +125,23 @@ class MapView @JvmOverloads constructor(
             canvas.drawText(point.name, cx + 34f, cy + 11f, labelPaint)
         }
 
-        // Current position — same visual as a point but white fill + dashed ring to show it's live
+        // Current position — filled with youColor, crosshair + dashed ring on top
         if (currentCoords != null) {
             val cx = sx(currentCoords.first)
             val cy = sy(currentCoords.second)
 
-            fillPaint.color = Color.WHITE
+            fillPaint.color = youColor
             canvas.drawCircle(cx, cy, 22f, fillPaint)
 
-            strokePaint.color = Color.BLACK
+            strokePaint.color = Color.argb(180, 0, 0, 0)
             strokePaint.strokeWidth = 4f
             canvas.drawCircle(cx, cy, 22f, strokePaint)
 
-            // Crosshair
             strokePaint.strokeWidth = 2f
             canvas.drawLine(cx - 38f, cy, cx + 38f, cy, strokePaint)
             canvas.drawLine(cx, cy - 38f, cx, cy + 38f, strokePaint)
 
-            // Dashed ring to distinguish from saved points
-            ringPaint.color = Color.parseColor("#888888")
+            ringPaint.color = Color.argb(140, 0, 0, 0)
             canvas.drawCircle(cx, cy, 38f, ringPaint)
 
             labelPaint.textAlign = Paint.Align.LEFT
@@ -139,7 +150,7 @@ class MapView @JvmOverloads constructor(
             labelPaint.color = Color.BLACK
         }
 
-        // Axis legend — top-right corner, away from all grid labels
+        // Axis legend — top-right corner
         val bssids = allBssids
         dimLabelPaint.textAlign = Paint.Align.RIGHT
         if (bssids.isNotEmpty()) {
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 bbaaba4..7e4ec10 100644
--- a/app/src/main/java/net/fuzzyworld/haunt/debug/PointsAdapter.kt
+++ b/app/src/main/java/net/fuzzyworld/haunt/debug/PointsAdapter.kt
@@ -1,5 +1,6 @@
 package net.fuzzyworld.haunt.debug
 
+import android.content.Context
 import android.graphics.Color
 import android.graphics.drawable.GradientDrawable
 import android.view.LayoutInflater
@@ -11,53 +12,125 @@ import androidx.recyclerview.widget.RecyclerView
 import net.fuzzyworld.haunt.databinding.ItemDebugPointBinding
 
 private val PRESET_COLORS = listOf(
-    Color.HSVToColor(floatArrayOf(0f,   0.75f, 0.90f)), // red
-    Color.HSVToColor(floatArrayOf(25f,  0.85f, 0.90f)), // orange
-    Color.HSVToColor(floatArrayOf(50f,  0.80f, 0.85f)), // yellow
-    Color.HSVToColor(floatArrayOf(90f,  0.70f, 0.80f)), // lime
-    Color.HSVToColor(floatArrayOf(130f, 0.70f, 0.75f)), // green
-    Color.HSVToColor(floatArrayOf(165f, 0.70f, 0.78f)), // teal
-    Color.HSVToColor(floatArrayOf(195f, 0.75f, 0.85f)), // cyan
-    Color.HSVToColor(floatArrayOf(215f, 0.75f, 0.90f)), // sky
-    Color.HSVToColor(floatArrayOf(240f, 0.70f, 0.90f)), // blue
-    Color.HSVToColor(floatArrayOf(270f, 0.70f, 0.85f)), // purple
-    Color.HSVToColor(floatArrayOf(310f, 0.70f, 0.85f)), // magenta
-    Color.HSVToColor(floatArrayOf(340f, 0.75f, 0.90f)), // pink
+    Color.HSVToColor(floatArrayOf(0f,   0.75f, 0.90f)),
+    Color.HSVToColor(floatArrayOf(25f,  0.85f, 0.90f)),
+    Color.HSVToColor(floatArrayOf(50f,  0.80f, 0.85f)),
+    Color.HSVToColor(floatArrayOf(90f,  0.70f, 0.80f)),
+    Color.HSVToColor(floatArrayOf(130f, 0.70f, 0.75f)),
+    Color.HSVToColor(floatArrayOf(165f, 0.70f, 0.78f)),
+    Color.HSVToColor(floatArrayOf(195f, 0.75f, 0.85f)),
+    Color.HSVToColor(floatArrayOf(215f, 0.75f, 0.90f)),
+    Color.HSVToColor(floatArrayOf(240f, 0.70f, 0.90f)),
+    Color.HSVToColor(floatArrayOf(270f, 0.70f, 0.85f)),
+    Color.HSVToColor(floatArrayOf(310f, 0.70f, 0.85f)),
+    Color.HSVToColor(floatArrayOf(340f, 0.75f, 0.90f)),
 )
 
+private fun showColorPicker(context: Context, currentColor: Int, onPick: (Int) -> Unit) {
+    val dp = context.resources.displayMetrics.density
+    val chipSize = (48 * dp).toInt()
+    val chipMargin = (8 * dp).toInt()
+
+    val grid = GridLayout(context).apply {
+        columnCount = 4
+        setPadding(chipMargin, chipMargin, chipMargin, chipMargin)
+    }
+
+    var dialog: AlertDialog? = null
+    PRESET_COLORS.forEach { color ->
+        val chip = View(context).apply {
+            background = GradientDrawable().apply {
+                shape = GradientDrawable.OVAL
+                setColor(color)
+                if (color == currentColor) setStroke((3 * dp).toInt(), Color.BLACK)
+            }
+            setOnClickListener { onPick(color); dialog?.dismiss() }
+        }
+        val lp = GridLayout.LayoutParams().apply {
+            width = chipSize; height = chipSize
+            setMargins(chipMargin, chipMargin, chipMargin, chipMargin)
+        }
+        grid.addView(chip, lp)
+    }
+
+    dialog = AlertDialog.Builder(context)
+        .setTitle("Pick a color")
+        .setView(grid)
+        .setNegativeButton("Cancel", null)
+        .create()
+    dialog.show()
+}
+
 class PointsAdapter(
     private val onRename: (id: String, newName: String) -> Unit,
     private val onDelete: (id: String) -> Unit,
     private val onRecolor: (id: String, color: Int) -> Unit,
-) : RecyclerView.Adapter<PointsAdapter.ViewHolder>() {
+    private val onRecolorYou: (color: Int) -> Unit,
+) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
+
+    companion object {
+        private const val TYPE_YOU = 0
+        private const val TYPE_POINT = 1
+    }
 
     private var points: List<DebugPoint> = emptyList()
     var axisBssids: List<String> = emptyList()
+    var youColor: Int = Color.GRAY
+    var youReadings: Map<String, Double> = emptyMap()
 
     fun submitList(list: List<DebugPoint>) {
         points = list
         notifyDataSetChanged()
     }
 
-    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
-        val binding = ItemDebugPointBinding.inflate(
-            LayoutInflater.from(parent.context), parent, false
-        )
-        return ViewHolder(binding)
+    override fun getItemCount() = points.size + 1
+    override fun getItemViewType(position: Int) = if (position == 0) TYPE_YOU else TYPE_POINT
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+        val binding = ItemDebugPointBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+        return if (viewType == TYPE_YOU) YouViewHolder(binding) else PointViewHolder(binding)
+    }
+
+    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+        when (holder) {
+            is YouViewHolder -> holder.bind()
+            is PointViewHolder -> holder.bind(points[position - 1])
+        }
     }
 
-    override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(points[position])
-    override fun getItemCount() = points.size
+    inner class YouViewHolder(private val b: ItemDebugPointBinding) : RecyclerView.ViewHolder(b.root) {
+        fun bind() {
+            b.colorSwatch.setBackgroundColor(youColor)
+            b.colorSwatch.setOnClickListener {
+                showColorPicker(b.root.context, youColor) { color -> onRecolorYou(color) }
+            }
+            b.nameEdit.setText("you")
+            b.nameEdit.isEnabled = false
+            b.deleteButton.visibility = View.GONE
+
+            val x = axisBssids.getOrNull(0)?.let { youReadings[it] }
+            val y = axisBssids.getOrNull(1)?.let { youReadings[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 -> "—"
+            }
+        }
+    }
 
-    inner class ViewHolder(private val b: ItemDebugPointBinding) : RecyclerView.ViewHolder(b.root) {
+    inner class PointViewHolder(private val b: ItemDebugPointBinding) : RecyclerView.ViewHolder(b.root) {
         fun bind(point: DebugPoint) {
             b.colorSwatch.setBackgroundColor(point.color)
+            b.colorSwatch.setOnClickListener {
+                showColorPicker(b.root.context, point.color) { color -> onRecolor(point.id, color) }
+            }
+            b.nameEdit.isEnabled = true
+            b.deleteButton.visibility = View.VISIBLE
             b.nameEdit.setText(point.name)
             b.nameEdit.setOnFocusChangeListener { _, hasFocus ->
                 if (!hasFocus) onRename(point.id, b.nameEdit.text.toString().trim().ifEmpty { point.name })
             }
             b.deleteButton.setOnClickListener { onDelete(point.id) }
-            b.colorSwatch.setOnClickListener { showColorPicker(point) }
 
             val x = axisBssids.getOrNull(0)?.let { point.readings[it] }
             val y = axisBssids.getOrNull(1)?.let { point.readings[it] }
@@ -67,44 +140,5 @@ class PointsAdapter(
                 else -> ""
             }
         }
-
-        private fun showColorPicker(point: DebugPoint) {
-            val ctx = b.root.context
-            val dp = ctx.resources.displayMetrics.density
-            val chipSize = (48 * dp).toInt()
-            val chipMargin = (8 * dp).toInt()
-
-            val grid = GridLayout(ctx).apply {
-                columnCount = 4
-                setPadding(chipMargin, chipMargin, chipMargin, chipMargin)
-            }
-
-            var dialog: AlertDialog? = null
-            PRESET_COLORS.forEach { color ->
-                val chip = View(ctx).apply {
-                    background = GradientDrawable().apply {
-                        shape = GradientDrawable.OVAL
-                        setColor(color)
-                        if (color == point.color) setStroke((3 * dp).toInt(), Color.BLACK)
-                    }
-                    setOnClickListener {
-                        onRecolor(point.id, color)
-                        dialog?.dismiss()
-                    }
-                }
-                val lp = GridLayout.LayoutParams().apply {
-                    width = chipSize; height = chipSize
-                    setMargins(chipMargin, chipMargin, chipMargin, chipMargin)
-                }
-                grid.addView(chip, lp)
-            }
-
-            dialog = AlertDialog.Builder(ctx)
-                .setTitle("Pick a color")
-                .setView(grid)
-                .setNegativeButton("Cancel", null)
-                .create()
-            dialog.show()
-        }
     }
 }