Start working on retrieving collar details.

This commit is contained in:
Danila Fedorin 2020-05-12 17:30:23 -07:00
parent 246d43be80
commit 0f4f4ab8a9
7 changed files with 174 additions and 16 deletions

View File

@ -3,18 +3,37 @@ package com.danilafe.fencelessgrazing
import android.graphics.Color import android.graphics.Color
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle 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.CollarPos
import com.danilafe.fencelessgrazing.model.CollarSummary
import com.danilafe.fencelessgrazing.model.Polygon 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.util.GeoPoint
import org.osmdroid.views.MapView 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() { class CollarDetailActivity : AppCompatActivity() {
private lateinit var collarName: TextView
private lateinit var collarPos: TextView
private lateinit var token: String private lateinit var token: String
private lateinit var queue: RequestQueue
private var collarId: Int = -1 private var collarId: Int = -1
private lateinit var map: MapView private lateinit var map: MapView
private var refreshTimer = Timer()
private lateinit var mapPolygon: org.osmdroid.views.overlay.Polygon private lateinit var mapPolygon: org.osmdroid.views.overlay.Polygon
private var boundingBox: Polygon? = null private var boundingBox: Polygon? = null
set(newValue) { set(newValue) {
@ -23,44 +42,108 @@ class CollarDetailActivity : AppCompatActivity() {
field = newValue field = newValue
} }
private lateinit var mapMarker: Marker
private var dataPoints: List<GeoPoint> = listOf()
set(newValue) {
if(newValue.isNotEmpty()) updateAnimalHistory(newValue)
else clearAnimalHistory()
field = newValue
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_collar_detail) setContentView(R.layout.activity_collar_detail)
collarName = findViewById(R.id.collarName)
collarPos = findViewById(R.id.collarPos)
token = intent.getStringExtra("token")!! token = intent.getStringExtra("token")!!
collarId = intent.getIntExtra("identifier", -1) collarId = intent.getIntExtra("identifier", -1)
queue = Volley.newRequestQueue(this)
map = findViewById(R.id.detailMap) map = findViewById(R.id.detailMap)
mapPolygon = org.osmdroid.views.overlay.Polygon(map) mapPolygon = org.osmdroid.views.overlay.Polygon(map)
mapMarker = Marker(map)
configureMapPolygon() 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() { private fun configureMapPolygon() {
mapPolygon.fillPaint.color = Color.CYAN mapPolygon.fillPaint.color = Color.parseColor("#32a852")
mapPolygon.fillPaint.alpha = 127 mapPolygon.fillPaint.alpha = 127
mapPolygon.outlinePaint.color = Color.parseColor("#227539")
mapPolygon.outlinePaint.strokeWidth = 0.0f mapPolygon.outlinePaint.strokeWidth = 0.0f
mapPolygon.title = "Valid Grazing Area" mapPolygon.title = "Valid Grazing Area"
} }
private fun updateAnimalHistory(points : List<GeoPoint>) {
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) { private fun updateBoundingBox(oldValue: Polygon?, polygon: Polygon) {
if(oldValue == null) map.overlays.add(mapPolygon) if(oldValue == null) map.overlays.add(mapPolygon)
val points = polygon.dataPoints.map { GeoPoint(it.longitude.toDouble(), it.latitude.toDouble()) } val points = polygon.dataPoints.map { GeoPoint(it.longitude.toDouble(), it.latitude.toDouble()) }
val polygonPoints = points.toMutableList() val polygonPoints = points.toMutableList()
polygonPoints.add(polygonPoints[0]) polygonPoints.add(polygonPoints[0])
mapPolygon.points = polygonPoints mapPolygon.points = polygonPoints
map.invalidate()
} }
private fun clearBoundingBox() { private fun clearBoundingBox() {
map.overlays.remove(mapPolygon) map.overlays.remove(mapPolygon)
} map.invalidate()
override fun onPause() {
super.onPause()
map.onPause()
}
override fun onResume() {
super.onResume()
map.onResume()
} }
} }

View File

@ -11,8 +11,8 @@ class CollarViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindData(summary: CollarSummary, collarClickListener: CollarClickListener) { fun bindData(summary: CollarSummary, collarClickListener: CollarClickListener) {
nameView.text = summary.name nameView.text = summary.name
// TODO figure out how to get getString here. positionView.text = nameView.resources.getString(R.string.collarSummaryLocation,
positionView.text = "Currently at ${summary.pos.longitude}, ${summary.pos.latitude}" summary.pos.longitude.toDouble(), summary.pos.latitude.toDouble())
itemView.setOnClickListener { itemView.setOnClickListener {
collarClickListener.onCollarClick(summary) collarClickListener.onCollarClick(summary)
} }

View File

@ -0,0 +1,3 @@
package com.danilafe.fencelessgrazing.model
data class CollarDetails(val id: Int, val name: String)

View File

@ -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<CollarDetails>,
error: Response.ErrorListener
) : StringRequest(
Method.GET, "${baseUrl}/collars/$collarId/details",
GsonListener(
object : TypeToken<CollarDetails>() {}.type,
listener
), error
) {
override fun getHeaders(): MutableMap<String, String> {
return mutableMapOf("Authorization" to "Bearer $token")
}
}

View File

@ -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<List<CollarPos>>,
error: Response.ErrorListener
) : StringRequest(
Method.GET, "${baseUrl}/collars/$collarId/history",
GsonListener(
object : TypeToken<List<CollarPos>>() {}.type,
listener
), error
) {
override fun getHeaders(): MutableMap<String, String> {
return mutableMapOf("Authorization" to "Bearer $token")
}
}

View File

@ -14,4 +14,23 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/collarName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:textAppearance="@style/TextAppearance.AppCompat.Display1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/detailMap" />
<TextView
android:id="@+id/collarPos"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/collarName" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -12,7 +12,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:text="TextView"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" /> android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<TextView <TextView
@ -20,6 +19,5 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp" />
android:text="TextView" />
</LinearLayout> </LinearLayout>