Initial commit: PilotVault multi-service project
Add API Server (Go/PocketBase), Web App (Go BFF + Vue), Fly App (Flutter/DJI MSDK), Adobe Plugin, and Docker/Docker AIO deployment configs. Design assets and build artifacts are gitignored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
No files matched your search
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,91 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- ── Permissions required by the DJI Mobile SDK ─────────────────────────── -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
|
||||
<!-- Biometric / face authentication (local_auth). -->
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
|
||||
<!-- USB host/accessory: DJI remote controllers connect to the phone over USB. -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.usb.host"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.usb.accessory"
|
||||
android:required="false" />
|
||||
|
||||
<application
|
||||
android:label="PilotVault Fly"
|
||||
android:name=".DjiApplication"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:allowBackup="false"
|
||||
android:usesCleartextTraffic="true">
|
||||
|
||||
<!-- DJI MSDK V4 uses the legacy Apache HTTP stack (org.apache.http.*),
|
||||
which was removed from the default classpath in Android 9 (API 28).
|
||||
Without this, registerApp() crashes with NoClassDefFoundError on
|
||||
org.apache.http.params.BasicHttpParams. -->
|
||||
<uses-library
|
||||
android:name="org.apache.http.legacy"
|
||||
android:required="false" />
|
||||
|
||||
<!-- DJI App Key. The value is injected from gradle.properties (DJI_API_KEY). -->
|
||||
<meta-data
|
||||
android:name="com.dji.sdk.API_KEY"
|
||||
android:value="${DJI_API_KEY}" />
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Launch / route the app when a DJI product is attached over USB. -->
|
||||
<intent-filter>
|
||||
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
|
||||
android:resource="@xml/accessory_filter" />
|
||||
</activity>
|
||||
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT" />
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.dji.flutter.dji_msdk_sample
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import com.cySdkyc.clx.Helper
|
||||
|
||||
/**
|
||||
* The DJI Mobile SDK V4 relocates and lazily loads its classes through the
|
||||
* Secneo [Helper]. It MUST be installed in [attachBaseContext] — before any
|
||||
* DJI class is touched — otherwise SDK registration crashes with a
|
||||
* NoClassDefFoundError / UnsatisfiedLinkError.
|
||||
*
|
||||
* Registered in AndroidManifest.xml via android:name=".DjiApplication".
|
||||
*/
|
||||
class DjiApplication : Application() {
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
super.attachBaseContext(base)
|
||||
// Unpacks and prepares the DJI SDK native/dex payload.
|
||||
Helper.install(this)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package com.dji.flutter.dji_msdk_sample
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import dji.common.battery.BatteryState
|
||||
import dji.common.error.DJIError
|
||||
import dji.common.error.DJISDKError
|
||||
import dji.common.flightcontroller.FlightControllerState
|
||||
import dji.sdk.base.BaseComponent
|
||||
import dji.sdk.base.BaseProduct
|
||||
import dji.sdk.products.Aircraft
|
||||
import dji.sdk.sdkmanager.DJISDKInitEvent
|
||||
import dji.sdk.sdkmanager.DJISDKManager
|
||||
import io.flutter.plugin.common.BinaryMessenger
|
||||
import io.flutter.plugin.common.EventChannel
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
|
||||
/**
|
||||
* Bridges the DJI Mobile SDK V4 to Flutter.
|
||||
*
|
||||
* - [METHOD_CHANNEL] handles imperative calls from Dart (register, connect, query).
|
||||
* - [EVENT_CHANNEL] streams registration / connection / telemetry updates to Dart.
|
||||
*
|
||||
* All SDK callbacks arrive on arbitrary threads, so every event is marshalled to
|
||||
* the main thread before being pushed into the Flutter [EventChannel.EventSink].
|
||||
*/
|
||||
class DjiSdkBridge(
|
||||
private val appContext: Context,
|
||||
messenger: BinaryMessenger,
|
||||
) : MethodChannel.MethodCallHandler, EventChannel.StreamHandler {
|
||||
|
||||
companion object {
|
||||
private const val METHOD_CHANNEL = "dji_msdk/methods"
|
||||
private const val EVENT_CHANNEL = "dji_msdk/events"
|
||||
}
|
||||
|
||||
private val mainHandler = Handler(Looper.getMainLooper())
|
||||
private val methodChannel = MethodChannel(messenger, METHOD_CHANNEL)
|
||||
private val eventChannel = EventChannel(messenger, EVENT_CHANNEL)
|
||||
|
||||
private var eventSink: EventChannel.EventSink? = null
|
||||
|
||||
init {
|
||||
methodChannel.setMethodCallHandler(this)
|
||||
eventChannel.setStreamHandler(this)
|
||||
}
|
||||
|
||||
// ── MethodChannel ──────────────────────────────────────────────────────────
|
||||
|
||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||
when (call.method) {
|
||||
"getSdkVersion" ->
|
||||
result.success(DJISDKManager.getInstance().sdkVersion)
|
||||
|
||||
"registerApp" -> {
|
||||
registerApp()
|
||||
result.success(null)
|
||||
}
|
||||
|
||||
"startConnection" ->
|
||||
result.success(DJISDKManager.getInstance().startConnectionToProduct())
|
||||
|
||||
"stopConnection" -> {
|
||||
DJISDKManager.getInstance().stopConnectionToProduct()
|
||||
result.success(null)
|
||||
}
|
||||
|
||||
"getProductInfo" ->
|
||||
result.success(connectionMap(DJISDKManager.getInstance().product))
|
||||
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
// ── EventChannel ───────────────────────────────────────────────────────────
|
||||
|
||||
override fun onListen(arguments: Any?, sink: EventChannel.EventSink?) {
|
||||
eventSink = sink
|
||||
}
|
||||
|
||||
override fun onCancel(arguments: Any?) {
|
||||
eventSink = null
|
||||
}
|
||||
|
||||
private fun emit(event: Map<String, Any?>) {
|
||||
mainHandler.post { eventSink?.success(event) }
|
||||
}
|
||||
|
||||
// ── DJI SDK registration & product lifecycle ─────────────────────────────────
|
||||
|
||||
private fun registerApp() {
|
||||
emit(mapOf("type" to "registration", "state" to "registering"))
|
||||
|
||||
DJISDKManager.getInstance().registerApp(
|
||||
appContext,
|
||||
object : DJISDKManager.SDKManagerCallback {
|
||||
|
||||
override fun onRegister(error: DJIError?) {
|
||||
if (error == DJISDKError.REGISTRATION_SUCCESS) {
|
||||
emit(mapOf("type" to "registration", "state" to "success"))
|
||||
// Begin scanning for an attached product (USB RC / Wi-Fi).
|
||||
DJISDKManager.getInstance().startConnectionToProduct()
|
||||
} else {
|
||||
emit(
|
||||
mapOf(
|
||||
"type" to "registration",
|
||||
"state" to "failed",
|
||||
"error" to (error?.description ?: "Unknown registration error"),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onProductConnect(product: BaseProduct?) {
|
||||
emit(connectionMap(product))
|
||||
bindComponentCallbacks(product)
|
||||
}
|
||||
|
||||
override fun onProductChanged(product: BaseProduct?) {
|
||||
emit(connectionMap(product))
|
||||
bindComponentCallbacks(product)
|
||||
}
|
||||
|
||||
override fun onProductDisconnect() {
|
||||
emit(mapOf("type" to "connection", "connected" to false, "model" to null))
|
||||
}
|
||||
|
||||
override fun onComponentChange(
|
||||
key: BaseProduct.ComponentKey?,
|
||||
oldComponent: BaseComponent?,
|
||||
newComponent: BaseComponent?,
|
||||
) {
|
||||
// A component (e.g. flight controller, battery) appeared/changed —
|
||||
// (re)attach the telemetry callbacks.
|
||||
bindComponentCallbacks(DJISDKManager.getInstance().product)
|
||||
}
|
||||
|
||||
override fun onInitProcess(event: DJISDKInitEvent?, totalProcess: Int) {
|
||||
emit(mapOf("type" to "init", "event" to event?.toString()))
|
||||
}
|
||||
|
||||
override fun onDatabaseDownloadProgress(current: Long, total: Long) {
|
||||
emit(mapOf("type" to "database", "current" to current, "total" to total))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun connectionMap(product: BaseProduct?): Map<String, Any?> {
|
||||
val connected = product != null && product.isConnected
|
||||
val model = product?.model?.displayName
|
||||
return mapOf("type" to "connection", "connected" to connected, "model" to model)
|
||||
}
|
||||
|
||||
/** Attaches flight-controller and battery state listeners when on an aircraft. */
|
||||
private fun bindComponentCallbacks(product: BaseProduct?) {
|
||||
if (product !is Aircraft) return
|
||||
|
||||
product.flightController?.setStateCallback { state: FlightControllerState ->
|
||||
val location = state.aircraftLocation
|
||||
emit(
|
||||
mapOf(
|
||||
"type" to "telemetry",
|
||||
"satelliteCount" to state.satelliteCount,
|
||||
"isFlying" to state.isFlying,
|
||||
"flightMode" to state.flightModeString,
|
||||
"altitude" to location?.altitude,
|
||||
"latitude" to location?.latitude,
|
||||
"longitude" to location?.longitude,
|
||||
"velocityX" to state.velocityX,
|
||||
"velocityY" to state.velocityY,
|
||||
"velocityZ" to state.velocityZ,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
product.battery?.setStateCallback { batteryState: BatteryState ->
|
||||
emit(
|
||||
mapOf(
|
||||
"type" to "battery",
|
||||
"percent" to batteryState.chargeRemainingInPercent,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.dji.flutter.dji_msdk_sample
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.SurfaceTexture
|
||||
import android.view.TextureView
|
||||
import android.view.View
|
||||
import dji.sdk.camera.VideoFeeder
|
||||
import dji.sdk.codec.DJICodecManager
|
||||
import io.flutter.plugin.common.StandardMessageCodec
|
||||
import io.flutter.plugin.platform.PlatformView
|
||||
import io.flutter.plugin.platform.PlatformViewFactory
|
||||
|
||||
/**
|
||||
* Flutter [PlatformView] that renders the DJI product's live H.264 primary video
|
||||
* feed as a full-bleed background for the Flight Control HUD.
|
||||
*
|
||||
* Pipeline: a [TextureView]'s [SurfaceTexture] is handed to a [DJICodecManager]
|
||||
* (hardware decoder). [VideoFeeder]'s primary-feed data listener pushes raw
|
||||
* frames straight into the decoder, which renders onto the surface.
|
||||
*
|
||||
* Registered under [VIEW_TYPE] in `MainActivity.configureFlutterEngine`; embedded
|
||||
* on the Dart side by `AndroidView(viewType: 'dji_msdk/video')`.
|
||||
*
|
||||
* Note: this uses the *primary* video feed, which is correct for the vast
|
||||
* majority of products. A few older transcoding models (e.g. Mavic Pro) expose
|
||||
* their live view only on the transcoded feed — if such a product renders black,
|
||||
* switch to `VideoFeeder.getInstance().provideTranscodedVideoFeed()`.
|
||||
*/
|
||||
class DjiVideoView(context: Context) : PlatformView, TextureView.SurfaceTextureListener {
|
||||
|
||||
companion object {
|
||||
const val VIEW_TYPE = "dji_msdk/video"
|
||||
}
|
||||
|
||||
private val textureView = TextureView(context).also {
|
||||
it.surfaceTextureListener = this
|
||||
}
|
||||
|
||||
private var codecManager: DJICodecManager? = null
|
||||
|
||||
// Pushes raw H.264 frames from the SDK straight into the hardware decoder.
|
||||
private val videoDataListener = VideoFeeder.VideoDataListener { data, size ->
|
||||
codecManager?.sendDataToDecoder(data, size)
|
||||
}
|
||||
|
||||
override fun getView(): View = textureView
|
||||
|
||||
override fun dispose() {
|
||||
teardown()
|
||||
}
|
||||
|
||||
// ── TextureView.SurfaceTextureListener ───────────────────────────────────
|
||||
|
||||
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
|
||||
if (codecManager == null) {
|
||||
codecManager = DJICodecManager(textureView.context, surface, width, height)
|
||||
}
|
||||
VideoFeeder.getInstance()?.primaryVideoFeed?.addVideoDataListener(videoDataListener)
|
||||
}
|
||||
|
||||
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {
|
||||
codecManager?.onSurfaceSizeChanged(width, height, 0)
|
||||
}
|
||||
|
||||
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
|
||||
teardown()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) = Unit
|
||||
|
||||
/** Detaches the feed listener and releases the decoder + its surface. */
|
||||
private fun teardown() {
|
||||
VideoFeeder.getInstance()?.primaryVideoFeed?.removeVideoDataListener(videoDataListener)
|
||||
codecManager?.let {
|
||||
it.cleanSurface()
|
||||
it.destroyCodec()
|
||||
}
|
||||
codecManager = null
|
||||
}
|
||||
}
|
||||
|
||||
/** Builds a [DjiVideoView] for each `AndroidView(viewType: 'dji_msdk/video')`. */
|
||||
class DjiVideoViewFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
|
||||
override fun create(context: Context, viewId: Int, args: Any?): PlatformView =
|
||||
DjiVideoView(context)
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.dji.flutter.dji_msdk_sample
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
|
||||
// FlutterFragmentActivity (not FlutterActivity) is required by local_auth so the
|
||||
// platform BiometricPrompt can attach to a FragmentActivity host.
|
||||
class MainActivity : FlutterFragmentActivity() {
|
||||
|
||||
private var bridge: DjiSdkBridge? = null
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
bridge = DjiSdkBridge(applicationContext, flutterEngine.dartExecutor.binaryMessenger)
|
||||
// Live DJI video feed rendered behind the Flight Control HUD.
|
||||
flutterEngine.platformViewsController.registry
|
||||
.registerViewFactory(DjiVideoView.VIEW_TYPE, DjiVideoViewFactory())
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
requestDjiPermissions()
|
||||
}
|
||||
|
||||
/**
|
||||
* The DJI SDK needs these dangerous permissions granted before it can connect
|
||||
* to / communicate with a product. We request them up front from the host
|
||||
* Activity so the Flutter UI can stay focused on the SDK flow.
|
||||
*/
|
||||
private fun requestDjiPermissions() {
|
||||
val missing = REQUIRED_PERMISSIONS.filter {
|
||||
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
if (missing.isNotEmpty()) {
|
||||
ActivityCompat.requestPermissions(this, missing.toTypedArray(), PERMISSION_REQUEST_CODE)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PERMISSION_REQUEST_CODE = 12321
|
||||
|
||||
private val REQUIRED_PERMISSIONS = buildList {
|
||||
add(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
add(Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||
add(Manifest.permission.READ_PHONE_STATE)
|
||||
add(Manifest.permission.RECORD_AUDIO)
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
|
||||
add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
}
|
||||
}.toTypedArray()
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground>
|
||||
<inset
|
||||
android:drawable="@drawable/ic_launcher_foreground"
|
||||
android:inset="16%" />
|
||||
</foreground>
|
||||
</adaptive-icon>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#0F1E3D</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- DJI remote controllers / aircraft enumerated as USB accessories. -->
|
||||
<usb-accessory model="T600" manufacturer="DJI" />
|
||||
<usb-accessory model="AG410" manufacturer="DJI" />
|
||||
<usb-accessory model="com.dji.logiclink" manufacturer="DJI" />
|
||||
<usb-accessory model="WM160" manufacturer="DJI" />
|
||||
<usb-accessory model="WM160_FCC" manufacturer="DJI" />
|
||||
</resources>
|
||||
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||