feat: edit waypoints

pull/583/head
andrekir 2023-02-03 19:07:15 -03:00 zatwierdzone przez Andre K
rodzic 63ac168fc8
commit ce66a9425d
4 zmienionych plików z 124 dodań i 55 usunięć

Wyświetl plik

@ -53,7 +53,7 @@ interface PacketDao {
@Transaction @Transaction
fun getDataPacketById(requestId: Int): DataPacket? { fun getDataPacketById(requestId: Int): DataPacket? {
return getDataPackets().firstOrNull { it.id == requestId } return getDataPackets().lastOrNull { it.id == requestId }
} }
@Transaction @Transaction

Wyświetl plik

@ -161,17 +161,15 @@ class UIViewModel @Inject constructor(
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
val contacts: LiveData<Map<String, Packet>> = _packets.mapLatest { list -> val contacts: LiveData<Map<String, Packet>> = _packets.mapLatest { list ->
list.associateBy { packet -> packet.contact_key } list.filter { it.port_num == Portnums.PortNum.TEXT_MESSAGE_APP_VALUE }
.filter { it.value.port_num == Portnums.PortNum.TEXT_MESSAGE_APP_VALUE } .associateBy { packet -> packet.contact_key }
}.asLiveData() }.asLiveData()
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
val waypoints: LiveData<Map<Int?, Packet>> = _packets.mapLatest { list -> val waypoints: LiveData<Map<Int, Packet>> = _packets.mapLatest { list ->
list.associateBy { packet -> packet.data.waypoint?.id } list.filter { it.port_num == Portnums.PortNum.WAYPOINT_APP_VALUE }
.filterValues { .associateBy { packet -> packet.data.waypoint!!.id }
val expired = (it.data.waypoint?.expire ?: 0) < System.currentTimeMillis() / 1000 .filterValues { it.data.waypoint!!.expire > System.currentTimeMillis() / 1000 }
it.port_num == Portnums.PortNum.WAYPOINT_APP_VALUE && !expired
}
}.asLiveData() }.asLiveData()
fun generatePacketId(): Int? { fun generatePacketId(): Int? {
@ -198,7 +196,7 @@ class UIViewModel @Inject constructor(
val dest = if (channel != null) contactKey.substring(1) else contactKey val dest = if (channel != null) contactKey.substring(1) else contactKey
val p = DataPacket(dest, channel ?: 0, wpt) val p = DataPacket(dest, channel ?: 0, wpt)
if (wpt.id != 0) sendDataPacket(p.copy(id = wpt.id)) if (wpt.id != 0) sendDataPacket(p)
} }
private fun sendDataPacket(p: DataPacket) { private fun sendDataPacket(p: DataPacket) {
@ -294,6 +292,7 @@ class UIViewModel @Inject constructor(
/// hardware info about our local device (can be null) /// hardware info about our local device (can be null)
private val _myNodeInfo = MutableLiveData<MyNodeInfo?>() private val _myNodeInfo = MutableLiveData<MyNodeInfo?>()
val myNodeInfo: LiveData<MyNodeInfo?> get() = _myNodeInfo val myNodeInfo: LiveData<MyNodeInfo?> get() = _myNodeInfo
val myNodeNum get() = _myNodeInfo.value?.myNodeNum
fun setMyNodeInfo(info: MyNodeInfo?) { fun setMyNodeInfo(info: MyNodeInfo?) {
_myNodeInfo.value = info _myNodeInfo.value = info
@ -526,7 +525,7 @@ class UIViewModel @Inject constructor(
viewModelScope.launch(Dispatchers.Main) { viewModelScope.launch(Dispatchers.Main) {
// Extract distances to this device from position messages and put (node,SNR,distance) in // Extract distances to this device from position messages and put (node,SNR,distance) in
// the file_uri // the file_uri
val myNodeNum = myNodeInfo.value?.myNodeNum ?: return@launch val myNodeNum = myNodeNum ?: return@launch
// Capture the current node value while we're still on main thread // Capture the current node value while we're still on main thread
val nodes = nodeDB.nodes.value ?: emptyMap() val nodes = nodeDB.nodes.value ?: emptyMap()

Wyświetl plik

@ -20,9 +20,11 @@ import androidx.core.content.ContextCompat
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import com.geeksville.mesh.BuildConfig import com.geeksville.mesh.BuildConfig
import com.geeksville.mesh.DataPacket import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.MeshProtos.Waypoint
import com.geeksville.mesh.NodeInfo import com.geeksville.mesh.NodeInfo
import com.geeksville.mesh.R import com.geeksville.mesh.R
import com.geeksville.mesh.android.Logging import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.copy
import com.geeksville.mesh.database.entity.Packet import com.geeksville.mesh.database.entity.Packet
import com.geeksville.mesh.databinding.MapViewBinding import com.geeksville.mesh.databinding.MapViewBinding
import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.model.UIViewModel
@ -57,6 +59,7 @@ import org.osmdroid.views.overlay.Polygon
import org.osmdroid.views.overlay.gridlines.LatLonGridlineOverlay2 import org.osmdroid.views.overlay.gridlines.LatLonGridlineOverlay2
import org.osmdroid.views.overlay.infowindow.InfoWindow import org.osmdroid.views.overlay.infowindow.InfoWindow
import java.io.File import java.io.File
import java.text.DateFormat
import kotlin.math.log2 import kotlin.math.log2
@ -76,7 +79,8 @@ class MapFragment : ScreenFragment("Map Fragment"), Logging {
private val prefsName = "org.geeksville.osm.prefs" private val prefsName = "org.geeksville.osm.prefs"
private val mapStyleId = "map_style_id" private val mapStyleId = "map_style_id"
private var nodePositions = listOf<MarkerWithLabel>() private var nodePositions = listOf<MarkerWithLabel>()
private var wayPoints = listOf<MarkerWithLabel>() private var waypoints = mapOf<Int, Waypoint?>()
private var waypointMarkers = listOf<MarkerWithLabel>()
private val nodeLayer = 1 private val nodeLayer = 1
// Distance of bottom corner to top corner of bounding box // Distance of bottom corner to top corner of bounding box
@ -133,6 +137,7 @@ class MapFragment : ScreenFragment("Map Fragment"), Logging {
} }
model.waypoints.observe(viewLifecycleOwner) { model.waypoints.observe(viewLifecycleOwner) {
debug("New waypoints received: ${it.size}") debug("New waypoints received: ${it.size}")
waypoints = it.mapValues { p -> p.value.data.waypoint }
onWaypointChanged(it.values) onWaypointChanged(it.values)
drawOverlays() drawOverlays()
} }
@ -244,21 +249,88 @@ class MapFragment : ScreenFragment("Map Fragment"), Logging {
}.start() }.start()
} }
fun showMarkerLongPressDialog(id: Int) { private data class DialogBuilder(
debug("marker long pressed id=${id}") val builder: MaterialAlertDialogBuilder,
MaterialAlertDialogBuilder(requireContext()) val nameInput: EditText,
.setTitle("${getString(R.string.delete)}?") val descInput: EditText,
.setNeutralButton(R.string.cancel) { _, _ -> val lockedSwitch: SwitchMaterial,
debug("User canceled marker edit dialog") ) {
} val name get() = nameInput.text.toString().trim()
// .setNegativeButton(R.string.edit) { _, _ -> val description get() = descInput.text.toString().trim()
// debug("Negative button pressed") // TODO add Edit option }
// }
.setPositiveButton(getString(R.string.delete)) { _, _ -> private fun createEditDialog(context: Context, title: String): DialogBuilder {
debug("User deleted local waypoint $id") val builder = MaterialAlertDialogBuilder(context)
val layout = LayoutInflater.from(context).inflate(R.layout.dialog_add_waypoint, null)
val nameInput: EditText = layout.findViewById(R.id.waypointName)
val descInput: EditText= layout.findViewById(R.id.waypointDescription)
val lockedSwitch: SwitchMaterial = layout.findViewById(R.id.waypointLocked)
builder.setTitle(title)
builder.setView(layout)
return DialogBuilder(builder, nameInput, descInput, lockedSwitch)
}
private fun showDeleteMarkerDialog(id: Int) {
val waypoint = waypoints[id]
val builder = MaterialAlertDialogBuilder(requireContext())
builder.setTitle(R.string.waypoint_delete)
builder.setNeutralButton(R.string.cancel) { _, _ ->
debug("User canceled marker delete dialog")
}
builder.setNegativeButton(R.string.delete_for_me) { _, _ ->
debug("User deleted waypoint $id for me")
model.deleteWaypoint(id)
}
if (waypoint != null && waypoint.lockedTo in setOf(0, model.myNodeNum ?: 0))
builder.setPositiveButton(R.string.delete_for_everyone) { _, _ ->
debug("User deleted waypoint $id for everyone")
model.sendWaypoint(waypoint.copy { expire = 1 })
model.deleteWaypoint(id) model.deleteWaypoint(id)
} }
.show() val dialog = builder.show()
for (button in setOf(
AlertDialog.BUTTON_NEUTRAL,
AlertDialog.BUTTON_NEGATIVE,
AlertDialog.BUTTON_POSITIVE
)) with(dialog.getButton(button)) { textSize = 12F; isAllCaps = false }
}
private fun showEditMarkerDialog(waypoint: Waypoint) {
val dialog = createEditDialog(requireContext(), getString(R.string.waypoint_edit))
dialog.nameInput.setText(waypoint.name)
dialog.descInput.setText(waypoint.description)
dialog.lockedSwitch.isEnabled = false
dialog.lockedSwitch.isChecked = waypoint.lockedTo != 0
dialog.builder.setNeutralButton(R.string.cancel) { _, _ ->
debug("User canceled marker edit dialog")
}
dialog.builder.setNegativeButton(R.string.delete) { _, _ ->
debug("User clicked delete waypoint ${waypoint.id}")
showDeleteMarkerDialog(waypoint.id)
}
dialog.builder.setPositiveButton(getString(R.string.send)) { _, _ ->
debug("User edited waypoint ${waypoint.id}")
model.sendWaypoint(waypoint.copy {
name = dialog.name.ifEmpty { return@setPositiveButton }
description = dialog.description
expire = Int.MAX_VALUE // TODO add expire picker
icon = 0 // TODO add emoji picker
})
}
dialog.builder.show()
}
fun showMarkerLongPressDialog(id: Int) {
debug("marker long pressed id=${id}")
val waypoint = waypoints[id]
// edit only when unlocked or lockedTo myNodeNum
if (waypoint != null && waypoint.lockedTo in setOf(0, model.myNodeNum ?: 0))
showEditMarkerDialog(waypoint)
else
showDeleteMarkerDialog(id)
} }
private fun downloadJobAlert() { private fun downloadJobAlert() {
@ -444,12 +516,14 @@ class MapFragment : ScreenFragment("Map Fragment"), Logging {
lateinit var marker: MarkerWithLabel lateinit var marker: MarkerWithLabel
pt.data.waypoint?.let { pt.data.waypoint?.let {
val lock = if (it.lockedTo != 0) "\uD83D\uDD12" else "" val lock = if (it.lockedTo != 0) "\uD83D\uDD12" else ""
val time = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT)
.format(pt.received_time)
val label = it.name + " " + formatAgo((pt.received_time / 1000).toInt()) val label = it.name + " " + formatAgo((pt.received_time / 1000).toInt())
val emoji = String(Character.toChars(if (it.icon == 0) 128205 else it.icon)) val emoji = String(Character.toChars(if (it.icon == 0) 128205 else it.icon))
marker = MarkerWithLabel(map, label, emoji) marker = MarkerWithLabel(map, label, emoji)
marker.id = "${it.id}" marker.id = "${it.id}"
marker.title = "${it.name} (${getUsername(pt.data.from)}$lock)" marker.title = "${it.name} (${getUsername(pt.data.from)}$lock)"
marker.snippet = it.description marker.snippet = "[$time] " + it.description
marker.position = GeoPoint(it.latitudeI * 1e-7, it.longitudeI * 1e-7) marker.position = GeoPoint(it.latitudeI * 1e-7, it.longitudeI * 1e-7)
marker.setVisible(false) marker.setVisible(false)
} }
@ -457,7 +531,7 @@ class MapFragment : ScreenFragment("Map Fragment"), Logging {
} }
return wayPoint return wayPoint
} }
wayPoints = getCurrentWayPoints() waypointMarkers = getCurrentWayPoints()
} }
private fun onNodesChanged(nodes: Collection<NodeInfo>) { private fun onNodesChanged(nodes: Collection<NodeInfo>) {
@ -525,7 +599,7 @@ class MapFragment : ScreenFragment("Map Fragment"), Logging {
addCopyright() // Copyright is required for certain map sources addCopyright() // Copyright is required for certain map sources
createLatLongGrid(false) createLatLongGrid(false)
map.overlayManager.addAll(nodeLayer, nodePositions) map.overlayManager.addAll(nodeLayer, nodePositions)
map.overlayManager.addAll(nodeLayer, wayPoints) map.overlayManager.addAll(nodeLayer, waypointMarkers)
map.overlayManager.add(nodeLayer, MapEventsOverlay(object : MapEventsReceiver { map.overlayManager.add(nodeLayer, MapEventsOverlay(object : MapEventsReceiver {
override fun singleTapConfirmedHelper(p: GeoPoint): Boolean { override fun singleTapConfirmedHelper(p: GeoPoint): Boolean {
InfoWindow.closeAllInfoWindowsOn(map) InfoWindow.closeAllInfoWindowsOn(map)
@ -536,33 +610,24 @@ class MapFragment : ScreenFragment("Map Fragment"), Logging {
performHapticFeedback() performHapticFeedback()
if (!model.isConnected()) return true if (!model.isConnected()) return true
val layout = LayoutInflater.from(context) val dialog = createEditDialog(requireContext(), getString(R.string.waypoint_new))
.inflate(R.layout.dialog_add_waypoint, null) dialog.builder.setNeutralButton(R.string.cancel) { _, _ ->
debug("User canceled marker create dialog")
val nameInput: EditText = layout.findViewById(R.id.waypointName) }
val descriptionInput: EditText= layout.findViewById(R.id.waypointDescription) dialog.builder.setPositiveButton(getString(R.string.send)) { _, _ ->
val lockedInput: SwitchMaterial = layout.findViewById(R.id.waypointLocked) debug("User created waypoint")
model.sendWaypoint(waypoint {
MaterialAlertDialogBuilder(requireContext()) name = dialog.name.ifEmpty { return@setPositiveButton }
.setView(layout) description = dialog.description
.setNeutralButton(R.string.cancel) { _, _ -> id = model.generatePacketId() ?: return@setPositiveButton
debug("User canceled marker create dialog") latitudeI = (p.latitude * 1e7).toInt()
} longitudeI = (p.longitude * 1e7).toInt()
.setPositiveButton(getString(R.string.send)) { _, _ -> expire = Int.MAX_VALUE // TODO add expire picker
debug("User created waypoint") icon = 0 // TODO add emoji picker
model.sendWaypoint(waypoint { lockedTo = if (!dialog.lockedSwitch.isChecked) 0 else model.myNodeNum ?: 0
name = nameInput.text.toString().ifEmpty { return@setPositiveButton } })
description = descriptionInput.text.toString() }
id = model.generatePacketId() ?: return@setPositiveButton dialog.builder.show()
latitudeI = (p.latitude * 1e7).toInt()
longitudeI = (p.longitude * 1e7).toInt()
expire = Int.MAX_VALUE // TODO add expire picker
icon = 0 // TODO add emoji picker
lockedTo = if (!lockedInput.isChecked) 0
else model.myNodeInfo.value?.myNodeNum ?: 0
})
}
.show()
return true return true
} }
})) }))

Wyświetl plik

@ -111,6 +111,8 @@
<item quantity="other">Delete %s messages?</item> <item quantity="other">Delete %s messages?</item>
</plurals> </plurals>
<string name="delete">Delete</string> <string name="delete">Delete</string>
<string name="delete_for_everyone">Delete for everyone</string>
<string name="delete_for_me">Delete for me</string>
<string name="select_all">Select all</string> <string name="select_all">Select all</string>
<string name="modem_config_slow_long">Long Range / Slow</string> <string name="modem_config_slow_long">Long Range / Slow</string>
<string name="map_style_selection">Style Selection</string> <string name="map_style_selection">Style Selection</string>
@ -169,4 +171,7 @@
<string name="map_download_errors">Download complete with %s errors</string> <string name="map_download_errors">Download complete with %s errors</string>
<string name="map_cache_tiles">%s tiles</string> <string name="map_cache_tiles">%s tiles</string>
<string name="map_subDescription">bearing: %1$s° distance: %2$s</string> <string name="map_subDescription">bearing: %1$s° distance: %2$s</string>
<string name="waypoint_edit">Edit waypoint</string>
<string name="waypoint_delete">Delete waypoint?</string>
<string name="waypoint_new">New waypoint</string>
</resources> </resources>