Give the Fly App a GO 4-style settings sheet

The "•••" button now opens a right-side glass sheet over the live view
with Safety, Vision, Remote, Video link, Battery, Gimbal and General
tabs. Each row is a one-line spec bound to a KeyManager key: rows hide
when the aircraft does not support their key, writes show at once and
roll back if refused, and ground-only settings lock in flight.

KeyBridge gains argument-less action keys and converts range and LED
settings values. settings_menu_page.dart is retired.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
tajniak81andClaude Opus 5.5 committed 2026-10-19 21:13:45 +02:00
1 parent 60c2fd7f64
commit 284de5442a
19 files changed
+2132 -342

No files matched your search

@@ -2,8 +2,10 @@ package com.pilotvault.flyapp
import dji.common.camera.ResolutionAndFrameRate
import dji.common.error.DJIError
import dji.common.flightcontroller.LEDsSettings
import dji.common.flightcontroller.LocationCoordinate3D
import dji.common.model.LocationCoordinate2D
import dji.common.util.DJIParamMinMaxCapability
import dji.keysdk.AirLinkKey
import dji.keysdk.BatteryKey
import dji.keysdk.CameraKey
@@ -13,6 +15,7 @@ import dji.keysdk.GimbalKey
import dji.keysdk.KeyManager
import dji.keysdk.ProductKey
import dji.keysdk.RemoteControllerKey
import dji.keysdk.callback.ActionCallback
import dji.keysdk.callback.GetCallback
import dji.keysdk.callback.KeyListener
import dji.keysdk.callback.SetCallback
@@ -21,8 +24,9 @@ import io.flutter.plugin.common.MethodChannel
/**
* Generic MSDK V4 `KeyManager` access (plan §1.3): one `getKey` / `setKey` /
* `listenKey` / `cancelKey` / `isKeySupported` method set instead of a bespoke
* method per simple value, so a settings row (P6) is one key name, not new Kotlin.
* `listenKey` / `cancelKey` / `isKeySupported` / `performKeyAction` method set
* instead of a bespoke method per simple value, so a settings row (P6) is one
* key name, not new Kotlin.
*
* A key is addressed as `{component, key, index?, subIndex?}`:
* - `component`: `FlightController`, `FlightAssistant`, `RTK`, `Camera`, `Gimbal`,
@@ -44,7 +48,7 @@ class KeyBridge(private val ctx: BridgeCtx) : SubBridge {
override fun handle(call: MethodCall, result: MethodChannel.Result): Boolean {
when (call.method) {
"getKey", "setKey", "listenKey", "isKeySupported" -> Unit
"getKey", "setKey", "listenKey", "isKeySupported", "performKeyAction" -> Unit
"cancelKey" -> {
val id = call.argument<String>("id")
ctx.mainHandler.post {
@@ -79,6 +83,17 @@ class KeyBridge(private val ctx: BridgeCtx) : SubBridge {
}
})
"setKey" -> setKey(km, key, call.argument<Any>("value"), result)
// Action keys (`START_PAIRING`, …) without arguments; ones that take SDK
// objects keep a bespoke method.
"performKeyAction" -> km.performAction(key, object : ActionCallback {
override fun onSuccess() {
ctx.mainHandler.post { result.success(null) }
}
override fun onFailure(error: DJIError) {
ctx.mainHandler.post { result.error("DJI_ERROR", error.description, null) }
}
})
"listenKey" -> ctx.mainHandler.post {
val id = idOf(call)
if (!listeners.containsKey(id)) {
@@ -226,12 +241,29 @@ object KeyValues {
is ResolutionAndFrameRate -> mapOf("resolution" to v.resolution?.name, "frameRate" to v.frameRate?.name)
is LocationCoordinate3D -> mapOf("latitude" to v.latitude, "longitude" to v.longitude, "altitude" to v.altitude.toDouble())
is LocationCoordinate2D -> mapOf("latitude" to v.latitude, "longitude" to v.longitude)
// `…_RANGE` keys (max flight height / radius, cinematic sensitivities).
is DJIParamMinMaxCapability -> mapOf("supported" to v.isSupported(), "min" to v.min?.toDouble(), "max" to v.max?.toDouble())
is LEDsSettings -> mapOf(
"front" to v.areFrontLEDsOn(),
"rear" to v.areRearLEDsOn(),
"statusIndicator" to v.isStatusIndicatorOn(),
"beacons" to v.areBeaconsOn(),
)
else -> v.toString()
}
/** Converts a Dart value to the Java type of [sample], the key's current value. */
fun coerce(raw: Any, sample: Any?): Any {
if (sample == null) return raw
if (sample is LEDsSettings && raw is Map<*, *>) {
fun on(k: String, was: Boolean) = raw[k] as? Boolean ?: was
return LEDsSettings.Builder()
.frontLEDsOn(on("front", sample.areFrontLEDsOn()))
.rearLEDsOn(on("rear", sample.areRearLEDsOn()))
.statusIndicatorOn(on("statusIndicator", sample.isStatusIndicatorOn()))
.beaconsOn(on("beacons", sample.areBeaconsOn()))
.build()
}
if (sample is Enum<*>) {
val cls = sample.javaClass.let { if (it.isEnum) it else it.superclass }
return cls.enumConstants?.firstOrNull { (it as Enum<*>).name == raw.toString() } ?: raw