feat: edit waypoints

master
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
fun getDataPacketById(requestId: Int): DataPacket? {
return getDataPackets().firstOrNull { it.id == requestId }
return getDataPackets().lastOrNull { it.id == requestId }
}
@Transaction

Wyświetl plik

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

Wyświetl plik

@ -20,9 +20,11 @@ import androidx.core.content.ContextCompat
import androidx.fragment.app.activityViewModels
import com.geeksville.mesh.BuildConfig
import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.MeshProtos.Waypoint
import com.geeksville.mesh.NodeInfo
import com.geeksville.mesh.R
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.copy
import com.geeksville.mesh.database.entity.Packet
import com.geeksville.mesh.databinding.MapViewBinding
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.infowindow.InfoWindow
import java.io.File
import java.text.DateFormat
import kotlin.math.log2
@ -76,7 +79,8 @@ class MapFragment : ScreenFragment("Map Fragment"), Logging {
private val prefsName = "org.geeksville.osm.prefs"
private val mapStyleId = "map_style_id"
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
// Distance of bottom corner to top corner of bounding box
@ -133,6 +137,7 @@ class MapFragment : ScreenFragment("Map Fragment"), Logging {
}
model.waypoints.observe(viewLifecycleOwner) {
debug("New waypoints received: ${it.size}")
waypoints = it.mapValues { p -> p.value.data.waypoint }
onWaypointChanged(it.values)
drawOverlays()
}
@ -244,21 +249,88 @@ class MapFragment : ScreenFragment("Map Fragment"), Logging {
}.start()
}
fun showMarkerLongPressDialog(id: Int) {
debug("marker long pressed id=${id}")
MaterialAlertDialogBuilder(requireContext())
.setTitle("${getString(R.string.delete)}?")
.setNeutralButton(R.string.cancel) { _, _ ->
debug("User canceled marker edit dialog")
}
// .setNegativeButton(R.string.edit) { _, _ ->
// debug("Negative button pressed") // TODO add Edit option
// }
.setPositiveButton(getString(R.string.delete)) { _, _ ->
debug("User deleted local waypoint $id")
private data class DialogBuilder(
val builder: MaterialAlertDialogBuilder,
val nameInput: EditText,
val descInput: EditText,
val lockedSwitch: SwitchMaterial,
) {
val name get() = nameInput.text.toString().trim()
val description get() = descInput.text.toString().trim()
}
private fun createEditDialog(context: Context, title: String): DialogBuilder {
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)
}
.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() {
@ -444,12 +516,14 @@ class MapFragment : ScreenFragment("Map Fragment"), Logging {
lateinit var marker: MarkerWithLabel
pt.data.waypoint?.let {
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 emoji = String(Character.toChars(if (it.icon == 0) 128205 else it.icon))
marker = MarkerWithLabel(map, label, emoji)
marker.id = "${it.id}"
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.setVisible(false)
}
@ -457,7 +531,7 @@ class MapFragment : ScreenFragment("Map Fragment"), Logging {
}
return wayPoint
}
wayPoints = getCurrentWayPoints()
waypointMarkers = getCurrentWayPoints()
}
private fun onNodesChanged(nodes: Collection<NodeInfo>) {
@ -525,7 +599,7 @@ class MapFragment : ScreenFragment("Map Fragment"), Logging {
addCopyright() // Copyright is required for certain map sources
createLatLongGrid(false)
map.overlayManager.addAll(nodeLayer, nodePositions)
map.overlayManager.addAll(nodeLayer, wayPoints)
map.overlayManager.addAll(nodeLayer, waypointMarkers)
map.overlayManager.add(nodeLayer, MapEventsOverlay(object : MapEventsReceiver {
override fun singleTapConfirmedHelper(p: GeoPoint): Boolean {
InfoWindow.closeAllInfoWindowsOn(map)
@ -536,33 +610,24 @@ class MapFragment : ScreenFragment("Map Fragment"), Logging {
performHapticFeedback()
if (!model.isConnected()) return true
val layout = LayoutInflater.from(context)
.inflate(R.layout.dialog_add_waypoint, null)
val nameInput: EditText = layout.findViewById(R.id.waypointName)
val descriptionInput: EditText= layout.findViewById(R.id.waypointDescription)
val lockedInput: SwitchMaterial = layout.findViewById(R.id.waypointLocked)
MaterialAlertDialogBuilder(requireContext())
.setView(layout)
.setNeutralButton(R.string.cancel) { _, _ ->
debug("User canceled marker create dialog")
}
.setPositiveButton(getString(R.string.send)) { _, _ ->
debug("User created waypoint")
model.sendWaypoint(waypoint {
name = nameInput.text.toString().ifEmpty { return@setPositiveButton }
description = descriptionInput.text.toString()
id = model.generatePacketId() ?: return@setPositiveButton
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()
val dialog = createEditDialog(requireContext(), getString(R.string.waypoint_new))
dialog.builder.setNeutralButton(R.string.cancel) { _, _ ->
debug("User canceled marker create dialog")
}
dialog.builder.setPositiveButton(getString(R.string.send)) { _, _ ->
debug("User created waypoint")
model.sendWaypoint(waypoint {
name = dialog.name.ifEmpty { return@setPositiveButton }
description = dialog.description
id = model.generatePacketId() ?: return@setPositiveButton
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 (!dialog.lockedSwitch.isChecked) 0 else model.myNodeNum ?: 0
})
}
dialog.builder.show()
return true
}
}))

Wyświetl plik

@ -111,6 +111,8 @@
<item quantity="other">Delete %s messages?</item>
</plurals>
<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="modem_config_slow_long">Long Range / Slow</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_cache_tiles">%s tiles</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>