Working on cache downloader

This commit is contained in:
PWRxPSYCHO
2022-09-22 08:35:33 -04:00
parent 4d81689f21
commit 9b1dfb0d02
5 changed files with 424 additions and 16 deletions

View File

@@ -37,6 +37,9 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.osmdroid.bonuspack.kml.KmlDocument
import org.osmdroid.views.MapView
import org.osmdroid.views.overlay.FolderOverlay
import java.io.BufferedWriter
import java.io.FileNotFoundException
import java.io.FileWriter
@@ -480,6 +483,31 @@ class UIViewModel @Inject constructor(
}
}
fun parseUrl(url: String, map: MapView) {
viewModelScope.launch(Dispatchers.IO) {
parseIt(url, map)
}
}
// For Future Use
// model.parseUrl(
// "https://www.google.com/maps/d/kml?forcekml=1&mid=1FmqWhZG3PG3dY92x9yf2RlREcK7kMZs&lid=-ivSjBCePsM",
// map
// )
private fun parseIt(url: String, map: MapView) {
val kmlDoc = KmlDocument()
try {
kmlDoc.parseKMLUrl(url)
val kmlOverlay = kmlDoc.mKmlRoot.buildOverlay(map, null, null, kmlDoc) as FolderOverlay
kmlDoc.mKmlRoot.mItems
kmlDoc.mKmlRoot.mName
map.overlayManager.overlays().add(kmlOverlay)
} catch (ex: Exception) {
debug("Failed to download .kml $ex")
}
}
fun addQuickChatAction(name: String, value: String, mode: QuickChatAction.Mode) {
viewModelScope.launch(Dispatchers.Main) {
val action = QuickChatAction(0, name, value, mode, _quickChatActions.value.size)

View File

@@ -1,5 +1,6 @@
package com.geeksville.mesh.ui
import android.app.AlertDialog
import android.content.Context
import android.content.SharedPreferences
import android.graphics.Canvas
@@ -7,33 +8,41 @@ import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import android.widget.SeekBar.OnSeekBarChangeListener
import androidx.core.content.ContextCompat
import androidx.fragment.app.activityViewModels
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.BuildConfig
import com.geeksville.mesh.NodeInfo
import com.geeksville.mesh.R
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.databinding.MapViewBinding
import com.geeksville.mesh.model.CustomTileSource
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.util.formatAgo
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.FloatingActionButton
import dagger.hilt.android.AndroidEntryPoint
import org.osmdroid.api.IMapController
import org.osmdroid.config.Configuration
import org.osmdroid.tileprovider.cachemanager.CacheManager
import org.osmdroid.tileprovider.tilesource.ITileSource
import org.osmdroid.util.BoundingBox
import org.osmdroid.util.GeoPoint
import org.osmdroid.views.CustomZoomButtonsController
import org.osmdroid.views.MapView
import org.osmdroid.views.overlay.CopyrightOverlay
import org.osmdroid.views.overlay.Marker
import org.osmdroid.views.overlay.*
import kotlin.math.pow
@AndroidEntryPoint
class MapFragment : ScreenFragment("Map"), Logging {
class MapFragment : ScreenFragment("Map"), Logging, View.OnClickListener, OnSeekBarChangeListener,
TextWatcher {
private lateinit var binding: MapViewBinding
private lateinit var map: MapView
@@ -41,20 +50,38 @@ class MapFragment : ScreenFragment("Map"), Logging {
private lateinit var mPrefs: SharedPreferences
private val model: UIViewModel by activityViewModels()
private lateinit var cacheManager: CacheManager
private lateinit var btnCache: FloatingActionButton
private lateinit var cacheNorth: EditText
private lateinit var cacheSouth: EditText
private lateinit var cacheEast: EditText
private lateinit var cacheWest: EditText
private lateinit var cacheEstimate: TextView
private lateinit var zoomMin: SeekBar
private lateinit var zoomMax: SeekBar
private var downloadPrompt: AlertDialog? = null
private var alertDialog: AlertDialog? = null
private lateinit var executeJob: Button
private val defaultMinZoom = 1.5
private val nodeZoomLevel = 8.5
private val defaultZoomSpeed = 3000L
private val prefsName = "org.andnav.osm.prefs"
private val prefsName = "org.geeksville.osm.prefs"
private val mapStyleId = "map_style_id"
private var nodePositions = listOf<MarkerWithLabel>()
private val nodeLayer = 1
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
binding = MapViewBinding.inflate(inflater)
btnCache = binding.root.findViewById(R.id.downloadButton)
return binding.root
}
@@ -89,6 +116,191 @@ class MapFragment : ScreenFragment("Map"), Logging {
}
zoomToNodes(mapController)
}
btnCache.setOnClickListener(this)
}
override fun onClick(v: View) {
when (v.id) {
R.id.executeJob -> updateEstimate(true)
R.id.downloadButton -> showCacheManagerDialog()
}
}
private fun showCacheManagerDialog() {
val alertDialogBuilder = AlertDialog.Builder(
activity
)
// set title
alertDialogBuilder.setTitle("Cache Manager")
//.setMessage(R.string.cache_manager_description);
// set dialog message
alertDialogBuilder.setItems(
arrayOf<CharSequence>(
"Cache current size",
"Cache Download",
resources.getString(R.string.cancel)
)
) { dialog, which ->
when (which) {
0 -> showCurrentCacheInfo()
1 -> {
downloadJobAlert()
dialog.dismiss()
}
else -> dialog.dismiss()
}
}
// create alert dialog
alertDialog = alertDialogBuilder.create()
// show it
alertDialog!!.show()
}
private fun showCurrentCacheInfo() {
Toast.makeText(activity, "Calculating...", Toast.LENGTH_SHORT).show()
Thread {
val alertDialogBuilder = AlertDialog.Builder(
activity
)
// set title
alertDialogBuilder.setTitle("Cache Manager")
.setMessage(
"""
Cache Capacity (mb): ${cacheManager.cacheCapacity() * 2.0.pow(-20.0)}
Cache Usage (mb): ${cacheManager.currentCacheUsage() * 2.0.pow(-20.0)}
""".trimIndent()
)
// set dialog message
alertDialogBuilder.setItems(
arrayOf<CharSequence>(
resources.getString(R.string.cancel)
)
) { dialog, _ -> dialog.dismiss() }
activity!!.runOnUiThread { // show it
// create alert dialog
val alertDialog = alertDialogBuilder.create()
alertDialog.show()
}
}.start()
}
private fun downloadJobAlert() {
//prompt for input params
val builder = AlertDialog.Builder(activity)
val view = View.inflate(activity, R.layout.cache_mgr_input, null)
val boundingBox: BoundingBox = map.boundingBox
zoomMax = view.findViewById(R.id.slider_zoom_max)
zoomMax.max = map.maxZoomLevel.toInt()
zoomMax.setOnSeekBarChangeListener(this)
zoomMin = view.findViewById(R.id.slider_zoom_min)
zoomMin.max = map.maxZoomLevel.toInt()
zoomMin.progress = map.minZoomLevel.toInt()
zoomMin.setOnSeekBarChangeListener(this)
cacheEast = view.findViewById(R.id.cache_east)
cacheEast.setText(boundingBox.lonEast.toString() + "")
cacheNorth = view.findViewById(R.id.cache_north)
cacheNorth.setText(boundingBox.latNorth.toString() + "")
cacheSouth = view.findViewById(R.id.cache_south)
cacheSouth.setText(boundingBox.latSouth.toString() + "")
cacheWest = view.findViewById(R.id.cache_west)
cacheWest.setText(boundingBox.lonWest.toString() + "")
cacheEstimate = view.findViewById(R.id.cache_estimate)
//change listeners for both validation and to trigger the download estimation
cacheEast.addTextChangedListener(this)
cacheNorth.addTextChangedListener(this)
cacheSouth.addTextChangedListener(this)
cacheWest.addTextChangedListener(this)
executeJob = view.findViewById(R.id.executeJob)
executeJob.setOnClickListener {
builder.setOnCancelListener {
cacheEast.text = null
cacheSouth.text = null
cacheEstimate.text = ""
cacheNorth.text = null
cacheWest.text = null
zoomMin.progress = 0
zoomMax.progress = 0
}
}
builder.setView(view)
builder.setCancelable(true)
downloadPrompt = builder.create()
downloadPrompt!!.show()
}
/**
* if true, start the job
* if false, just update the dialog box
*/
private fun updateEstimate(startJob: Boolean) {
try {
if (cacheWest.text != null && cacheNorth.text != null && cacheSouth.text != null && zoomMax.progress != 0 && zoomMin.progress != 0) {
val n: Double = cacheNorth.text.toString().toDouble()
val s: Double = cacheSouth.text.toString().toDouble()
val e: Double = cacheEast.text.toString().toDouble()
val w: Double = cacheWest.text.toString().toDouble()
val zoommin: Int = zoomMin.progress
val zoommax: Int = zoomMax.progress
//nesw
val bb = BoundingBox(n, e, s, w)
val tilecount: Int = cacheManager.possibleTilesInArea(bb, zoommin, zoommax)
cacheEstimate.text = ("$tilecount tiles")
if (startJob) {
if (downloadPrompt != null) {
downloadPrompt!!.dismiss()
downloadPrompt = null
}
//this triggers the download
cacheManager.downloadAreaAsync(activity,
bb,
zoommin,
zoommax,
object : CacheManager.CacheManagerCallback {
override fun onTaskComplete() {
Toast.makeText(activity, "Download complete!", Toast.LENGTH_LONG)
.show()
}
override fun onTaskFailed(errors: Int) {
Toast.makeText(
activity,
"Download complete with $errors errors",
Toast.LENGTH_LONG
).show()
}
override fun updateProgress(
progress: Int, currentZoomLevel: Int, zoomMin: Int, zoomMax: Int
) {
//NOOP since we are using the build in UI
}
override fun downloadStarted() {
//NOOP since we are using the build in UI
}
override fun setPossibleTilesInArea(total: Int) {
//NOOP since we are using the build in UI
}
})
}
}
} catch (ex: Exception) {
ex.printStackTrace()
}
}
private fun chooseMapStyle() {
@@ -138,8 +350,7 @@ class MapFragment : ScreenFragment("Map"), Logging {
marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
marker.position = GeoPoint(p.latitude, p.longitude)
marker.icon = ContextCompat.getDrawable(
requireActivity(),
R.drawable.ic_baseline_location_on_24
requireActivity(), R.drawable.ic_baseline_location_on_24
)
}
marker
@@ -159,8 +370,7 @@ class MapFragment : ScreenFragment("Map"), Logging {
* Adds copyright to map depending on what source is showing
*/
private fun addCopyright() {
val copyrightNotice: String =
map.tileProvider.tileSource.copyrightNotice
val copyrightNotice: String = map.tileProvider.tileSource.copyrightNotice
val copyrightOverlay = CopyrightOverlay(context)
copyrightOverlay.setCopyrightNotice(copyrightNotice)
map.overlays.add(copyrightOverlay)
@@ -168,6 +378,7 @@ class MapFragment : ScreenFragment("Map"), Logging {
private fun setupMapProperties() {
if (this::map.isInitialized) {
cacheManager = CacheManager(map)
map.setDestroyMode(false) // keeps map instance alive when in the background.
map.isVerticalMapRepetitionEnabled = false // disables map repetition
map.setScrollableAreaLimitLatitude(
@@ -194,8 +405,7 @@ class MapFragment : ScreenFragment("Map"), Logging {
nodesWithPosition.forEach {
points.add(
GeoPoint(
it.position!!.latitude,
it.position!!.longitude
it.position!!.latitude, it.position!!.longitude
)
)
}
@@ -219,6 +429,12 @@ class MapFragment : ScreenFragment("Map"), Logging {
override fun onPause() {
map.onPause()
if (alertDialog != null && alertDialog!!.isShowing) {
alertDialog!!.dismiss()
}
if (downloadPrompt != null && downloadPrompt!!.isShowing) {
downloadPrompt!!.dismiss()
}
super.onPause()
}
@@ -270,6 +486,26 @@ class MapFragment : ScreenFragment("Map"), Logging {
c.drawText(mLabel, (p.x - 0f), (p.y - 110f), textPaint)
}
}
override fun onProgressChanged(p0: SeekBar?, p1: Int, p2: Boolean) {
updateEstimate(false)
}
override fun onStartTrackingTouch(p0: SeekBar?) {
}
override fun onStopTrackingTouch(p0: SeekBar?) {
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
updateEstimate(false)
}
override fun afterTextChanged(p0: Editable?) {
}
}