feat(train): routes kept as sealed rows, planned on the map (ADR 0030, D44)
Routes are synced `route` rows (the model `SavedRoute`, as Flutter has a `Route`), sealed as plans are; the migration `routes` only creates the table, its space index and its cascade to the sync engine's spaces. A route's payload holds its name, activity, profile, waypoints and the way planned, points and elevations delta-coded and gzipped. Home gets a fourth tab, Routes, and the planner: waypoints tapped on the map, held and dragged, the last undone, a new route's first where the phone is (GPS through the recorder's channel), the way planned by the instance's BRouter after each change, an earlier answer dropped; its distance, climb, descent, elevation and surfaces under the map. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
a8f7c26e69
commit
aea99c68ca
1 changed files with 24035 additions and 12 deletions
|
|
@ -3,8 +3,14 @@ package app.halkyon.train
|
|||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.location.Location
|
||||
import android.location.LocationListener
|
||||
import android.location.LocationManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.CancellationSignal
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.EventChannel
|
||||
|
|
@ -67,6 +73,7 @@ class MainActivity : FlutterActivity() {
|
|||
"stop" -> result.success(RecorderService.stop().let { null })
|
||||
"show" -> result.success(RecorderService.show(call.arguments as String).let { null })
|
||||
"isRecording" -> result.success(RecorderService.isRecording())
|
||||
"position" -> position(result)
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
|
@ -166,7 +173,59 @@ class MainActivity : FlutterActivity() {
|
|||
else -> emptyList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Where the phone is, for a route's first waypoint (ADR 0030, D44): GPS's
|
||||
* last fix where it is at most two minutes old, else a new one, waited
|
||||
* for 15 s at most; null without the location permission or a fix.
|
||||
*/
|
||||
@Suppress("MissingPermission") // Checked first.
|
||||
private fun position(result: MethodChannel.Result) {
|
||||
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||
result.success(null)
|
||||
return
|
||||
}
|
||||
val manager = getSystemService(LocationManager::class.java)
|
||||
var answered = false
|
||||
fun answer(location: Location?) {
|
||||
if (answered) return
|
||||
answered = true
|
||||
result.success(location?.let { mapOf("lat" to it.latitude, "lon" to it.longitude) })
|
||||
}
|
||||
val last = manager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
|
||||
if (last != null && System.currentTimeMillis() - last.time <= FRESH_MS) {
|
||||
answer(last)
|
||||
return
|
||||
}
|
||||
if (!manager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
||||
answer(last)
|
||||
return
|
||||
}
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
val cancel = CancellationSignal()
|
||||
handler.postDelayed({
|
||||
cancel.cancel()
|
||||
answer(last)
|
||||
}, WAIT_MS)
|
||||
manager.getCurrentLocation(LocationManager.GPS_PROVIDER, cancel, mainExecutor) { answer(it ?: last) }
|
||||
} else {
|
||||
val listener = object : LocationListener {
|
||||
override fun onLocationChanged(location: Location) {
|
||||
manager.removeUpdates(this)
|
||||
answer(location)
|
||||
}
|
||||
}
|
||||
handler.postDelayed({
|
||||
manager.removeUpdates(listener)
|
||||
answer(last)
|
||||
}, WAIT_MS)
|
||||
manager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0L, 0f, listener, Looper.getMainLooper())
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val RECORDER_REQUEST = 1
|
||||
const val FRESH_MS = 120_000L
|
||||
const val WAIT_MS = 15_000L
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue