Document activities and fragments.
This commit is contained in:
parent
b48317c0c0
commit
9ab9377201
|
@ -37,6 +37,10 @@ android {
|
||||||
dokka {
|
dokka {
|
||||||
outputFormat = 'html'
|
outputFormat = 'html'
|
||||||
outputDirectory = "$buildDir/dokka"
|
outputDirectory = "$buildDir/dokka"
|
||||||
|
|
||||||
|
configuration {
|
||||||
|
includeNonPublic = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
|
|
@ -8,7 +8,6 @@ import android.widget.Toast
|
||||||
import com.android.volley.RequestQueue
|
import com.android.volley.RequestQueue
|
||||||
import com.android.volley.Response
|
import com.android.volley.Response
|
||||||
import com.android.volley.toolbox.Volley
|
import com.android.volley.toolbox.Volley
|
||||||
import com.danilafe.fencelessgrazing.model.CollarPos
|
|
||||||
import com.danilafe.fencelessgrazing.model.Polygon
|
import com.danilafe.fencelessgrazing.model.Polygon
|
||||||
import com.danilafe.fencelessgrazing.requests.CollarDetailRequest
|
import com.danilafe.fencelessgrazing.requests.CollarDetailRequest
|
||||||
import com.danilafe.fencelessgrazing.requests.CollarHistoryRequest
|
import com.danilafe.fencelessgrazing.requests.CollarHistoryRequest
|
||||||
|
@ -19,22 +18,76 @@ import org.osmdroid.views.overlay.Polyline
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.concurrent.timerTask
|
import kotlin.concurrent.timerTask
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The activity that handles the display of a single collar, including
|
||||||
|
* the map with the collar's location, the collar's name, and how many
|
||||||
|
* times the collar left the prescribed grazing area in the last day. This
|
||||||
|
* information is gathered from repeated [history][CollarHistoryRequest] and
|
||||||
|
* [details][CollarDetailRequest] requests.
|
||||||
|
*/
|
||||||
class CollarDetailActivity : AppCompatActivity() {
|
class CollarDetailActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [TextView] holding the collar's current name.
|
||||||
|
*/
|
||||||
private lateinit var collarName: TextView
|
private lateinit var collarName: TextView
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [TextView] holding the collar's current position.
|
||||||
|
*/
|
||||||
private lateinit var collarPos: TextView
|
private lateinit var collarPos: TextView
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [TextView] holding the number of times the collar left the grazing area.
|
||||||
|
*/
|
||||||
private lateinit var collarStimulus: TextView
|
private lateinit var collarStimulus: TextView
|
||||||
|
|
||||||
|
/** Cached value of the API token retrieved from shared preferences.
|
||||||
|
*/
|
||||||
private lateinit var token: String
|
private lateinit var token: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Volley queue used for sending requests to the API.
|
||||||
|
*/
|
||||||
private lateinit var queue: RequestQueue
|
private lateinit var queue: RequestQueue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The identifier of the collar whose details are being viewed. This is
|
||||||
|
* provided by the activity's [Intent][android.content.Intent].
|
||||||
|
*/
|
||||||
private var collarId: Int = -1
|
private var collarId: Int = -1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the map has been centered on the location of the collar.
|
||||||
|
* Rather than have the map constantly jump around following the collar as it moves, we only
|
||||||
|
* move the map's center when this variable is `false`.
|
||||||
|
*/
|
||||||
private var centerSet: Boolean = false
|
private var centerSet: Boolean = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The map view that actually handles the display of the OpenStreetMaps tiles and markers.
|
||||||
|
*/
|
||||||
private lateinit var map: MapView
|
private lateinit var map: MapView
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timer used for scheduling repeated API calls. The timer is started
|
||||||
|
* in [onResume], and stopped in [onPause].
|
||||||
|
*/
|
||||||
private var refreshTimer = Timer()
|
private var refreshTimer = Timer()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The OpenStreetMaps polygon to be drawn on the map. This polygon is used
|
||||||
|
* to display the valid grazing area for the collar; its vertices
|
||||||
|
* are updated whenever the [boundingBox] property is changed.
|
||||||
|
*/
|
||||||
private lateinit var mapPolygon: org.osmdroid.views.overlay.Polygon
|
private lateinit var mapPolygon: org.osmdroid.views.overlay.Polygon
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [Polygon] that represents the collar's valid grazing area.
|
||||||
|
* Setting this property will also update the [mapPolygon], which
|
||||||
|
* is the visual representation of the grazing area on the map.
|
||||||
|
* Additionally, setting this property will also update the [map].
|
||||||
|
*/
|
||||||
private var boundingBox: Polygon? = null
|
private var boundingBox: Polygon? = null
|
||||||
set(newValue) {
|
set(newValue) {
|
||||||
if(newValue != null) updateBoundingBox(field, newValue)
|
if(newValue != null) updateBoundingBox(field, newValue)
|
||||||
|
@ -42,8 +95,24 @@ class CollarDetailActivity : AppCompatActivity() {
|
||||||
field = newValue
|
field = newValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The OpenStreetMaps marker used to display the collar's current location
|
||||||
|
* on the map. This marker is updated whenever the [dataPoints] property is set.
|
||||||
|
*/
|
||||||
private lateinit var mapMarker: Marker
|
private lateinit var mapMarker: Marker
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The OpenStreetMaps line used to display the path the collar has taken thus fur.
|
||||||
|
* This line is updated whenever the [dataPoints] property is set.
|
||||||
|
*/
|
||||||
private lateinit var mapPolyline: Polyline
|
private lateinit var mapPolyline: Polyline
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The full history of the collar's location. Setting this property
|
||||||
|
* updates the [mapMarker] and [mapPolyline] properties, which
|
||||||
|
* are used to display the collar and its location history, respectively, on the map.
|
||||||
|
* Additionally, setting this property will also update the [map].
|
||||||
|
*/
|
||||||
private var dataPoints: List<GeoPoint> = listOf()
|
private var dataPoints: List<GeoPoint> = listOf()
|
||||||
set(newValue) {
|
set(newValue) {
|
||||||
if(newValue.isNotEmpty()) updateAnimalHistory(newValue)
|
if(newValue.isNotEmpty()) updateAnimalHistory(newValue)
|
||||||
|
@ -55,9 +124,7 @@ class CollarDetailActivity : AppCompatActivity() {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_collar_detail)
|
setContentView(R.layout.activity_collar_detail)
|
||||||
|
|
||||||
collarName = findViewById(R.id.collarName)
|
findViews()
|
||||||
collarPos = findViewById(R.id.collarPos)
|
|
||||||
collarStimulus = findViewById(R.id.collarStimulus)
|
|
||||||
|
|
||||||
token = getSharedPreferences("FencelessGrazing", 0).getString("token", null)!!
|
token = getSharedPreferences("FencelessGrazing", 0).getString("token", null)!!
|
||||||
collarId = intent.getIntExtra("identifier", -1)
|
collarId = intent.getIntExtra("identifier", -1)
|
||||||
|
@ -69,15 +136,6 @@ class CollarDetailActivity : AppCompatActivity() {
|
||||||
mapMarker = Marker(map)
|
mapMarker = Marker(map)
|
||||||
mapPolyline = Polyline(map)
|
mapPolyline = Polyline(map)
|
||||||
configureMapPolygon()
|
configureMapPolygon()
|
||||||
|
|
||||||
boundingBox = Polygon(
|
|
||||||
listOf(
|
|
||||||
CollarPos("20", "20"),
|
|
||||||
CollarPos("20", "21"),
|
|
||||||
CollarPos("21", "21"),
|
|
||||||
CollarPos("21", "20")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
|
@ -94,6 +152,19 @@ class CollarDetailActivity : AppCompatActivity() {
|
||||||
refreshTimer.schedule(timerTask { triggerRefresh() }, 0L, 5000L)
|
refreshTimer.schedule(timerTask { triggerRefresh() }, 0L, 5000L)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locates all the views in the activity, setting the respective
|
||||||
|
* `lateinit` properties.
|
||||||
|
*/
|
||||||
|
private fun findViews() {
|
||||||
|
collarName = findViewById(R.id.collarName)
|
||||||
|
collarPos = findViewById(R.id.collarPos)
|
||||||
|
collarStimulus = findViewById(R.id.collarStimulus)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends API requests that retrieve updated information about the collar.
|
||||||
|
*/
|
||||||
private fun triggerRefresh() {
|
private fun triggerRefresh() {
|
||||||
val historyRequest = CollarHistoryRequest(getString(R.string.apiUrl), collarId, token,
|
val historyRequest = CollarHistoryRequest(getString(R.string.apiUrl), collarId, token,
|
||||||
Response.Listener {
|
Response.Listener {
|
||||||
|
@ -116,6 +187,9 @@ class CollarDetailActivity : AppCompatActivity() {
|
||||||
queue.add(detailRequest)
|
queue.add(detailRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets [mapPolygon]'s color, borders, and other relevant settings.
|
||||||
|
*/
|
||||||
private fun configureMapPolygon() {
|
private fun configureMapPolygon() {
|
||||||
mapPolygon.fillPaint.color = Color.parseColor("#32a852")
|
mapPolygon.fillPaint.color = Color.parseColor("#32a852")
|
||||||
mapPolygon.fillPaint.alpha = 127
|
mapPolygon.fillPaint.alpha = 127
|
||||||
|
@ -124,6 +198,12 @@ class CollarDetailActivity : AppCompatActivity() {
|
||||||
mapPolygon.title = "Valid Grazing Area"
|
mapPolygon.title = "Valid Grazing Area"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a new location history of the animal, configures
|
||||||
|
* updates the [map] to include the relevant elements (such as the animal's
|
||||||
|
* [marker][mapMarker]). Additionally, if the map is not centered,
|
||||||
|
* this sets its center to the animal's most recent location.
|
||||||
|
*/
|
||||||
private fun updateAnimalHistory(points : List<GeoPoint>) {
|
private fun updateAnimalHistory(points : List<GeoPoint>) {
|
||||||
val currentPoint = points.first()
|
val currentPoint = points.first()
|
||||||
collarPos.text = getString(R.string.collarSummaryLocation, currentPoint.longitude, currentPoint.latitude)
|
collarPos.text = getString(R.string.collarSummaryLocation, currentPoint.longitude, currentPoint.latitude)
|
||||||
|
@ -138,12 +218,20 @@ class CollarDetailActivity : AppCompatActivity() {
|
||||||
map.invalidate()
|
map.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If an animal's history is empty, hides all the elements
|
||||||
|
* that depend on said history from the [map].
|
||||||
|
*/
|
||||||
private fun clearAnimalHistory() {
|
private fun clearAnimalHistory() {
|
||||||
map.overlays.remove(mapMarker)
|
if(map.overlays.contains(mapMarker)) map.overlays.remove(mapMarker)
|
||||||
map.overlays.remove(mapPolyline)
|
if(map.overlays.contains(mapPolyline)) map.overlays.remove(mapPolyline)
|
||||||
map.invalidate()
|
map.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the [mapPolygon] with the new valid grazing area.
|
||||||
|
* The polygon is added to the [map] if necessary.
|
||||||
|
*/
|
||||||
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()) }
|
||||||
|
@ -153,6 +241,10 @@ class CollarDetailActivity : AppCompatActivity() {
|
||||||
map.invalidate()
|
map.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If an animal's bounding box is not set, hides
|
||||||
|
* the [mapPolygon] from the [map].
|
||||||
|
*/
|
||||||
private fun clearBoundingBox() {
|
private fun clearBoundingBox() {
|
||||||
map.overlays.remove(mapPolygon)
|
map.overlays.remove(mapPolygon)
|
||||||
map.invalidate()
|
map.invalidate()
|
||||||
|
|
|
@ -4,7 +4,6 @@ import android.content.Intent
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Button
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
@ -24,21 +23,54 @@ import java.util.*
|
||||||
import kotlin.concurrent.timerTask
|
import kotlin.concurrent.timerTask
|
||||||
|
|
||||||
class CollarListActivity : AppCompatActivity() {
|
class CollarListActivity : AppCompatActivity() {
|
||||||
// The list of collar summaries and its list adapter.
|
|
||||||
|
/**
|
||||||
|
* The list of collar summaries retrieved from the API.
|
||||||
|
*/
|
||||||
private val summaries : MutableList<CollarSummary> = mutableListOf()
|
private val summaries : MutableList<CollarSummary> = mutableListOf()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The adapter used to display the [summaries] on the screen.
|
||||||
|
*/
|
||||||
private lateinit var summaryAdapter: CollarSummaryAdapter
|
private lateinit var summaryAdapter: CollarSummaryAdapter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The view of the summaries backed by the [summaryAdapter].
|
||||||
|
*/
|
||||||
private lateinit var collarList: RecyclerView
|
private lateinit var collarList: RecyclerView
|
||||||
|
|
||||||
// The API token and request queue.
|
/**
|
||||||
|
* The API token and request queue.
|
||||||
|
*/
|
||||||
private lateinit var token: String
|
private lateinit var token: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Volley queue used for sending API requests.
|
||||||
|
*/
|
||||||
private lateinit var queue: RequestQueue
|
private lateinit var queue: RequestQueue
|
||||||
|
|
||||||
// The OpenStreetMap map.
|
/**
|
||||||
|
* Whether or not the map has been centered on the location of the collars.
|
||||||
|
* Rather than have the map constantly jump around following the collars as they move, we only
|
||||||
|
* move the map's center when this variable is `false`.
|
||||||
|
*/
|
||||||
private var centerSet: Boolean = false
|
private var centerSet: Boolean = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actual view used to display the OpenStreetMaps tiles and the locations of the collars.
|
||||||
|
*/
|
||||||
private lateinit var map: MapView
|
private lateinit var map: MapView
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping of collar identifiers to the marker that represents them, used
|
||||||
|
* to update markers without clearing the user's selection.
|
||||||
|
*/
|
||||||
private val collarOverlays: MutableMap<Int, Marker> = mutableMapOf()
|
private val collarOverlays: MutableMap<Int, Marker> = mutableMapOf()
|
||||||
|
|
||||||
// Timer used to schedule refresh
|
/**
|
||||||
|
* The timer used for scheduling repeated API calls. The timer is started
|
||||||
|
* in [onResume], and stopped in [onPause].
|
||||||
|
*/
|
||||||
private var refreshTimer = Timer()
|
private var refreshTimer = Timer()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
@ -72,10 +104,17 @@ class CollarListActivity : AppCompatActivity() {
|
||||||
refreshTimer.purge()
|
refreshTimer.purge()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called by the "open statistics" button. Opens the statistics
|
||||||
|
* activity.
|
||||||
|
*/
|
||||||
fun openStatistics(view: View) {
|
fun openStatistics(view: View) {
|
||||||
startActivity(Intent(this, StatisticsActivity::class.java))
|
startActivity(Intent(this, StatisticsActivity::class.java))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the request to the API to retrieve an updated list of active collars.
|
||||||
|
*/
|
||||||
private fun triggerRefresh() {
|
private fun triggerRefresh() {
|
||||||
val request = CollarRequest(getString(R.string.apiUrl), token,
|
val request = CollarRequest(getString(R.string.apiUrl), token,
|
||||||
Response.Listener {
|
Response.Listener {
|
||||||
|
@ -91,11 +130,21 @@ class CollarListActivity : AppCompatActivity() {
|
||||||
queue.add(request)
|
queue.add(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locates all the views in the activity, setting the respective
|
||||||
|
* `lateinit` properties.
|
||||||
|
*/
|
||||||
private fun findViews() {
|
private fun findViews() {
|
||||||
collarList = findViewById(R.id.collarSummaryList)
|
collarList = findViewById(R.id.collarSummaryList)
|
||||||
map = findViewById(R.id.map)
|
map = findViewById(R.id.map)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the activity [CollarDetailActivity] to give the user more information
|
||||||
|
* on a particular collar.
|
||||||
|
*
|
||||||
|
* @param collar the collar being viewed.
|
||||||
|
*/
|
||||||
private fun startCollarDetailActivity(collar: CollarSummary) {
|
private fun startCollarDetailActivity(collar: CollarSummary) {
|
||||||
val newIntent = Intent(this, CollarDetailActivity::class.java).apply {
|
val newIntent = Intent(this, CollarDetailActivity::class.java).apply {
|
||||||
putExtra("identifier", collar.id)
|
putExtra("identifier", collar.id)
|
||||||
|
@ -103,6 +152,10 @@ class CollarListActivity : AppCompatActivity() {
|
||||||
startActivity(newIntent)
|
startActivity(newIntent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the [collarList] with the [summaryAdapter], allowing it to
|
||||||
|
* properly display and handle clicks on the items.
|
||||||
|
*/
|
||||||
private fun setupCollarList() {
|
private fun setupCollarList() {
|
||||||
val layoutManager = LinearLayoutManager(collarList.context)
|
val layoutManager = LinearLayoutManager(collarList.context)
|
||||||
summaryAdapter = CollarSummaryAdapter(summaries, object : CollarClickListener {
|
summaryAdapter = CollarSummaryAdapter(summaries, object : CollarClickListener {
|
||||||
|
@ -116,11 +169,17 @@ class CollarListActivity : AppCompatActivity() {
|
||||||
collarList.addItemDecoration(DividerItemDecoration(collarList.context, layoutManager.orientation))
|
collarList.addItemDecoration(DividerItemDecoration(collarList.context, layoutManager.orientation))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the map with new collar information.
|
||||||
|
*/
|
||||||
private fun updateMap() {
|
private fun updateMap() {
|
||||||
updateMapCenter()
|
updateMapCenter()
|
||||||
updateMapOverlay()
|
updateMapOverlay()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the map's center to the average location of the animals.
|
||||||
|
*/
|
||||||
private fun updateMapCenter() {
|
private fun updateMapCenter() {
|
||||||
if(centerSet) return
|
if(centerSet) return
|
||||||
|
|
||||||
|
@ -130,6 +189,9 @@ class CollarListActivity : AppCompatActivity() {
|
||||||
map.controller.setCenter(GeoPoint(averageLongitude, averageLatitude))
|
map.controller.setCenter(GeoPoint(averageLongitude, averageLatitude))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the markers on the map with the animals' new locations.
|
||||||
|
*/
|
||||||
private fun updateMapOverlay() {
|
private fun updateMapOverlay() {
|
||||||
val currentSet = mutableSetOf<Int>()
|
val currentSet = mutableSetOf<Int>()
|
||||||
summaries.forEach {
|
summaries.forEach {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import androidx.fragment.app.Fragment
|
||||||
import com.android.volley.RequestQueue
|
import com.android.volley.RequestQueue
|
||||||
import com.android.volley.Response
|
import com.android.volley.Response
|
||||||
import com.android.volley.toolbox.Volley
|
import com.android.volley.toolbox.Volley
|
||||||
|
import com.danilafe.fencelessgrazing.model.CollarDistance
|
||||||
import com.danilafe.fencelessgrazing.requests.DistanceTraveledRequest
|
import com.danilafe.fencelessgrazing.requests.DistanceTraveledRequest
|
||||||
import com.github.mikephil.charting.charts.BarChart
|
import com.github.mikephil.charting.charts.BarChart
|
||||||
import com.github.mikephil.charting.data.BarData
|
import com.github.mikephil.charting.data.BarData
|
||||||
|
@ -17,10 +18,21 @@ import com.github.mikephil.charting.data.BarEntry
|
||||||
import com.github.mikephil.charting.formatter.IndexAxisValueFormatter
|
import com.github.mikephil.charting.formatter.IndexAxisValueFormatter
|
||||||
import com.github.mikephil.charting.utils.ColorTemplate
|
import com.github.mikephil.charting.utils.ColorTemplate
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One of the tabs from the [StatisticsActivity]. Contains the graph
|
||||||
|
* of the distance traveled by each animal, retrieved via the [DistanceTraveledRequest]
|
||||||
|
* from the API.
|
||||||
|
*/
|
||||||
class DistanceTraveledGraph() : Fragment() {
|
class DistanceTraveledGraph() : Fragment() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The BarChart used to display the distance data.
|
||||||
|
*/
|
||||||
private lateinit var distanceTraveledChart: BarChart
|
private lateinit var distanceTraveledChart: BarChart
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Volley queue used for making API requests.
|
||||||
|
*/
|
||||||
private lateinit var queue : RequestQueue
|
private lateinit var queue : RequestQueue
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
|
@ -37,31 +49,20 @@ class DistanceTraveledGraph() : Fragment() {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
queue = Volley.newRequestQueue(requireActivity().applicationContext)
|
queue = Volley.newRequestQueue(requireActivity().applicationContext)
|
||||||
|
|
||||||
|
setupChart()
|
||||||
triggerRefresh()
|
triggerRefresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the API request to retrieve updated distance data.
|
||||||
|
*/
|
||||||
private fun triggerRefresh() {
|
private fun triggerRefresh() {
|
||||||
val activity = requireActivity()
|
val activity = requireActivity()
|
||||||
val sharedPrefs = activity.getSharedPreferences("FencelessGrazing", 0)
|
val sharedPrefs = activity.getSharedPreferences("FencelessGrazing", 0)
|
||||||
val token = sharedPrefs.getString("token", null)!!
|
val token = sharedPrefs.getString("token", null)!!
|
||||||
val request = DistanceTraveledRequest(activity.getString(R.string.apiUrl), token,
|
val request = DistanceTraveledRequest(activity.getString(R.string.apiUrl), token,
|
||||||
Response.Listener {
|
Response.Listener {
|
||||||
val entries = mutableListOf<BarEntry>()
|
updateChartData(it)
|
||||||
val labels = it.map { c -> c.name }
|
|
||||||
it.forEachIndexed { i, v ->
|
|
||||||
entries.add(BarEntry(i.toFloat(), v.distance))
|
|
||||||
}
|
|
||||||
val dataSet = BarDataSet(entries, "Distance Traveled")
|
|
||||||
dataSet.colors = ColorTemplate.VORDIPLOM_COLORS.toList()
|
|
||||||
val data = BarData(dataSet)
|
|
||||||
data.barWidth = 0.9f
|
|
||||||
distanceTraveledChart.legend.textSize = 20.0f
|
|
||||||
distanceTraveledChart.data = data
|
|
||||||
distanceTraveledChart.setFitBars(true)
|
|
||||||
distanceTraveledChart.xAxis.valueFormatter = IndexAxisValueFormatter(labels)
|
|
||||||
distanceTraveledChart.xAxis.granularity = 1.0f
|
|
||||||
distanceTraveledChart.xAxis.isGranularityEnabled = true
|
|
||||||
distanceTraveledChart.invalidate()
|
|
||||||
},
|
},
|
||||||
Response.ErrorListener {
|
Response.ErrorListener {
|
||||||
Toast.makeText(activity, "Failed to retrieve distance traveled!", Toast.LENGTH_SHORT).show()
|
Toast.makeText(activity, "Failed to retrieve distance traveled!", Toast.LENGTH_SHORT).show()
|
||||||
|
@ -69,4 +70,32 @@ class DistanceTraveledGraph() : Fragment() {
|
||||||
)
|
)
|
||||||
queue.add(request)
|
queue.add(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the [distanceTraveledChart] with newly retrieved distance data.
|
||||||
|
*/
|
||||||
|
private fun updateChartData(distances: List<CollarDistance>) {
|
||||||
|
val entries = mutableListOf<BarEntry>()
|
||||||
|
val labels = distances.map { c -> c.name }
|
||||||
|
distances.forEachIndexed { i, v ->
|
||||||
|
entries.add(BarEntry(i.toFloat(), v.distance))
|
||||||
|
}
|
||||||
|
val dataSet = BarDataSet(entries, "Distance Traveled")
|
||||||
|
dataSet.colors = ColorTemplate.PASTEL_COLORS.toList()
|
||||||
|
val data = BarData(dataSet)
|
||||||
|
data.barWidth = 0.9f
|
||||||
|
distanceTraveledChart.data = data
|
||||||
|
distanceTraveledChart.xAxis.valueFormatter = IndexAxisValueFormatter(labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the [distanceTraveledChart] with the proper visual settings.
|
||||||
|
*/
|
||||||
|
private fun setupChart() {
|
||||||
|
distanceTraveledChart.legend.textSize = 20.0f
|
||||||
|
distanceTraveledChart.setFitBars(true)
|
||||||
|
distanceTraveledChart.xAxis.granularity = 1.0f
|
||||||
|
distanceTraveledChart.xAxis.isGranularityEnabled = true
|
||||||
|
distanceTraveledChart.invalidate()
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -10,13 +10,24 @@ import com.android.volley.Response
|
||||||
import com.android.volley.toolbox.Volley
|
import com.android.volley.toolbox.Volley
|
||||||
import com.danilafe.fencelessgrazing.requests.LoginRequest
|
import com.danilafe.fencelessgrazing.requests.LoginRequest
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First activity in the app. Prompts the user for their
|
||||||
|
* login information, and attempts authentication to the API.
|
||||||
|
*/
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
|
|
||||||
|
// If we've already logged in, no need to do it again.
|
||||||
|
val prefs = getSharedPreferences("FencelessGrazing", 0)
|
||||||
|
if(prefs.contains("token")) startCollarActivity()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the authentication request to the API.
|
||||||
|
*/
|
||||||
fun attemptLogin(view: View) {
|
fun attemptLogin(view: View) {
|
||||||
val usernameField: TextView = findViewById(R.id.username)
|
val usernameField: TextView = findViewById(R.id.username)
|
||||||
val passwordField: TextView = findViewById(R.id.password)
|
val passwordField: TextView = findViewById(R.id.password)
|
||||||
|
@ -30,12 +41,19 @@ class MainActivity : AppCompatActivity() {
|
||||||
editor.putString("token", it.token)
|
editor.putString("token", it.token)
|
||||||
editor.apply()
|
editor.apply()
|
||||||
|
|
||||||
val newIntent = Intent(this, CollarListActivity::class.java)
|
startCollarActivity()
|
||||||
startActivity(newIntent)
|
|
||||||
},
|
},
|
||||||
Response.ErrorListener {
|
Response.ErrorListener {
|
||||||
Toast.makeText(this, "Failed to log in! $it", Toast.LENGTH_LONG).show()
|
Toast.makeText(this, "Failed to log in! $it", Toast.LENGTH_LONG).show()
|
||||||
})
|
})
|
||||||
requestQueue.add(loginRequest)
|
requestQueue.add(loginRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Once authentication is complete, moves on to the list of collars.
|
||||||
|
*/
|
||||||
|
private fun startCollarActivity() {
|
||||||
|
val newIntent = Intent(this, CollarListActivity::class.java)
|
||||||
|
startActivity(newIntent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,25 @@ import androidx.viewpager2.widget.ViewPager2
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity containing various graphs created from collar data on the server.
|
||||||
|
*/
|
||||||
class StatisticsActivity : AppCompatActivity() {
|
class StatisticsActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tab layout allowing users to switch between graphs.
|
||||||
|
*/
|
||||||
private lateinit var tabLayout: TabLayout
|
private lateinit var tabLayout: TabLayout
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The view pager providing swiping functionality between views.
|
||||||
|
*/
|
||||||
private lateinit var viewPager: ViewPager2
|
private lateinit var viewPager: ViewPager2
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
/**
|
||||||
|
* The list of tab names, in the order they appear in the [viewPager].
|
||||||
|
*/
|
||||||
val tabNames = arrayOf("Distance Traveled")
|
val tabNames = arrayOf("Distance Traveled")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user