Compare commits

...

2 Commits

Author SHA1 Message Date
Danila Fedorin 7b53546126 Add collar overlay to map 2020-02-16 17:40:30 -08:00
Danila Fedorin cfae237d17 Add a basic map view to app 2020-02-16 16:36:25 -08:00
5 changed files with 99 additions and 25 deletions

View File

@ -23,6 +23,10 @@ android {
} }
} }
repositories {
mavenCentral()
}
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
@ -35,4 +39,6 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.0' androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'org.osmdroid:osmdroid-android:6.1.5'
implementation "androidx.preference:preference-ktx:1.1.0"
} }

View File

@ -3,6 +3,8 @@
package="com.danilafe.fencelessgrazing"> package="com.danilafe.fencelessgrazing">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application <application
android:allowBackup="true" android:allowBackup="true"

View File

@ -2,28 +2,103 @@ package com.danilafe.fencelessgrazing
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle import android.os.Bundle
import android.widget.Toast
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.android.volley.RequestQueue
import com.android.volley.Response
import com.android.volley.toolbox.Volley
import com.danilafe.fencelessgrazing.model.CollarSummary
import com.danilafe.fencelessgrazing.requests.CollarRequest
import org.osmdroid.config.Configuration
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
import org.osmdroid.util.GeoPoint
import org.osmdroid.views.MapView
import org.osmdroid.views.overlay.Marker
class CollarListActivity : AppCompatActivity() { class CollarListActivity : AppCompatActivity() {
private lateinit var token: String // The list of collar summaries and its list adapter.
private val summaries : MutableList<CollarSummary> = mutableListOf()
private lateinit var summaryAdapter: CollarSummaryAdapter private lateinit var summaryAdapter: CollarSummaryAdapter
// The API token and request queue.
private lateinit var token: String
private lateinit var queue: RequestQueue
// The OpenStreetMap map.
private lateinit var map: MapView
private val collarOverlays: MutableMap<Int, Marker> = mutableMapOf()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Configuration.getInstance().load(applicationContext,
PreferenceManager.getDefaultSharedPreferences(applicationContext))
setContentView(R.layout.activity_collar_list) setContentView(R.layout.activity_collar_list)
val collarList: RecyclerView = findViewById(R.id.collarSummaryList) val collarList: RecyclerView = findViewById(R.id.collarSummaryList)
val layoutManager = LinearLayoutManager(collarList.context) val layoutManager = LinearLayoutManager(collarList.context)
token = intent.getStringExtra("token")!! token = intent.getStringExtra("token")!!
summaryAdapter = CollarSummaryAdapter(collarList.context, getString(R.string.apiUrl), token) summaryAdapter = CollarSummaryAdapter(summaries)
map = findViewById(R.id.map)
map.setTileSource(TileSourceFactory.MAPNIK)
queue = Volley.newRequestQueue(this)
collarList.adapter = summaryAdapter collarList.adapter = summaryAdapter
collarList.layoutManager = layoutManager collarList.layoutManager = layoutManager
collarList.addItemDecoration(DividerItemDecoration(collarList.context, layoutManager.orientation)) collarList.addItemDecoration(DividerItemDecoration(collarList.context, layoutManager.orientation))
summaryAdapter.triggerRefresh() triggerRefresh()
} }
override fun onResume() {
super.onResume()
map.onResume()
}
override fun onPause() {
super.onPause()
map.onPause()
}
private fun triggerRefresh() {
val request = CollarRequest(getString(R.string.apiUrl), token,
Response.Listener {
summaries.clear()
summaries.addAll(it)
summaryAdapter.notifyDataSetChanged()
updateMapOverlay()
},
Response.ErrorListener {
Toast.makeText(this, "Failed to retrieve collar list!", Toast.LENGTH_SHORT).show()
}
)
queue.add(request)
}
private fun updateMapOverlay() {
val currentSet = mutableSetOf<Int>()
summaries.forEach {
// Create or update overlay
val overlay = collarOverlays[it.id] ?: Marker(map)
overlay.title = it.name
overlay.position = GeoPoint(it.pos.longitude.toDouble(), it.pos.latitude.toDouble())
// Store new / existing overlay.
if(!collarOverlays.containsKey(it.id)) map.overlays.add(overlay)
collarOverlays[it.id] = overlay
currentSet.add(it.id)
}
val previousSet = collarOverlays.keys
previousSet.forEach {
if(!currentSet.contains(it)) {
map.overlays.remove(collarOverlays[it])
collarOverlays.remove(it)
}
}
}
} }

View File

@ -12,9 +12,7 @@ import com.danilafe.fencelessgrazing.model.CollarSummary
import com.danilafe.fencelessgrazing.requests.CollarRequest import com.danilafe.fencelessgrazing.requests.CollarRequest
class CollarSummaryAdapter( class CollarSummaryAdapter(
private val context: Context, private val items : List<CollarSummary>
private val baseUrl: String,
private val token: String
) : ListAdapter<CollarSummary, CollarViewHolder>(DiffCallback()) { ) : ListAdapter<CollarSummary, CollarViewHolder>(DiffCallback()) {
class DiffCallback : DiffUtil.ItemCallback<CollarSummary>() { class DiffCallback : DiffUtil.ItemCallback<CollarSummary>() {
@ -25,9 +23,6 @@ class CollarSummaryAdapter(
= oldItem == newItem = oldItem == newItem
} }
private val requestQueue = Volley.newRequestQueue(context)
private val items = mutableListOf<CollarSummary>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CollarViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CollarViewHolder {
val layout = LayoutInflater.from(parent.context).inflate(viewType, parent, false) val layout = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
return CollarViewHolder(layout) return CollarViewHolder(layout)
@ -43,18 +38,5 @@ class CollarSummaryAdapter(
override fun getItemCount(): Int = items.size override fun getItemCount(): Int = items.size
fun triggerRefresh() {
val request = CollarRequest(baseUrl, token,
Response.Listener {
items.clear()
items.addAll(it)
notifyDataSetChanged()
},
Response.ErrorListener {
Toast.makeText(context, "Failed to retrieve collar list!", Toast.LENGTH_SHORT).show()
}
)
requestQueue.add(request)
}
} }

View File

@ -6,11 +6,20 @@
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".CollarListActivity"> tools:context=".CollarListActivity">
<androidx.recyclerview.widget.RecyclerView <org.osmdroid.views.MapView
android:id="@+id/collarSummaryList" android:id="@+id/map"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="0dp"
app:layout_constraintDimensionRatio="16:9"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/collarSummaryList"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/map"
tools:layout_editor_absoluteX="0dp" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>