diff --git a/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl b/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl index 48309b20c..329f3715e 100644 --- a/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl +++ b/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl @@ -88,13 +88,16 @@ interface IMeshService { void setChannels(in byte []payload); /// Send Shutdown admin packet to nodeNum - void requestShutdown(in String nodeId); + void requestShutdown(in int idNum); /// Send Reboot admin packet to nodeNum - void requestReboot(in String nodeId); + void requestReboot(in int idNum); /// Send FactoryReset admin packet to nodeNum - void requestFactoryReset(in String nodeId); + void requestFactoryReset(in int idNum); + + /// Send NodedbReset admin packet to nodeNum + void requestNodedbReset(in int idNum); /** Is the packet radio currently connected to the phone? Returns a ConnectionState string. 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 607305943..d79ce445c 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -237,11 +237,6 @@ class UIViewModel @Inject constructor( // We consider hasWifi = ESP32 fun isESP32() = myNodeInfo.value?.hasWifi == true - fun hasAXP(): Boolean { - val hasAXP = listOf(4, 7, 9) // mesh.proto 'HardwareModel' enums with AXP192 chip - return hasAXP.contains(nodeDB.ourNodeInfo?.user?.hwModel?.number) - } - /// hardware info about our local device (can be null) private val _myNodeInfo = MutableLiveData() val myNodeInfo: LiveData get() = _myNodeInfo @@ -360,16 +355,36 @@ class UIViewModel @Inject constructor( } } - fun requestShutdown() { - meshService?.requestShutdown(DataPacket.ID_LOCAL) + fun requestShutdown(idNum: Int) { + try { + meshService?.requestShutdown(idNum) + } catch (ex: RemoteException) { + errormsg("RemoteException: ${ex.message}") + } } - fun requestReboot() { - meshService?.requestReboot(DataPacket.ID_LOCAL) + fun requestReboot(idNum: Int) { + try { + meshService?.requestReboot(idNum) + } catch (ex: RemoteException) { + errormsg("RemoteException: ${ex.message}") + } } - fun requestFactoryReset() { - meshService?.requestFactoryReset(DataPacket.ID_LOCAL) + fun requestFactoryReset(idNum: Int) { + try { + meshService?.requestFactoryReset(idNum) + } catch (ex: RemoteException) { + errormsg("RemoteException: ${ex.message}") + } + } + + fun requestNodedbReset(idNum: Int) { + try { + meshService?.requestNodedbReset(idNum) + } catch (ex: RemoteException) { + errormsg("RemoteException: ${ex.message}") + } } /** diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt index 2ad6b954a..3f3ab92e4 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -1341,7 +1341,7 @@ class MeshService : Service(), Logging { else { discardNodeDB() debug("Installing new node DB") - myNodeInfo = newMyNodeInfo// Install myNodeInfo as current + myNodeInfo = newMyNodeInfo // Install myNodeInfo as current newNodes.forEach(::installNodeInfo) newNodes.clear() // Just to save RAM ;-) @@ -1383,24 +1383,30 @@ class MeshService : Service(), Logging { }) } - private fun requestShutdown(nodeId: String) { - sendToRadio(newMeshPacketTo(toNodeNum(nodeId)).buildAdminPacket { + private fun requestShutdown(idNum: Int) { + sendToRadio(newMeshPacketTo(idNum).buildAdminPacket { shutdownSeconds = 5 }) } - private fun requestReboot(nodeId: String) { - sendToRadio(newMeshPacketTo(toNodeNum(nodeId)).buildAdminPacket { + private fun requestReboot(idNum: Int) { + sendToRadio(newMeshPacketTo(idNum).buildAdminPacket { rebootSeconds = 5 }) } - private fun requestFactoryReset(nodeId: String) { - sendToRadio(newMeshPacketTo(toNodeNum(nodeId)).buildAdminPacket { + private fun requestFactoryReset(idNum: Int) { + sendToRadio(newMeshPacketTo(idNum).buildAdminPacket { factoryReset = 1 }) } + private fun requestNodedbReset(idNum: Int) { + sendToRadio(newMeshPacketTo(idNum).buildAdminPacket { + nodedbReset = 1 + }) + } + /** * Start the modern (REV2) API configuration flow */ @@ -1721,16 +1727,20 @@ class MeshService : Service(), Logging { stopLocationRequests() } - override fun requestShutdown(nodeId: String) = toRemoteExceptions { - this@MeshService.requestShutdown(nodeId) + override fun requestShutdown(idNum: Int) = toRemoteExceptions { + this@MeshService.requestShutdown(idNum) } - override fun requestReboot(nodeId: String) = toRemoteExceptions { - this@MeshService.requestReboot(nodeId) + override fun requestReboot(idNum: Int) = toRemoteExceptions { + this@MeshService.requestReboot(idNum) } - override fun requestFactoryReset(nodeId: String) = toRemoteExceptions { - this@MeshService.requestFactoryReset(nodeId) + override fun requestFactoryReset(idNum: Int) = toRemoteExceptions { + this@MeshService.requestFactoryReset(idNum) + } + + override fun requestNodedbReset(idNum: Int) = toRemoteExceptions { + this@MeshService.requestNodedbReset(idNum) } } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt index 8e4b2435b..fdb81614c 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt @@ -55,9 +55,6 @@ class AdvancedSettingsFragment : ScreenFragment("Advanced Settings"), Logging { binding.lsSleepView.isEnabled = connected && model.config.power.isPowerSaving binding.positionBroadcastSwitch.isEnabled = connected binding.lsSleepSwitch.isEnabled = connected && model.isESP32() - binding.shutdownButton.isEnabled = connected && model.hasAXP() - binding.rebootButton.isEnabled = connected - binding.factoryResetButton.isEnabled = connected } binding.positionBroadcastPeriodEditText.on(EditorInfo.IME_ACTION_DONE) { @@ -111,41 +108,5 @@ class AdvancedSettingsFragment : ScreenFragment("Advanced Settings"), Logging { debug("User changed isPowerSaving to $isChecked") } } - - binding.shutdownButton.setOnClickListener { - MaterialAlertDialogBuilder(requireContext()) - .setMessage("${getString(R.string.shutdown)}?") - .setNeutralButton(R.string.cancel) { _, _ -> - } - .setPositiveButton(getString(R.string.okay)) { _, _ -> - debug("User clicked requestShutdown") - model.requestShutdown() - } - .show() - } - - binding.rebootButton.setOnClickListener { - MaterialAlertDialogBuilder(requireContext()) - .setMessage("${getString(R.string.reboot)}?") - .setNeutralButton(R.string.cancel) { _, _ -> - } - .setPositiveButton(getString(R.string.okay)) { _, _ -> - debug("User clicked requestReboot") - model.requestReboot() - } - .show() - } - - binding.factoryResetButton.setOnClickListener { - MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.are_you_sure_factory_reset) - .setMessage(R.string.factory_reset_description) - .setNeutralButton(R.string.cancel) { _, _ -> - } - .setPositiveButton(R.string.okay) { _, _ -> - model.requestFactoryReset() - } - .show() - } } } \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt index 769a9e4f3..f95c170f2 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt @@ -379,12 +379,9 @@ class MapFragment : ScreenFragment("Map"), Logging, View.OnClickListener, OnSeek val label = it.name + " " + formatAgo(it.expire) marker = MarkerWithLabel(map, label) marker.title = it.name + marker.snippet = it.description marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM) marker.position = GeoPoint(it.latitudeI.toDouble(), it.longitudeI.toDouble()) - marker.icon = ContextCompat.getDrawable( - requireActivity(), - R.drawable.ic_baseline_location_on_24 - ) } marker } 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 2328e7d26..edbccdfb5 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt @@ -3,8 +3,10 @@ package com.geeksville.mesh.ui import android.os.Bundle import android.text.method.LinkMovementMethod import android.view.LayoutInflater +import android.view.MenuItem import android.view.View import android.view.ViewGroup +import androidx.appcompat.widget.PopupMenu import androidx.core.content.ContextCompat import androidx.core.os.bundleOf import androidx.core.text.HtmlCompat @@ -12,13 +14,14 @@ import androidx.fragment.app.activityViewModels import androidx.fragment.app.setFragmentResult import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.geeksville.mesh.android.Logging import com.geeksville.mesh.NodeInfo import com.geeksville.mesh.R +import com.geeksville.mesh.android.Logging import com.geeksville.mesh.databinding.AdapterNodeLayoutBinding import com.geeksville.mesh.databinding.NodelistFragmentBinding import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.util.formatAgo +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import java.net.URLEncoder @@ -35,6 +38,7 @@ class UsersFragment : ScreenFragment("Users"), Logging { // Provide a direct reference to each of the views within a data item // Used to cache the views within the item layout for fast access class ViewHolder(itemView: AdapterNodeLayoutBinding) : RecyclerView.ViewHolder(itemView.root) { + val chipNode = itemView.chipNode val nodeNameView = itemView.nodeNameView val distanceView = itemView.distanceView val coordsView = itemView.coordsView @@ -47,6 +51,90 @@ class UsersFragment : ScreenFragment("Users"), Logging { private val nodesAdapter = object : RecyclerView.Adapter() { + private var nodes = arrayOf() + + private fun popup(view: View, position: Int) { + val node = nodes[position] + val user = node.user + val showAdmin = position == 0 // TODO add admin channel check + val popup = PopupMenu(requireContext(), view) + popup.inflate(R.menu.menu_nodes) + popup.menu.findItem(R.id.direct_message).isVisible = position > 0 + popup.menu.setGroupVisible(R.id.group_admin, showAdmin) + popup.setOnMenuItemClickListener { item: MenuItem -> + when (item.itemId) { + R.id.direct_message -> { + if (position > 0 && user != null) { + debug("calling MessagesFragment filter: 0${user.id}") + setFragmentResult( + "requestKey", + bundleOf( + "contactKey" to "0${user.id}", + "contactName" to user.longName + ) + ) + parentFragmentManager.beginTransaction() + .replace(R.id.mainActivityLayout, MessagesFragment()) + .addToBackStack(null) + .commit() + } + } + R.id.reboot -> { + MaterialAlertDialogBuilder(requireContext()) + .setTitle("${getString(R.string.reboot)}\n${user?.longName}?") + .setIcon(R.drawable.ic_twotone_warning_24) + .setNeutralButton(R.string.cancel) { _, _ -> + } + .setPositiveButton(getString(R.string.okay)) { _, _ -> + debug("User clicked requestReboot") + model.requestReboot(node.num) + } + .show() + } + R.id.shutdown -> { + MaterialAlertDialogBuilder(requireContext()) + .setTitle("${getString(R.string.shutdown)}\n${user?.longName}?") + .setIcon(R.drawable.ic_twotone_warning_24) + .setNeutralButton(R.string.cancel) { _, _ -> + } + .setPositiveButton(getString(R.string.okay)) { _, _ -> + debug("User clicked requestShutdown") + model.requestShutdown(node.num) + } + .show() + } + R.id.factory_reset -> { + MaterialAlertDialogBuilder(requireContext()) + .setTitle("${getString(R.string.factory_reset)}\n${user?.longName}?") + .setIcon(R.drawable.ic_twotone_warning_24) + .setMessage(R.string.factory_reset_description) + .setNeutralButton(R.string.cancel) { _, _ -> + } + .setPositiveButton(R.string.okay) { _, _ -> + debug("User clicked requestFactoryReset") + model.requestFactoryReset(node.num) + } + .show() + } + R.id.nodedb_reset -> { + MaterialAlertDialogBuilder(requireContext()) + .setTitle("${getString(R.string.nodedb_reset)}\n${user?.longName}?") + .setIcon(R.drawable.ic_twotone_warning_24) + .setMessage(R.string.nodedb_reset_description) + .setNeutralButton(R.string.cancel) { _, _ -> + } + .setPositiveButton(getString(R.string.okay)) { _, _ -> + debug("User clicked requestNodedbReset") + model.requestNodedbReset(node.num) + } + .show() + } + } + true + } + popup.show() + } + /** * Called when RecyclerView needs a new [ViewHolder] of the given type to represent * an item. @@ -110,7 +198,9 @@ class UsersFragment : ScreenFragment("Users"), Logging { */ override fun onBindViewHolder(holder: ViewHolder, position: Int) { val n = nodes[position] - val name = n.user?.longName ?: n.user?.id ?: "Unknown node" + val user = n.user + holder.chipNode.text = user?.shortName ?: "UNK" + val name = user?.longName ?: "Unknown node" holder.nodeNameView.text = name val pos = n.validPosition @@ -165,25 +255,15 @@ class UsersFragment : ScreenFragment("Users"), Logging { holder.signalView.visibility = View.INVISIBLE } } + holder.chipNode.setOnClickListener { + popup(it, position) + } holder.itemView.setOnLongClickListener { - val node = n.user - if (position > 0 && node != null) { - debug("calling MessagesFragment filter:${node.id}") - setFragmentResult( - "requestKey", - bundleOf("contactKey" to "0${node.id}", "contactName" to name) - ) - parentFragmentManager.beginTransaction() - .replace(R.id.mainActivityLayout, MessagesFragment()) - .addToBackStack(null) - .commit() - } + popup(it, position) true } } - private var nodes = arrayOf() - /// Called when our node DB changes fun onNodesChanged(nodesIn: Array) { if (nodesIn.size > 1) @@ -210,10 +290,7 @@ class UsersFragment : ScreenFragment("Users"), Logging { holder.batteryPctView.text = text holder.powerIcon.setImageDrawable(context?.let { - ContextCompat.getDrawable( - it, - image - ) + ContextCompat.getDrawable(it, image) }) } diff --git a/app/src/main/res/drawable/help.xml b/app/src/main/res/drawable/help.xml deleted file mode 100644 index 0b2cd9765..000000000 --- a/app/src/main/res/drawable/help.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_baseline_settings_input_antenna_24.xml b/app/src/main/res/drawable/ic_baseline_settings_input_antenna_24.xml deleted file mode 100644 index 9379c0f4d..000000000 --- a/app/src/main/res/drawable/ic_baseline_settings_input_antenna_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_twotone_person_24.xml b/app/src/main/res/drawable/ic_twotone_person_24.xml deleted file mode 100644 index 5306b665e..000000000 --- a/app/src/main/res/drawable/ic_twotone_person_24.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/ic_twotone_warning_24.xml b/app/src/main/res/drawable/ic_twotone_warning_24.xml new file mode 100644 index 000000000..e75aa8b08 --- /dev/null +++ b/app/src/main/res/drawable/ic_twotone_warning_24.xml @@ -0,0 +1,15 @@ + + + + diff --git a/app/src/main/res/drawable/navigation.xml b/app/src/main/res/drawable/navigation.xml deleted file mode 100644 index 1b5ee3904..000000000 --- a/app/src/main/res/drawable/navigation.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/signal_level.xml b/app/src/main/res/drawable/signal_level.xml deleted file mode 100644 index c560971ec..000000000 --- a/app/src/main/res/drawable/signal_level.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/layout/adapter_contact_layout.xml b/app/src/main/res/layout/adapter_contact_layout.xml index b1aa9e7ce..3cc4fd38d 100644 --- a/app/src/main/res/layout/adapter_contact_layout.xml +++ b/app/src/main/res/layout/adapter_contact_layout.xml @@ -18,10 +18,11 @@ diff --git a/app/src/main/res/layout/adapter_node_layout.xml b/app/src/main/res/layout/adapter_node_layout.xml index 51e139a8a..37f902985 100644 --- a/app/src/main/res/layout/adapter_node_layout.xml +++ b/app/src/main/res/layout/adapter_node_layout.xml @@ -15,19 +15,15 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - + android:layout_margin="8dp" + android:text="@string/some_username" + android:textAlignment="center" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintStart_toEndOf="@+id/chip_node" + app:layout_constraintTop_toTopOf="@+id/chip_node" /> + app:layout_constraintEnd_toEndOf="@+id/chip_node" + app:layout_constraintStart_toStartOf="@+id/chip_node" + app:layout_constraintTop_toBottomOf="@+id/chip_node" /> @@ -68,10 +59,10 @@ android:id="@+id/batteryIcon" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:layout_marginEnd="8dp" + android:layout_margin="8dp" + app:layout_constraintBottom_toBottomOf="@+id/batteryPercentageView" app:layout_constraintEnd_toStartOf="@+id/batteryPercentageView" - app:layout_constraintTop_toTopOf="parent" + app:layout_constraintTop_toTopOf="@+id/batteryPercentageView" app:srcCompat="@drawable/ic_battery_full_24" /> + app:layout_constraintTop_toTopOf="@+id/nodeNameView" /> + app:layout_constraintTop_toTopOf="@+id/coords_view" /> + app:layout_constraintTop_toBottomOf="@id/lastConnectionView" /> - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/settings_fragment.xml b/app/src/main/res/layout/settings_fragment.xml index 48190d6a2..b704f7f71 100644 --- a/app/src/main/res/layout/settings_fragment.xml +++ b/app/src/main/res/layout/settings_fragment.xml @@ -58,7 +58,6 @@ android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginEnd="16dp" - android:entries="@array/regions" android:theme="@style/AppTheme.Spinner" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/regionLabel" diff --git a/app/src/main/res/menu/menu_nodes.xml b/app/src/main/res/menu/menu_nodes.xml new file mode 100644 index 000000000..ee6b0d9e2 --- /dev/null +++ b/app/src/main/res/menu/menu_nodes.xml @@ -0,0 +1,26 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/protobufs.xml b/app/src/main/res/values/protobufs.xml deleted file mode 100644 index 275b8b600..000000000 --- a/app/src/main/res/values/protobufs.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - Unset - US - EU433 - EU868 - CN - JP - ANZ - KR - TW - RU - IN - TH - - \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index de6e03f40..b5af03e6a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -151,8 +151,10 @@ Instantly send Empty channel names use the default encryption key (any device on %s can read your messages). Factory reset - Are you sure you want to factory reset? This will clear all device configuration you have done. Bluetooth disabled. Meshtastic needs Nearby devices permission to find and connect to devices via Bluetooth. You can turn it off when not in use. + Direct Message + NodeDB reset + This will clear all nodes from this list.