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 1807b33ed..095549dd7 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -265,6 +265,7 @@ class UIViewModel @Inject constructor( } fun requestTraceroute(destNum: Int) { + info("Requesting traceroute for '$destNum'") try { val packetId = meshService?.packetId ?: return meshService?.requestTraceroute(packetId, destNum) @@ -274,16 +275,18 @@ class UIViewModel @Inject constructor( } fun removeNode(nodeNum: Int) = viewModelScope.launch(Dispatchers.IO) { + info("Removing node '$nodeNum'") try { val packetId = meshService?.packetId ?: return@launch meshService?.removeByNodenum(packetId, nodeNum) nodeDB.deleteNode(nodeNum) } catch (ex: RemoteException) { - errormsg("Request traceroute error: ${ex.message}") + errormsg("Remove node error: ${ex.message}") } } fun requestPosition(destNum: Int, position: Position = Position(0.0, 0.0, 0)) { + info("Requesting position for '$destNum'") try { meshService?.requestPosition(destNum, position) } catch (ex: RemoteException) { diff --git a/app/src/main/java/com/geeksville/mesh/ui/NodeMenu.kt b/app/src/main/java/com/geeksville/mesh/ui/NodeMenu.kt new file mode 100644 index 000000000..18afe8a47 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/NodeMenu.kt @@ -0,0 +1,62 @@ +package com.geeksville.mesh.ui + +import android.view.Gravity +import android.view.MenuItem +import android.view.View +import androidx.appcompat.widget.PopupMenu +import com.geeksville.mesh.NodeInfo +import com.geeksville.mesh.R +import com.google.android.material.dialog.MaterialAlertDialogBuilder + +internal fun View.nodeMenu( + node: NodeInfo, + ignoreIncomingList: List, + isOurNode: Boolean = false, + showAdmin: Boolean = false, + isManaged: Boolean = false, + onMenuItemAction: MenuItem.() -> Unit, +) = PopupMenu(context, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0).apply { + val isIgnored = ignoreIncomingList.contains(node.num) + + inflate(R.menu.menu_nodes) + menu.apply { + setGroupVisible(R.id.group_remote, !isOurNode) + setGroupVisible(R.id.group_admin, showAdmin) + setGroupEnabled(R.id.group_admin, !isManaged) + findItem(R.id.ignore).apply { + isEnabled = isIgnored || ignoreIncomingList.size < 3 + isChecked = isIgnored + } + } + setOnMenuItemClickListener { item -> + when (item.itemId) { + R.id.remove -> { + MaterialAlertDialogBuilder(context) + .setTitle(R.string.remove) + .setMessage(R.string.remove_node_text) + .setNeutralButton(R.string.cancel) { _, _ -> } + .setPositiveButton(R.string.send) { _, _ -> + item.onMenuItemAction() + } + .show() + } + + R.id.ignore -> { + val message = if (isIgnored) R.string.ignore_remove else R.string.ignore_add + MaterialAlertDialogBuilder(context) + .setTitle(R.string.ignore) + .setMessage(context.getString(message, node.user?.longName)) + .setNeutralButton(R.string.cancel) { _, _ -> } + .setPositiveButton(R.string.send) { _, _ -> + item.onMenuItemAction() + } + .show() + item.isChecked = !item.isChecked + } + + else -> item.onMenuItemAction() + } + true + } + show() +} 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 2c6f9db10..e6185a028 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt @@ -1,12 +1,9 @@ package com.geeksville.mesh.ui import android.os.Bundle -import android.view.Gravity import android.view.LayoutInflater -import android.view.MenuItem import android.view.View import android.view.ViewGroup -import androidx.appcompat.widget.PopupMenu import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.layout.Row @@ -34,7 +31,6 @@ import com.geeksville.mesh.android.Logging import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.ui.components.NodeFilterTextField import com.geeksville.mesh.ui.theme.AppTheme -import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch @@ -43,82 +39,63 @@ class UsersFragment : ScreenFragment("Users"), Logging { private val model: UIViewModel by activityViewModels() - private fun popup(view: View, node: NodeInfo) { + private fun popup(node: NodeInfo) { if (!model.isConnected()) return - val user = node.user ?: return val isOurNode = node.num == model.myNodeNum - val showAdmin = isOurNode || model.hasAdminChannel val ignoreIncomingList = model.ignoreIncomingList - val isIgnored = ignoreIncomingList.contains(node.num) - val popup = - PopupMenu(view.context, view, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0) - popup.inflate(R.menu.menu_nodes) - popup.menu.setGroupVisible(R.id.group_remote, !isOurNode) - 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 = isIgnored || ignoreIncomingList.size < 3 - isChecked = isIgnored - } - popup.setOnMenuItemClickListener { item: MenuItem -> - when (item.itemId) { + + requireView().nodeMenu( + node = node, + ignoreIncomingList = ignoreIncomingList, + isOurNode = isOurNode, + showAdmin = isOurNode || model.hasAdminChannel, + isManaged = model.isManaged, + ) { + when (itemId) { R.id.direct_message -> { - val contactKey = "${node.channel}${user.id}" - debug("calling MessagesFragment filter: $contactKey") - parentFragmentManager.navigateToMessages(contactKey, user.longName) + navigateToMessages(node) } R.id.request_position -> { - debug("requesting position for '${user.longName}'") model.requestPosition(node.num) } R.id.traceroute -> { - debug("requesting traceroute for '${user.longName}'") model.requestTraceroute(node.num) } R.id.remove -> { - MaterialAlertDialogBuilder(view.context) - .setTitle(R.string.remove) - .setMessage(getString(R.string.remove_node_text)) - .setNeutralButton(R.string.cancel) { _, _ -> } - .setPositiveButton(R.string.send) { _, _ -> - debug("removing node '${user.longName}'") - model.removeNode(node.num) - } - .show() + model.removeNode(node.num) } R.id.ignore -> { - val message = if (isIgnored) R.string.ignore_remove else R.string.ignore_add - MaterialAlertDialogBuilder(view.context) - .setTitle(R.string.ignore) - .setMessage(getString(message, user.longName)) - .setNeutralButton(R.string.cancel) { _, _ -> } - .setPositiveButton(R.string.send) { _, _ -> - model.ignoreIncomingList = ignoreIncomingList.toMutableList().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 + model.ignoreIncomingList = ignoreIncomingList.toMutableList().apply { + if (contains(node.num)) { + debug("removed '${node.num}' from ignore list") + remove(node.num) + } else { + debug("added '${node.num}' to ignore list") + add(node.num) } - .show() + } } R.id.remote_admin -> { - debug("calling remote admin --> destNum: ${node.num.toUInt()}") - parentFragmentManager.navigateToRadioConfig(node.num) + navigateToRadioConfig(node) } } - true } - popup.show() + } + + private fun navigateToMessages(node: NodeInfo) = node.user?.let { user -> + val contactKey = "${node.channel}${user.id}" + info("calling MessagesFragment filter: $contactKey") + parentFragmentManager.navigateToMessages(contactKey, user.longName) + } + + private fun navigateToRadioConfig(node: NodeInfo) { + info("calling RadioConfig --> destNum: ${node.num}") + parentFragmentManager.navigateToRadioConfig(node.num) } override fun onCreateView( @@ -130,7 +107,7 @@ class UsersFragment : ScreenFragment("Users"), Logging { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { AppTheme { - NodesScreen(model = model, onClick = { popup(requireView(), it) }) + NodesScreen(model = model, onClick = ::popup) } } }