diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index 6bc72e062..a8066694c 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -394,6 +394,15 @@ class UIViewModel @Inject constructor( updateLoraConfig { it.copy { region = value } } } + var ignoreIncomingList: MutableList + get() = config.lora.ignoreIncomingList + set(value) = updateLoraConfig { + it.copy { + ignoreIncoming.clear() + ignoreIncoming.addAll(value) + } + } + fun gpsString(p: Position): String { return when (config.display.gpsFormat) { Config.DisplayConfig.GpsCoordinateFormat.DEC -> GPSFormat.DEC(p) diff --git a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt index 27c373ef0..5f8cf8e0c 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt @@ -2,7 +2,9 @@ package com.geeksville.mesh.ui import android.content.res.ColorStateList import android.os.Bundle +import android.text.SpannableString import android.text.method.LinkMovementMethod +import android.text.style.StrikethroughSpan import android.view.LayoutInflater import android.view.MenuItem import android.view.View @@ -54,17 +56,29 @@ class UsersFragment : ScreenFragment("Users"), Logging { private val nodesAdapter = object : RecyclerView.Adapter() { private var nodes = arrayOf() + val ignoreIncomingList: MutableList = mutableListOf() + + private fun CharSequence.strike() = SpannableString(this).apply { + setSpan(StrikethroughSpan(), 0, this.length, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE) + } + + private fun CharSequence.strikeIf(isIgnored: Boolean) = if (isIgnored) strike() else this private fun popup(view: View, position: Int) { if (!model.isConnected()) return val node = nodes[position] val user = node.user ?: return val showAdmin = position == 0 || model.adminChannelIndex > 0 + val isIgnored = ignoreIncomingList.contains(node.num) val popup = PopupMenu(requireContext(), view) popup.inflate(R.menu.menu_nodes) popup.menu.setGroupVisible(R.id.group_remote, position > 0) popup.menu.setGroupVisible(R.id.group_admin, showAdmin) popup.menu.setGroupEnabled(R.id.group_admin, !model.isManaged) + popup.menu.findItem(R.id.ignore).apply { + isEnabled = ignoreIncomingList.size < 3 + isChecked = isIgnored + } popup.setOnMenuItemClickListener { item: MenuItem -> when (item.itemId) { R.id.direct_message -> { @@ -89,6 +103,27 @@ class UsersFragment : ScreenFragment("Users"), Logging { debug("requesting traceroute for '${user.longName}'") model.requestTraceroute(node.num) } + R.id.ignore -> { + val message = if (isIgnored) R.string.ignore_remove else R.string.ignore_add + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.ignore) + .setMessage(getString(message, user.longName)) + .setNeutralButton(R.string.cancel) { _, _ -> } + .setPositiveButton(R.string.send) { _, _ -> + model.ignoreIncomingList = ignoreIncomingList.apply { + if (isIgnored) { + debug("removed '${user.longName}' from ignore list") + remove(node.num) + } else { + debug("added '${user.longName}' to ignore list") + add(node.num) + } + } + item.isChecked = !item.isChecked + notifyItemChanged(position) + } + .show() + } R.id.remote_admin -> { debug("calling remote admin --> destNum: ${node.num.toUInt()}") parentFragmentManager.beginTransaction() @@ -167,8 +202,9 @@ class UsersFragment : ScreenFragment("Users"), Logging { val n = nodes[position] val user = n.user val (textColor, nodeColor) = n.colors + val isIgnored: Boolean = ignoreIncomingList.contains(n.num) with(holder.chipNode) { - text = user?.shortName ?: "UNK" + text = (user?.shortName ?: "UNK").strikeIf(isIgnored) chipBackgroundColor = ColorStateList.valueOf(nodeColor) setTextColor(textColor) } @@ -285,6 +321,13 @@ class UsersFragment : ScreenFragment("Users"), Logging { nodesAdapter.onNodesChanged(it.perhapsReindexBy(model.myNodeNum)) } + model.localConfig.asLiveData().observe(viewLifecycleOwner) { config -> + nodesAdapter.ignoreIncomingList.apply { + clear() + addAll(config.lora.ignoreIncomingList) + } + } + model.packetResponse.asLiveData().observe(viewLifecycleOwner) { meshLog -> meshLog?.meshPacket?.let { meshPacket -> val routeList = meshLog.routeDiscovery?.routeList ?: return@let diff --git a/app/src/main/res/menu/menu_nodes.xml b/app/src/main/res/menu/menu_nodes.xml index c921e1c6f..7cc6e40ae 100644 --- a/app/src/main/res/menu/menu_nodes.xml +++ b/app/src/main/res/menu/menu_nodes.xml @@ -14,6 +14,11 @@ android:id="@+id/traceroute" android:title="@string/traceroute" app:showAsAction="withText" /> + Direct Message NodeDB reset This will clear all nodes from this list. + Ignore + Add \'%s\' to ignore list? Your radio will reboot after making this change. + Remove \'%s\' from ignore list? Your radio will reboot after making this change. Select download region 5 Miles 10 miles