Bläddra i källkod

1、增加大疆飞行器模拟器,用于模拟飞行,以便获取各类参数

不会爬树的猴 1 år sedan
förälder
incheckning
e5c3db5838
31 ändrade filer med 1162 tillägg och 142 borttagningar
  1. 0 12
      .idea/deploymentTargetDropDown.xml
  2. 7 0
      .idea/misc.xml
  3. 3 0
      app/build.gradle
  4. 2 0
      app/src/main/java/com/cr/adapter/CrAdapter.kt
  5. 4 7
      app/src/main/java/com/cr/adapter/DownloadDataAdapter.kt
  6. 16 0
      app/src/main/java/com/cr/common/CrDataCommon.kt
  7. 59 1
      app/src/main/java/com/cr/common/CrFlightControlInfo.kt
  8. 19 3
      app/src/main/java/com/cr/cruav/AvLogin.kt
  9. 22 0
      app/src/main/java/com/cr/cruav/AvMain.kt
  10. 8 0
      app/src/main/java/com/cr/cruav/CrApplication.kt
  11. 2 0
      app/src/main/java/com/cr/data/utils.kt
  12. 13 2
      app/src/main/java/com/cr/dialog/DialogLoadingUtil.kt
  13. 45 0
      app/src/main/java/com/cr/event/EventMapCapture.kt
  14. 23 0
      app/src/main/java/com/cr/map/MapStateModel.kt
  15. 19 0
      app/src/main/java/com/cr/map/MapType.kt
  16. 27 4
      app/src/main/java/com/cr/models/SetItemModel.kt
  17. 71 5
      app/src/main/java/com/cr/pages/CrFragment.kt
  18. 67 7
      app/src/main/java/com/cr/pages/FragmentDynamicInfo.kt
  19. 7 4
      app/src/main/java/com/cr/pages/FragmentFPV.kt
  20. 247 49
      app/src/main/java/com/cr/pages/FragmentMap.kt
  21. 66 1
      app/src/main/java/com/cr/pages/FragmentSet.kt
  22. 9 0
      app/src/main/java/com/cr/pages/FragmentSetMain.kt
  23. 204 0
      app/src/main/java/com/cr/pages/FragmentSimulator.kt
  24. 7 2
      app/src/main/java/com/cr/pages/FragmentTopInfo.kt
  25. 47 10
      app/src/main/java/com/cr/viewmodel/CrFlightControlVM.kt
  26. 16 5
      app/src/main/java/com/cr/widget/CrCompassWidget.kt
  27. 14 11
      app/src/main/res/layout/av_main.xml
  28. 68 0
      app/src/main/res/layout/frag_dji_simulator.xml
  29. 45 15
      app/src/main/res/layout/frag_dynamic_info.xml
  30. 14 4
      app/src/main/res/values/strings.xml
  31. 11 0
      app/src/main/res/values/themes.xml

+ 0 - 12
.idea/deploymentTargetDropDown.xml

@@ -1,18 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="deploymentTargetDropDown">
-    <runningDeviceTargetSelectedWithDropDown>
-      <Target>
-        <type value="RUNNING_DEVICE_TARGET" />
-        <deviceKey>
-          <Key>
-            <type value="SERIAL_NUMBER" />
-            <value value="10.88.88.112:5555" />
-          </Key>
-        </deviceKey>
-      </Target>
-    </runningDeviceTargetSelectedWithDropDown>
-    <timeTargetWasSelectedWithDropDown value="2023-08-05T00:47:36.314218Z" />
     <runningDeviceTargetsSelectedWithDialog>
       <Target>
         <type value="RUNNING_DEVICE_TARGET" />

+ 7 - 0
.idea/misc.xml

@@ -6,4 +6,11 @@
   <component name="ProjectType">
     <option name="id" value="Android" />
   </component>
+  <component name="VisualizationToolProject">
+    <option name="state">
+      <ProjectState>
+        <option name="scale" value="0.03820412716118238" />
+      </ProjectState>
+    </option>
+  </component>
 </project>

+ 3 - 0
app/build.gradle

@@ -107,4 +107,7 @@ dependencies {
 
     //TODO 引入屏幕适配框架
     implementation 'me.jessyan:autosize:1.2.1'
+
+    //TODO 地理计算工具
+    implementation 'com.mapbox.mapboxsdk:mapbox-sdk-turf:6.13.0'
 }

+ 2 - 0
app/src/main/java/com/cr/adapter/CrAdapter.kt

@@ -27,6 +27,8 @@ open class CrAdapter<T>@JvmOverloads constructor(
     // define: 2023/4/11 视图管理器
     var inflater: LayoutInflater? = null
 
+    protected var mainHandler = Handler(Looper.getMainLooper())
+
     /**
      *  初始化
      */

+ 4 - 7
app/src/main/java/com/cr/adapter/DownloadDataAdapter.kt

@@ -11,6 +11,7 @@ import android.widget.ImageView
 import android.widget.ProgressBar
 import android.widget.TextView
 import com.cr.common.CrFileManager
+import com.cr.cruav.CrApplication
 import com.cr.cruav.R
 import com.cr.data.CrUtil
 import com.cr.dialog.DialogNormal
@@ -28,12 +29,6 @@ class DownloadDataAdapter @JvmOverloads constructor(
     context: Context,
     dataList: MutableList<DownloadDataModel>
 ) : CrAdapter<DownloadDataModel>(context, dataList),View.OnClickListener {
-    // todo: 2023/4/11 多线程
-    private val handler:Handler = object:Handler(Looper.getMainLooper()){
-        override fun handleMessage(msg: Message) {
-            super.handleMessage(msg)
-        }
-    }
     companion object {
         class ViewHolder {
             var lblTitle: TextView? = null
@@ -109,7 +104,9 @@ class DownloadDataAdapter @JvmOverloads constructor(
 
                     // todo: 2023/4/12 下载错误
                     override fun onFailed(message: String) {
-                        DialogNormal(context!!,"警告",message).show()
+                        mainHandler.post {
+                            DialogNormal(CrApplication.getContext(),"警告",message).show()
+                        }
                     }
 
                     // todo: 2023/4/12 下载进度

+ 16 - 0
app/src/main/java/com/cr/common/CrDataCommon.kt

@@ -0,0 +1,16 @@
+package com.cr.common
+
+/**
+ * 操作系统:MAC系统
+ * 创建者:王成
+ * 创建日期:2023/8/15 15:46
+ * 描述:data类型基础类
+ */
+open class CrDataCommon {
+    /**
+     * 重置函数
+     */
+    open fun reset(){
+
+    }
+}

+ 59 - 1
app/src/main/java/com/cr/common/CrFlightControlInfo.kt

@@ -1,5 +1,8 @@
 package com.cr.common
 
+import com.cr.cruav.CrApplication
+import com.cr.cruav.R
+import com.cr.data.DEFAULT_LOCATION
 import com.cr.data.DEFAULT_STR
 
 /**
@@ -8,7 +11,7 @@ import com.cr.data.DEFAULT_STR
  * 创建日期:2023/3/9 15:57
  * 描述:飞行器信息
  */
-data class CrFlightControlInfo(var isConnection: Boolean = false) {
+data class CrFlightControlInfo(var isConnection: Boolean = false) :CrDataCommon(){
     // define: 2023/3/9 电池电量
     var batteryPercent: Int = 0
 
@@ -23,4 +26,59 @@ data class CrFlightControlInfo(var isConnection: Boolean = false) {
 
     // define: 2023/3/14 指南针状态
     var compassState: String = DEFAULT_STR
+
+    // define: 2023/8/15 飞行器位置
+    var longitude:Double = 0.0
+    var latitude:Double = 0.0
+    var altitude:Double = 0.0
+    var takeoffAltitude:Double = 0.0  // define: 2023/8/15 起飞海拔
+
+    var longitudeStr:String = DEFAULT_LOCATION
+    var latitudeStr:String = DEFAULT_LOCATION
+    var altitudeStr:String = DEFAULT_STR
+    var takeoffAltitudeStr:String = DEFAULT_STR
+
+    // define: 2023/8/15 速度
+    var speedX:Double = 0.0
+    var speedY:Double = 0.0
+    var speedZ:Double = 0.0
+
+    // define: 2023/8/15 返航位置
+    var homeLongitude:Double = 0.0
+    var homeLatitude:Double = 0.0
+    var homeDistance:Double = 0.0
+    var homeAltitude:Double = 0.0
+    var homeDistanceStr:String = DEFAULT_STR
+    var homeAltitudeStr:String = DEFAULT_STR
+
+
+
+    /**
+     * 重置
+     */
+    override fun reset() {
+        isConnection = false
+        batteryPercent = 0
+        productName = DEFAULT_STR
+        gpsCount = DEFAULT_STR
+        imuState = DEFAULT_STR
+        compassState = DEFAULT_STR
+        longitude = 0.0
+        latitude = 0.0
+        altitude = 0.0
+        takeoffAltitude = 0.0
+        longitudeStr = DEFAULT_LOCATION
+        latitudeStr = DEFAULT_LOCATION
+        altitudeStr = DEFAULT_STR
+        takeoffAltitudeStr = DEFAULT_STR
+        speedX = 0.0
+        speedY = 0.0
+        speedZ = 0.0
+        homeLongitude = 0.0
+        homeLatitude = 0.0
+        homeAltitude = 0.0
+        homeDistance = 0.0
+        homeDistanceStr = DEFAULT_STR
+        homeAltitudeStr = DEFAULT_STR
+    }
 }

+ 19 - 3
app/src/main/java/com/cr/cruav/AvLogin.kt

@@ -30,6 +30,7 @@ import com.cr.widget.CrEditTextWidget
 import com.squareup.otto.Subscribe
 import org.json.JSONArray
 import kotlinx.android.synthetic.main.av_login.*
+import kotlinx.coroutines.*
 
 class AvLogin : CrActivity(), OnClickListener {
     // define: 2023/3/31 权限列表
@@ -145,14 +146,29 @@ class AvLogin : CrActivity(), OnClickListener {
 //                    it.setBarVisible(isVisible = true)
 //                    showFragment(it)
 //                }
-                showMessage(CrUnitManager.getDeviceDpi(this))
-//                var dp:Float = context!!.resources.getDimension(R.dimen.cr_1_dp)/context!!.resources.displayMetrics.density
-//                showMessage("最小dp:${CrUnitManager.getSmallestWidthDP(context!!)} dp:$dp")
+//                showMessage(CrUnitManager.getDeviceDpi(this))
+                GlobalScope.launch {
+                    withContext(Dispatchers.IO){
+                        var one = one()
+                        var two = two(one)
+                    }
+                }
             }
 
         }
     }
 
+    private suspend fun one():String{
+        delay(5000)
+        CrUtil.print("函数一返回")
+        return "函数一测试"
+    }
+
+    private suspend fun two(demo:String){
+        delay(1000)
+        CrUtil.print(demo)
+    }
+
     /**
      * 登录验证
      * @param userName String 账号

+ 22 - 0
app/src/main/java/com/cr/cruav/AvMain.kt

@@ -11,6 +11,7 @@ import com.cr.data.CrUtil
 import com.cr.event.CrCommonAction
 import com.cr.event.EventCommon
 import com.cr.event.EventFragmentBarAction
+import com.cr.event.EventMapCapture
 import com.cr.map.CaseModel
 import com.cr.map.EventMap
 import com.cr.map.MapAction
@@ -92,6 +93,9 @@ class AvMain : CrActivity(), View.OnClickListener {
 //        init()
         // todo: 2023/4/3 订阅事件
         CrApplication.getEventBus().register(this)
+        // todo: 2023/8/14 此语句非常重要 需要创建一个静态的Context,且该上下文可以用于创建对话框
+        // todo: 2023/8/14 如果AvMain窗体关闭,则在窗体中继续执行该方法
+        CrApplication.setContext(this)
     }
 
     /**
@@ -107,6 +111,8 @@ class AvMain : CrActivity(), View.OnClickListener {
         tools_tools.setOnClickListener(this)
         tools_case_tools.setOnClickListener(this)
         tools_append_point_from_map.setOnClickListener(this)
+        tools_case_local.setOnClickListener(this)
+        tools_case_net.setOnClickListener(this)
     }
 
     /**
@@ -275,30 +281,46 @@ class AvMain : CrActivity(), View.OnClickListener {
     override fun onClick(view: View?) {
         when (view?.id) {
             R.id.tools_set -> {
+                // todo: 2023/8/14 设置工具
                 showFragment(fragmentSet!!)
             }
             R.id.tools_layer -> {
+                // todo: 2023/8/14 图层控制
                 showFragment(fragmentLayerControl!!)
                 fragmentLayerControl?.initPage()
             }
             R.id.tools_doodle -> {
+                // todo: 2023/8/14 涂鸦工具
                 showFragment(fragmentDoodle!!)
             }
             R.id.tools_mark -> {
+                // todo: 2023/8/14 标志工具
                 showFragment(fragmentMark!!)
             }
             R.id.tools_tools -> {
+                // todo: 2023/8/14 工具箱
                 showFragment(fragmentTools!!)
             }
             R.id.tools_case_tools -> {
+                // todo: 2023/8/14 案件工具箱
                 showFragment(fragmentCaseTools!!)
             }
             R.id.tools_append_point_from_map -> {
+                // todo: 2023/8/14 添加案件点
                 CrApplication.getEventBus().post(EventMap(MapAction.MapTapAddWaypoint))
             }
             R.id.tools_wx -> {
+                // todo: 2023/8/14 微信分享
                 CrApplication.getEventBus().post(EventMap(MapAction.MapTapCaseWxAndUpload))
             }
+            R.id.tools_case_local->{
+                // todo: 2023/8/14 本地案件查询
+                CrApplication.getEventBus().post(EventMapCapture(EventMapCapture.CaptureAction.CAPTURE_ACTION_START,"Case20230103",null))
+            }
+            R.id.tools_case_net->{
+                // todo: 2023/8/14 网络案件查询
+
+            }
         }
     }
 

+ 8 - 0
app/src/main/java/com/cr/cruav/CrApplication.kt

@@ -76,6 +76,14 @@ class CrApplication : Application() {
         fun getContext():Context{
             return context!!
         }
+
+        /**
+         * 设置上下文
+         * @param context Context
+         */
+        fun setContext(context: Context){
+            this.context = context
+        }
     }
 
 }

+ 2 - 0
app/src/main/java/com/cr/data/utils.kt

@@ -14,6 +14,7 @@ import android.widget.TextView
 import android.widget.Toast
 import androidx.annotation.RequiresApi
 import com.cr.cruav.CrApplication
+import com.cr.cruav.R
 import com.cr.dialog.DialogNormal
 import dji.sdk.keyvalue.value.camera.CameraExposureCompensation
 import dji.sdk.keyvalue.value.camera.CameraISO
@@ -24,6 +25,7 @@ import java.util.regex.Pattern
 
 const val DEFAULT_STR = "N/A"
 const val DEFAULT_TIME = "00:00:00"
+const val DEFAULT_LOCATION = "000.00000"
 const val DEFAULT_RES_ID = -1
 const val IN_INNER_NETWORK_STR = "IN_INNER"
 const val IN_OUT_NETWORK_STR = "IN_OUT"

+ 13 - 2
app/src/main/java/com/cr/dialog/DialogLoadingUtil.kt

@@ -1,6 +1,5 @@
 package com.cr.dialog
 
-import android.app.Activity
 import android.app.AlertDialog
 import android.content.Context
 import android.view.LayoutInflater
@@ -8,7 +7,6 @@ import android.view.View
 import android.view.WindowManager
 import android.widget.TextView
 import com.cr.cruav.R
-import java.lang.ref.WeakReference
 
 /**
  * 操作系统:MAC系统
@@ -71,5 +69,18 @@ class DialogLoadingUtil {
             }
         }
 
+        /**
+         * 更新消息内容
+         * @param message String 消息内容
+         */
+        fun update(message:String){
+            loadingDialog?.let {
+                if(it.isShowing){
+                    var lblMessage:TextView = it.findViewById(R.id.load_message)
+                    lblMessage?.text = message
+                }
+            }
+        }
+
     }
 }

+ 45 - 0
app/src/main/java/com/cr/event/EventMapCapture.kt

@@ -0,0 +1,45 @@
+package com.cr.event
+
+/**
+ * 操作系统:MAC系统
+ * 创建者:王成
+ * 创建日期:2023/8/14 14:44
+ * 描述:地图截屏事件
+ */
+class EventMapCapture @JvmOverloads constructor(
+    action:CaptureAction = CaptureAction.CAPTURE_ACTION_NON,
+    joinCaseName:String?=null,
+    imageNames:List<String>?= null
+) {
+    enum class CaptureAction{
+        /**
+         * 开始截屏
+         */
+        CAPTURE_ACTION_START,
+
+        /**
+         * 截屏完成
+         */
+        CAPTURE_ACTION_COMPLETE,
+
+        /**
+         * 状态未知
+         */
+        CAPTURE_ACTION_NON
+    }
+    // define: 2023/8/14 截屏动作
+    var action:CaptureAction = CaptureAction.CAPTURE_ACTION_NON
+    // define: 2023/8/14 截屏图片名称
+    var imageNames:List<String>?=null
+    // define: 2023/8/14 关联的案件名称
+    var joinCaseName:String?= null
+
+    /**
+     * 初始化
+     */
+    init {
+        this.action = action
+        this.imageNames = imageNames
+        this.joinCaseName = joinCaseName
+    }
+}

+ 23 - 0
app/src/main/java/com/cr/map/MapStateModel.kt

@@ -0,0 +1,23 @@
+package com.cr.map
+
+/**
+ * 操作系统:MAC系统
+ * 创建者:王成
+ * 创建日期:2023/8/8 08:50
+ * 描述:地图图层状态
+ */
+class MapStateModel @JvmOverloads constructor(
+    index:Int = 0,
+    visible:Boolean = false,
+    mapType: MapType = MapType.MAP_TYPE_O_LAYER
+){
+    var index:Int = 0  // define: 2023/8/8 图层索引
+    var visible:Boolean = false  // define: 2023/8/8 图层是否显示
+    var mapType:MapType = MapType.MAP_TYPE_O_LAYER  // define: 2023/8/8 图层类型
+
+    init {
+        this.index = index
+        this.visible = visible
+        this.mapType = mapType
+    }
+}

+ 19 - 0
app/src/main/java/com/cr/map/MapType.kt

@@ -0,0 +1,19 @@
+package com.cr.map
+
+/**
+ * 操作系统:MAC系统
+ * 创建者:王成
+ * 创建日期:2023/8/8 08:52
+ * 描述:地图图层类型
+ */
+enum class MapType {
+    /**
+     * 操作图层
+     */
+    MAP_TYPE_O_LAYER,
+
+    /**
+     * 绘制图层
+     */
+    MAP_TYPE_D_LAYER,
+}

+ 27 - 4
app/src/main/java/com/cr/models/SetItemModel.kt

@@ -8,10 +8,10 @@ package com.cr.models
  */
 class SetItemModel {
     var imageId = 0
-    var title  : String? = null
+    var title: String? = null
     var isArrow = false
     var isSwitch = false
-    var action : SettingAction? = null
+    var action: SettingAction? = null
     var switchIsOn = false
 
     /**
@@ -24,7 +24,14 @@ class SetItemModel {
      * @param switchIsOn Boolean 开关是否开启
      * @constructor
      */
-    constructor(imgId:Int,title:String,isArrow:Boolean,isSwitch:Boolean,action: SettingAction,switchIsOn:Boolean){
+    constructor(
+        imgId: Int,
+        title: String,
+        isArrow: Boolean,
+        isSwitch: Boolean,
+        action: SettingAction,
+        switchIsOn: Boolean
+    ) {
         this.imageId = imgId
         this.title = title
         this.isArrow = isArrow
@@ -37,53 +44,69 @@ class SetItemModel {
 /**
  * 操作类型
  */
-enum class SettingAction{
+enum class SettingAction {
     /**
      * 登录大疆账号
      */
     SA_DJI_LOGIN,
+
     /**
      * 下载数据
      */
     SA_DOWNLOAD_DATA,
+
     /**
      * 地图居中
      */
     SA_MAP_CENTER,
+
     /**
      * 设置IP和COM
      */
     SA_SET_IP_COM,
+
     /**
      * 查看历史航线
      */
     SA_OLD_AIRLINE,
+
     /**
      * 重新登录系统
      */
     SA_RE_LOGIN,
+
     /**
      * 删除地图缓存
      */
     SA_DEL_MAP_CACHE,
+
     /**
      * 查看大疆飞行记录
      */
     SA_SHOW_DJI_LOG,
+
     /**
      * 删除取证数据
      */
     SA_DEL_QZ_DATA,
+
     /**
      * 删除航线数据
      */
     SA_DEL_LINE_DATA,
+
     /**
      * 删除资源数据
      */
     SA_DEL_RESOURCE_DATA,
+
     /**
      * 上传断网航点
      */
     SA_UPLOAD_NO_UP_AIR_POINT,
+
+    /**
+     * 模拟器
+     */
+    SA_SIMULATOR,
 }

+ 71 - 5
app/src/main/java/com/cr/pages/CrFragment.kt

@@ -5,8 +5,12 @@ import android.os.Handler
 import android.os.Looper
 import android.view.View
 import androidx.fragment.app.Fragment
+import com.cr.common.CrKeyManager
 import com.cr.cruav.CrApplication
+import com.cr.dialog.DialogLoadingUtil
 import com.cr.dialog.DialogNormal
+import com.cr.models.CompletionModel
+import com.cr.models.ICompletion
 
 /**
  * 操作系统:MAC系统
@@ -51,9 +55,9 @@ open class CrFragment :Fragment(){
      * @param warning String 警告消息
      */
     protected fun showWarning(warning: String) {
-        mainHandler.post(Runnable {
-            DialogNormal(context!!, "警告", warning).show()
-        })
+        mainHandler.post {
+            DialogNormal(CrApplication.getContext(), "警告", warning).show()
+        }
     }
 
     /**
@@ -61,7 +65,38 @@ open class CrFragment :Fragment(){
      * @param error String 错误消息
      */
     protected fun showError(error: String) {
-        DialogNormal(context!!, "错误", error).show()
+        mainHandler.post{
+            DialogNormal(CrApplication.getContext(), "错误", error).show()
+        }
+    }
+
+    /**
+     * 确认对话框
+     * @param message String 显示的消息内容
+     * @param actionButtonContent Array<String> 按钮文字数组 大小必须是2
+     * @param callback ICompletion<String> 回调 可以传null
+     */
+    protected fun showConfirm(message:String,actionButtonContent:Array<String>,callback:ICompletion<String>?){
+        if(actionButtonContent.size != 2) return
+        mainHandler.post {
+            var dialog = DialogNormal(CrApplication.getContext(),"确认",message)
+            dialog.setButtonsText(actionButtonContent[0],actionButtonContent[1])
+            dialog.setListener(object:DialogNormal.DialogNormalListener{
+                // todo: 2023/8/15 确认操作
+                override fun completion() {
+                    callback?.let {
+                        it.onCompletion(CompletionModel(true,""))
+                    }
+                }
+
+                // todo: 2023/8/15 关闭操作
+                override fun close() {
+
+                }
+
+            })
+            dialog.show()
+        }
     }
 
     /**
@@ -69,7 +104,38 @@ open class CrFragment :Fragment(){
      * @param information String 提示消息
      */
     protected fun showInformation(information: String) {
-        DialogNormal(CrApplication.getContext(), "提示", information).show()
+        mainHandler.post {
+            DialogNormal(CrApplication.getContext(), "提示", information).show()
+        }
+    }
+
+    /**
+     * 显示等待框
+     * @param message String 消息内容
+     */
+    protected fun showLoading(message:String){
+        mainHandler.post{
+            DialogLoadingUtil.show(CrApplication.getContext(),message)
+        }
+    }
+
+    /**
+     * 关闭等待框
+     */
+    protected fun closeLoading(){
+        mainHandler.post {
+            DialogLoadingUtil.dismiss()
+        }
+    }
+
+    /**
+     * 更新等待框内容
+     * @param message String
+     */
+    protected fun updateLoading(message:String){
+        mainHandler.post {
+            DialogLoadingUtil.update(message)
+        }
     }
 
     /**

+ 67 - 7
app/src/main/java/com/cr/pages/FragmentDynamicInfo.kt

@@ -11,15 +11,21 @@ import com.cr.common.CrCameraInfo
 import com.cr.cruav.CrApplication
 import com.cr.cruav.R
 import com.cr.data.CrUtil
+import com.cr.data.DEFAULT_STR
 import com.cr.event.CrCommonAction
 import com.cr.event.EventCommon
 import com.cr.viewmodel.CrCameraVM
+import com.cr.viewmodel.CrFlightControlVM
 import com.squareup.otto.Subscribe
 import dji.sdk.keyvalue.key.CameraKey
 import dji.sdk.keyvalue.key.KeyTools
 import dji.sdk.keyvalue.value.common.EmptyMsg
 import dji.v5.common.callback.CommonCallbacks
 import dji.v5.common.error.IDJIError
+import dji.v5.manager.account.LoginInfo
+import dji.v5.manager.account.LoginInfoUpdateListener
+import dji.v5.manager.account.LoginState
+import dji.v5.manager.account.UserAccountManager
 import org.w3c.dom.Text
 
 /**
@@ -31,6 +37,7 @@ import org.w3c.dom.Text
 class FragmentDynamicInfo : CrFragment(), View.OnClickListener {
     // todo: 2023/8/2 绑定模型
     private val cameraVm: CrCameraVM by activityViewModels()
+    private val flightControlVm: CrFlightControlVM by activityViewModels()
 
     // todo: 2023/8/2 组件定义
     private var lblCameraISO: TextView? = null // define: 2023/8/2 ISO值
@@ -44,7 +51,11 @@ class FragmentDynamicInfo : CrFragment(), View.OnClickListener {
     private var flagPhoto: TextView? = null  // define: 2023/8/3 照片模式标志
     private var flagVideo: TextView? = null // define: 2023/8/3 视频模式标志
 
-    private var lblMessage:TextView?=null // define: 2023/8/7 消息
+    private var lblMessage: TextView? = null // define: 2023/8/7 消息
+    private var lblAltitude: TextView? = null // define: 2023/8/15 相对航高
+    private var lblTakeoffAltitude:TextView? = null  // define: 2023/8/15 起飞海拔
+    private var lblDistanceByHome: TextView? = null // define: 2023/8/15 返航距离
+    private var lblAltitudeByHome:TextView?=null // define: 2023/8/15 返航高度
 
     /**
      * 初始化
@@ -64,6 +75,8 @@ class FragmentDynamicInfo : CrFragment(), View.OnClickListener {
         joinControls()
         // todo: 2023/8/7 注册事件
         CrApplication.getEventBus().register(this)
+        // todo: 2023/8/15 绑定大疆账号登录状态
+        bindDjiUserAccountLogin()
         return mainView
     }
 
@@ -93,6 +106,12 @@ class FragmentDynamicInfo : CrFragment(), View.OnClickListener {
             // todo: 2023/8/7 挂载消息控件
             lblMessage = it.findViewById(R.id.lbl_message)
 
+            // todo: 2023/8/15 挂载返航高度及相对航高
+            lblAltitude = it.findViewById(R.id.lbl_altitude)
+            lblTakeoffAltitude = it.findViewById(R.id.lbl_takeoff_altitude)
+            lblDistanceByHome = it.findViewById(R.id.lbl_home_distance)
+            lblAltitudeByHome = it.findViewById(R.id.lbl_home_altitude)
+
             // todo: 2023/8/2 挂载事件
             lblSDPhotoCount?.setOnClickListener(this)
         }
@@ -115,6 +134,8 @@ class FragmentDynamicInfo : CrFragment(), View.OnClickListener {
     override fun onDestroy() {
         // todo: 2023/8/7 取消事件监听
         CrApplication.getEventBus().unregister(this)
+        // todo: 2023/8/15 取消登录状态更新监听绑定
+        UserAccountManager.getInstance().removeLoginInfoUpdateListener(djiUserAccountLoginListener)
         super.onDestroy()
     }
 
@@ -136,22 +157,61 @@ class FragmentDynamicInfo : CrFragment(), View.OnClickListener {
                 lblSDVideoTime?.text = it.videoTime
 
                 // todo: 2023/8/4 相机模式
-                when(it.cameraModel){
-                    CrCameraInfo.CrCameraMode.NON->{
+                when (it.cameraModel) {
+                    CrCameraInfo.CrCameraMode.NON -> {
                         flagVideo?.visibility = View.GONE
                         flagPhoto?.visibility = View.GONE
                     }
-                    CrCameraInfo.CrCameraMode.PHOTO->{
+                    CrCameraInfo.CrCameraMode.PHOTO -> {
                         flagVideo?.visibility = View.GONE
                         flagPhoto?.visibility = View.VISIBLE
                     }
-                    CrCameraInfo.CrCameraMode.VIDEO->{
+                    CrCameraInfo.CrCameraMode.VIDEO -> {
                         flagVideo?.visibility = View.VISIBLE
                         flagPhoto?.visibility = View.GONE
                     }
                 }
             }
         }
+        // todo: 2023/8/15 订阅飞行器信息
+        flightControlVm.flightControlInfo.observe(requireActivity()) {
+            it?.let {
+                lblAltitude?.text = it.altitudeStr
+            }
+        }
+    }
+
+    /**
+     * 绑定大疆登录
+     */
+    private fun bindDjiUserAccountLogin() {
+        var loginState = UserAccountManager.getInstance().loginInfo.loginState
+        // todo: 2023/8/15 更新登录信息
+        updateDjiUserAccountLoginInfo(loginState)
+        // todo: 2023/8/15 绑定监听
+        UserAccountManager.getInstance().addLoginInfoUpdateListener(djiUserAccountLoginListener)
+    }
+
+    /**
+     * 更新大疆账户登录状态信息
+     * @param state LoginState 登录状态
+     */
+    private fun updateDjiUserAccountLoginInfo(state: LoginState) {
+        mainHandler.post {
+            if (state == LoginState.LOGGED_IN) {
+                lblDJILogin?.text = "已登录"
+            } else {
+                lblDJILogin?.text = DEFAULT_STR
+            }
+        }
+    }
+
+    /**
+     * 大疆账户登录变更监听
+     */
+    private val djiUserAccountLoginListener = LoginInfoUpdateListener {
+        // todo: 2023/8/15 更新登录信息
+        updateDjiUserAccountLoginInfo(it.loginState)
     }
 
     /**
@@ -178,9 +238,9 @@ class FragmentDynamicInfo : CrFragment(), View.OnClickListener {
     }
 
     @Subscribe
-    fun onMessage(event:EventCommon<String>){
+    fun onMessage(event: EventCommon<String>) {
         event?.let {
-            if(it.action == CrCommonAction.SHOW_MESSAGE){
+            if (it.action == CrCommonAction.SHOW_MESSAGE) {
                 lblMessage?.text = event.param
             }
         }

+ 7 - 4
app/src/main/java/com/cr/pages/FragmentFPV.kt

@@ -310,10 +310,13 @@ class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
      */
     override fun onResume() {
         super.onResume()
-        // todo: 2023/8/7 添加监听
-        curVideoChannel?.addStreamDataListener(streamDataListener)
-        // todo: 2023/8/7 启动图传
-        handlerYUV(true)
+        curVideoChannel?.let {
+            // todo: 2023/8/7 添加监听
+            it.addStreamDataListener(streamDataListener)
+            // todo: 2023/8/14 启用图传
+            handlerYUV(true)
+        }
+
     }
 
     /**

+ 247 - 49
app/src/main/java/com/cr/pages/FragmentMap.kt

@@ -1,28 +1,26 @@
 package com.cr.pages
 
+import android.graphics.Bitmap
 import android.graphics.Color
 import android.graphics.drawable.BitmapDrawable
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
+import com.cr.common.CrFileManager
 import com.cr.common.CrUnitManager
 import com.cr.common.DataManager
-import com.cr.common.CrFileManager
 import com.cr.cruav.CrApplication
 import com.cr.cruav.R
 import com.cr.data.CrUtil
 import com.cr.dialog.DialogInput
 import com.cr.dialog.DialogNormal
+import com.cr.event.EventMapCapture
 import com.cr.map.*
 import com.cr.models.CompletionModel
 import com.cr.models.ICompletion
 import com.esri.arcgisruntime.arcgisservices.LabelDefinition
-import com.esri.arcgisruntime.data.Feature
-import com.esri.arcgisruntime.data.FeatureTable
-import com.esri.arcgisruntime.data.Geodatabase
-import com.esri.arcgisruntime.data.QueryParameters
-import com.esri.arcgisruntime.data.ServiceFeatureTable
+import com.esri.arcgisruntime.data.*
 import com.esri.arcgisruntime.geometry.*
 import com.esri.arcgisruntime.layers.ArcGISMapImageLayer
 import com.esri.arcgisruntime.layers.ArcGISTiledLayer
@@ -36,6 +34,11 @@ import com.google.gson.JsonParser
 import com.google.gson.JsonPrimitive
 import com.squareup.otto.Subscribe
 import kotlinx.android.synthetic.main.frag_map.*
+import kotlinx.coroutines.*
+import java.io.File
+import java.io.FileOutputStream
+import java.lang.Runnable
+import java.util.*
 import java.util.concurrent.ExecutionException
 import kotlin.math.abs
 
@@ -171,6 +174,10 @@ class FragmentMap : CrAnimationFragment() {
     // todo: 2023/6/15 内部监听 自行设置
     private var eventListener: EventListener? = null
 
+    // todo: 2023/8/8 截屏相关
+    private var mapStateList = mutableListOf<MapStateModel>() // define: 2023/8/14 地图状态
+    private var captureNames = mutableListOf<String>()  // define: 2023/8/14 截屏图片名称
+
 
     /**
      * 重写创建View方法
@@ -1196,7 +1203,12 @@ class FragmentMap : CrAnimationFragment() {
                     asyncUpdate?.addDoneListener(Runnable {
                         try {
                             if (asyncUpdate.isDone) {
-                                if (callback != null) callback.onCompletion(CompletionModel(true, ""))
+                                if (callback != null) callback.onCompletion(
+                                    CompletionModel(
+                                        true,
+                                        ""
+                                    )
+                                )
                             }
                         } catch (ex: java.lang.IllegalArgumentException) {
                             if (callback != null) callback.onCompletion(
@@ -1289,7 +1301,7 @@ class FragmentMap : CrAnimationFragment() {
      */
     private fun caseRemoveCaseWaypoing(screenPoint: android.graphics.Point) {
         // todo: 2023/6/14 开始查询
-        var identifyAsync = map_mapView?.identifyLayerAsync(fLayerMedia,screenPoint,6.0,false)
+        var identifyAsync = map_mapView?.identifyLayerAsync(fLayerMedia, screenPoint, 6.0, false)
         identifyAsync?.addDoneListener(Runnable {
             try {
                 if (identifyAsync.isDone) {
@@ -1305,15 +1317,16 @@ class FragmentMap : CrAnimationFragment() {
                         dig.setListener(object : DialogNormal.DialogNormalListener {
                             // todo: 2023/6/14 确认删除
                             override fun completion() {
-                                caseRemoveWaypointAndUpdateCasePolygon(features,object:ICompletion<String>{
-                                    override fun onCompletion(completion: CompletionModel<String>) {
-                                        if(completion.isSuccess == true){
-                                            CrUtil.showToast("删除成功!")
-                                        }else{
-                                            showError(completion.result!!)
+                                caseRemoveWaypointAndUpdateCasePolygon(features,
+                                    object : ICompletion<String> {
+                                        override fun onCompletion(completion: CompletionModel<String>) {
+                                            if (completion.isSuccess == true) {
+                                                CrUtil.showToast("删除成功!")
+                                            } else {
+                                                showError(completion.result!!)
+                                            }
                                         }
-                                    }
-                                })
+                                    })
                             }
 
                             // todo: 2023/6/14 关闭
@@ -1337,20 +1350,23 @@ class FragmentMap : CrAnimationFragment() {
      * @param features List<Feature> 案件点集合
      * @param callback iCompletion 完成回调
      */
-    private fun caseRemoveWaypointAndUpdateCasePolygon(features:List<Feature>,callback: ICompletion<String>){
+    private fun caseRemoveWaypointAndUpdateCasePolygon(
+        features: List<Feature>,
+        callback: ICompletion<String>
+    ) {
         var deleteAsync = fTableMedia?.deleteFeaturesAsync(features)
         deleteAsync?.addDoneListener(Runnable {
             try {
                 if (deleteAsync.isDone) {
                     // todo: 2023/6/16 更新
                     var wheres = mutableListOf<String>()
-                    for(fea in features){
+                    for (fea in features) {
                         wheres.add(fea.attributes[FIELD_CASE_NAME].toString())
                     }
-                    caseUpdateCasePolygonByWhere(wheres,callback)
+                    caseUpdateCasePolygonByWhere(wheres, callback)
                 }
             } catch (ex: java.lang.IllegalArgumentException) {
-                if (callback != null) callback.onCompletion(CompletionModel(false,"案件点删除失败!"))
+                if (callback != null) callback.onCompletion(CompletionModel(false, "案件点删除失败!"))
             }
         })
     }
@@ -1360,25 +1376,25 @@ class FragmentMap : CrAnimationFragment() {
      * @param wheres List<String> 案件Id的集合
      * @param callback iCompletion 完成回调
      */
-    private fun caseUpdateCasePolygonByWhere(wheres:List<String>,callback:ICompletion<String>){
+    private fun caseUpdateCasePolygonByWhere(wheres: List<String>, callback: ICompletion<String>) {
         var updateWhere = ""
         // todo: 2023/6/16 组合条件
-        for(where in wheres){
-            updateWhere += if(updateWhere == ""){
-                String.format("'%s'",where)
-            }else{
-                String.format(",'%s'",where)
+        for (where in wheres) {
+            updateWhere += if (updateWhere == "") {
+                String.format("'%s'", where)
+            } else {
+                String.format(",'%s'", where)
             }
         }
         // todo: 2023/6/16 开始查询
         var queryParameters = QueryParameters()
         queryParameters.whereClause = updateWhere
         var asyncQuery = fTableCasePolygon?.queryFeaturesAsync(queryParameters)
-        asyncQuery?.addDoneListener(Runnable{
+        asyncQuery?.addDoneListener(Runnable {
             try {
-                if(asyncQuery.isDone){
+                if (asyncQuery.isDone) {
                     var features = mutableListOf<Feature>()
-                    for(fea in asyncQuery.get()){
+                    for (fea in asyncQuery.get()) {
                         fea.attributes[FIELD_CASE_POLYGON_ANJID] = ""
                         fea.attributes[FIELD_CASE_POLYGON_BZ] = FIELD_CASE_POLYGON_BZ_VALUE_NO
                         features.add(fea)
@@ -1387,16 +1403,26 @@ class FragmentMap : CrAnimationFragment() {
                     var asyncUpdate = fTableCasePolygon?.updateFeaturesAsync(features)
                     asyncUpdate?.addDoneListener(Runnable {
                         try {
-                            if(asyncUpdate.isDone){
-                                if(callback != null) callback.onCompletion(CompletionModel(true,""))
+                            if (asyncUpdate.isDone) {
+                                if (callback != null) callback.onCompletion(
+                                    CompletionModel(
+                                        true,
+                                        ""
+                                    )
+                                )
                             }
-                        }catch (ex:java.lang.IllegalArgumentException){
-                            if(callback != null) callback.onCompletion(CompletionModel(false,"关联案件更新失败!"))
+                        } catch (ex: java.lang.IllegalArgumentException) {
+                            if (callback != null) callback.onCompletion(
+                                CompletionModel(
+                                    false,
+                                    "关联案件更新失败!"
+                                )
+                            )
                         }
                     })
                 }
-            }catch(ex:java.lang.IllegalArgumentException) {
-                if(callback != null) callback.onCompletion(CompletionModel(false,"关联案件查询失败!"))
+            } catch (ex: java.lang.IllegalArgumentException) {
+                if (callback != null) callback.onCompletion(CompletionModel(false, "关联案件查询失败!"))
             }
         })
     }
@@ -1469,20 +1495,22 @@ class FragmentMap : CrAnimationFragment() {
                         asyncUpdate?.addDoneListener(Runnable {
                             try {
                                 if (asyncUpdate.isDone) {
-                                    caseUpdatePolygonByFeature(features[0], object : ICompletion<String> {
-                                        override fun onCompletion(completion: CompletionModel<String>) {
-                                            if (completion.isSuccess == true) {
-                                                CrUtil.showToast("移动完成!")
-                                                sketchEditor?.removeGeometryChangedListener(
-                                                    sketchGeometryChangeListener
-                                                )
-                                                sketchEditor?.stop()
-                                                fLayerMedia?.clearSelection()
-                                            } else {
-                                                showError(completion.result!!)
+                                    caseUpdatePolygonByFeature(
+                                        features[0],
+                                        object : ICompletion<String> {
+                                            override fun onCompletion(completion: CompletionModel<String>) {
+                                                if (completion.isSuccess == true) {
+                                                    CrUtil.showToast("移动完成!")
+                                                    sketchEditor?.removeGeometryChangedListener(
+                                                        sketchGeometryChangeListener
+                                                    )
+                                                    sketchEditor?.stop()
+                                                    fLayerMedia?.clearSelection()
+                                                } else {
+                                                    showError(completion.result!!)
+                                                }
                                             }
-                                        }
-                                    })
+                                        })
                                 }
                             } catch (ex: java.lang.IllegalArgumentException) {
                                 showError("移动错误,无法完成移动操作!")
@@ -1511,7 +1539,7 @@ class FragmentMap : CrAnimationFragment() {
                         CrUtil.showToast("未选择上传案件!")
                     } else {
                         fLayerMedia?.selectFeature(elements.last() as Feature)
-                        if(eventListener != null) eventListener?.onCaseUpdate(CaseModel(elements.last().attributes))
+                        if (eventListener != null) eventListener?.onCaseUpdate(CaseModel(elements.last().attributes))
                     }
                 }
             } catch (ex: java.lang.IllegalArgumentException) {
@@ -1520,6 +1548,164 @@ class FragmentMap : CrAnimationFragment() {
         })
     }
 
+    /**
+     * 保存截图
+     * @param joinCaseName String 关联的案件名称
+     */
+    private fun captures(joinCaseName:String) {
+        // todo: 2023/8/14 显示等待框
+        showLoading("初始化...")
+        // todo: 2023/8/14 初始化图片信息
+        captureNames.clear()
+        captureNames.add("${joinCaseName}_yx.jpg")
+        captureNames.add("${joinCaseName}_xz.jpg")
+        captureNames.add("${joinCaseName}_gh.jpg")
+        // todo: 2023/8/14 初始化信息
+        var opNameList = mutableListOf<MutableList<String>>()
+        var ovLayerList = mutableListOf<MutableList<GraphicsOverlay>>()
+        // todo: 2023/8/14  截图-影像图
+        val opNamesYX = mutableListOf<String>(
+            LAYER_NAME_YX,
+            LAYER_NAME_CONFIG_VILLAGE,
+            LAYER_NAME_CONFIG_TOWNS,
+            LAYER_NAME_CASE
+        )
+        val ovLayersYX = mutableListOf<GraphicsOverlay>(gLayerIco!!)
+        opNameList.add(opNamesYX)
+        ovLayerList.add(ovLayersYX)
+        // todo: 2023/8/14 截图-现状图
+        var opNamesXZ = mutableListOf<String>(
+            LAYER_NAME_XZ, LAYER_NAME_CONFIG_VILLAGE, LAYER_NAME_CONFIG_TOWNS, LAYER_NAME_CASE
+        )
+        var ovLayersXZ = mutableListOf<GraphicsOverlay>(gLayerIco!!)
+        opNameList.add(opNamesXZ)
+        ovLayerList.add(ovLayersXZ)
+        // todo: 2023/8/14 截图-规划图
+        var opNamesGH = mutableListOf<String>(
+            LAYER_NAME_GH,LAYER_NAME_CONFIG_VILLAGE,LAYER_NAME_CONFIG_TOWNS,LAYER_NAME_CASE
+        )
+        var ovLayersGH = mutableListOf<GraphicsOverlay>(gLayerIco!!)
+        opNameList.add(opNamesGH)
+        ovLayerList.add(ovLayersGH)
+        // todo: 2023/8/14 执行协程
+        GlobalScope.launch {
+            withContext(Dispatchers.IO) {
+                updateLoading("保存地图状态...")
+                saveMapLayerState()
+                updateLoading("截取影像图...")
+                capturesByName(opNameList[0],ovLayerList[0],captureNames[0])
+                updateLoading("截取现状图...")
+                capturesByName(opNameList[1],ovLayerList[1],captureNames[1])
+                updateLoading("截取规划图...")
+                capturesByName(opNameList[2],ovLayerList[2],captureNames[2])
+                updateLoading("恢复地图状态...")
+                restoreMapLayerState()
+                // todo: 2023/8/14 发送截图完成事件
+                CrApplication.getEventBus().post(EventMapCapture(EventMapCapture.CaptureAction.CAPTURE_ACTION_COMPLETE,null,captureNames))
+                // todo: 2023/8/14 关闭等待框
+                closeLoading()
+            }
+        }
+    }
+
+    /**
+     * 协程函数 可挂起
+     * 保存地图图层当前可视状态
+     */
+    private suspend fun saveMapLayerState() {
+        // todo: 2023/8/8 清除数据
+        mapStateList.clear()
+        // todo: 2023/8/8 循环操作类型图层
+        for (i in 0 until mMap?.operationalLayers!!.size) {
+            var visible = mMap?.operationalLayers!![i].isVisible
+            mapStateList.add(MapStateModel(i, visible, MapType.MAP_TYPE_O_LAYER))
+        }
+        // todo: 2023/8/8 循环绘制类型图层
+        for (i in 0 until map_mapView.graphicsOverlays.size) {
+            var visible = map_mapView.graphicsOverlays[i].isVisible
+            mapStateList.add(MapStateModel(i, visible, MapType.MAP_TYPE_D_LAYER))
+        }
+        delay(1000)
+    }
+
+    /**
+     * 协程函数 可挂起
+     * 恢复地图图层状态
+     */
+    private suspend fun restoreMapLayerState() {
+        for (model in mapStateList) {
+            if (model.mapType == MapType.MAP_TYPE_O_LAYER) {
+                mMap?.operationalLayers!![model.index].isVisible = model.visible
+            } else if (model.mapType == MapType.MAP_TYPE_D_LAYER) {
+                map_mapView.graphicsOverlays[model.index].isVisible = model.visible
+            }
+        }
+        delay(1000)
+    }
+
+    /**
+     * 地图截图
+     * @param opLayerNames List<String> 显示的操作图层名称
+     * @param ovLayerNames List<GraphicsOverlay> 显示的绘制图层名称
+     * @param saveName String 保存截图名称
+     */
+    private suspend fun capturesByName(
+        opLayerNames: List<String>,
+        ovLayerNames: List<GraphicsOverlay>,
+        saveName: String
+    ) {
+        // todo: 2023/8/14 控制操作图层
+        for (layer in mMap?.operationalLayers!!) {
+            layer.isVisible = opLayerNames.indexOf(layer.name) != -1
+        }
+        // todo: 2023/8/14 操作绘制图层
+        for (overlay in map_mapView.graphicsOverlays) {
+            overlay.isVisible = ovLayerNames.indexOf(overlay) != -1
+        }
+        // todo: 2023/8/14 延迟1.5秒 为了图层能正确加载
+        delay(1500)
+        toBitmapAndSave("${CrUtil.IMAGE_PATH}${saveName}")
+    }
+
+    /**
+     * 保存地图截图到指定路径文件
+     * @param filePath String 文件全路径
+     */
+    private suspend fun toBitmapAndSave(filePath: String) {
+        map_mapView.clearFocus()
+        map_mapView.isPressed = false
+        // todo: 2023/8/14 能绘制缓存就返回false
+        var willNotCache = map_mapView.willNotCacheDrawing()
+        map_mapView.setWillNotCacheDrawing(false)
+        var color: Int = map_mapView.drawingCacheBackgroundColor
+        map_mapView.drawingCacheBackgroundColor = 0
+        if (color != 0) {
+            map_mapView.destroyDrawingCache()
+        }
+        map_mapView.buildDrawingCache()
+        var cacheBitmap: Bitmap? = null
+        while (cacheBitmap == null) {
+            var export = map_mapView.exportImageAsync()
+            try {
+                cacheBitmap = export.get()
+            } catch (e: InterruptedException) {
+                e.message?.let { CrUtil.showToast(it) }
+            } catch (e: ExecutionException) {
+                e.message?.let { CrUtil.showToast(it) }
+            }
+        }
+        // todo: 2023/8/14 创建位图
+        var bitmap = Bitmap.createBitmap(cacheBitmap)
+        // todo: 2023/8/14 重置地图
+        map_mapView.destroyDrawingCache()
+        map_mapView.setWillNotCacheDrawing(willNotCache)
+        map_mapView.drawingCacheBackgroundColor = color
+        // todo: 2023/8/14 保存截图
+        var saveFile = File(filePath)
+        var stream = FileOutputStream(saveFile)
+        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream)
+    }
+
 
     /**
      * 订阅地图事件执行动作
@@ -1673,6 +1859,18 @@ class FragmentMap : CrAnimationFragment() {
     }
 
     /**
+     * 订阅截屏事件
+     */
+    @Subscribe
+    fun onCapture(event:EventMapCapture){
+        event?.let {
+            if(it.action == EventMapCapture.CaptureAction.CAPTURE_ACTION_START){
+                captures(it.joinCaseName!!)
+            }
+        }
+    }
+
+    /**
      * 设置事件监听
      * @param listener EventListener 事件监听
      */

+ 66 - 1
app/src/main/java/com/cr/pages/FragmentSet.kt

@@ -12,9 +12,17 @@ import com.cr.adapter.CrPageAdapter
 import com.cr.adapter.SettingAdapter
 import com.cr.cruav.CrApplication
 import com.cr.cruav.R
+import com.cr.data.CrUtil
+import com.cr.dialog.DialogNormal
 import com.cr.event.BarAction
 import com.cr.event.EventFragmentBarAction
+import com.cr.models.CompletionModel
+import com.cr.models.ICompletion
 import com.cr.models.SettingAction
+import dji.v5.common.callback.CommonCallbacks
+import dji.v5.common.error.IDJIError
+import dji.v5.manager.account.LoginState
+import dji.v5.manager.account.UserAccountManager
 
 /**
  * 操作系统:MAC系统
@@ -30,6 +38,7 @@ class FragmentSet : CrNavigationFragment() {
     private var pageMain:FragmentSetMain? = null
     private var pageSetIpAndCom:FragmentSetIpAndCom? = null
     private var pageDownloadData:FragmentSetDataDownload? = null
+    private var pageSimulator:FragmentSimulator?= null
 
     // define: 2023/4/11 定义控件
     private var viewPager: ViewPager2?=null
@@ -71,7 +80,7 @@ class FragmentSet : CrNavigationFragment() {
      * 初始化页面
      */
     private fun initPages(){
-        adapter = CrPageAdapter(this.activity!!)
+        adapter = CrPageAdapter(this.requireActivity())
         // todo: 2023/4/11 初始化主页面
         pageMain = FragmentSetMain()
         pageMain?.setCallback(mainListener)
@@ -86,6 +95,11 @@ class FragmentSet : CrNavigationFragment() {
         pageDownloadData = FragmentSetDataDownload()
         adapter?.addFragment(pageDownloadData!!)
 
+        // todo: 2023/8/15 初始化模拟器页面
+        pageSimulator = FragmentSimulator()
+        pageSimulator?.setAnimationDirection(AnimationDirection.RIGHT)
+        adapter?.addFragment(pageSimulator!!)
+
         // todo: 2023/4/11 设置监听
         viewPager?.adapter = adapter
 
@@ -126,6 +140,16 @@ class FragmentSet : CrNavigationFragment() {
                     nvBar?.crSetVisible(backIsVisible = true, dismissIsVisible = false)
                     nvBar?.crSetTitle(R.string.nv_title_download)
                 }
+                SettingAction.SA_DJI_LOGIN->{
+                    // todo: 2023/8/15 登录大疆账号
+                    setLoginDjiUserAccount()
+                }
+                SettingAction.SA_SIMULATOR->{
+                    // todo: 2023/8/15 大疆模拟器页面
+                    showPage(pageSimulator!!)
+                    nvBar?.crSetVisible(backIsVisible = true, dismissIsVisible = false)
+                    nvBar?.crSetTitle(R.string.nv_title_simulator)
+                }
             }
         }
 
@@ -137,6 +161,47 @@ class FragmentSet : CrNavigationFragment() {
     }
 
     /**
+     * 设置登录大疆账号
+     */
+    private fun setLoginDjiUserAccount(){
+        // todo: 2023/8/15 获取大疆账号
+        var loginUser = UserAccountManager.getInstance().loginInfo
+        if(loginUser.loginState == LoginState.LOGGED_IN){
+            var message = "当前登录账号[${loginUser.account}],是否重新登录?"
+            showConfirm(message, arrayOf("重新登录","取消"),object:ICompletion<String>{
+                override fun onCompletion(completion: CompletionModel<String>) {
+                    if(completion.isSuccess == true){
+                        // todo: 2023/8/15 先退出当前账号
+                        UserAccountManager.getInstance().logOutDJIUserAccount(object:CommonCallbacks.CompletionCallback{
+                            // todo: 2023/8/15 退出成功
+                            override fun onSuccess() {
+                                loginDjiUserAccount()
+                            }
+
+                            // todo: 2023/8/15 退出失败
+                            override fun onFailure(error: IDJIError) {
+                                showError("退出失败:${error.description()}")
+                            }
+
+                        })
+                    }
+                }
+            })
+        }else{
+            // todo: 2023/8/15 如果是未登录或登录已过期 则直接调出登录页面
+            loginDjiUserAccount()
+        }
+    }
+
+    /**
+     * 登录大疆账号
+     */
+    private fun loginDjiUserAccount(){
+        // todo: 2023/8/14 登录大疆账号
+        UserAccountManager.getInstance().logInDJIUserAccount(self!!.activity,false,null)
+    }
+
+    /**
      * 挂载控件
      */
     override fun joinControls() {

+ 9 - 0
app/src/main/java/com/cr/pages/FragmentSetMain.kt

@@ -182,6 +182,15 @@ class FragmentSetMain : CrNavigationFragment() {
                 switchIsOn = false
             )
         )
+        menuList.add(SetItemModel(
+            R.drawable.ico_aircraft,
+            "模拟器",
+            isArrow = false,
+            isSwitch = false,
+            action = SettingAction.SA_SIMULATOR,
+            switchIsOn = false
+        ))
+
         // todo: 2023/4/11 设置适配器
         var adapter = SettingAdapter(context, menuList)
         adapter.listener = listener

+ 204 - 0
app/src/main/java/com/cr/pages/FragmentSimulator.kt

@@ -0,0 +1,204 @@
+package com.cr.pages
+
+import android.graphics.Color
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.TextView
+import com.cr.cruav.R
+import com.cr.data.CrUtil
+import com.cr.data.DEFAULT_STR
+import com.cr.widget.CrEditTextWidget
+import dji.sdk.keyvalue.value.common.LocationCoordinate2D
+import dji.v5.common.callback.CommonCallbacks
+import dji.v5.common.error.IDJIError
+import dji.v5.manager.aircraft.simulator.InitializationSettings
+import dji.v5.manager.aircraft.simulator.SimulatorManager
+import dji.v5.manager.aircraft.simulator.SimulatorState
+import dji.v5.manager.aircraft.simulator.SimulatorStatusListener
+
+/**
+ * 操作系统:MAC系统
+ * 创建者:王成
+ * 创建日期:2023/8/15 10:10
+ * 描述:大疆模拟器页面
+ */
+class FragmentSimulator:CrNavigationFragment(), View.OnClickListener {
+    private var txtLongitude:CrEditTextWidget?=null  // define: 2023/8/15 初始经度
+    private var txtLatitude:CrEditTextWidget?= null // define: 2023/8/15 初始纬度
+    private var btnStart: Button?= null // define: 2023/8/15 启动模拟器
+    private var btnEnd:Button?= null // define: 2023/8/15 关闭模拟器
+    private var txtInfo:TextView?=null // define: 2023/8/15 展示信息
+    /**
+     * 初始化
+     * @param inflater LayoutInflater
+     * @param container ViewGroup?
+     * @param savedInstanceState Bundle?
+     * @return View?
+     */
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        self = this
+        mainView = inflater.inflate(R.layout.frag_dji_simulator,null)
+        // todo: 2023/8/15 挂载控件
+        joinControls()
+        return mainView
+    }
+
+    /**
+     * 覆写挂载控件方法
+     */
+    override fun joinControls() {
+        // todo: 2023/8/15 挂载控件
+        mainView?.let {
+            txtLongitude = it.findViewById(R.id.txt_longitude)
+            txtLatitude = it.findViewById(R.id.txt_latitude)
+            txtInfo = it.findViewById(R.id.txt_info)
+
+            // todo: 2023/8/15 更新信息
+            updateInfo(null)
+
+            btnStart = it.findViewById(R.id.btn_start)
+            btnEnd = it.findViewById(R.id.btn_end)
+            // todo: 2023/8/15 挂载监听
+            btnStart?.setOnClickListener(this)
+            btnEnd?.setOnClickListener(this)
+
+        }
+    }
+
+    /**
+     * 覆写初始化页面
+     */
+    override fun initPage() {
+        txtLongitude?.setContent("118.167")
+        txtLatitude?.setContent("35.155")
+    }
+
+    /**
+     * 根据状态更新信息
+     * @param state SimulatorState
+     */
+    private fun updateInfo(state:SimulatorState?){
+        var stringBuilder = StringBuilder()
+        if(state != null){
+            stringBuilder.append(String.format("电机是否运转:%s",if(state.areMotorsOn()) "是" else "否")).append("\r\n")
+            stringBuilder.append(String.format("是否飞行:%s",if(state.isFlying) "是" else "否")).append("\r\n")
+            stringBuilder.append(String.format("俯仰角:%.2f度",state.pitch)).append("\r\n")
+            stringBuilder.append(String.format("翻滚角:%.2f度",state.roll)).append("\r\n")
+            stringBuilder.append(String.format("方向角:%.2f度",state.yaw)).append("\r\n")
+            stringBuilder.append(String.format("经度:%.6f",state.location.longitude)).append("\r\n")
+            stringBuilder.append(String.format("纬度:%.6f",state.location.latitude)).append("\r\n")
+            txtInfo?.setTextColor(Color.YELLOW)
+        }else{
+            stringBuilder.append(String.format("电机是否运转:%s", DEFAULT_STR)).append("\r\n")
+            stringBuilder.append(String.format("是否飞行:%s", DEFAULT_STR)).append("\r\n")
+            stringBuilder.append(String.format("俯仰角:%.2f度",0f)).append("\r\n")
+            stringBuilder.append(String.format("翻滚角:%.2f度",0f)).append("\r\n")
+            stringBuilder.append(String.format("方向角:%.2f度",0f)).append("\r\n")
+            stringBuilder.append(String.format("经度:%.6f",0f)).append("\r\n")
+            stringBuilder.append(String.format("纬度:%.6f",0f)).append("\r\n")
+            txtInfo?.setTextColor(Color.WHITE)
+        }
+        txtInfo?.text = stringBuilder.toString()
+    }
+
+    /**
+     * 生命周期
+     * 销毁
+     */
+    override fun onDestroy() {
+        CrUtil.print("模拟器 页面销毁")
+        super.onDestroy()
+        // todo: 2023/8/15 移除监听
+        SimulatorManager.getInstance().removeSimulatorStateListener(simulatorListener)
+        SimulatorManager.getInstance().clearAllSimulatorStateListener()
+    }
+
+    /**
+     * 生命周期
+     * 后台到前台
+     */
+    override fun onResume() {
+        super.onResume()
+        // todo: 2023/8/15 添加监听
+        SimulatorManager.getInstance().addSimulatorStateListener(simulatorListener)
+        // todo: 2023/8/15 初始化页面
+        initPage()
+        CrUtil.print("模拟器 页面进前台")
+    }
+
+    /**
+     * 生命周期
+     * 后台运行
+     */
+    override fun onStop() {
+        super.onStop()
+        // todo: 2023/8/15 移除监听
+        SimulatorManager.getInstance().removeSimulatorStateListener(simulatorListener)
+        SimulatorManager.getInstance().clearAllSimulatorStateListener()
+        CrUtil.print("模拟器 页面进入后台")
+    }
+
+    /**
+     * 视图点击事件监听
+     * @param view View
+     */
+    override fun onClick(view: View?) {
+        when(view?.id){
+            R.id.btn_start->{
+                // todo: 2023/8/15 启动模拟器
+                if(SimulatorManager.getInstance().isSimulatorEnabled){
+                    showInformation("模拟器已开启")
+                }else{
+                    var longitude = txtLongitude?.getContent()?.toDouble()?:0.0
+                    var latitude = txtLatitude?.getContent()?.toDouble()?:0.0
+                    var location = LocationCoordinate2D(latitude,longitude)
+                    var settings = InitializationSettings.createInstance(location,10)
+                    SimulatorManager.getInstance().enableSimulator(settings,object:CommonCallbacks.CompletionCallback{
+                        // todo: 2023/8/15 成功
+                        override fun onSuccess() {
+                            showInformation("模拟器已开启")
+                        }
+
+                        // todo: 2023/8/15 失败
+                        override fun onFailure(error: IDJIError) {
+                            CrUtil.print(error.toString())
+                            showError("模拟器开启失败:${error.description()}")
+                        }
+                    })
+                }
+            }
+            R.id.btn_end->{
+                // todo: 2023/8/15 关闭模拟器
+                if(!SimulatorManager.getInstance().isSimulatorEnabled){
+                    showWarning("模拟器尚未开启")
+                }else{
+                    SimulatorManager.getInstance().disableSimulator(object:CommonCallbacks.CompletionCallback{
+                        // todo: 2023/8/15 成功
+                        override fun onSuccess() {
+                            showInformation("模拟器已停止")
+                            updateInfo(null)
+                        }
+
+                        // todo: 2023/8/15 失败
+                        override fun onFailure(error: IDJIError) {
+                            showError("模拟器停止失败:${error.description()}")
+                        }
+                    })
+                }
+            }
+        }
+    }
+
+    /**
+     * 模拟器状态 监听
+     */
+    private val simulatorListener =
+        SimulatorStatusListener { state -> updateInfo(state) }
+}

+ 7 - 2
app/src/main/java/com/cr/pages/FragmentTopInfo.kt

@@ -154,6 +154,9 @@ open class FragmentTopInfo : Fragment() {
                     dji_gps_count.text = it.gpsCount
                     dji_imu_state.text = it.imuState
                     dji_compass.text = it.compassState
+                    // todo: 2023/8/15 更新经纬度
+                    dji_gps_longitude?.text = it.longitudeStr
+                    dji_gps_latitude?.text = it.latitudeStr
                     if(!it.isConnection){
                         linkVm.reset()
                     }
@@ -168,8 +171,10 @@ open class FragmentTopInfo : Fragment() {
     private fun observerLink(){
         linkVm.linkInfo.observe(requireActivity()){
             if (msdkRegisterVm.registerInfo.value?.isRegister == true) {
-                dji_link_symbol?.setImageDrawable(linkSignalQualityIcon[CrUtil.getLinkSignalQuality(it.signalQuality)])
-                dji_link_value?.text = it.frequencyBand
+                it?.let {
+                    dji_link_symbol?.setImageDrawable(linkSignalQualityIcon[CrUtil.getLinkSignalQuality(it.signalQuality)])
+                    dji_link_value?.text = it.frequencyBand
+                }
             }
         }
     }

+ 47 - 10
app/src/main/java/com/cr/viewmodel/CrFlightControlVM.kt

@@ -9,6 +9,7 @@ import dji.sdk.keyvalue.key.FlightControllerKey
 import dji.sdk.keyvalue.key.KeyTools
 import dji.sdk.keyvalue.key.ProductKey
 import dji.sdk.keyvalue.value.flightcontroller.CompassCalibrationState
+import dji.sdk.keyvalue.value.flightcontroller.FlyLimitLicenseVersion
 import dji.sdk.keyvalue.value.flightcontroller.IMUCalibrationState
 import dji.sdk.keyvalue.value.product.ProductType
 import dji.v5.et.create
@@ -32,11 +33,14 @@ class CrFlightControlVM : CrViewModel() {
      */
     init {
         flightControlInfo.postValue(CrFlightControlInfo())
-        flightControlInfo.value?.batteryPercent = 0
-        flightControlInfo.value?.gpsCount = DEFAULT_STR
-        flightControlInfo.value?.productName = DEFAULT_STR
-        flightControlInfo.value?.imuState = DEFAULT_STR
-        flightControlInfo.value?.compassState = DEFAULT_STR
+        flightControlInfo.value?.reset()
+    }
+
+    /**
+     * 计算距离
+     */
+    private fun calculateDistance(){
+
     }
 
     /**
@@ -53,11 +57,7 @@ class CrFlightControlVM : CrViewModel() {
                     flightControlInfo.value?.productName = type.toString()
                     refresh(flightControlInfo)
                 }else{
-                    flightControlInfo.value?.isConnection = false
-                    flightControlInfo.value?.productName = DEFAULT_STR
-                    flightControlInfo.value?.gpsCount = DEFAULT_STR
-                    flightControlInfo.value?.imuState = DEFAULT_STR
-                    flightControlInfo.value?.compassState = DEFAULT_STR
+                    flightControlInfo.value?.reset()
                     refresh(flightControlInfo)
                 }
             }
@@ -130,5 +130,42 @@ class CrFlightControlVM : CrViewModel() {
                 refresh(flightControlInfo)
             }
         }
+        // todo: 2023/8/15 飞行器位置监听
+        FlightControllerKey.KeyAircraftLocation3D.create().listen(this){
+            it?.let {
+                flightControlInfo.value?.longitude = it.longitude
+                flightControlInfo.value?.latitude = it.latitude
+                flightControlInfo.value?.altitude = it.altitude
+                flightControlInfo.value?.longitudeStr = String.format("%.5f",it.longitude)
+                flightControlInfo.value?.latitudeStr = String.format("%.5f",it.latitude)
+                flightControlInfo.value?.altitudeStr = String.format("%.1f",it.altitude)
+                refresh(flightControlInfo)
+            }
+        }
+        // todo: 2023/8/15 速度监听
+        FlightControllerKey.KeyAircraftVelocity.create().listen(this){
+            it?.let {
+                flightControlInfo.value?.speedX = it.x
+                flightControlInfo.value?.speedY = it.y
+                flightControlInfo.value?.speedZ = it.z
+                refresh(flightControlInfo)
+            }
+        }
+        // todo: 2023/8/15 返航点
+        FlightControllerKey.KeyHomeLocation.create().listen(this){
+            it?.let {
+                flightControlInfo.value?.homeLongitude = it.longitude
+                flightControlInfo.value?.homeLatitude = it.latitude
+                refresh(flightControlInfo)
+            }
+        }
+        // todo: 2023/8/15 起飞海拔高度
+        FlightControllerKey.KeyTakeoffLocationAltitude.create().listen(this){
+            it?.let {
+                flightControlInfo.value?.takeoffAltitude = it
+                flightControlInfo.value?.takeoffAltitudeStr = String.format("%.1f",it)
+                refresh(flightControlInfo)
+            }
+        }
     }
 }

+ 16 - 5
app/src/main/java/com/cr/widget/CrCompassWidget.kt

@@ -12,8 +12,6 @@ import android.util.AttributeSet
 import com.cr.common.CrColorManager
 import com.cr.common.CrUnitManager
 import com.cr.cruav.R
-import com.cr.data.CrUtil
-import java.util.*
 
 /**
  * 操作系统:MAC系统
@@ -54,13 +52,17 @@ class CrCompassWidget @JvmOverloads constructor(
     }
 
     /**
-     * 加入窗体
+     * 初始化传感器
      */
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
+    private fun initSensor(){
         // todo: 2023/8/1 初始化传感器
         sensorManger = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
         sensorManger?.let { itSensorManager ->
+            // todo: 2023/8/15 检测传感器是否存在
+            if(itSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) == null ||
+                itSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) == null){
+                return
+            }
             // todo: 2023/8/1 获取传感器
             magneticSensor = itSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)
             accelerometerSensor = itSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
@@ -80,6 +82,14 @@ class CrCompassWidget @JvmOverloads constructor(
     }
 
     /**
+     * 加入窗体
+     */
+    override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+        initSensor()
+    }
+
+    /**
      * 移除窗体
      */
     override fun onDetachedFromWindow() {
@@ -88,6 +98,7 @@ class CrCompassWidget @JvmOverloads constructor(
         sensorManger?.let {
             it.unregisterListener(this)
         }
+        sensorManger = null
     }
 
     /**

+ 14 - 11
app/src/main/res/layout/av_main.xml

@@ -36,7 +36,7 @@
         android:background="@color/common_back"/>
     <!--动态信息容器-->
     <FrameLayout
-        android:layout_width="@dimen/cr_430_dp"
+        android:layout_width="@dimen/cr_450_dp"
         android:layout_height="@dimen/cr_150_dp"
         android:id="@+id/av_fragment_dynamic_info"
         app:layout_constraintBottom_toTopOf="@id/av_main_panel_bottom"
@@ -105,28 +105,31 @@
     </LinearLayout>
     <!--右侧弹窗容器-->
     <FrameLayout
+        style="@style/common_match_panel_v"
         android:id="@+id/av_frm_right_panel"
-        android:layout_width="@dimen/cr_200_dp"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
+        android:layout_width="@dimen/cr_240_dp"
+        app:layout_constraintBottom_toTopOf="@+id/av_main_panel_bottom"
         app:layout_constraintRight_toRightOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/av_fragment_top"/>
+        app:layout_constraintTop_toBottomOf="@id/av_fragment_top" />
     <!--左侧弹窗容器 大-->
     <FrameLayout
+        style="@style/common_match_panel_v"
         android:id="@+id/av_frm_left_big_panel"
         android:layout_width="@dimen/cr_200_dp"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/av_fragment_top"/>
+        app:layout_constraintTop_toBottomOf="@id/av_fragment_top"
+        app:layout_constraintBottom_toTopOf="@+id/av_main_panel_bottom"
+        />
     <!--左侧弹窗容器 小-->
     <FrameLayout
+        style="@style/common_match_panel_v"
         android:id="@+id/av_frm_left_smart_panel"
         android:layout_width="@dimen/cr_120_dp"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/av_fragment_top"/>
+        app:layout_constraintTop_toBottomOf="@id/av_fragment_top"
+        app:layout_constraintBottom_toTopOf="@+id/av_main_panel_bottom"
+        />
+    <!--中间弹窗-->
     <FrameLayout
         android:id="@+id/av_frm_center_panel"
         android:layout_width="match_parent"

+ 68 - 0
app/src/main/res/layout/frag_dji_simulator.xml

@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@drawable/shape_back_fragment">
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:padding="@dimen/common_padding"
+        android:orientation="vertical">
+        <TextView
+            style="@style/lbl_title_common"
+            android:text="@string/simulator_lbl_longitude"
+            android:layout_marginLeft="@dimen/common_margin"
+            android:layout_marginRight="@dimen/common_margin"
+            android:layout_marginTop="@dimen/common_margin"
+            />
+        <com.cr.widget.CrEditTextWidget
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="@dimen/common_margin"
+            android:layout_marginRight="@dimen/common_margin"
+            android:layout_marginTop="@dimen/common_margin"
+            android:id="@+id/txt_longitude"/>
+        <TextView
+            style="@style/lbl_title_common"
+            android:text="@string/simulator_lbl_latitude"
+            android:layout_marginLeft="@dimen/common_margin"
+            android:layout_marginRight="@dimen/common_margin"
+            android:layout_marginTop="@dimen/common_margin"
+            />
+        <com.cr.widget.CrEditTextWidget
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="@dimen/common_margin"
+            android:layout_marginRight="@dimen/common_margin"
+            android:layout_marginTop="@dimen/common_margin"
+            android:id="@+id/txt_latitude"/>
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/cr_10_dp"
+            android:layout_marginLeft="@dimen/cr_10_dp"
+            android:layout_marginRight="@dimen/cr_10_dp">
+            <Button
+                style="@style/btn_default"
+                android:text="@string/simulator_btn_start"
+                android:layout_weight="1"
+                android:id="@+id/btn_start"/>
+            <Button
+                style="@style/btn_default"
+                android:text="@string/simulator_btn_end"
+                android:layout_weight="1"
+                android:background="@drawable/btn_set_selector"
+                android:id="@+id/btn_end"/>
+        </LinearLayout>
+        <!-- 展示信息 -->
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="0"
+            android:layout_marginLeft="@dimen/common_margin"
+            android:layout_marginRight="@dimen/common_margin"
+            android:layout_marginTop="@dimen/common_margin"
+            android:id="@+id/txt_info"/>
+    </LinearLayout>
+</LinearLayout>

+ 45 - 15
app/src/main/res/layout/frag_dynamic_info.xml

@@ -71,7 +71,8 @@
                 style="@style/dji_top_component_panel">
                 <ImageView
                     style="@style/dji_top_component_img"
-                    android:src="@drawable/sd_zpsl"/>
+                    android:src="@drawable/sd_zpsl"
+                    android:contentDescription="@string/des_head"/>
                 <TextView
                     android:layout_width="@dimen/cr_10_dp"
                     android:layout_height="@dimen/cr_10_dp"
@@ -97,7 +98,8 @@
                 style="@style/dji_top_component_panel">
                 <ImageView
                     style="@style/dji_top_component_img"
-                    android:src="@drawable/sd_spsj"/>
+                    android:src="@drawable/sd_spsj"
+                    android:contentDescription="@string/des_head"/>
                 <TextView
                     android:layout_width="@dimen/cr_10_dp"
                     android:layout_height="@dimen/cr_10_dp"
@@ -119,6 +121,7 @@
         <View style="@style/view_split_v1"/>
         <LinearLayout
             style="@style/dji_camera_component"
+            android:layout_width="0dp"
             android:layout_weight="1">
             <LinearLayout
                 style="@style/dji_top_component_lbl_panel">
@@ -135,7 +138,7 @@
     <View style="@style/view_split_h1"/>
     <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
+        android:layout_height="0dp"
         android:layout_weight="1">
         <!--飞行器方位-->
         <LinearLayout
@@ -147,14 +150,14 @@
                 android:layout_height="match_parent"/>
         </LinearLayout>
         <LinearLayout
-            android:layout_width="match_parent"
+            android:layout_width="0dp"
             android:layout_height="match_parent"
             android:layout_weight="1"
             android:orientation="vertical">
             <!--速度 相对行高 返航距离-->
             <LinearLayout
                 android:layout_width="match_parent"
-                android:layout_height="match_parent"
+                android:layout_height="0dp"
                 android:layout_weight="1">
                 <LinearLayout
                     android:layout_width="@dimen/cr_70_dp"
@@ -163,8 +166,9 @@
                     <com.cr.widget.CrSpeedWidget
                         android:layout_width="match_parent"
                         android:layout_height="match_parent"
-                        app:speedProgress="30"
-                        app:speedTitle="@string/speed_x"/>
+                        app:speedProgress="0"
+                        app:speedTitle="@string/speed_x"
+                        android:id="@+id/speed_x"/>
                 </LinearLayout>
                 <LinearLayout
                     android:layout_width="@dimen/cr_70_dp"
@@ -173,8 +177,9 @@
                     <com.cr.widget.CrSpeedWidget
                         android:layout_width="match_parent"
                         android:layout_height="match_parent"
-                        app:speedProgress="30"
-                        app:speedTitle="@string/speed_y"/>
+                        app:speedProgress="0"
+                        app:speedTitle="@string/speed_y"
+                        android:id="@+id/speed_y"/>
                 </LinearLayout>
                 <LinearLayout
                     android:layout_width="@dimen/cr_70_dp"
@@ -183,28 +188,53 @@
                     <com.cr.widget.CrSpeedWidget
                         android:layout_width="match_parent"
                         android:layout_height="match_parent"
-                        app:speedProgress="30"
-                        app:speedTitle="@string/speed_z"/>
+                        app:speedProgress="0"
+                        app:speedTitle="@string/speed_z"
+                        android:id="@+id/speed_z"/>
                 </LinearLayout>
                 <View style="@style/view_split_v1"/>
-                <!--飞行器相对行高 及 返航信息-->
+                <!-- 相对高度 起飞海拔 -->
                 <LinearLayout
-                    android:layout_width="match_parent"
+                    android:layout_width="0dp"
                     android:layout_height="match_parent"
                     android:layout_weight="1"
                     android:orientation="vertical">
                     <TextView
                         style="@style/lbl_title_common_match"
+                        android:text="@string/title_takeoff_altitude"/>
+                    <TextView
+                        style="@style/lbl_value_common_match"
+                        android:text="@string/cr_string_default_value"
+                        android:id="@+id/lbl_takeoff_altitude"/>
+                    <TextView
+                        style="@style/lbl_title_common_match"
                         android:text="@string/title_altitude"/>
                     <TextView
                         style="@style/lbl_value_common_match"
-                        android:text="@string/cr_string_default_value"/>
+                        android:text="@string/cr_string_default_value"
+                        android:id="@+id/lbl_altitude"/>
+                </LinearLayout>
+                <View style="@style/view_split_v1"/>
+                <!-- 返航高度 返航距离 -->
+                <LinearLayout
+                    android:layout_width="0dp"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"
+                    android:orientation="vertical">
+                    <TextView
+                        style="@style/lbl_title_common_match"
+                        android:text="@string/title_go_home_altitude"/>
+                    <TextView
+                        style="@style/lbl_value_common_match"
+                        android:text="@string/cr_string_default_value"
+                        android:id="@+id/lbl_home_altitude"/>
                     <TextView
                         style="@style/lbl_title_common_match"
                         android:text="@string/title_go_home_distance"/>
                     <TextView
                         style="@style/lbl_value_common_match"
-                        android:text="@string/cr_string_default_value"/>
+                        android:text="@string/cr_string_default_value"
+                        android:id="@+id/lbl_home_distance"/>
                 </LinearLayout>
             </LinearLayout>
             <View style="@style/view_split_h1"/>

+ 14 - 4
app/src/main/res/values/strings.xml

@@ -10,16 +10,16 @@
     <string name="title_remote_controller" translatable="false">遥控器</string>
     <string name="title_flight_controller" translatable="false">飞行器</string>
     <string name="title_link" translatable="false">信号</string>
-    <string name="title_flight_height" translatable="false">航高</string>
-    <string name="title_takeoff_altitude" translatable="false">海拔</string>
+    <string name="title_takeoff_altitude" translatable="false">起飞海拔</string>
     <string name="title_imu" translatable="false">IMU</string>
     <string name="title_gps" translatable="false">卫星</string>
-    <string name="value_latitude" translatable="false">0.000000</string>
-    <string name="value_longitude" translatable="false">0.000000</string>
+    <string name="value_latitude" translatable="false">000.0000</string>
+    <string name="value_longitude" translatable="false">000.0000</string>
     <string name="value_product_remote_controller" translatable="false">遥控器未连接</string>
     <string name="value_rc_connection">遥控器已链接</string>
     <string name="title_altitude" translatable="false">相对航高</string>
     <string name="title_go_home_distance" translatable="false">返航距离</string>
+    <string name="title_go_home_altitude" translatable="false">返航高度</string>
     <!--相机-->
     <string name="title_camera_iso" translatable="false">ISO</string>
     <string name="title_camera_speed" translatable="false">快门速度</string>
@@ -83,6 +83,7 @@
     <string name="nv_title_image_editor">图片编辑</string>
     <string name="nv_title_set_ip_and_com">设置IP和COM</string>
     <string name="nv_title_dji_initialize">飞行器自检</string>
+    <string name="nv_title_simulator">大疆模拟器</string>
 
     <!--对话框-->
     <string name="dig_normal_btn_ok">确定</string>
@@ -162,6 +163,15 @@
     <string name="speed_y" translatable="false">纵向速度</string>
     <string name="speed_z" translatable="false">垂直速度</string>
 
+    <!-- 模拟器相关 -->
+    <string name="simulator_lbl_longitude">初始经度</string>
+    <string name="simulator_lbl_latitude">初始纬度</string>
+    <string name="simulator_btn_start">启动模拟器</string>
+    <string name="simulator_btn_end">停止模拟器</string>
+
+    <!-- 说明 -->
+    <string name="des_head">说明</string>
+
     <!--字体-->
     <string name="ico_nv_left" translatable="false">&#xe60b;</string>
     <string name="ico_nv_right" translatable="false">&#xe81a;</string>

+ 11 - 0
app/src/main/res/values/themes.xml

@@ -376,17 +376,28 @@
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">match_parent</item>
         <item name="android:gravity">center</item>
+        <item name="android:textSize">@dimen/sp_10</item>
         <item name="android:layout_weight">1</item>
     </style>
     <style name="lbl_value_common_match" parent="lbl_value_common">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">match_parent</item>
         <item name="android:gravity">center</item>
+        <item name="android:textSize">@dimen/sp_10</item>
         <item name="android:layout_weight">1</item>
     </style>
 
     <style name="Theme">Theme.NoTitleBar.Fullscreen</style>
 
+    <!-- ConstraintLayout 布局中通用内容 上下占满 -->
+    <style name="common_match_panel_v">
+        <item name="android:layout_width">0dp</item>
+        <item name="android:layout_height">0dp</item>
+        <item name="android:layout_marginTop">@dimen/common_margin</item>
+        <item name="android:layout_marginBottom">@dimen/common_margin</item>
+        <item name="android:orientation">vertical</item>
+    </style>
+
     <!--自定义文本框属性-->
     <declare-styleable name="ViewEditTextProperty">
         <attr name="crHint" />