From 0f4f4ab8a90ee7104f4e1ad9c1abb236472428a6 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Tue, 12 May 2020 17:30:23 -0700 Subject: [PATCH] Start working on retrieving collar details. --- .../fencelessgrazing/CollarDetailActivity.kt | 105 ++++++++++++++++-- .../fencelessgrazing/CollarViewHolder.kt | 4 +- .../fencelessgrazing/model/CollarDetails.kt | 3 + .../requests/CollarDetailRequest.kt | 28 +++++ .../requests/CollarHistoryRequest.kt | 27 +++++ .../res/layout/activity_collar_detail.xml | 19 ++++ .../main/res/layout/collar_summary_layout.xml | 4 +- 7 files changed, 174 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/com/danilafe/fencelessgrazing/model/CollarDetails.kt create mode 100644 app/src/main/java/com/danilafe/fencelessgrazing/requests/CollarDetailRequest.kt create mode 100644 app/src/main/java/com/danilafe/fencelessgrazing/requests/CollarHistoryRequest.kt diff --git a/app/src/main/java/com/danilafe/fencelessgrazing/CollarDetailActivity.kt b/app/src/main/java/com/danilafe/fencelessgrazing/CollarDetailActivity.kt index 46873fd..95f98b8 100644 --- a/app/src/main/java/com/danilafe/fencelessgrazing/CollarDetailActivity.kt +++ b/app/src/main/java/com/danilafe/fencelessgrazing/CollarDetailActivity.kt @@ -3,18 +3,37 @@ package com.danilafe.fencelessgrazing import android.graphics.Color import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import android.widget.TextView +import android.widget.Toast +import com.android.volley.Request +import com.android.volley.RequestQueue +import com.android.volley.Response +import com.android.volley.toolbox.Volley import com.danilafe.fencelessgrazing.model.CollarPos +import com.danilafe.fencelessgrazing.model.CollarSummary import com.danilafe.fencelessgrazing.model.Polygon +import com.danilafe.fencelessgrazing.requests.CollarDetailRequest +import com.danilafe.fencelessgrazing.requests.CollarHistoryRequest import org.osmdroid.util.GeoPoint import org.osmdroid.views.MapView +import org.osmdroid.views.overlay.Marker +import java.sql.Time +import java.util.* +import kotlin.concurrent.timerTask class CollarDetailActivity : AppCompatActivity() { + private lateinit var collarName: TextView + private lateinit var collarPos: TextView + private lateinit var token: String + private lateinit var queue: RequestQueue private var collarId: Int = -1 private lateinit var map: MapView + private var refreshTimer = Timer() + private lateinit var mapPolygon: org.osmdroid.views.overlay.Polygon private var boundingBox: Polygon? = null set(newValue) { @@ -23,44 +42,108 @@ class CollarDetailActivity : AppCompatActivity() { field = newValue } + private lateinit var mapMarker: Marker + private var dataPoints: List = listOf() + set(newValue) { + if(newValue.isNotEmpty()) updateAnimalHistory(newValue) + else clearAnimalHistory() + field = newValue + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_collar_detail) + collarName = findViewById(R.id.collarName) + collarPos = findViewById(R.id.collarPos) + token = intent.getStringExtra("token")!! collarId = intent.getIntExtra("identifier", -1) + queue = Volley.newRequestQueue(this) map = findViewById(R.id.detailMap) mapPolygon = org.osmdroid.views.overlay.Polygon(map) + mapMarker = Marker(map) configureMapPolygon() + + refreshTimer.schedule(timerTask { triggerRefresh() }, 0L, 5000L) + + boundingBox = Polygon( + listOf( + CollarPos("20", "20"), + CollarPos("20", "21"), + CollarPos("21", "21"), + CollarPos("21", "20") + ) + ) + } + + override fun onPause() { + super.onPause() + map.onPause() + refreshTimer.cancel() + } + + override fun onResume() { + super.onResume() + map.onResume() + refreshTimer = Timer() + refreshTimer.schedule(timerTask { triggerRefresh() }, 0L, 5000L) + } + + private fun triggerRefresh() { + val historyRequest = CollarHistoryRequest(getString(R.string.apiUrl), collarId, token, + Response.Listener { + dataPoints = it.map { p -> GeoPoint(p.longitude.toDouble(), p.latitude.toDouble()) } + }, + Response.ErrorListener { + Toast.makeText(this, "Failed to retrieve history of collar", Toast.LENGTH_SHORT).show() + } + ) + val detailRequest = CollarDetailRequest(getString(R.string.apiUrl), collarId, token, + Response.Listener { + collarName.text = it.name + }, + Response.ErrorListener { + Toast.makeText(this, "Failed to retrieve details of collar", Toast.LENGTH_SHORT).show() + } + ) + queue.add(historyRequest) + queue.add(detailRequest) } private fun configureMapPolygon() { - mapPolygon.fillPaint.color = Color.CYAN + mapPolygon.fillPaint.color = Color.parseColor("#32a852") mapPolygon.fillPaint.alpha = 127 + mapPolygon.outlinePaint.color = Color.parseColor("#227539") mapPolygon.outlinePaint.strokeWidth = 0.0f mapPolygon.title = "Valid Grazing Area" } + private fun updateAnimalHistory(points : List) { + val currentPoint = points.first() + mapMarker.position = currentPoint + collarPos.text = getString(R.string.collarSummaryLocation, currentPoint.longitude, currentPoint.latitude) + if(!map.overlays.contains(mapMarker)) map.overlays.add(mapMarker) + map.invalidate() + } + + private fun clearAnimalHistory() { + map.overlays.remove(mapMarker) + map.invalidate() + } + private fun updateBoundingBox(oldValue: Polygon?, polygon: Polygon) { if(oldValue == null) map.overlays.add(mapPolygon) val points = polygon.dataPoints.map { GeoPoint(it.longitude.toDouble(), it.latitude.toDouble()) } val polygonPoints = points.toMutableList() polygonPoints.add(polygonPoints[0]) mapPolygon.points = polygonPoints + map.invalidate() } private fun clearBoundingBox() { map.overlays.remove(mapPolygon) - } - - override fun onPause() { - super.onPause() - map.onPause() - } - - override fun onResume() { - super.onResume() - map.onResume() + map.invalidate() } } diff --git a/app/src/main/java/com/danilafe/fencelessgrazing/CollarViewHolder.kt b/app/src/main/java/com/danilafe/fencelessgrazing/CollarViewHolder.kt index 8c67032..a1bf77d 100644 --- a/app/src/main/java/com/danilafe/fencelessgrazing/CollarViewHolder.kt +++ b/app/src/main/java/com/danilafe/fencelessgrazing/CollarViewHolder.kt @@ -11,8 +11,8 @@ class CollarViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun bindData(summary: CollarSummary, collarClickListener: CollarClickListener) { nameView.text = summary.name - // TODO figure out how to get getString here. - positionView.text = "Currently at ${summary.pos.longitude}, ${summary.pos.latitude}" + positionView.text = nameView.resources.getString(R.string.collarSummaryLocation, + summary.pos.longitude.toDouble(), summary.pos.latitude.toDouble()) itemView.setOnClickListener { collarClickListener.onCollarClick(summary) } diff --git a/app/src/main/java/com/danilafe/fencelessgrazing/model/CollarDetails.kt b/app/src/main/java/com/danilafe/fencelessgrazing/model/CollarDetails.kt new file mode 100644 index 0000000..af925fa --- /dev/null +++ b/app/src/main/java/com/danilafe/fencelessgrazing/model/CollarDetails.kt @@ -0,0 +1,3 @@ +package com.danilafe.fencelessgrazing.model + +data class CollarDetails(val id: Int, val name: String) \ No newline at end of file diff --git a/app/src/main/java/com/danilafe/fencelessgrazing/requests/CollarDetailRequest.kt b/app/src/main/java/com/danilafe/fencelessgrazing/requests/CollarDetailRequest.kt new file mode 100644 index 0000000..7e5b79f --- /dev/null +++ b/app/src/main/java/com/danilafe/fencelessgrazing/requests/CollarDetailRequest.kt @@ -0,0 +1,28 @@ +package com.danilafe.fencelessgrazing.requests + +import com.android.volley.Response +import com.android.volley.toolbox.StringRequest +import com.danilafe.fencelessgrazing.model.CollarDetails +import com.danilafe.fencelessgrazing.model.CollarPos +import com.danilafe.fencelessgrazing.model.CollarSummary +import com.google.gson.reflect.TypeToken + +class CollarDetailRequest( + baseUrl: String, + collarId: Int, + private val token : String, + listener: Response.Listener, + error: Response.ErrorListener +) : StringRequest( + Method.GET, "${baseUrl}/collars/$collarId/details", + GsonListener( + object : TypeToken() {}.type, + listener + ), error +) { + + override fun getHeaders(): MutableMap { + return mutableMapOf("Authorization" to "Bearer $token") + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/danilafe/fencelessgrazing/requests/CollarHistoryRequest.kt b/app/src/main/java/com/danilafe/fencelessgrazing/requests/CollarHistoryRequest.kt new file mode 100644 index 0000000..7dfb5fa --- /dev/null +++ b/app/src/main/java/com/danilafe/fencelessgrazing/requests/CollarHistoryRequest.kt @@ -0,0 +1,27 @@ +package com.danilafe.fencelessgrazing.requests + +import com.android.volley.Response +import com.android.volley.toolbox.StringRequest +import com.danilafe.fencelessgrazing.model.CollarPos +import com.danilafe.fencelessgrazing.model.CollarSummary +import com.google.gson.reflect.TypeToken + +class CollarHistoryRequest( + baseUrl: String, + collarId: Int, + private val token : String, + listener: Response.Listener>, + error: Response.ErrorListener +) : StringRequest( + Method.GET, "${baseUrl}/collars/$collarId/history", + GsonListener( + object : TypeToken>() {}.type, + listener + ), error +) { + + override fun getHeaders(): MutableMap { + return mutableMapOf("Authorization" to "Bearer $token") + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_collar_detail.xml b/app/src/main/res/layout/activity_collar_detail.xml index 54b15f9..28101a1 100644 --- a/app/src/main/res/layout/activity_collar_detail.xml +++ b/app/src/main/res/layout/activity_collar_detail.xml @@ -14,4 +14,23 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/collar_summary_layout.xml b/app/src/main/res/layout/collar_summary_layout.xml index 6119eba..d2ea881 100644 --- a/app/src/main/res/layout/collar_summary_layout.xml +++ b/app/src/main/res/layout/collar_summary_layout.xml @@ -12,7 +12,6 @@ android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginTop="4dp" - android:text="TextView" android:textAppearance="@style/TextAppearance.AppCompat.Medium" /> + android:layout_marginBottom="8dp" />