From 48a6d1fff1c5ea9e8d5cab6a31c73e97f43e2c4e Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Wed, 13 May 2020 23:53:08 -0700 Subject: [PATCH] Get the boundary setting activity working. --- .../fencelessgrazing/model/CollarPos.kt | 7 +- .../authenticated/AuthenticatedPostRequest.kt | 30 +++++ .../authenticated/SetBoundaryRequest.kt | 16 +++ .../ui/activities/BoundaryEditorActivity.kt | 127 ++++++++++++++++++ 4 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/danilafe/fencelessgrazing/requests/authenticated/AuthenticatedPostRequest.kt create mode 100644 app/src/main/java/com/danilafe/fencelessgrazing/requests/authenticated/SetBoundaryRequest.kt create mode 100644 app/src/main/java/com/danilafe/fencelessgrazing/ui/activities/BoundaryEditorActivity.kt diff --git a/app/src/main/java/com/danilafe/fencelessgrazing/model/CollarPos.kt b/app/src/main/java/com/danilafe/fencelessgrazing/model/CollarPos.kt index 736c594..d0eb965 100644 --- a/app/src/main/java/com/danilafe/fencelessgrazing/model/CollarPos.kt +++ b/app/src/main/java/com/danilafe/fencelessgrazing/model/CollarPos.kt @@ -1,9 +1,14 @@ package com.danilafe.fencelessgrazing.model +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize + /** * GPS coordinate returned by many of the project's API endpoints. * * @param longitude the longitude of the GPS point. * @param latitude the latitude of the GPS point. */ -data class CollarPos(val longitude: String, val latitude: String) +@Parcelize +data class CollarPos(val longitude: String, val latitude: String) : Parcelable + diff --git a/app/src/main/java/com/danilafe/fencelessgrazing/requests/authenticated/AuthenticatedPostRequest.kt b/app/src/main/java/com/danilafe/fencelessgrazing/requests/authenticated/AuthenticatedPostRequest.kt new file mode 100644 index 0000000..6a81def --- /dev/null +++ b/app/src/main/java/com/danilafe/fencelessgrazing/requests/authenticated/AuthenticatedPostRequest.kt @@ -0,0 +1,30 @@ +package com.danilafe.fencelessgrazing.requests.authenticated + +import com.android.volley.Response +import com.android.volley.toolbox.JsonObjectRequest +import com.android.volley.toolbox.StringRequest +import com.google.gson.Gson +import com.google.gson.JsonObject +import org.json.JSONObject + +open class AuthenticatedPostRequest( + private val value: T, + baseUrl: String, + apiEndpoint: String, + private val token: String, + listener: Response.Listener, + error: Response.ErrorListener +) : StringRequest(Method.POST, "${baseUrl}${apiEndpoint}", + listener, error +) { + + override fun getBody(): ByteArray = Gson().toJson(value).toByteArray() + + override fun getBodyContentType(): String = "application/json" + + override fun getHeaders(): MutableMap { + val newMap = HashMap(super.getHeaders()) + newMap["Authorization"] = "Bearer $token" + return newMap + } +} \ No newline at end of file diff --git a/app/src/main/java/com/danilafe/fencelessgrazing/requests/authenticated/SetBoundaryRequest.kt b/app/src/main/java/com/danilafe/fencelessgrazing/requests/authenticated/SetBoundaryRequest.kt new file mode 100644 index 0000000..301820d --- /dev/null +++ b/app/src/main/java/com/danilafe/fencelessgrazing/requests/authenticated/SetBoundaryRequest.kt @@ -0,0 +1,16 @@ +package com.danilafe.fencelessgrazing.requests.authenticated + +import com.android.volley.Response +import com.danilafe.fencelessgrazing.model.CollarPos + +class SetBoundaryRequest( + baseUrl: String, + token: String, + identifier: Int, + coordinates: List, + listener: Response.Listener, + error: Response.ErrorListener +) : AuthenticatedPostRequest>( + coordinates, baseUrl, "/collars/${identifier}/boundary/set", + token, listener, error +) \ No newline at end of file diff --git a/app/src/main/java/com/danilafe/fencelessgrazing/ui/activities/BoundaryEditorActivity.kt b/app/src/main/java/com/danilafe/fencelessgrazing/ui/activities/BoundaryEditorActivity.kt new file mode 100644 index 0000000..23f548f --- /dev/null +++ b/app/src/main/java/com/danilafe/fencelessgrazing/ui/activities/BoundaryEditorActivity.kt @@ -0,0 +1,127 @@ +package com.danilafe.fencelessgrazing.ui.activities + +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.os.PersistableBundle +import android.view.View +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import com.android.volley.RequestQueue +import com.android.volley.Response +import com.android.volley.toolbox.Volley +import com.danilafe.fencelessgrazing.R +import com.danilafe.fencelessgrazing.model.CollarPos +import com.danilafe.fencelessgrazing.requests.authenticated.SetBoundaryRequest +import com.danilafe.fencelessgrazing.ui.components.GrazingPolygon +import org.osmdroid.util.GeoPoint +import org.osmdroid.views.MapView +import org.osmdroid.views.overlay.Marker +import org.osmdroid.views.overlay.Polygon + +class BoundaryEditorActivity : AppCompatActivity(), + Marker.OnMarkerDragListener, + Marker.OnMarkerClickListener { + + private lateinit var map: MapView + private val markers: MutableList = mutableListOf() + private lateinit var polygon: GrazingPolygon + private lateinit var center: GeoPoint + private var identifier: Int = -1 + private lateinit var queue: RequestQueue + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_boundary_editor) + + val requestedCenter = intent.getParcelableExtra("center")!! + center = GeoPoint( + requestedCenter.longitude.toDouble(), + requestedCenter.latitude.toDouble()) + identifier = intent.getIntExtra("identifier", -1) + queue = Volley.newRequestQueue(applicationContext) + + map = findViewById(R.id.editorMap) + polygon = GrazingPolygon(map) + map.overlays.add(polygon) + map.controller.setZoom(9.5) + map.controller.setCenter(center) + } + + private fun removeMarker(m: Marker) { + markers.remove(m) + map.overlays.remove(m) + updatePolygon() + map.invalidate() + } + + fun sendBoundary(v: View) { + val request = SetBoundaryRequest( + getString(R.string.apiUrl), + getSharedPreferences("FencelessGrazing",0).getString("token",null)!!, + identifier, markers.map { + CollarPos(it.position.longitude.toString(), it.position.latitude.toString()) + }, + Response.Listener { + finish() + }, + Response.ErrorListener { + Toast.makeText( + this, + "Failed to update grazing boundaries", + Toast.LENGTH_SHORT).show() + } + ) + queue.add(request) + } + + fun addMarker(v : View) { + if(markers.size >= 10) { + Toast.makeText( + this, + "Cannot add more than ten vertices to grazing boundary", + Toast.LENGTH_SHORT).show() + return + } + + val newMarker = Marker(map) + newMarker.isDraggable = true + newMarker.setOnMarkerClickListener(this) + newMarker.setOnMarkerDragListener(this) + newMarker.position = center + markers.add(newMarker) + map.overlays.add(newMarker) + updatePolygon() + map.invalidate() + } + + private fun updatePolygon() { + polygon.points.clear() + if(markers.isEmpty()) return + val newPoints = markers.map { it.position } + polygon.points = newPoints + polygon.points.add(newPoints.first()) + } + + override fun onMarkerDragEnd(marker: Marker?) { + updatePolygon() + map.invalidate() + } + + override fun onMarkerDragStart(marker: Marker?) { + + } + + override fun onMarkerDrag(marker: Marker?) { + + } + + override fun onMarkerClick(m: Marker, mv: MapView): Boolean { + AlertDialog.Builder(this) + .setTitle("Delete Marker") + .setMessage("Do you want to delete this marker?") + .setPositiveButton("Delete") { _, _ -> removeMarker(m) } + .setNegativeButton("Cancel") { d, _ -> d.dismiss() } + .show() + return true + } +}