Compare commits
	
		
			6 Commits
		
	
	
		
			e416ce7b5c
			...
			45ef877b4f
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								
								 | 
						45ef877b4f | |
| 
							
							
								
								 | 
						823949a7d7 | |
| 
							
							
								
								 | 
						bb56200f79 | |
| 
							
							
								
								 | 
						8e4287100b | |
| 
							
							
								
								 | 
						3ad016fc75 | |
| 
							
							
								
								 | 
						91bdec4aa6 | 
| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
<<<<<<< HEAD
 | 
			
		||||
# ----------- Gradle / 构建产物 -----------
 | 
			
		||||
.gradle/
 | 
			
		||||
build/
 | 
			
		||||
*.apk
 | 
			
		||||
*.aab
 | 
			
		||||
 | 
			
		||||
# ----------- 本地密钥 / 环境文件 -----------
 | 
			
		||||
local.properties
 | 
			
		||||
*.jks
 | 
			
		||||
*.keystore
 | 
			
		||||
google-services.json
 | 
			
		||||
 | 
			
		||||
# ----------- IDEA / AS 缓存 & 个人配置 -----------
 | 
			
		||||
.idea/caches/
 | 
			
		||||
.idea/libraries/
 | 
			
		||||
.idea/workspace.xml
 | 
			
		||||
.idea/tasks.xml
 | 
			
		||||
.idea/navEditor.xml
 | 
			
		||||
.idea/assetWizardSettings.xml
 | 
			
		||||
*.iml
 | 
			
		||||
 | 
			
		||||
# ----------- OS 垃圾 -----------
 | 
			
		||||
.DS_Store
 | 
			
		||||
Thumbs.db
 | 
			
		||||
 | 
			
		||||
# ----------- NDK / 外部构建缓存 -----------
 | 
			
		||||
.externalNativeBuild
 | 
			
		||||
.cxx
 | 
			
		||||
*.hprof
 | 
			
		||||
=======
 | 
			
		||||
# ---> Android
 | 
			
		||||
# Gradle files
 | 
			
		||||
.gradle/
 | 
			
		||||
build/
 | 
			
		||||
 | 
			
		||||
# Local configuration file (sdk path, etc)
 | 
			
		||||
local.properties
 | 
			
		||||
 | 
			
		||||
# Log/OS Files
 | 
			
		||||
*.log
 | 
			
		||||
 | 
			
		||||
# Android Studio generated files and folders
 | 
			
		||||
captures/
 | 
			
		||||
.externalNativeBuild/
 | 
			
		||||
.cxx/
 | 
			
		||||
*.apk
 | 
			
		||||
output.json
 | 
			
		||||
 | 
			
		||||
# IntelliJ
 | 
			
		||||
*.iml
 | 
			
		||||
.idea/
 | 
			
		||||
misc.xml
 | 
			
		||||
deploymentTargetDropDown.xml
 | 
			
		||||
render.experimental.xml
 | 
			
		||||
 | 
			
		||||
# Keystore files
 | 
			
		||||
*.jks
 | 
			
		||||
*.keystore
 | 
			
		||||
 | 
			
		||||
# Google Services (e.g. APIs or Firebase)
 | 
			
		||||
google-services.json
 | 
			
		||||
 | 
			
		||||
# Android Profiling
 | 
			
		||||
*.hprof
 | 
			
		||||
 | 
			
		||||
>>>>>>> 91bdec4aa6da285c4dd9a861e3b748515542f0b4
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
# Default ignored files
 | 
			
		||||
/shelf/
 | 
			
		||||
/workspace.xml
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
MSDKSample
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project version="4">
 | 
			
		||||
  <component name="CompilerConfiguration">
 | 
			
		||||
    <bytecodeTargetLevel target="17" />
 | 
			
		||||
  </component>
 | 
			
		||||
</project>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project version="4">
 | 
			
		||||
  <component name="GradleMigrationSettings" migrationVersion="1" />
 | 
			
		||||
  <component name="GradleSettings">
 | 
			
		||||
    <option name="linkedExternalProjectsSettings">
 | 
			
		||||
      <GradleProjectSettings>
 | 
			
		||||
        <option name="testRunner" value="GRADLE" />
 | 
			
		||||
        <option name="distributionType" value="DEFAULT_WRAPPED" />
 | 
			
		||||
        <option name="externalProjectPath" value="$PROJECT_DIR$" />
 | 
			
		||||
        <option name="gradleJvm" value="17" />
 | 
			
		||||
        <option name="modules">
 | 
			
		||||
          <set>
 | 
			
		||||
            <option value="$PROJECT_DIR$" />
 | 
			
		||||
            <option value="$PROJECT_DIR$/android-sdk-v5-uxsdk" />
 | 
			
		||||
            <option value="$PROJECT_DIR$/app" />
 | 
			
		||||
          </set>
 | 
			
		||||
        </option>
 | 
			
		||||
      </GradleProjectSettings>
 | 
			
		||||
    </option>
 | 
			
		||||
  </component>
 | 
			
		||||
</project>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project version="4">
 | 
			
		||||
  <component name="RemoteRepositoriesConfiguration">
 | 
			
		||||
    <remote-repository>
 | 
			
		||||
      <option name="id" value="central" />
 | 
			
		||||
      <option name="name" value="Maven Central repository" />
 | 
			
		||||
      <option name="url" value="https://repo1.maven.org/maven2" />
 | 
			
		||||
    </remote-repository>
 | 
			
		||||
    <remote-repository>
 | 
			
		||||
      <option name="id" value="jboss.community" />
 | 
			
		||||
      <option name="name" value="JBoss Community repository" />
 | 
			
		||||
      <option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
 | 
			
		||||
    </remote-repository>
 | 
			
		||||
    <remote-repository>
 | 
			
		||||
      <option name="id" value="MavenRepo" />
 | 
			
		||||
      <option name="name" value="MavenRepo" />
 | 
			
		||||
      <option name="url" value="https://repo.maven.apache.org/maven2/" />
 | 
			
		||||
    </remote-repository>
 | 
			
		||||
    <remote-repository>
 | 
			
		||||
      <option name="id" value="BintrayJCenter" />
 | 
			
		||||
      <option name="name" value="BintrayJCenter" />
 | 
			
		||||
      <option name="url" value="https://jcenter.bintray.com/" />
 | 
			
		||||
    </remote-repository>
 | 
			
		||||
    <remote-repository>
 | 
			
		||||
      <option name="id" value="Google" />
 | 
			
		||||
      <option name="name" value="Google" />
 | 
			
		||||
      <option name="url" value="https://dl.google.com/dl/android/maven2/" />
 | 
			
		||||
    </remote-repository>
 | 
			
		||||
  </component>
 | 
			
		||||
</project>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project version="4">
 | 
			
		||||
  <component name="KotlinJpsPluginSettings">
 | 
			
		||||
    <option name="version" value="1.7.21" />
 | 
			
		||||
  </component>
 | 
			
		||||
</project>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
<project version="4">
 | 
			
		||||
  <component name="ExternalStorageConfigurationManager" enabled="true" />
 | 
			
		||||
  <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
 | 
			
		||||
    <output url="file://$PROJECT_DIR$/build/classes" />
 | 
			
		||||
  </component>
 | 
			
		||||
  <component name="ProjectType">
 | 
			
		||||
    <option name="id" value="Android" />
 | 
			
		||||
  </component>
 | 
			
		||||
</project>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project version="4">
 | 
			
		||||
  <component name="VcsDirectoryMappings">
 | 
			
		||||
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
 | 
			
		||||
  </component>
 | 
			
		||||
</project>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,70 @@
 | 
			
		|||
apply plugin: 'com.android.library'
 | 
			
		||||
apply plugin: 'kotlin-android'
 | 
			
		||||
apply plugin: 'kotlin-kapt'
 | 
			
		||||
 | 
			
		||||
android {
 | 
			
		||||
    compileSdkVersion Integer.parseInt(project.ANDROID_COMPILE_SDK_VERSION)
 | 
			
		||||
 | 
			
		||||
    resourcePrefix "uxsdk_"
 | 
			
		||||
    defaultConfig {
 | 
			
		||||
        minSdkVersion Integer.parseInt(project.ANDROID_MIN_SDK_VERSION)
 | 
			
		||||
        targetSdkVersion Integer.parseInt(project.ANDROID_TARGET_SDK_VERSION)
 | 
			
		||||
        versionCode 1
 | 
			
		||||
        versionName "1.0"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    compileOptions {
 | 
			
		||||
        sourceCompatibility JavaVersion.VERSION_1_8
 | 
			
		||||
        targetCompatibility JavaVersion.VERSION_1_8
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    kotlinOptions{
 | 
			
		||||
        jvmTarget = JavaVersion.VERSION_1_8
 | 
			
		||||
        freeCompilerArgs += ["-Xjvm-default=all"]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    buildFeatures {
 | 
			
		||||
        viewBinding true
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    implementation deps.annotation
 | 
			
		||||
    implementation deps.appcompat
 | 
			
		||||
    implementation deps.multidex
 | 
			
		||||
    implementation deps.legacySupport
 | 
			
		||||
    implementation deps.recyclerview
 | 
			
		||||
    implementation deps.okio
 | 
			
		||||
    implementation deps.wire
 | 
			
		||||
    implementation deps.constraintLayout
 | 
			
		||||
    implementation deps.lifecycleJava8
 | 
			
		||||
    implementation deps.lifecycleRuntime
 | 
			
		||||
    implementation deps.lifecycleProcess
 | 
			
		||||
    implementation deps.media
 | 
			
		||||
    implementation deps.kotlinLib
 | 
			
		||||
    implementation deps.ktxCore
 | 
			
		||||
    implementation deps.rx3Android
 | 
			
		||||
    implementation deps.wpmzSdk
 | 
			
		||||
    implementation deps.rx3Kt
 | 
			
		||||
    implementation deps.lottie
 | 
			
		||||
    implementation deps.cardview
 | 
			
		||||
    implementation deps.mikepenzCommunityMaterial
 | 
			
		||||
    implementation deps.mikepenzGoogleMaterial
 | 
			
		||||
    implementation deps.mikepenzIconicsViews
 | 
			
		||||
    implementation deps.mikepenzIconicsCore
 | 
			
		||||
    implementation deps.mikepenzIonicons
 | 
			
		||||
    implementation deps.material
 | 
			
		||||
 | 
			
		||||
    api deps.maplibreTurf
 | 
			
		||||
    api deps.maplibreSdk
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    api deps.playservicesplaces
 | 
			
		||||
    api deps.playservicesmaps
 | 
			
		||||
    api deps.playserviceslocation
 | 
			
		||||
    api deps.playservicesbase
 | 
			
		||||
 | 
			
		||||
    compileOnly deps.aircraftProvided
 | 
			
		||||
    implementation deps.aircraft
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    package="dji.v5.ux">
 | 
			
		||||
 | 
			
		||||
    <uses-permission android:name="android.permission.VIBRATE" />
 | 
			
		||||
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
 | 
			
		||||
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
 | 
			
		||||
 | 
			
		||||
</manifest>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,95 @@
 | 
			
		|||
<!--
 | 
			
		||||
  ~ Copyright (c) 2018-2020 DJI
 | 
			
		||||
  ~
 | 
			
		||||
  ~ Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
  ~ of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
  ~ in the Software without restriction, including without limitation the rights
 | 
			
		||||
  ~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
  ~ copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
  ~ furnished to do so, subject to the following conditions:
 | 
			
		||||
  ~
 | 
			
		||||
  ~ The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
  ~ copies or substantial portions of the Software.
 | 
			
		||||
  ~
 | 
			
		||||
  ~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
  ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
  ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
  ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
  ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
  ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
  ~ SOFTWARE.
 | 
			
		||||
  ~
 | 
			
		||||
  -->
 | 
			
		||||
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<!-- saved from url=(0048)http://djistatic.com/agreement/dji-go-4-tos.html -->
 | 
			
		||||
<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 | 
			
		||||
	
 | 
			
		||||
	<title>DJI AirSense Warnings</title>
 | 
			
		||||
	<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
			
		||||
	<meta name="description" content="DJI Pilot App Terms of Use">
 | 
			
		||||
	<meta name="keywords" content="djigo4,goapp,dji">
 | 
			
		||||
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
 | 
			
		||||
	<meta http-equiv="cleartype" content="on">
 | 
			
		||||
 | 
			
		||||
	<style> *{margin:0;padding:0}body{font-size:14px;font-family:sans-serif;color:#555}h1{text-align:center;margin:30px 20px;color:#000;font-size:20px}a{color:#000}p{line-height:20px;margin:0 0 20px;word-break:break-all;font-size:14px}strong{font-weight:bold;color:#000}.content{width:80%;margin:0 auto}ol{padding-left:20px}li{margin-bottom:25px;line-height:20px}dt{font-weight:bold;margin-bottom:10px;color:#000}h3{margin-bottom:15px;color:#000}h4{font-weight:bold;color:#333;margin-bottom:10px}ul{padding-left:20px}@media (max-width:640px){.content{width:90%}}
 | 
			
		||||
</style>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
 | 
			
		||||
	<h1>DJI AirSense Warnings</h1>
 | 
			
		||||
	<div class="content">
 | 
			
		||||
 | 
			
		||||
		
 | 
			
		||||
		<ul>
 | 
			
		||||
			
 | 
			
		||||
			<li>
 | 
			
		||||
				DJI AirSense can detect nearby civil aircrafts and send out warnings only under certain circumstances;it will NOT control DJI aircraft to avoid other aircraft automatically. Make sure to fly with your aircraft within visual line of sight at all times, and always fly with caution. After receiving warnings, lower your aircraft to safe height. In addition, DJI AirSense has the following limitations:
 | 
			
		||||
 | 
			
		||||
			</li>
 | 
			
		||||
			
 | 
			
		||||
		</ul>
 | 
			
		||||
		<ol>
 | 
			
		||||
			<li>
 | 
			
		||||
				<dl>
 | 
			
		||||
					<dt>
 | 
			
		||||
                        DJI AirSense can only receive messages sent from civil aircraft equipped with an ADS-B out device under 1090ES (RTCA DO-260) or UAT (RTCA Do-282) standards. For civil aircraft without ADS-B outs or with malfunctioning ADS-B outs, DJI AirSense cannot receive related broadcasted messages or send out warnings.
 | 
			
		||||
					</dt>
 | 
			
		||||
				</dl>
 | 
			
		||||
			</li>
 | 
			
		||||
			<li>
 | 
			
		||||
				<dl>
 | 
			
		||||
					<dt>
 | 
			
		||||
                        When there are obstacles in between a civil aircraft and DJI aircraft, DJI AirSense will fail to receive ADS-B messages sent from civil aircraft or to send out warnings.
 | 
			
		||||
					</dt>
 | 
			
		||||
				</dl>
 | 
			
		||||
			</li>
 | 
			
		||||
			<li>
 | 
			
		||||
				<dl>
 | 
			
		||||
					<dt>
 | 
			
		||||
                        DJI AirSense may fail to receive ADS-B messages sent from civil aircraft or send out warnings due to ever changing circumstances and interference. It is highly recommended to fly with caution and stay aware of your surroundings during flight.
 | 
			
		||||
					</dt>
 | 
			
		||||
				</dl>
 | 
			
		||||
			</li>
 | 
			
		||||
			<li>
 | 
			
		||||
				<dl>
 | 
			
		||||
					<dt>
 | 
			
		||||
                        DJI AirSense cannot send out warnings when the DJI aircraft cannot accurately determine its location.
 | 
			
		||||
					</dt>
 | 
			
		||||
				</dl>
 | 
			
		||||
			</li>
 | 
			
		||||
			<li>
 | 
			
		||||
				<dl>
 | 
			
		||||
					<dt>
 | 
			
		||||
                        DJI AirSense cannot receive ADS-B messages sent from civil aircraft or send out warnings when it is disabled or misconfigured.
 | 
			
		||||
					</dt>
 | 
			
		||||
				</dl>
 | 
			
		||||
			</li>
 | 
			
		||||
			
 | 
			
		||||
		</ol>
 | 
			
		||||
		            
 | 
			
		||||
      
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
</body></html>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 212 B  | 
| 
		 After Width: | Height: | Size: 577 B  | 
| 
		 After Width: | Height: | Size: 8.8 KiB  | 
| 
		 After Width: | Height: | Size: 1.2 KiB  | 
| 
		 After Width: | Height: | Size: 1.4 KiB  | 
| 
		 After Width: | Height: | Size: 10 KiB  | 
| 
		 After Width: | Height: | Size: 10 KiB  | 
| 
		 After Width: | Height: | Size: 214 B  | 
| 
		 After Width: | Height: | Size: 357 B  | 
| 
		 After Width: | Height: | Size: 496 B  | 
| 
		 After Width: | Height: | Size: 528 B  | 
| 
		 After Width: | Height: | Size: 577 B  | 
| 
		 After Width: | Height: | Size: 1.2 KiB  | 
| 
		 After Width: | Height: | Size: 1.4 KiB  | 
| 
		 After Width: | Height: | Size: 10 KiB  | 
| 
		 After Width: | Height: | Size: 214 B  | 
| 
		 After Width: | Height: | Size: 212 B  | 
| 
		 After Width: | Height: | Size: 528 B  | 
| 
		 After Width: | Height: | Size: 496 B  | 
| 
		 After Width: | Height: | Size: 357 B  | 
| 
		 After Width: | Height: | Size: 8.8 KiB  | 
| 
		 After Width: | Height: | Size: 13 KiB  | 
| 
		 After Width: | Height: | Size: 2.2 KiB  | 
| 
		 After Width: | Height: | Size: 625 B  | 
| 
		 After Width: | Height: | Size: 5.5 KiB  | 
| 
		 After Width: | Height: | Size: 453 B  | 
| 
		 After Width: | Height: | Size: 579 B  | 
| 
		 After Width: | Height: | Size: 602 B  | 
| 
		 After Width: | Height: | Size: 231 B  | 
| 
		 After Width: | Height: | Size: 230 B  | 
| 
		 After Width: | Height: | Size: 527 B  | 
| 
		 After Width: | Height: | Size: 1.4 KiB  | 
| 
		 After Width: | Height: | Size: 12 KiB  | 
| 
		 After Width: | Height: | Size: 683 B  | 
| 
		 After Width: | Height: | Size: 835 B  | 
| 
		 After Width: | Height: | Size: 587 B  | 
| 
		 After Width: | Height: | Size: 518 B  | 
| 
		 After Width: | Height: | Size: 269 B  | 
| 
		 After Width: | Height: | Size: 10 KiB  | 
| 
		 After Width: | Height: | Size: 1023 B  | 
| 
		 After Width: | Height: | Size: 1.0 KiB  | 
| 
		 After Width: | Height: | Size: 2.4 KiB  | 
| 
		 After Width: | Height: | Size: 6.1 KiB  | 
| 
						 | 
				
			
			@ -0,0 +1,200 @@
 | 
			
		|||
package dji.v5.ux.accessory
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.util.AttributeSet
 | 
			
		||||
import android.view.LayoutInflater
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import android.widget.*
 | 
			
		||||
import androidx.core.content.ContextCompat
 | 
			
		||||
import dji.v5.utils.common.LogUtils
 | 
			
		||||
import dji.v5.ux.R
 | 
			
		||||
import dji.v5.ux.core.extension.hide
 | 
			
		||||
import dji.v5.ux.core.extension.show
 | 
			
		||||
import java.util.ArrayList
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Description :RTK坐标系和RTK服务类型选择器
 | 
			
		||||
 *
 | 
			
		||||
 * @author: Byte.Cai
 | 
			
		||||
 *  date : 2022/7/25
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2022, DJI All Rights Reserved.
 | 
			
		||||
 */
 | 
			
		||||
private const val TAG = "DescSpinnerCell"
 | 
			
		||||
 | 
			
		||||
open class DescSpinnerCell @kotlin.jvm.JvmOverloads constructor(
 | 
			
		||||
    context: Context,
 | 
			
		||||
    val attrs: AttributeSet? = null,
 | 
			
		||||
    val defStyleAttr: Int = 0,
 | 
			
		||||
) : LinearLayout(context, attrs, defStyleAttr) {
 | 
			
		||||
    private var mSpinner: Spinner? = null
 | 
			
		||||
    private var mSummary: TextView? = null
 | 
			
		||||
    private var mDesc: TextView? = null
 | 
			
		||||
    private var mAdapter: ArrayAdapter<String>? = null
 | 
			
		||||
 | 
			
		||||
    private var mSelectedPosition = 0
 | 
			
		||||
    private var mSelectedListener: OnItemSelectedListener? = null
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        initView()
 | 
			
		||||
        initListener()
 | 
			
		||||
        initAttrs()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun initView() {
 | 
			
		||||
        LayoutInflater.from(context).inflate(R.layout.uxsdk_spinner_desc_layout, this, true)
 | 
			
		||||
        mSpinner = findViewById(R.id.spinner)
 | 
			
		||||
        mSummary = findViewById(R.id.summary)
 | 
			
		||||
        mDesc = findViewById(R.id.desc)
 | 
			
		||||
 | 
			
		||||
        mAdapter = object : ArrayAdapter<String>(context, R.layout.uxsdk_spinner_item_bord) {
 | 
			
		||||
            override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View? {
 | 
			
		||||
                var rootView = super.getDropDownView(position, convertView, parent)
 | 
			
		||||
                rootView?.let {
 | 
			
		||||
                    val view = rootView as TextView
 | 
			
		||||
                    // 右边的下拉图标
 | 
			
		||||
                    if (position != 0 && checkRightCompoundDrawable(view)) {
 | 
			
		||||
                        val rightDrawable = view.compoundDrawables[2].mutate()
 | 
			
		||||
                        rightDrawable.alpha = 0
 | 
			
		||||
                        view.setCompoundDrawables(null, null, rightDrawable, null)
 | 
			
		||||
 | 
			
		||||
                    }
 | 
			
		||||
                    if (mSelectedPosition == position) {
 | 
			
		||||
                        view.setTextColor(ContextCompat.getColor(context, R.color.uxsdk_white))
 | 
			
		||||
                    } else {
 | 
			
		||||
                        view.setTextColor(ContextCompat.getColor(context, R.color.uxsdk_white_75_percent))
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                return rootView
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            private fun checkRightCompoundDrawable(view: TextView?): Boolean {
 | 
			
		||||
                return view?.compoundDrawables != null && view.compoundDrawables.size == 4 && view.compoundDrawables[2] != null
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        mAdapter?.setDropDownViewResource(R.layout.uxsdk_spinner_item_drop)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private fun initListener() {
 | 
			
		||||
        // 这里需要设置不保存状态。否则在view被销毁重新加载并恢复时,由于spinnerID相同,导致value被复用
 | 
			
		||||
        mSpinner?.isSaveEnabled = false
 | 
			
		||||
        mSpinner?.adapter = mAdapter
 | 
			
		||||
        mSpinner?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
 | 
			
		||||
            override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
 | 
			
		||||
                LogUtils.i(TAG, "onItemSelected , mSelectedPosition=$mSelectedPosition,position=$position")
 | 
			
		||||
 | 
			
		||||
                if (mSelectedPosition != position) {
 | 
			
		||||
                    mSpinner?.setSelection(position, true)
 | 
			
		||||
                    mSelectedListener?.onItemSelected(position)
 | 
			
		||||
                    mSelectedPosition = position
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            override fun onNothingSelected(parent: AdapterView<*>?) {
 | 
			
		||||
                LogUtils.e(TAG, "onNothingSelected")
 | 
			
		||||
                //不需要实现
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun initAttrs() {
 | 
			
		||||
        val ta = context.theme.obtainStyledAttributes(attrs, R.styleable.DescSpinnerCell, defStyleAttr, 0)
 | 
			
		||||
        if (ta.hasValue(R.styleable.DescSpinnerCell_uxsdk_summary)) {
 | 
			
		||||
            mSummary?.show()
 | 
			
		||||
            mSummary?.text = ta.getString(R.styleable.DescSpinnerCell_uxsdk_summary)
 | 
			
		||||
        } else {
 | 
			
		||||
            mSummary?.hide()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (ta.hasValue(R.styleable.DescSpinnerCell_uxsdk_desc)) {
 | 
			
		||||
            mDesc?.show()
 | 
			
		||||
            mDesc?.text = ta.getString(R.styleable.DescSpinnerCell_uxsdk_desc)
 | 
			
		||||
        } else {
 | 
			
		||||
            mDesc?.hide()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (ta.hasValue(R.styleable.DescSpinnerCell_uxsdk_entries)) {
 | 
			
		||||
            var entries = ta.getTextArray(R.styleable.DescSpinnerCell_uxsdk_entries)
 | 
			
		||||
            if (entries != null && entries.isNotEmpty()) {
 | 
			
		||||
                val list: MutableList<String> = ArrayList(entries.size)
 | 
			
		||||
                for (s in entries) {
 | 
			
		||||
                    list.add(s.toString())
 | 
			
		||||
                }
 | 
			
		||||
                setEntries(list)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    interface OnItemSelectedListener {
 | 
			
		||||
        fun onItemSelected(position: Int)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun addOnItemSelectedListener(listener: OnItemSelectedListener?) {
 | 
			
		||||
        mSelectedListener = listener
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    open fun setSummaryText(summaryText: String) {
 | 
			
		||||
        if (mSummary?.text?.equals(summaryText) != true) {
 | 
			
		||||
            mSummary?.show()
 | 
			
		||||
            mSummary?.text = summaryText
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open fun setSummaryText(summaryTextId: Int) {
 | 
			
		||||
        mSummary?.show()
 | 
			
		||||
        mSummary?.setText(summaryTextId)
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open fun setSDescText(descText: String) {
 | 
			
		||||
        if (mDesc?.text?.equals(descText) != true) {
 | 
			
		||||
            mDesc?.show()
 | 
			
		||||
            mDesc?.text = descText
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open fun getDescText():TextView?{
 | 
			
		||||
        return mDesc
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open fun getSelectPosition() :Int{
 | 
			
		||||
        return mSelectedPosition;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open fun setEntries(entries: List<String?>) {
 | 
			
		||||
        mAdapter?.clear()
 | 
			
		||||
        mAdapter?.addAll(entries)
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun select(position: Int) {
 | 
			
		||||
        if (position >= 0 && position < mAdapter?.count ?: -1) {
 | 
			
		||||
            mSpinner?.setSelection(position, true)
 | 
			
		||||
            mSelectedPosition = position
 | 
			
		||||
        }
 | 
			
		||||
        invalidate()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    override fun setEnabled(enable: Boolean) {
 | 
			
		||||
        super.setEnabled(enable)
 | 
			
		||||
        mSpinner?.isEnabled = enable
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,369 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.accessory
 | 
			
		||||
 | 
			
		||||
import android.annotation.SuppressLint
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.res.ColorStateList
 | 
			
		||||
import android.graphics.drawable.Drawable
 | 
			
		||||
import android.os.Build
 | 
			
		||||
import android.util.AttributeSet
 | 
			
		||||
import android.widget.CompoundButton
 | 
			
		||||
import android.widget.Switch
 | 
			
		||||
import android.widget.TextView
 | 
			
		||||
import androidx.annotation.*
 | 
			
		||||
import androidx.core.content.res.use
 | 
			
		||||
import dji.v5.utils.common.DisplayUtil
 | 
			
		||||
import dji.v5.utils.common.LogUtils
 | 
			
		||||
import dji.v5.ux.R
 | 
			
		||||
import io.reactivex.rxjava3.core.Flowable
 | 
			
		||||
import io.reactivex.rxjava3.disposables.Disposable
 | 
			
		||||
import io.reactivex.rxjava3.processors.PublishProcessor
 | 
			
		||||
import dji.v5.ux.core.base.DJISDKModel
 | 
			
		||||
import dji.v5.ux.core.base.SchedulerProvider
 | 
			
		||||
import dji.v5.ux.core.base.widget.ConstraintLayoutWidget
 | 
			
		||||
import dji.v5.ux.core.communication.ObservableInMemoryKeyedStore
 | 
			
		||||
import dji.v5.ux.core.extension.*
 | 
			
		||||
import dji.v5.ux.core.util.UxErrorHandle
 | 
			
		||||
 | 
			
		||||
private const val TAG = "RTKEnabledWidget"
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This widget displays a switch that will enable or disable RTK.
 | 
			
		||||
 */
 | 
			
		||||
open class RTKEnabledWidget @JvmOverloads constructor(
 | 
			
		||||
    context: Context,
 | 
			
		||||
    attrs: AttributeSet? = null,
 | 
			
		||||
    defStyleAttr: Int = 0
 | 
			
		||||
) : ConstraintLayoutWidget<RTKEnabledWidget.ModelState>(context, attrs, defStyleAttr), CompoundButton.OnCheckedChangeListener {
 | 
			
		||||
 | 
			
		||||
    //region Fields
 | 
			
		||||
    private val rtkTitleTextView: TextView = findViewById(R.id.textview_rtk_title)
 | 
			
		||||
    private val rtkEnabledSwitch: Switch = findViewById(R.id.switch_rtk_enabled)
 | 
			
		||||
    private val rtkEnabledDescriptionTextView: TextView = findViewById(R.id.textview_rtk_enabled_description)
 | 
			
		||||
    private val uiUpdateStateProcessor: PublishProcessor<UIState> = PublishProcessor.create()
 | 
			
		||||
 | 
			
		||||
    private val widgetModel by lazy {
 | 
			
		||||
        RTKEnabledWidgetModel(
 | 
			
		||||
                DJISDKModel.getInstance(),
 | 
			
		||||
                ObservableInMemoryKeyedStore.getInstance())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Background of the title text
 | 
			
		||||
     */
 | 
			
		||||
    var titleTextBackground: Drawable?
 | 
			
		||||
        get() = rtkTitleTextView.background
 | 
			
		||||
        set(value) {
 | 
			
		||||
            rtkTitleTextView.background = value
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Size of the title text
 | 
			
		||||
     */
 | 
			
		||||
    var titleTextSize: Float
 | 
			
		||||
        @Dimension
 | 
			
		||||
        get() = rtkTitleTextView.textSize
 | 
			
		||||
        set(@Dimension textSize) {
 | 
			
		||||
            rtkTitleTextView.textSize = textSize
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Color of the title text
 | 
			
		||||
     */
 | 
			
		||||
    var titleTextColor: Int
 | 
			
		||||
        @ColorInt
 | 
			
		||||
        get() = rtkTitleTextView.textColor
 | 
			
		||||
        set(@ColorInt textColor) {
 | 
			
		||||
            rtkTitleTextView.textColor = textColor
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The drawable resource for the RTK enabled switch's thumb
 | 
			
		||||
     */
 | 
			
		||||
    var rtkEnabledSwitchThumbIcon: Drawable
 | 
			
		||||
        @JvmName("getRTKEnabledSwitchThumbIcon")
 | 
			
		||||
        get() = rtkEnabledSwitch.thumbDrawable
 | 
			
		||||
        @JvmName("setRTKEnabledSwitchThumbIcon")
 | 
			
		||||
        set(value) {
 | 
			
		||||
            rtkEnabledSwitch.thumbDrawable = value
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The drawable resource for the RTK enabled switch's track
 | 
			
		||||
     */
 | 
			
		||||
    var rtkEnabledSwitchTrackIcon: Drawable
 | 
			
		||||
        @JvmName("getRTKEnabledSwitchTrackIcon")
 | 
			
		||||
        get() = rtkEnabledSwitch.trackDrawable
 | 
			
		||||
        @JvmName("setRTKEnabledSwitchTrackIcon")
 | 
			
		||||
        set(value) {
 | 
			
		||||
            rtkEnabledSwitch.trackDrawable = value
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The text color state list for the RTK enabled switch's track
 | 
			
		||||
     */
 | 
			
		||||
    var rtkEnabledSwitchTrackColor: ColorStateList?
 | 
			
		||||
        @RequiresApi(Build.VERSION_CODES.M)
 | 
			
		||||
        @JvmName("getRTKEnabledSwitchTrackColor")
 | 
			
		||||
        get() = rtkEnabledSwitch.trackTintList
 | 
			
		||||
        @RequiresApi(Build.VERSION_CODES.M)
 | 
			
		||||
        @JvmName("setRTKEnabledSwitchTrackColor")
 | 
			
		||||
        set(value) {
 | 
			
		||||
            rtkEnabledSwitch.trackTintList = value
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Background of the description text
 | 
			
		||||
     */
 | 
			
		||||
    var descriptionTextBackground: Drawable?
 | 
			
		||||
        get() = rtkEnabledDescriptionTextView.background
 | 
			
		||||
        set(value) {
 | 
			
		||||
            rtkEnabledDescriptionTextView.background = value
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Size of the description text
 | 
			
		||||
     */
 | 
			
		||||
    var descriptionTextSize: Float
 | 
			
		||||
        @Dimension
 | 
			
		||||
        get() = rtkEnabledDescriptionTextView.textSize
 | 
			
		||||
        set(@Dimension textSize) {
 | 
			
		||||
            rtkEnabledDescriptionTextView.textSize = textSize
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Color of the description text
 | 
			
		||||
     */
 | 
			
		||||
    var descriptionTextColor: Int
 | 
			
		||||
        @ColorInt
 | 
			
		||||
        get() = rtkEnabledDescriptionTextView.textColor
 | 
			
		||||
        set(@ColorInt textColor) {
 | 
			
		||||
            rtkEnabledDescriptionTextView.textColor = textColor
 | 
			
		||||
        }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Constructor
 | 
			
		||||
    override fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
 | 
			
		||||
        inflate(context, R.layout.uxsdk_widget_rtk_enabled, this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        rtkEnabledSwitch.setOnCheckedChangeListener(this)
 | 
			
		||||
        attrs?.let { initAttributes(context, it) }
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Lifecycle
 | 
			
		||||
    override fun onAttachedToWindow() {
 | 
			
		||||
        super.onAttachedToWindow()
 | 
			
		||||
        if (!isInEditMode) {
 | 
			
		||||
            widgetModel.setup()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDetachedFromWindow() {
 | 
			
		||||
        if (!isInEditMode) {
 | 
			
		||||
            widgetModel.cleanup()
 | 
			
		||||
        }
 | 
			
		||||
        super.onDetachedFromWindow()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) {
 | 
			
		||||
        addDisposable(widgetModel.canEnableRTK.firstOrError()
 | 
			
		||||
                .observeOn(SchedulerProvider.ui())
 | 
			
		||||
                .subscribe({ canEnableRTK: Boolean ->
 | 
			
		||||
                    if (!canEnableRTK) {
 | 
			
		||||
                        setRTKSwitch(!isChecked)
 | 
			
		||||
                        showLongToast(R.string.uxsdk_rtk_enabled_motors_running)
 | 
			
		||||
                    } else {
 | 
			
		||||
                        setRTKEnabled(isChecked)
 | 
			
		||||
                    }
 | 
			
		||||
                }, UxErrorHandle.logErrorConsumer(TAG, "canEnableRTK: ")))
 | 
			
		||||
        uiUpdateStateProcessor.onNext(UIState.SwitchChanged(isChecked))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun reactToModelChanges() {
 | 
			
		||||
        addReaction(widgetModel.rtkEnabled
 | 
			
		||||
                .observeOn(SchedulerProvider.ui())
 | 
			
		||||
                .subscribe { updateUIForRTKEnabled(it) })
 | 
			
		||||
        addReaction(widgetModel.productConnection
 | 
			
		||||
                .observeOn(SchedulerProvider.ui())
 | 
			
		||||
                .subscribe { widgetStateDataProcessor.onNext(ModelState.ProductConnected(it)) })
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Reaction helpers
 | 
			
		||||
    private fun updateUIForRTKEnabled(rtkEnabled: Boolean) {
 | 
			
		||||
        setRTKSwitch(rtkEnabled)
 | 
			
		||||
        widgetStateDataProcessor.onNext(ModelState.RTKEnabledUpdated(rtkEnabled))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setRTKEnabled(enabled: Boolean) {
 | 
			
		||||
        addDisposable(widgetModel.rtkEnabled
 | 
			
		||||
                .firstOrError()
 | 
			
		||||
                .observeOn(SchedulerProvider.ui())
 | 
			
		||||
                .subscribe({ rtkEnabled: Boolean ->
 | 
			
		||||
                    if (rtkEnabled != enabled) {
 | 
			
		||||
                        addDisposable(toggleRTK(enabled))
 | 
			
		||||
                    }
 | 
			
		||||
                }, UxErrorHandle.logErrorConsumer(TAG, "rtkEnabled: ")))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun toggleRTK(enabled: Boolean): Disposable {
 | 
			
		||||
        return widgetModel.setRTKEnabled(enabled)
 | 
			
		||||
                .observeOn(SchedulerProvider.ui())
 | 
			
		||||
                .subscribe({}) { throwable: Throwable ->
 | 
			
		||||
                    setRTKSwitch(!enabled)
 | 
			
		||||
                    LogUtils.e(TAG, "setRTKEnabled: " + throwable.localizedMessage)
 | 
			
		||||
                }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setRTKSwitch(isChecked: Boolean) {
 | 
			
		||||
        rtkEnabledSwitch.setOnCheckedChangeListener(null)
 | 
			
		||||
        rtkEnabledSwitch.isChecked = isChecked
 | 
			
		||||
        rtkEnabledSwitch.setOnCheckedChangeListener(this)
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Customization
 | 
			
		||||
    override fun getIdealDimensionRatioString(): String {
 | 
			
		||||
        return getString(R.string.uxsdk_widget_rtk_enabled_ratio)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set text appearance of the title text
 | 
			
		||||
     *
 | 
			
		||||
     * @param textAppearanceResId Style resource for text appearance
 | 
			
		||||
     */
 | 
			
		||||
    fun setTitleTextAppearance(@StyleRes textAppearanceResId: Int) {
 | 
			
		||||
        rtkTitleTextView.setTextAppearance(context, textAppearanceResId)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the resource ID for the RTK enabled switch's thumb
 | 
			
		||||
     *
 | 
			
		||||
     * @param resourceId Integer ID of the drawable resource
 | 
			
		||||
     */
 | 
			
		||||
    fun setRTKEnabledSwitchThumbIcon(@DrawableRes resourceId: Int) {
 | 
			
		||||
        rtkEnabledSwitchThumbIcon = getDrawable(resourceId)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the resource ID for the RTK enabled switch's track
 | 
			
		||||
     *
 | 
			
		||||
     * @param resourceId Integer ID of the drawable resource
 | 
			
		||||
     */
 | 
			
		||||
    fun setRTKEnabledSwitchTrackIcon(@DrawableRes resourceId: Int) {
 | 
			
		||||
        rtkEnabledSwitchTrackIcon = getDrawable(resourceId)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set text appearance of the description text
 | 
			
		||||
     *
 | 
			
		||||
     * @param textAppearanceResId Style resource for text appearance
 | 
			
		||||
     */
 | 
			
		||||
    fun setDescriptionTextAppearance(@StyleRes textAppearanceResId: Int) {
 | 
			
		||||
        rtkEnabledDescriptionTextView.setTextAppearance(context, textAppearanceResId)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressLint("Recycle")
 | 
			
		||||
    private fun initAttributes(context: Context, attrs: AttributeSet) {
 | 
			
		||||
        context.obtainStyledAttributes(attrs, R.styleable.RTKEnabledWidget).use { typedArray ->
 | 
			
		||||
            typedArray.getResourceIdAndUse(R.styleable.RTKEnabledWidget_uxsdk_titleTextAppearance) {
 | 
			
		||||
                setTitleTextAppearance(it)
 | 
			
		||||
            }
 | 
			
		||||
            typedArray.getDimensionAndUse(R.styleable.RTKEnabledWidget_uxsdk_titleTextSize) {
 | 
			
		||||
                titleTextSize = DisplayUtil.pxToSp(context, it)
 | 
			
		||||
            }
 | 
			
		||||
            typedArray.getColorAndUse(R.styleable.RTKEnabledWidget_uxsdk_titleTextColor) {
 | 
			
		||||
                titleTextColor = it
 | 
			
		||||
            }
 | 
			
		||||
            typedArray.getDrawableAndUse(R.styleable.RTKEnabledWidget_uxsdk_titleTextBackground) {
 | 
			
		||||
                titleTextBackground = it
 | 
			
		||||
            }
 | 
			
		||||
            typedArray.getDrawableAndUse(R.styleable.RTKEnabledWidget_uxsdk_rtkEnabledSwitchThumbIcon) {
 | 
			
		||||
                rtkEnabledSwitchThumbIcon = it
 | 
			
		||||
            }
 | 
			
		||||
            typedArray.getDrawableAndUse(R.styleable.RTKEnabledWidget_uxsdk_rtkEnabledSwitchTrackIcon) {
 | 
			
		||||
                rtkEnabledSwitchTrackIcon = it
 | 
			
		||||
            }
 | 
			
		||||
            typedArray.getResourceIdAndUse(R.styleable.RTKEnabledWidget_uxsdk_descriptionTextAppearance) {
 | 
			
		||||
                setDescriptionTextAppearance(it)
 | 
			
		||||
            }
 | 
			
		||||
            typedArray.getDimensionAndUse(R.styleable.RTKEnabledWidget_uxsdk_descriptionTextSize) {
 | 
			
		||||
                descriptionTextSize = DisplayUtil.pxToSp(context, it)
 | 
			
		||||
            }
 | 
			
		||||
            typedArray.getColorAndUse(R.styleable.RTKEnabledWidget_uxsdk_descriptionTextColor) {
 | 
			
		||||
                descriptionTextColor = it
 | 
			
		||||
            }
 | 
			
		||||
            typedArray.getDrawableAndUse(R.styleable.RTKEnabledWidget_uxsdk_descriptionTextBackground) {
 | 
			
		||||
                descriptionTextBackground = it
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Hooks
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the [UIState] updates
 | 
			
		||||
     */
 | 
			
		||||
    fun getUIStateUpdates(): Flowable<UIState> {
 | 
			
		||||
        return uiUpdateStateProcessor.onBackpressureBuffer()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Widget UI update State
 | 
			
		||||
     */
 | 
			
		||||
    sealed class UIState {
 | 
			
		||||
        /**
 | 
			
		||||
         * RTK enabled switch check changed update
 | 
			
		||||
         */
 | 
			
		||||
        data class SwitchChanged(val isChecked: Boolean) : UIState()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the [ModelState] updates
 | 
			
		||||
     */
 | 
			
		||||
    @SuppressWarnings
 | 
			
		||||
    override fun getWidgetStateUpdate(): Flowable<ModelState> {
 | 
			
		||||
        return super.getWidgetStateUpdate()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Class defines the widget state updates
 | 
			
		||||
     */
 | 
			
		||||
    sealed class ModelState {
 | 
			
		||||
        /**
 | 
			
		||||
         * Product connection update
 | 
			
		||||
         */
 | 
			
		||||
        data class ProductConnected(val isConnected: Boolean) : ModelState()
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * RTK enabled update
 | 
			
		||||
         */
 | 
			
		||||
        data class RTKEnabledUpdated(val isRTKEnabled: Boolean) : ModelState()
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,114 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.accessory
 | 
			
		||||
 | 
			
		||||
import dji.sdk.keyvalue.key.FlightControllerKey
 | 
			
		||||
import dji.sdk.keyvalue.key.DJIKey
 | 
			
		||||
import dji.sdk.keyvalue.key.RtkMobileStationKey
 | 
			
		||||
import dji.sdk.keyvalue.value.rtkmobilestation.RTKHomePointDataSource
 | 
			
		||||
import dji.sdk.keyvalue.value.rtkmobilestation.RTKHomePointInfo
 | 
			
		||||
import dji.sdk.keyvalue.value.rtkmobilestation.RTKTakeoffAltitudeInfo
 | 
			
		||||
import dji.sdk.keyvalue.key.KeyTools
 | 
			
		||||
import dji.v5.manager.KeyManager
 | 
			
		||||
import dji.v5.ux.core.base.DJISDKModel
 | 
			
		||||
import dji.v5.ux.core.base.WidgetModel
 | 
			
		||||
import dji.v5.ux.core.communication.ObservableInMemoryKeyedStore
 | 
			
		||||
import dji.v5.ux.core.util.DataProcessor
 | 
			
		||||
import io.reactivex.rxjava3.core.Completable
 | 
			
		||||
import io.reactivex.rxjava3.core.Flowable
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Widget Model for the [RTKEnabledWidget] used to define
 | 
			
		||||
 * the underlying logic and communication
 | 
			
		||||
 */
 | 
			
		||||
private const val TAG = "RTKEnabledWidgetModel"
 | 
			
		||||
 | 
			
		||||
class RTKEnabledWidgetModel(
 | 
			
		||||
    djiSdkModel: DJISDKModel,
 | 
			
		||||
    keyedStore: ObservableInMemoryKeyedStore
 | 
			
		||||
) : WidgetModel(djiSdkModel, keyedStore) {
 | 
			
		||||
 | 
			
		||||
    //region Fields
 | 
			
		||||
    private val isRTKEnabledKey: DJIKey<Boolean> = KeyTools.createKey(
 | 
			
		||||
        RtkMobileStationKey.KeyRTKEnable)
 | 
			
		||||
    private val isRTKEnabledProcessor: DataProcessor<Boolean> = DataProcessor.create(false)
 | 
			
		||||
    private val isMotorOnProcessor: DataProcessor<Boolean> = DataProcessor.create(false)
 | 
			
		||||
    private val homePointDataSourceProcessor: DataProcessor<RTKHomePointInfo> = DataProcessor.create(RTKHomePointInfo())
 | 
			
		||||
    private val isRTKTakeoffHeightSetProcessor: DataProcessor<RTKTakeoffAltitudeInfo> = DataProcessor.create(RTKTakeoffAltitudeInfo())
 | 
			
		||||
    private val canEnableRTKProcessor: DataProcessor<Boolean> = DataProcessor.create(true)
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Data
 | 
			
		||||
    /**
 | 
			
		||||
     * Get whether RTK is enabled.
 | 
			
		||||
     */
 | 
			
		||||
    val rtkEnabled: Flowable<Boolean>
 | 
			
		||||
        @JvmName("getRTKEnabled")
 | 
			
		||||
        get() = isRTKEnabledProcessor.toFlowable()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get whether RTK can be enabled.
 | 
			
		||||
     */
 | 
			
		||||
    val canEnableRTK: Flowable<Boolean>
 | 
			
		||||
        get() = canEnableRTKProcessor.toFlowable()
 | 
			
		||||
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Lifecycle
 | 
			
		||||
    override fun inSetup() {
 | 
			
		||||
        bindDataProcessor(isRTKEnabledKey, isRTKEnabledProcessor)
 | 
			
		||||
        bindDataProcessor(
 | 
			
		||||
            KeyTools.createKey(
 | 
			
		||||
                FlightControllerKey.KeyAreMotorsOn), isMotorOnProcessor)
 | 
			
		||||
        bindDataProcessor(
 | 
			
		||||
            KeyTools.createKey(
 | 
			
		||||
                RtkMobileStationKey.KeyRTKHomePointInfo), homePointDataSourceProcessor)
 | 
			
		||||
        bindDataProcessor(
 | 
			
		||||
            KeyTools.createKey(
 | 
			
		||||
                RtkMobileStationKey.KeyRTKTakeoffAltitudeInfo), isRTKTakeoffHeightSetProcessor)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun inCleanup() {
 | 
			
		||||
        KeyManager.getInstance().cancelListen(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * RTK能否开启,这里捆绑了几个逻辑:
 | 
			
		||||
     * 1、只有在电机关闭时才可以设置RTK开启/关闭状态;
 | 
			
		||||
     * 2、如果电机已开启,则需要飞行高度已设置+返航点的类型设置为RTK,这时才可以开启/关闭RTK
 | 
			
		||||
     */
 | 
			
		||||
    override fun updateStates() {
 | 
			
		||||
        canEnableRTKProcessor.onNext(
 | 
			
		||||
            !isMotorOnProcessor.value || (isRTKTakeoffHeightSetProcessor.value.valid
 | 
			
		||||
                    && homePointDataSourceProcessor.value.homePointDataSource == RTKHomePointDataSource.RTK)
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region User interaction
 | 
			
		||||
    fun setRTKEnabled(enabled: Boolean): Completable {
 | 
			
		||||
        return djiSdkModel.setValue(isRTKEnabledKey, enabled)
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,106 @@
 | 
			
		|||
package dji.v5.ux.accessory
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.util.AttributeSet
 | 
			
		||||
import android.widget.CompoundButton
 | 
			
		||||
import android.widget.Switch
 | 
			
		||||
import dji.v5.manager.aircraft.rtk.RTKCenter
 | 
			
		||||
import dji.v5.ux.R
 | 
			
		||||
import dji.v5.ux.core.base.DJISDKModel
 | 
			
		||||
import dji.v5.ux.core.base.SchedulerProvider
 | 
			
		||||
import dji.v5.ux.core.base.widget.ConstraintLayoutWidget
 | 
			
		||||
import dji.v5.ux.core.communication.ObservableInMemoryKeyedStore
 | 
			
		||||
import dji.v5.ux.core.extension.getString
 | 
			
		||||
import dji.v5.ux.core.util.UxErrorHandle
 | 
			
		||||
import io.reactivex.rxjava3.core.CompletableObserver
 | 
			
		||||
import io.reactivex.rxjava3.disposables.Disposable
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Description : This widget displays a switch that will enable or disable RTK Keep Status.
 | 
			
		||||
 | 
			
		||||
 *
 | 
			
		||||
 * @author: Byte.Cai
 | 
			
		||||
 *  date : 2022/7/11
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2022, DJI All Rights Reserved.
 | 
			
		||||
 */
 | 
			
		||||
private const val TAG = "RTKKeepStatusWidget"
 | 
			
		||||
 | 
			
		||||
class RTKKeepStatusWidget @JvmOverloads constructor(
 | 
			
		||||
    context: Context,
 | 
			
		||||
    attrs: AttributeSet? = null,
 | 
			
		||||
    defStyleAttr: Int = 0,
 | 
			
		||||
) : ConstraintLayoutWidget<Boolean>(context, attrs, defStyleAttr), CompoundButton.OnCheckedChangeListener {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private val rtkKeepStatusSwitch: Switch = findViewById(R.id.rtk_keep_status_switch)
 | 
			
		||||
 | 
			
		||||
    private val widgetModel by lazy {
 | 
			
		||||
        RTKKeepStatusWidgetModel(
 | 
			
		||||
            DJISDKModel.getInstance(),
 | 
			
		||||
            ObservableInMemoryKeyedStore.getInstance(),
 | 
			
		||||
            RTKCenter.getInstance()
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
 | 
			
		||||
        inflate(context, R.layout.uxsdk_widget_keep_status_enable_layout, this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        rtkKeepStatusSwitch.setOnCheckedChangeListener(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onAttachedToWindow() {
 | 
			
		||||
        super.onAttachedToWindow()
 | 
			
		||||
        if (!isInEditMode) {
 | 
			
		||||
            widgetModel.setup()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDetachedFromWindow() {
 | 
			
		||||
        if (!isInEditMode) {
 | 
			
		||||
            widgetModel.cleanup()
 | 
			
		||||
        }
 | 
			
		||||
        super.onDetachedFromWindow()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun reactToModelChanges() {
 | 
			
		||||
        addReaction(widgetModel.rtkKeepStatusEnable
 | 
			
		||||
            .observeOn(SchedulerProvider.ui())
 | 
			
		||||
            .subscribe {
 | 
			
		||||
                setRTKSwitch(it)
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setRTKSwitch(isChecked: Boolean) {
 | 
			
		||||
        rtkKeepStatusSwitch.setOnCheckedChangeListener(null)
 | 
			
		||||
        rtkKeepStatusSwitch.isChecked = isChecked
 | 
			
		||||
        rtkKeepStatusSwitch.setOnCheckedChangeListener(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getIdealDimensionRatioString(): String {
 | 
			
		||||
        return getString(R.string.uxsdk_widget_rtk_keep_status_ratio)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) {
 | 
			
		||||
        widgetModel.setRTKKeepStatusEnable(isChecked)
 | 
			
		||||
            .observeOn(SchedulerProvider.ui())
 | 
			
		||||
            .subscribe(object : CompletableObserver {
 | 
			
		||||
                override fun onSubscribe(d: Disposable) {
 | 
			
		||||
                    //Do nothing
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                override fun onComplete() {
 | 
			
		||||
                    //Do nothing
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                override fun onError(e: Throwable) {
 | 
			
		||||
                    setRTKSwitch(!isChecked)
 | 
			
		||||
                    UxErrorHandle.logErrorConsumer(TAG, "canEnableRTK: ")
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
package dji.v5.ux.accessory
 | 
			
		||||
 | 
			
		||||
import dji.v5.common.callback.CommonCallbacks
 | 
			
		||||
import dji.v5.common.error.IDJIError
 | 
			
		||||
import dji.v5.common.error.RxError
 | 
			
		||||
import dji.v5.manager.aircraft.rtk.RTKSystemState
 | 
			
		||||
import dji.v5.manager.aircraft.rtk.RTKSystemStateListener
 | 
			
		||||
import dji.v5.manager.interfaces.IRTKCenter
 | 
			
		||||
import dji.v5.ux.core.base.DJISDKModel
 | 
			
		||||
import dji.v5.ux.core.base.WidgetModel
 | 
			
		||||
import dji.v5.ux.core.communication.ObservableInMemoryKeyedStore
 | 
			
		||||
import dji.v5.ux.core.util.DataProcessor
 | 
			
		||||
import io.reactivex.rxjava3.core.Completable
 | 
			
		||||
import io.reactivex.rxjava3.core.Flowable
 | 
			
		||||
import io.reactivex.rxjava3.schedulers.Schedulers
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Description :Widget Model for the [RTKKeepStatusWidget] used to define
 | 
			
		||||
 * the underlying logic and communication
 | 
			
		||||
 *
 | 
			
		||||
 * @author: Byte.Cai
 | 
			
		||||
 *  date : 2022/7/11
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2022, DJI All Rights Reserved.
 | 
			
		||||
 */
 | 
			
		||||
class RTKKeepStatusWidgetModel(
 | 
			
		||||
    djiSdkModel: DJISDKModel,
 | 
			
		||||
    uxKeyManager: ObservableInMemoryKeyedStore,
 | 
			
		||||
    private val rtkCenter: IRTKCenter,
 | 
			
		||||
) : WidgetModel(djiSdkModel, uxKeyManager), RTKSystemStateListener {
 | 
			
		||||
    private val isRTKKeepStatusEnabledProcessor: DataProcessor<Boolean> = DataProcessor.create(false)
 | 
			
		||||
 | 
			
		||||
    override fun inSetup() {
 | 
			
		||||
        rtkCenter.addRTKSystemStateListener(this)
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun inCleanup() {
 | 
			
		||||
        rtkCenter.removeRTKSystemStateListener(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val rtkKeepStatusEnable: Flowable<Boolean>
 | 
			
		||||
        get() = isRTKKeepStatusEnabledProcessor.toFlowable()
 | 
			
		||||
 | 
			
		||||
    fun setRTKKeepStatusEnable(enabled: Boolean): Completable {
 | 
			
		||||
        return Completable.create {
 | 
			
		||||
            rtkCenter.setRTKMaintainAccuracyEnabled(enabled, object : CommonCallbacks.CompletionCallback {
 | 
			
		||||
                override fun onSuccess() {
 | 
			
		||||
                    it.onComplete()
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                override fun onFailure(error: IDJIError) {
 | 
			
		||||
                    it.onError(RxError(error))
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
        }.subscribeOn(Schedulers.computation())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onUpdate(rtkSystemState: RTKSystemState?) {
 | 
			
		||||
        rtkSystemState?.let {
 | 
			
		||||
            isRTKKeepStatusEnabledProcessor.onNext(it.rtkMaintainAccuracyEnabled)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,331 @@
 | 
			
		|||
package dji.v5.ux.accessory
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import dji.sdk.keyvalue.value.rtkbasestation.RTKReferenceStationSource
 | 
			
		||||
import dji.sdk.keyvalue.value.rtkbasestation.RTKServiceState
 | 
			
		||||
import dji.sdk.keyvalue.value.rtkbasestation.RTKStationConnetState
 | 
			
		||||
import dji.sdk.keyvalue.value.rtkmobilestation.RTKLocation
 | 
			
		||||
import dji.v5.common.error.IDJIError
 | 
			
		||||
import dji.v5.manager.aircraft.rtk.*
 | 
			
		||||
import dji.v5.manager.aircraft.rtk.network.INetworkServiceInfoListener
 | 
			
		||||
import dji.v5.manager.aircraft.rtk.station.RTKStationConnectStatusListener
 | 
			
		||||
import dji.v5.manager.interfaces.IRTKCenter
 | 
			
		||||
import dji.v5.utils.common.LogUtils
 | 
			
		||||
import dji.v5.ux.core.base.DJISDKModel
 | 
			
		||||
import dji.v5.ux.core.base.SchedulerProvider
 | 
			
		||||
import dji.v5.ux.core.base.WidgetModel
 | 
			
		||||
import dji.v5.ux.core.communication.GlobalPreferenceKeys
 | 
			
		||||
import dji.v5.ux.core.communication.GlobalPreferencesInterface
 | 
			
		||||
import dji.v5.ux.core.communication.ObservableInMemoryKeyedStore
 | 
			
		||||
import dji.v5.ux.core.communication.UXKeys
 | 
			
		||||
import dji.v5.ux.core.util.DataProcessor
 | 
			
		||||
import dji.v5.ux.core.util.UnitConversionUtil
 | 
			
		||||
import io.reactivex.rxjava3.core.Flowable
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Description :Widget Model for the [RTKSatelliteStatusWidget] used to define
 | 
			
		||||
 * the underlying logic and communication
 | 
			
		||||
 *
 | 
			
		||||
 * @author: Byte.Cai
 | 
			
		||||
 *  date : 2022/5/23
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2022, DJI All Rights Reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
class RTKSatelliteStatusWidgetModel(
 | 
			
		||||
    djiSdkModel: DJISDKModel,
 | 
			
		||||
    uxKeyManager: ObservableInMemoryKeyedStore,
 | 
			
		||||
    private val preferencesManager: GlobalPreferencesInterface?,
 | 
			
		||||
    private val rtkCenter: IRTKCenter,
 | 
			
		||||
) :
 | 
			
		||||
    WidgetModel(djiSdkModel, uxKeyManager) {
 | 
			
		||||
 | 
			
		||||
    private val TAG = LogUtils.getTag(this)
 | 
			
		||||
    private val rtkLocationInfoProcessor: DataProcessor<RTKLocationInfo> = DataProcessor.create(RTKLocationInfo())
 | 
			
		||||
    private val rtkSystemStateProcessor: DataProcessor<RTKSystemState> = DataProcessor.create(RTKSystemState())
 | 
			
		||||
    private val rtkStationConnectStateProcessor: DataProcessor<RTKStationConnetState> =
 | 
			
		||||
        DataProcessor.create(RTKStationConnetState.UNKNOWN)
 | 
			
		||||
    private val rtkNetworkServiceInfoProcessor: DataProcessor<RTKServiceState> =
 | 
			
		||||
        DataProcessor.create(RTKServiceState.UNKNOWN)
 | 
			
		||||
 | 
			
		||||
    private val unitTypeProcessor: DataProcessor<UnitConversionUtil.UnitType> =
 | 
			
		||||
        DataProcessor.create(UnitConversionUtil.UnitType.METRIC)
 | 
			
		||||
 | 
			
		||||
    //rtk连接数据的封装Processor,用于widget显示rtk是否连接和数据是否正在使用
 | 
			
		||||
    private val rtkBaseStationStateProcessor: DataProcessor<RTKBaseStationState> =
 | 
			
		||||
        DataProcessor.create(RTKBaseStationState.DISCONNECTED)
 | 
			
		||||
    private val rtkNetworkServiceStateProcessor: DataProcessor<RTKNetworkServiceState> = DataProcessor.create(
 | 
			
		||||
        RTKNetworkServiceState(
 | 
			
		||||
            RTKServiceState.UNKNOWN,
 | 
			
		||||
            isRTKBeingUsed = false,
 | 
			
		||||
            isNetworkServiceOpen = false,
 | 
			
		||||
            rtkSignal = RTKReferenceStationSource.UNKNOWN
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    //标准差
 | 
			
		||||
    private val standardDeviationProcessor: DataProcessor<StandardDeviation> = DataProcessor.create(
 | 
			
		||||
        StandardDeviation(
 | 
			
		||||
            0f,
 | 
			
		||||
            0f,
 | 
			
		||||
            0f,
 | 
			
		||||
            UnitConversionUtil.UnitType.METRIC
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    private val rtkLocationInfoListener = RTKLocationInfoListener {
 | 
			
		||||
        rtkLocationInfoProcessor.onNext(it)
 | 
			
		||||
        updateStandardDeviation(it.rtkLocation)
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    private val rtkSystemStateListener = RTKSystemStateListener {
 | 
			
		||||
        rtkSystemStateProcessor.onNext(it)
 | 
			
		||||
        //RTKSystemState涉及RTK服务类型的改变,所以有关于RTK服务类型的都需要更新
 | 
			
		||||
        updateRTKConnectionState()
 | 
			
		||||
        updateRTKListener(it.rtkReferenceStationSource)
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    private val stationConnectStatusListener = RTKStationConnectStatusListener {
 | 
			
		||||
        LogUtils.i(TAG, it)
 | 
			
		||||
        rtkStationConnectStateProcessor.onNext(it)
 | 
			
		||||
        updateRTKConnectionState()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private var mRTKServiceState = RTKServiceState.UNKNOWN
 | 
			
		||||
 | 
			
		||||
    private val networkServiceInfoListener: INetworkServiceInfoListener = object :
 | 
			
		||||
        INetworkServiceInfoListener {
 | 
			
		||||
        override fun onServiceStateUpdate(state: RTKServiceState?) {
 | 
			
		||||
            state?.let {
 | 
			
		||||
                if (mRTKServiceState != state) {
 | 
			
		||||
                    LogUtils.i(TAG, "onServiceStateUpdate RTKServiceState=$state")
 | 
			
		||||
                    mRTKServiceState = state
 | 
			
		||||
                    rtkNetworkServiceInfoProcessor.onNext(it)
 | 
			
		||||
                    updateRTKConnectionState()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        override fun onErrorCodeUpdate(error: IDJIError?) {
 | 
			
		||||
            error?.let {
 | 
			
		||||
                LogUtils.e(TAG, error.toString())
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @get:JvmName("getRTKLocationInfo")
 | 
			
		||||
    val rtkLocationInfo: Flowable<RTKLocationInfo>
 | 
			
		||||
        get() = rtkLocationInfoProcessor.toFlowable()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @get:JvmName("getRTKSystemState")
 | 
			
		||||
    val rtkSystemState: Flowable<RTKSystemState>
 | 
			
		||||
        get() = rtkSystemStateProcessor.toFlowable()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the standard deviation of the location accuracy.
 | 
			
		||||
     */
 | 
			
		||||
    val standardDeviation: Flowable<StandardDeviation>
 | 
			
		||||
        get() = standardDeviationProcessor.toFlowable()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the state of the RTK base station.
 | 
			
		||||
     */
 | 
			
		||||
    @get:JvmName("getRTKBaseStationState")
 | 
			
		||||
    val rtkBaseStationState: Flowable<RTKBaseStationState>
 | 
			
		||||
        get() = rtkBaseStationStateProcessor.toFlowable()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the state of the network service.
 | 
			
		||||
     */
 | 
			
		||||
    @get:JvmName("getRTKNetworkServiceState")
 | 
			
		||||
    val rtkNetworkServiceState: Flowable<RTKNetworkServiceState>
 | 
			
		||||
        get() = rtkNetworkServiceStateProcessor.toFlowable()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    //region Constructor
 | 
			
		||||
    init {
 | 
			
		||||
        if (preferencesManager != null) {
 | 
			
		||||
            unitTypeProcessor.onNext(preferencesManager.unitType)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    override fun inSetup() {
 | 
			
		||||
        rtkCenter.addRTKLocationInfoListener(rtkLocationInfoListener)
 | 
			
		||||
        rtkCenter.addRTKSystemStateListener(rtkSystemStateListener)
 | 
			
		||||
        rtkCenter.qxrtkManager.addNetworkRTKServiceInfoListener(networkServiceInfoListener)
 | 
			
		||||
        rtkCenter.customRTKManager.addNetworkRTKServiceInfoListener(networkServiceInfoListener)
 | 
			
		||||
        rtkCenter.cmccrtkManager.addNetworkRTKServiceInfoListener(networkServiceInfoListener)
 | 
			
		||||
        rtkCenter.rtkStationManager.addRTKStationConnectStatusListener(stationConnectStatusListener)
 | 
			
		||||
        //测试发现productConnection有时候返回true比较慢,所以在其值返回也是要刷新依赖其者的状态
 | 
			
		||||
        productConnection.observeOn(SchedulerProvider.ui()).subscribe {
 | 
			
		||||
            updateRTKConnectionState()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val unitKey = UXKeys.create(GlobalPreferenceKeys.UNIT_TYPE)
 | 
			
		||||
        bindDataProcessor(unitKey, unitTypeProcessor)
 | 
			
		||||
        updateRTKConnectionState()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun inCleanup() {
 | 
			
		||||
        rtkCenter.removeRTKLocationInfoListener(rtkLocationInfoListener)
 | 
			
		||||
        rtkCenter.removeRTKSystemStateListener(rtkSystemStateListener)
 | 
			
		||||
        rtkCenter.rtkStationManager.removeRTKStationConnectStatusListener(stationConnectStatusListener)
 | 
			
		||||
        rtkCenter.qxrtkManager.removeNetworkRTKServiceInfoListener(networkServiceInfoListener)
 | 
			
		||||
        rtkCenter.customRTKManager.removeNetworkRTKServiceInfoListener(networkServiceInfoListener)
 | 
			
		||||
        rtkCenter.cmccrtkManager.removeNetworkRTKServiceInfoListener(networkServiceInfoListener)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun updateRTKListener(rtkSource: RTKReferenceStationSource) {
 | 
			
		||||
        when (rtkSource) {
 | 
			
		||||
            RTKReferenceStationSource.QX_NETWORK_SERVICE -> {
 | 
			
		||||
                rtkCenter.customRTKManager.removeNetworkRTKServiceInfoListener(networkServiceInfoListener)
 | 
			
		||||
                rtkCenter.cmccrtkManager.removeNetworkRTKServiceInfoListener(networkServiceInfoListener)
 | 
			
		||||
                rtkCenter.rtkStationManager.removeRTKStationConnectStatusListener(stationConnectStatusListener)
 | 
			
		||||
                rtkCenter.qxrtkManager.addNetworkRTKServiceInfoListener(networkServiceInfoListener)
 | 
			
		||||
            }
 | 
			
		||||
            RTKReferenceStationSource.CUSTOM_NETWORK_SERVICE -> {
 | 
			
		||||
                rtkCenter.qxrtkManager.removeNetworkRTKServiceInfoListener(networkServiceInfoListener)
 | 
			
		||||
                rtkCenter.cmccrtkManager.removeNetworkRTKServiceInfoListener(networkServiceInfoListener)
 | 
			
		||||
                rtkCenter.rtkStationManager.removeRTKStationConnectStatusListener(stationConnectStatusListener)
 | 
			
		||||
                rtkCenter.customRTKManager.addNetworkRTKServiceInfoListener(networkServiceInfoListener)
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            RTKReferenceStationSource.NTRIP_NETWORK_SERVICE -> {
 | 
			
		||||
                rtkCenter.qxrtkManager.removeNetworkRTKServiceInfoListener(networkServiceInfoListener)
 | 
			
		||||
                rtkCenter.customRTKManager.removeNetworkRTKServiceInfoListener(networkServiceInfoListener)
 | 
			
		||||
                rtkCenter.rtkStationManager.removeRTKStationConnectStatusListener(stationConnectStatusListener)
 | 
			
		||||
                rtkCenter.cmccrtkManager.addNetworkRTKServiceInfoListener(networkServiceInfoListener)
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            RTKReferenceStationSource.BASE_STATION -> {
 | 
			
		||||
                rtkCenter.qxrtkManager.removeNetworkRTKServiceInfoListener(networkServiceInfoListener)
 | 
			
		||||
                rtkCenter.customRTKManager.removeNetworkRTKServiceInfoListener(networkServiceInfoListener)
 | 
			
		||||
                rtkCenter.cmccrtkManager.removeNetworkRTKServiceInfoListener(networkServiceInfoListener)
 | 
			
		||||
                rtkCenter.rtkStationManager.addRTKStationConnectStatusListener(stationConnectStatusListener)
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            else -> {
 | 
			
		||||
                //do nothing
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private fun updateStandardDeviation(rtkLocation: RTKLocation?) {
 | 
			
		||||
        var stdLatitude = 0f
 | 
			
		||||
        var stdLongitude = 0f
 | 
			
		||||
        var stdAltitude = 0f
 | 
			
		||||
        rtkLocation?.let {
 | 
			
		||||
            if (unitTypeProcessor.value == UnitConversionUtil.UnitType.IMPERIAL) {
 | 
			
		||||
                stdLatitude = UnitConversionUtil.convertMetersToFeet(it.stdLatitude.toFloat())
 | 
			
		||||
                stdLongitude = UnitConversionUtil.convertMetersToFeet(it.stdLongitude.toFloat())
 | 
			
		||||
                stdAltitude = UnitConversionUtil.convertMetersToFeet(it.stdAltitude.toFloat())
 | 
			
		||||
            } else {
 | 
			
		||||
                stdLatitude = it.stdLatitude.toFloat()
 | 
			
		||||
                stdLongitude = it.stdLongitude.toFloat()
 | 
			
		||||
                stdAltitude = it.stdAltitude.toFloat()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        standardDeviationProcessor.onNext(
 | 
			
		||||
            StandardDeviation(
 | 
			
		||||
                stdLatitude,
 | 
			
		||||
                stdLongitude,
 | 
			
		||||
                stdAltitude,
 | 
			
		||||
                unitTypeProcessor.value
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sends the latest network service state or base station state to the corresponding flowable.
 | 
			
		||||
     */
 | 
			
		||||
    fun updateRTKConnectionState() {
 | 
			
		||||
        rtkSystemStateProcessor.value.rtkReferenceStationSource?.let {
 | 
			
		||||
            if (isNetworkServiceOpen(it)) {
 | 
			
		||||
                updateNetworkServiceState()
 | 
			
		||||
            } else {
 | 
			
		||||
                updateBaseStationState()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The state of the network service.
 | 
			
		||||
     */
 | 
			
		||||
    data class RTKNetworkServiceState(
 | 
			
		||||
        val state: RTKServiceState?,
 | 
			
		||||
        val isRTKBeingUsed: Boolean?,
 | 
			
		||||
        val isNetworkServiceOpen: Boolean?,
 | 
			
		||||
        @get:JvmName("getRTKSignal")
 | 
			
		||||
        val rtkSignal: RTKReferenceStationSource?,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The state of the RTK base station
 | 
			
		||||
     */
 | 
			
		||||
    enum class RTKBaseStationState {
 | 
			
		||||
        /**
 | 
			
		||||
         * The RTK base station is connected and in use.
 | 
			
		||||
         */
 | 
			
		||||
        CONNECTED_IN_USE,
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The RTK base station is connected and not in use.
 | 
			
		||||
         */
 | 
			
		||||
        CONNECTED_NOT_IN_USE,
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The RTK base station is disconnected.
 | 
			
		||||
         */
 | 
			
		||||
        DISCONNECTED
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The standard deviation of the location accuracy.
 | 
			
		||||
     */
 | 
			
		||||
    data class StandardDeviation(
 | 
			
		||||
        val latitude: Float,
 | 
			
		||||
        val longitude: Float,
 | 
			
		||||
        val altitude: Float,
 | 
			
		||||
        val unitType: UnitConversionUtil.UnitType,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    //region Helper methods
 | 
			
		||||
    private fun updateNetworkServiceState() {
 | 
			
		||||
        rtkNetworkServiceStateProcessor.onNext(
 | 
			
		||||
            RTKNetworkServiceState(
 | 
			
		||||
                rtkNetworkServiceInfoProcessor.value,
 | 
			
		||||
                rtkSystemStateProcessor.value.rtkHealthy,
 | 
			
		||||
                isNetworkServiceOpen(rtkSystemStateProcessor.value.rtkReferenceStationSource),
 | 
			
		||||
                rtkSystemStateProcessor.value.rtkReferenceStationSource
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun updateBaseStationState() {
 | 
			
		||||
        if (rtkStationConnectStateProcessor.value == RTKStationConnetState.CONNECTED && productConnectionProcessor.value) {
 | 
			
		||||
            if (rtkSystemStateProcessor.value.rtkHealthy) {
 | 
			
		||||
                rtkBaseStationStateProcessor.onNext(RTKBaseStationState.CONNECTED_IN_USE)
 | 
			
		||||
            } else {
 | 
			
		||||
                rtkBaseStationStateProcessor.onNext(RTKBaseStationState.CONNECTED_NOT_IN_USE)
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            rtkBaseStationStateProcessor.onNext(RTKBaseStationState.DISCONNECTED)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private fun isNetworkServiceOpen(rtkSignal: RTKReferenceStationSource?): Boolean {
 | 
			
		||||
        return rtkSignal == RTKReferenceStationSource.QX_NETWORK_SERVICE
 | 
			
		||||
                || rtkSignal == RTKReferenceStationSource.CUSTOM_NETWORK_SERVICE
 | 
			
		||||
                || rtkSignal == RTKReferenceStationSource.NTRIP_NETWORK_SERVICE
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,373 @@
 | 
			
		|||
package dji.v5.ux.accessory
 | 
			
		||||
 | 
			
		||||
import android.os.Handler
 | 
			
		||||
import android.widget.Toast
 | 
			
		||||
import dji.rtk.CoordinateSystem
 | 
			
		||||
import dji.sdk.keyvalue.key.*
 | 
			
		||||
import dji.sdk.keyvalue.value.product.ProductType
 | 
			
		||||
import dji.sdk.keyvalue.value.remotecontroller.RCMode
 | 
			
		||||
import dji.sdk.keyvalue.value.rtkbasestation.RTKCustomNetworkSetting
 | 
			
		||||
import dji.sdk.keyvalue.value.rtkbasestation.RTKReferenceStationSource
 | 
			
		||||
import dji.v5.common.callback.CommonCallbacks
 | 
			
		||||
import dji.v5.common.error.IDJIError
 | 
			
		||||
import dji.v5.common.utils.RxUtil
 | 
			
		||||
import dji.v5.et.create
 | 
			
		||||
import dji.v5.et.get
 | 
			
		||||
import dji.v5.manager.aircraft.rtk.RTKCenter
 | 
			
		||||
import dji.v5.manager.aircraft.rtk.RTKSystemStateListener
 | 
			
		||||
import dji.v5.utils.common.*
 | 
			
		||||
import dji.v5.ux.R
 | 
			
		||||
import dji.v5.ux.core.util.DataProcessor
 | 
			
		||||
import dji.v5.ux.core.util.ViewUtil
 | 
			
		||||
import io.reactivex.rxjava3.core.Flowable
 | 
			
		||||
import java.util.concurrent.atomic.AtomicBoolean
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Description :用于实现自动重连RTK逻辑
 | 
			
		||||
 *
 | 
			
		||||
 * @author: Byte.Cai
 | 
			
		||||
 *  date : 2022/8/16
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2022, DJI All Rights Reserved.
 | 
			
		||||
 */
 | 
			
		||||
object RTKStartServiceHelper {
 | 
			
		||||
    private const val TAG = "RTKStartServiceHelper"
 | 
			
		||||
 | 
			
		||||
    private val rtkCenter = RTKCenter.getInstance()
 | 
			
		||||
    private val qxRTKManager = RTKCenter.getInstance().qxrtkManager
 | 
			
		||||
    private val customManager = RTKCenter.getInstance().customRTKManager
 | 
			
		||||
    private val cmccRtkManager = RTKCenter.getInstance().cmccrtkManager
 | 
			
		||||
    private var isStartByUser=false
 | 
			
		||||
 | 
			
		||||
    private var productType: ProductType = ProductType.UNKNOWN
 | 
			
		||||
    private var rtkDongleConnection = false
 | 
			
		||||
    private var fcConnected = false
 | 
			
		||||
    private val rtkModuleAvailableProcessor = DataProcessor.create(false)
 | 
			
		||||
    private var rtkSource: RTKReferenceStationSource = RTKReferenceStationSource.UNKNOWN
 | 
			
		||||
    private val isStartRTKing = AtomicBoolean(false)
 | 
			
		||||
    private val isHasStartRTK = AtomicBoolean(false)
 | 
			
		||||
    private val handle = Handler()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private val rtkSystemStateListener = RTKSystemStateListener {
 | 
			
		||||
        if (rtkSource != it.rtkReferenceStationSource) {
 | 
			
		||||
            rtkSource = it.rtkReferenceStationSource
 | 
			
		||||
            LogUtils.i(TAG, "rtkSource change into:$rtkSource")
 | 
			
		||||
            //避免未输入账号信息下去启动导致的失败
 | 
			
		||||
            if (rtkSource != RTKReferenceStationSource.CUSTOM_NETWORK_SERVICE) {
 | 
			
		||||
                startRtkService()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        //观测RTK模块的连接情况
 | 
			
		||||
        observerRTKNoduleAvailable()
 | 
			
		||||
        //观测RTKSource的变化
 | 
			
		||||
        rtkCenter.addRTKSystemStateListener(rtkSystemStateListener)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun observerRTKNoduleAvailable() {
 | 
			
		||||
        RxUtil.addListener(
 | 
			
		||||
            KeyTools.createKey(
 | 
			
		||||
                ProductKey.KeyProductType), this).subscribe {
 | 
			
		||||
            if (it != ProductType.UNRECOGNIZED && productType != it) {
 | 
			
		||||
                LogUtils.i(TAG, "productType=$it")
 | 
			
		||||
                productType = it
 | 
			
		||||
                updateData()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        RxUtil.addListener(
 | 
			
		||||
            KeyTools.createKey(
 | 
			
		||||
                RtkMobileStationKey.KeyIsRTKDongleConnect), this).subscribe {
 | 
			
		||||
            if (rtkDongleConnection != it) {
 | 
			
		||||
                LogUtils.i(TAG, "rtkDongleConnection=$it")
 | 
			
		||||
                rtkDongleConnection = it
 | 
			
		||||
                updateData()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        RxUtil.addListener(
 | 
			
		||||
            KeyTools.createKey(
 | 
			
		||||
                FlightControllerKey.KeyConnection), this).subscribe {
 | 
			
		||||
            if (fcConnected != it) {
 | 
			
		||||
                LogUtils.i(TAG, "fcConnected=$it")
 | 
			
		||||
                fcConnected = it
 | 
			
		||||
                updateData()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Synchronized
 | 
			
		||||
    private fun updateData() {
 | 
			
		||||
        val isRtkModuleAvailable = when (productType) {
 | 
			
		||||
            ProductType.DJI_MAVIC_3_ENTERPRISE_SERIES ->
 | 
			
		||||
                // 外接RTK的判断RTK Dongle连接状态
 | 
			
		||||
                rtkDongleConnection && fcConnected
 | 
			
		||||
            else ->
 | 
			
		||||
                // 其他行业飞机内置RTK的飞机都是true
 | 
			
		||||
                fcConnected
 | 
			
		||||
        }
 | 
			
		||||
        rtkModuleAvailableProcessor.onNext(isRtkModuleAvailable)
 | 
			
		||||
 | 
			
		||||
        if (isRtkModuleAvailable && !isHasStartRTK.get()) {
 | 
			
		||||
            startRtkService()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Synchronized
 | 
			
		||||
    fun startRtkService(isStartByUser:Boolean=false) {
 | 
			
		||||
        this.isStartByUser =isStartByUser
 | 
			
		||||
        LogUtils.i(TAG, "startRtkService")
 | 
			
		||||
        if (!rtkModuleAvailableProcessor.value) {
 | 
			
		||||
            LogUtils.e(TAG, "rtkModule is unAvailable,startRtkServiceIfNeed fail!")
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        if (!isNeedStartRtkNetworkService()) {
 | 
			
		||||
            LogUtils.e(TAG, "don not need start rtk Service!")
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        LogUtils.i(TAG, "rtkSource=$rtkSource")
 | 
			
		||||
        when (rtkSource) {
 | 
			
		||||
            RTKReferenceStationSource.CUSTOM_NETWORK_SERVICE -> startRtkCustomNetworkService()
 | 
			
		||||
            RTKReferenceStationSource.QX_NETWORK_SERVICE -> startQxRtkService()
 | 
			
		||||
            RTKReferenceStationSource.NTRIP_NETWORK_SERVICE -> startCMCCRtkService()
 | 
			
		||||
            RTKReferenceStationSource.BASE_STATION -> {
 | 
			
		||||
                LogUtils.i(TAG, "D-RTK2 固件底层已实现自动重连,不需要外部手动重连")
 | 
			
		||||
            }
 | 
			
		||||
            else -> {
 | 
			
		||||
                LogUtils.e(TAG, "UnKnown rtkSource:$rtkSource")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Synchronized
 | 
			
		||||
    private fun startCMCCRtkService() {
 | 
			
		||||
        val rtkNetworkCoordinateSystem = RTKUtil.getNetRTKCoordinateSystem(RTKReferenceStationSource.NTRIP_NETWORK_SERVICE)
 | 
			
		||||
        setStartRTKState(true)
 | 
			
		||||
        isHasStartRTK.set(false)
 | 
			
		||||
        LogUtils.i(TAG, "startCMCCRtkService,rtkNetworkCoordinateSystem=$rtkNetworkCoordinateSystem")
 | 
			
		||||
        if (rtkNetworkCoordinateSystem == null) {
 | 
			
		||||
            setStartRTKState(false)
 | 
			
		||||
            LogUtils.e(TAG, "getCMCCRtk CoordinateSystem == null,startCMCCRtkService finish!")
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        rtkNetworkCoordinateSystem.let {
 | 
			
		||||
            LogUtils.i(TAG, "startCMCCRtkService,coordinateName=$rtkNetworkCoordinateSystem")
 | 
			
		||||
            cmccRtkManager.stopNetworkRTKService(object : CommonCallbacks.CompletionCallback {
 | 
			
		||||
                override fun onSuccess() {
 | 
			
		||||
                    cmccRtkManager.startNetworkRTKService(rtkNetworkCoordinateSystem, object :
 | 
			
		||||
                        CommonCallbacks.CompletionCallback {
 | 
			
		||||
                        override fun onSuccess() {
 | 
			
		||||
                            LogUtils.i(TAG, "startCMCCRtkService success")
 | 
			
		||||
                            setStartRTKState(false)
 | 
			
		||||
                            isHasStartRTK.set(true)
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        override fun onFailure(error: IDJIError) {
 | 
			
		||||
                            LogUtils.e(TAG, "startCMCCRtkService fail:rtkNetworkCoordinateSystem=$rtkNetworkCoordinateSystem,error=$error")
 | 
			
		||||
                            setStartRTKState(false)
 | 
			
		||||
                            isHasStartRTK.set(false)
 | 
			
		||||
                            if (isStartByUser) {
 | 
			
		||||
                                showToast(StringUtils.getResStr(R.string.uxsdk_rtk_setting_menu_setting_fail))
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                    })
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                override fun onFailure(error: IDJIError) {
 | 
			
		||||
                    LogUtils.e(TAG, "stopNetworkRTKService fail:$error")
 | 
			
		||||
                    isHasStartRTK.set(false)
 | 
			
		||||
                    setStartRTKState(false)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun showToast(msg: String) {
 | 
			
		||||
        ViewUtil.showToast(ContextUtil.getContext(), msg, Toast.LENGTH_SHORT)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 启动千寻RTK
 | 
			
		||||
     */
 | 
			
		||||
    @Synchronized
 | 
			
		||||
    private fun startQxRtkService() {
 | 
			
		||||
        var rtkNetworkCoordinateSystem = RTKUtil.getNetRTKCoordinateSystem(RTKReferenceStationSource.QX_NETWORK_SERVICE)
 | 
			
		||||
        LogUtils.i(TAG, "startQxRtkService rtkNetworkCoordinateSystem=$rtkNetworkCoordinateSystem")
 | 
			
		||||
        if (rtkNetworkCoordinateSystem != null) {
 | 
			
		||||
            startQxRtkService(rtkNetworkCoordinateSystem)
 | 
			
		||||
        } else {
 | 
			
		||||
            RTKCenter.getInstance().qxrtkManager.getNetworkRTKCoordinateSystem(object :
 | 
			
		||||
                CommonCallbacks.CompletionCallbackWithParam<CoordinateSystem> {
 | 
			
		||||
                override fun onSuccess(t: CoordinateSystem?) {
 | 
			
		||||
                    t?.let { startQxRtkService(it) }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                override fun onFailure(error: IDJIError) {
 | 
			
		||||
                    //未实现
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun startQxRtkService(coordinateSystem: CoordinateSystem) {
 | 
			
		||||
        setStartRTKState(true)
 | 
			
		||||
        isHasStartRTK.set(false)
 | 
			
		||||
 | 
			
		||||
        qxRTKManager.stopNetworkRTKService(object : CommonCallbacks.CompletionCallback {
 | 
			
		||||
            override fun onSuccess() {
 | 
			
		||||
                qxRTKManager.startNetworkRTKService(coordinateSystem, object :
 | 
			
		||||
                    CommonCallbacks.CompletionCallback {
 | 
			
		||||
                    override fun onSuccess() {
 | 
			
		||||
                        LogUtils.i(TAG, "startQxRtkService success")
 | 
			
		||||
                        setStartRTKState(false)
 | 
			
		||||
                        isHasStartRTK.set(true)
 | 
			
		||||
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    override fun onFailure(error: IDJIError) {
 | 
			
		||||
                        LogUtils.e(TAG, "startQxRtkService fail:$error")
 | 
			
		||||
                        if (isStartByUser) {
 | 
			
		||||
                            showToast(StringUtils.getResStr(R.string.uxsdk_rtk_setting_menu_setting_fail))
 | 
			
		||||
                        }
 | 
			
		||||
                        setStartRTKState(false)
 | 
			
		||||
                        isHasStartRTK.set(false)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            override fun onFailure(error: IDJIError) {
 | 
			
		||||
                LogUtils.e(TAG, "stopNetworkRTKService fail:$error")
 | 
			
		||||
                isHasStartRTK.set(false)
 | 
			
		||||
                setStartRTKState(false)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 从本地缓存中获取自定义网络RTK配置信息启动自定义网络RTK
 | 
			
		||||
     */
 | 
			
		||||
    @Synchronized
 | 
			
		||||
    private fun startRtkCustomNetworkService() {
 | 
			
		||||
        isHasStartRTK.set(false)
 | 
			
		||||
        setStartRTKState(true)
 | 
			
		||||
        LogUtils.i(TAG, "startRtkCustomNetworkService")
 | 
			
		||||
        val rtkCustomNetworkSetting: RTKCustomNetworkSetting? = RTKUtil.getRtkCustomNetworkSetting()
 | 
			
		||||
        if (rtkCustomNetworkSetting == null) {
 | 
			
		||||
            setStartRTKState(false)
 | 
			
		||||
            LogUtils.e(TAG, "get rtkCustomNetworkSetting == null,startRtkCustomNetworkService finish!")
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        rtkCustomNetworkSetting.let {
 | 
			
		||||
            LogUtils.i(TAG, "rtkCustomNetworkSetting=$it")
 | 
			
		||||
            customManager.stopNetworkRTKService(object : CommonCallbacks.CompletionCallback {
 | 
			
		||||
                override fun onSuccess() {
 | 
			
		||||
                    customManager.customNetworkRTKSettings = it
 | 
			
		||||
                    customManager.startNetworkRTKService(object :
 | 
			
		||||
                        CommonCallbacks.CompletionCallback {
 | 
			
		||||
                        override fun onSuccess() {
 | 
			
		||||
                            LogUtils.i(TAG, "startRtkCustomNetworkService success")
 | 
			
		||||
                            setStartRTKState(false)
 | 
			
		||||
                            isHasStartRTK.set(true)
 | 
			
		||||
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        override fun onFailure(error: IDJIError) {
 | 
			
		||||
                            LogUtils.e(TAG, "startRtkCustomNetworkService fail:$error")
 | 
			
		||||
                            setStartRTKState(false)
 | 
			
		||||
                            isHasStartRTK.set(false)
 | 
			
		||||
                            if (isStartByUser) {
 | 
			
		||||
                                ViewUtil.showToast(ContextUtil.getContext(),R.string.uxsdk_rtk_setting_menu_customer_rtk_save_failed_tips,Toast.LENGTH_SHORT)
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                    })
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                override fun onFailure(error: IDJIError) {
 | 
			
		||||
                    LogUtils.e(TAG, "stopNetworkRTKService fail:$error")
 | 
			
		||||
                    setStartRTKState(false)
 | 
			
		||||
                    isHasStartRTK.set(false)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否允许启动网络RTK (未判断网络数据模式)
 | 
			
		||||
     */
 | 
			
		||||
    private fun isNeedStartRtkNetworkService(): Boolean {
 | 
			
		||||
        val isConnected: Boolean = FlightControllerKey.KeyConnection.create().get(false)
 | 
			
		||||
        return (isConnected
 | 
			
		||||
                && isNetworkRTK(rtkSource)
 | 
			
		||||
                && NetworkUtils.isNetworkAvailable()
 | 
			
		||||
                && !isChannelB()
 | 
			
		||||
                && rtkModuleAvailableProcessor.value)
 | 
			
		||||
                && !isStartRTKing.get()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    //后续供其他RTK相关Widget使用
 | 
			
		||||
    val rtkModuleAvailable: Flowable<Boolean>
 | 
			
		||||
        get() = rtkModuleAvailableProcessor.toFlowable()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 判断一个差分数据源是否是网络RTK
 | 
			
		||||
     */
 | 
			
		||||
    fun isNetworkRTK(source: RTKReferenceStationSource?): Boolean {
 | 
			
		||||
        return when (source) {
 | 
			
		||||
            RTKReferenceStationSource.QX_NETWORK_SERVICE,
 | 
			
		||||
            RTKReferenceStationSource.CUSTOM_NETWORK_SERVICE,
 | 
			
		||||
            RTKReferenceStationSource.NTRIP_NETWORK_SERVICE,
 | 
			
		||||
            -> true
 | 
			
		||||
            else -> false
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否为B控,非双控机型返回false
 | 
			
		||||
     */
 | 
			
		||||
    fun isChannelB(): Boolean {
 | 
			
		||||
        return RCMode.CHANNEL_B == RemoteControllerKey.KeyRcMachineMode.create().get(RCMode.UNKNOWN)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setStartRTKState(isRTKStart: Boolean) {
 | 
			
		||||
        if (isRTKStart) {
 | 
			
		||||
            isStartRTKing.set(true)
 | 
			
		||||
            handle.postDelayed({
 | 
			
		||||
                LogUtils.e(TAG, "start rtk service timeout")
 | 
			
		||||
                isStartRTKing.set(false)
 | 
			
		||||
            }, 15 * 1000)
 | 
			
		||||
        } else {
 | 
			
		||||
            isStartRTKing.set(false)
 | 
			
		||||
            handle.removeCallbacksAndMessages(null)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,393 @@
 | 
			
		|||
package dji.v5.ux.accessory
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.os.Looper
 | 
			
		||||
import android.text.SpannableStringBuilder
 | 
			
		||||
import android.text.Spanned
 | 
			
		||||
import android.text.TextPaint
 | 
			
		||||
import android.text.method.LinkMovementMethod
 | 
			
		||||
import android.text.style.ClickableSpan
 | 
			
		||||
import android.text.style.ForegroundColorSpan
 | 
			
		||||
import android.util.AttributeSet
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.widget.Button
 | 
			
		||||
import android.widget.ImageView
 | 
			
		||||
import android.widget.TextView
 | 
			
		||||
import android.widget.Toast
 | 
			
		||||
import androidx.constraintlayout.widget.ConstraintLayout
 | 
			
		||||
import androidx.recyclerview.widget.LinearLayoutManager
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import dji.sdk.keyvalue.value.rtkbasestation.RTKStationConnetState
 | 
			
		||||
import dji.sdk.keyvalue.value.rtkbasestation.RTKStationInfo
 | 
			
		||||
import dji.v5.manager.aircraft.rtk.RTKCenter
 | 
			
		||||
import dji.v5.manager.aircraft.rtk.station.ConnectedRTKStationInfo
 | 
			
		||||
import dji.v5.utils.common.LogUtils
 | 
			
		||||
import dji.v5.utils.common.StringUtils
 | 
			
		||||
import dji.v5.ux.R
 | 
			
		||||
import dji.v5.ux.accessory.data.DJIRTKBaseStationConnectInfo
 | 
			
		||||
import dji.v5.ux.accessory.data.RtkStationScanAdapter
 | 
			
		||||
import dji.v5.ux.accessory.item.RtkGuidanceView
 | 
			
		||||
import dji.v5.ux.core.base.DJISDKModel
 | 
			
		||||
import dji.v5.ux.core.base.SchedulerProvider
 | 
			
		||||
import dji.v5.ux.core.base.widget.ConstraintLayoutWidget
 | 
			
		||||
import dji.v5.ux.core.communication.ObservableInMemoryKeyedStore
 | 
			
		||||
import dji.v5.ux.core.extension.getString
 | 
			
		||||
import dji.v5.ux.core.extension.hide
 | 
			
		||||
import dji.v5.ux.core.extension.show
 | 
			
		||||
import io.reactivex.rxjava3.core.Observable
 | 
			
		||||
import io.reactivex.rxjava3.disposables.Disposable
 | 
			
		||||
import java.util.concurrent.TimeUnit
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Description :D-RTK2 扫描连接Widget
 | 
			
		||||
 *
 | 
			
		||||
 * @author: Byte.Cai
 | 
			
		||||
 *  date : 2022/9/1
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2022, DJI All Rights Reserved.
 | 
			
		||||
 */
 | 
			
		||||
private const val TAG = "RTKStationConnectWidget"
 | 
			
		||||
 | 
			
		||||
class RTKStationConnectWidget @JvmOverloads constructor(
 | 
			
		||||
    context: Context,
 | 
			
		||||
    attrs: AttributeSet? = null,
 | 
			
		||||
    defStyleAttr: Int = 0,
 | 
			
		||||
) : ConstraintLayoutWidget<Boolean>(context, attrs, defStyleAttr), RtkStationScanAdapter.OnItemClickListener, View.OnClickListener {
 | 
			
		||||
    private var rtkStationScanAdapter: RtkStationScanAdapter
 | 
			
		||||
    private val stationList = ArrayList<DJIRTKBaseStationConnectInfo>()
 | 
			
		||||
    private val searchBt: Button = findViewById(R.id.bt_rtk_signal_search_again)
 | 
			
		||||
    private val checkReasonTv: TextView = findViewById(R.id.tv_rtk_signal_problem_checked_reason)
 | 
			
		||||
    private val searchIv: ImageView = findViewById(R.id.iv_rtk_signal_search_iv)
 | 
			
		||||
    private val stationListView: RecyclerView = findViewById(R.id.rl_rtk_signal_searching_list)
 | 
			
		||||
    private val stationScanningView: ConstraintLayout = findViewById(R.id.cl_rtk_has_found)
 | 
			
		||||
    private val stationHasNotFoundView: ConstraintLayout = findViewById(R.id.cl_rtk_not_found)
 | 
			
		||||
    private var connectState = RTKStationConnetState.UNKNOWN
 | 
			
		||||
    private val scanHandler = android.os.Handler(Looper.getMainLooper())
 | 
			
		||||
    private var scanTimeOutDisposable: Disposable? = null
 | 
			
		||||
    private var isMotorOn = false
 | 
			
		||||
    private var firstEnter = false
 | 
			
		||||
    private val SCAN_TIME_OUT = 5.0
 | 
			
		||||
 | 
			
		||||
    private val widgetModel by lazy {
 | 
			
		||||
        RTKStationConnectWidgetModel(
 | 
			
		||||
            DJISDKModel.getInstance(),
 | 
			
		||||
            ObservableInMemoryKeyedStore.getInstance(),
 | 
			
		||||
            RTKCenter.getInstance()
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        //初始化stationTRTK列表
 | 
			
		||||
        val layoutManager = LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false)
 | 
			
		||||
        stationListView.layoutManager = layoutManager
 | 
			
		||||
        rtkStationScanAdapter = RtkStationScanAdapter(getContext(), stationList)
 | 
			
		||||
        stationListView.adapter = rtkStationScanAdapter
 | 
			
		||||
        searchBt.setOnClickListener(this)
 | 
			
		||||
        searchIv.setOnClickListener(this)
 | 
			
		||||
        rtkStationScanAdapter.setOnItemClickListener(this)
 | 
			
		||||
        initCheckReasonContent()
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
 | 
			
		||||
        inflate(context, R.layout.uxsdk_widget_rtk_connect_status_layout, this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onAttachedToWindow() {
 | 
			
		||||
        super.onAttachedToWindow()
 | 
			
		||||
        if (!isInEditMode) {
 | 
			
		||||
            widgetModel.setup()
 | 
			
		||||
        }
 | 
			
		||||
        firstEnter = true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 初始化CheckReason View的属性
 | 
			
		||||
     */
 | 
			
		||||
    private fun initCheckReasonContent() {
 | 
			
		||||
        val reason: String = StringUtils.getResStr(R.string.uxsdk_rtk_base_station_not_found_reason)
 | 
			
		||||
        val description: String = StringUtils.getResStr(R.string.uxsdk_rtk_connect_description)
 | 
			
		||||
        //设置mCheckReasonTv的部分内容可点击属性
 | 
			
		||||
        val spannableStringBuilder = SpannableStringBuilder()
 | 
			
		||||
        spannableStringBuilder.append(reason).append(" ").append(description)
 | 
			
		||||
        //mCheckReasonTv的部分内容点击事件
 | 
			
		||||
        val clickableSpan: ClickableSpan = object : ClickableSpan() {
 | 
			
		||||
            override fun onClick(view: View) {
 | 
			
		||||
                //RTK引导界面
 | 
			
		||||
                val guidanceView = RtkGuidanceView(context)
 | 
			
		||||
                guidanceView.showPopupWindow(view)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            override fun updateDrawState(ds: TextPaint) {
 | 
			
		||||
                ds.isUnderlineText = false
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        //点击范围为description部分
 | 
			
		||||
        spannableStringBuilder.setSpan(clickableSpan, reason.length, spannableStringBuilder.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
 | 
			
		||||
        //设置可点击部分的字体颜色
 | 
			
		||||
        val foregroundColorSpan = ForegroundColorSpan(resources.getColor(R.color.uxsdk_blue_highlight))
 | 
			
		||||
        spannableStringBuilder.setSpan(foregroundColorSpan, reason.length + 1, spannableStringBuilder.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
 | 
			
		||||
        //设置为超链接方式
 | 
			
		||||
        checkReasonTv.movementMethod = LinkMovementMethod.getInstance()
 | 
			
		||||
        checkReasonTv.text = spannableStringBuilder
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    override fun onDetachedFromWindow() {
 | 
			
		||||
        if (!isInEditMode) {
 | 
			
		||||
            widgetModel.cleanup()
 | 
			
		||||
        }
 | 
			
		||||
        super.onDetachedFromWindow()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onClick(v: View?) {
 | 
			
		||||
        when (v?.id) {
 | 
			
		||||
            R.id.iv_rtk_signal_search_iv,
 | 
			
		||||
            R.id.bt_rtk_signal_search_again,
 | 
			
		||||
            -> startScanning()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun reactToModelChanges() {
 | 
			
		||||
        addReaction(widgetModel.connectedRTKStationInfo.observeOn(SchedulerProvider.ui()).subscribe {
 | 
			
		||||
            handleReconnectedStationInfo(it)
 | 
			
		||||
        })
 | 
			
		||||
        addReaction(widgetModel.isMotorOn.subscribe {
 | 
			
		||||
            isMotorOn = it
 | 
			
		||||
        })
 | 
			
		||||
        addReaction(widgetModel.stationConnectStatus.observeOn(SchedulerProvider.ui()).subscribe {
 | 
			
		||||
            updateConnectStatus(it)
 | 
			
		||||
        })
 | 
			
		||||
        addReaction(widgetModel.stationList.observeOn(SchedulerProvider.ui()).subscribe {
 | 
			
		||||
            handleStationRTKList(it)
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getIdealDimensionRatioString(): String? {
 | 
			
		||||
        return getString(R.string.uxsdk_widget_rtk_keep_status_ratio)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private fun updateRefreshUI(boolean: Boolean) {
 | 
			
		||||
        //底部的重新扫描按钮是否可点击
 | 
			
		||||
        searchBt.isClickable = boolean
 | 
			
		||||
        searchBt.setBackgroundResource(if (boolean) R.drawable.uxsdk_bg_white_radius else R.drawable.uxsdk_bg_gray_radius)
 | 
			
		||||
        searchIv.visibility = if (boolean) visibility else GONE
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun handleStationRTKList(list: List<DJIRTKBaseStationConnectInfo>?) {
 | 
			
		||||
        //过滤重复的数据,防止界面重新刷新
 | 
			
		||||
        if (checkNeedUpdateUI(list)) {
 | 
			
		||||
            stationList.clear()
 | 
			
		||||
            LogUtils.i(TAG, "has found rtk,clear stationList")
 | 
			
		||||
            list?.let {
 | 
			
		||||
                for (i in it) {
 | 
			
		||||
                    LogUtils.i(TAG, "stationName=${i.rtkStationName},signalLevel=${i.signalLevel}")
 | 
			
		||||
                    stationList.add(i)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            searchIv.setImageResource(R.drawable.uxsdk_ic_refresh)
 | 
			
		||||
            rtkStationScanAdapter.notifyDataSetChanged()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun updateConnectStatus(rtkBaseStationConnectState: RTKStationConnetState?) {
 | 
			
		||||
        if (rtkBaseStationConnectState == null) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        LogUtils.i(TAG, "Current station status is $rtkBaseStationConnectState")
 | 
			
		||||
        when (rtkBaseStationConnectState) {
 | 
			
		||||
            RTKStationConnetState.IDLE,
 | 
			
		||||
            RTKStationConnetState.UNKNOWN,
 | 
			
		||||
            -> {
 | 
			
		||||
                if (firstEnter) {
 | 
			
		||||
                    LogUtils.i(TAG, "first enter,startScanning auto")
 | 
			
		||||
                    firstEnter = false
 | 
			
		||||
                    updateConnectStatus(RTKStationConnetState.SCANNING)
 | 
			
		||||
                    //这里延时2秒发送扫描命令,避免针对已连上RTK的设备(固件底层会自动重连),再次扫描固件自动重连逻辑会被破坏
 | 
			
		||||
                    scanHandler.postDelayed({
 | 
			
		||||
                        startScanning()
 | 
			
		||||
                    }, 2000)
 | 
			
		||||
                } else {
 | 
			
		||||
                    searchIv.setImageResource(R.drawable.uxsdk_ic_refresh)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            RTKStationConnetState.DISCONNECTED -> {
 | 
			
		||||
                stationHasNotFoundView.hide()
 | 
			
		||||
                stationScanningView.show()
 | 
			
		||||
                searchIv.setImageResource(R.drawable.uxsdk_ic_refresh)
 | 
			
		||||
                Toast.makeText(context, "Station has disconnected", Toast.LENGTH_SHORT).show()
 | 
			
		||||
            }
 | 
			
		||||
            RTKStationConnetState.SCANNING -> {
 | 
			
		||||
                stationHasNotFoundView.hide()
 | 
			
		||||
                stationScanningView.show()
 | 
			
		||||
                searchIv.setImageResource(R.drawable.uxsdk_rotate_progress_circle)
 | 
			
		||||
                LogUtils.i(TAG, "scan rtk ing...")
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            RTKStationConnetState.CONNECTED -> {
 | 
			
		||||
                LogUtils.i(TAG, "rtk has connected")
 | 
			
		||||
                stationHasNotFoundView.hide()
 | 
			
		||||
                stationScanningView.show()
 | 
			
		||||
                searchIv.setImageResource(R.drawable.uxsdk_ic_refresh)
 | 
			
		||||
                //将连接成功的基站放在列表首页
 | 
			
		||||
                if (stationList.remove(selectedRTKStationConnectInfo)) {
 | 
			
		||||
                    stationList.add(0, selectedRTKStationConnectInfo)
 | 
			
		||||
                }
 | 
			
		||||
                rtkStationScanAdapter.notifyDataSetChanged()
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            else -> {
 | 
			
		||||
                stationHasNotFoundView.hide()
 | 
			
		||||
                stationScanningView.show()
 | 
			
		||||
                searchIv.setImageResource(R.drawable.uxsdk_ic_refresh)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        connectState = rtkBaseStationConnectState
 | 
			
		||||
        //返回连接状态,更新UI
 | 
			
		||||
        selectedRTKStationConnectInfo.refresh(rtkBaseStationConnectState)
 | 
			
		||||
        //飞行过程中不允许点击扫描按钮
 | 
			
		||||
        updateRefreshUI(!isMotorOn)
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private fun checkNeedUpdateUI(list: List<RTKStationInfo>?): Boolean {
 | 
			
		||||
        if (list?.size != stationList.size) {
 | 
			
		||||
            return true
 | 
			
		||||
        }
 | 
			
		||||
        if (!stationList.containsAll(list) || !list.containsAll(stationList)) {
 | 
			
		||||
            return true
 | 
			
		||||
        }
 | 
			
		||||
        return false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 选中某个基站,注意这里selectedRTKStationConnectInfo不能初始化为null
 | 
			
		||||
     */
 | 
			
		||||
    private var selectedRTKStationConnectInfo: DJIRTKBaseStationConnectInfo = DJIRTKBaseStationConnectInfo()
 | 
			
		||||
    override fun onItemClick(view: View?, position: Int) {
 | 
			
		||||
        selectedRTKStationConnectInfo = stationList[position]
 | 
			
		||||
        LogUtils.i(TAG, "click and connecting rtk:$selectedRTKStationConnectInfo")
 | 
			
		||||
        //连接某一个基站时,将其他基站的连接状态重置为空闲,因为一次只能连接一个基站
 | 
			
		||||
        for (stationInfo in stationList) {
 | 
			
		||||
            stationInfo.connectStatus = RTKStationConnetState.IDLE
 | 
			
		||||
        }
 | 
			
		||||
        selectedRTKStationConnectInfo.refresh(RTKStationConnetState.CONNECTING)
 | 
			
		||||
        startConnectStation(selectedRTKStationConnectInfo)
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun DJIRTKBaseStationConnectInfo.refresh(connectState: RTKStationConnetState?) {
 | 
			
		||||
        connectState?.let {
 | 
			
		||||
            LogUtils.i(TAG, "connectState=$connectState")
 | 
			
		||||
            this.connectStatus = it
 | 
			
		||||
        }
 | 
			
		||||
        rtkStationScanAdapter.notifyDataSetChanged()
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private fun startConnectStation(selectedRTKStationConnectInfo: DJIRTKBaseStationConnectInfo) {
 | 
			
		||||
        selectedRTKStationConnectInfo.run {
 | 
			
		||||
            addDisposable(widgetModel.startConnectToRTKStation(baseStationId).observeOn(SchedulerProvider.ui()).subscribe({
 | 
			
		||||
                LogUtils.i(TAG, "$rtkStationName connect success")
 | 
			
		||||
            }, {
 | 
			
		||||
                //连接失败,恢复未连接状态
 | 
			
		||||
                selectedRTKStationConnectInfo.refresh(RTKStationConnetState.IDLE)
 | 
			
		||||
                Toast.makeText(context, StringUtils.getResStr(R.string.uxsdk_rtk_base_station_connect_fail), Toast.LENGTH_SHORT).show()
 | 
			
		||||
                LogUtils.e(TAG, "${selectedRTKStationConnectInfo.rtkStationName}connect fail!!!")
 | 
			
		||||
            }))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun startScanning() {
 | 
			
		||||
        LogUtils.i(TAG, "startScanning now")
 | 
			
		||||
        //添加计时器
 | 
			
		||||
        scanTimeOut()
 | 
			
		||||
        //清除已有的基站列表
 | 
			
		||||
        stationList.clear()
 | 
			
		||||
        rtkStationScanAdapter.notifyDataSetChanged()
 | 
			
		||||
        //发送命令开始扫描
 | 
			
		||||
        addDisposable(widgetModel.startSearchStationRTK().observeOn(SchedulerProvider.ui()).subscribe({
 | 
			
		||||
            stationHasNotFoundView.hide()
 | 
			
		||||
            stationScanningView.show()
 | 
			
		||||
        }, {
 | 
			
		||||
            stationHasNotFoundView.show()
 | 
			
		||||
            stationScanningView.hide()
 | 
			
		||||
            Toast.makeText(context, StringUtils.getResStr(R.string.uxsdk_rtk_base_station_search_false_and_try_again), Toast.LENGTH_SHORT).show()
 | 
			
		||||
            LogUtils.e(TAG, "startSearchStationRTK fail:${it.localizedMessage}")
 | 
			
		||||
 | 
			
		||||
        }))
 | 
			
		||||
        //某些情况下不会返回状态,需要手动更新状态
 | 
			
		||||
        updateConnectStatus(RTKStationConnetState.SCANNING)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private fun scanTimeOut() {
 | 
			
		||||
        disposeTimeout(scanTimeOutDisposable)
 | 
			
		||||
        scanTimeOutDisposable = Observable.timer(SCAN_TIME_OUT.toLong(), TimeUnit.SECONDS)
 | 
			
		||||
            .observeOn(SchedulerProvider.ui()).subscribe({
 | 
			
		||||
                if (!isHasFoundRTK()) {
 | 
			
		||||
                    LogUtils.e(TAG, "scanTimeOut ,stop search station RTK")
 | 
			
		||||
                    //扫描到基站列表超时,则停止扫描;同时展示RTK找不到的布局,
 | 
			
		||||
                    stationHasNotFoundView.show()
 | 
			
		||||
                    stationScanningView.hide()
 | 
			
		||||
                    widgetModel.stopSearchStationRTK()
 | 
			
		||||
                    //停止扫码不会返回状态,需要手动更新状态
 | 
			
		||||
                    updateConnectStatus(RTKStationConnetState.IDLE)
 | 
			
		||||
                } else {
 | 
			
		||||
                    LogUtils.i(TAG, "scan finish,has found rtk")
 | 
			
		||||
                    //扫描到基站,则隐藏基站未找到的界面,显示基站列表页,同时更新对应刷新Image Icon为刷新图片
 | 
			
		||||
                    stationHasNotFoundView.hide()
 | 
			
		||||
                    stationScanningView.show()
 | 
			
		||||
                    //停止扫描
 | 
			
		||||
                    widgetModel.stopSearchStationRTK()
 | 
			
		||||
                    searchIv.setImageResource(R.drawable.uxsdk_ic_refresh)
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
            }, {
 | 
			
		||||
                LogUtils.e(TAG, it.localizedMessage)
 | 
			
		||||
                disposeTimeout(scanTimeOutDisposable)
 | 
			
		||||
            })
 | 
			
		||||
        scanTimeOutDisposable?.let {
 | 
			
		||||
            if (!it.isDisposed) {
 | 
			
		||||
                addDisposable(it)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private fun isHasFoundRTK(): Boolean {
 | 
			
		||||
        return connectState == RTKStationConnetState.CONNECTED || connectState == RTKStationConnetState.CONNECTING || stationList.isNotEmpty()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 关闭计时器
 | 
			
		||||
     */
 | 
			
		||||
    private fun disposeTimeout(timeOutDisposable: Disposable?) {
 | 
			
		||||
        timeOutDisposable?.let {
 | 
			
		||||
            if (!it.isDisposed) {
 | 
			
		||||
                it.dispose()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private fun handleReconnectedStationInfo(infoConnected: ConnectedRTKStationInfo?) {
 | 
			
		||||
        infoConnected?.run {
 | 
			
		||||
            //第一次连接过基站后,再次重启飞机或者重启App固件会帮忙自动连接基站。这里就是为了构建自动重连的基站信息
 | 
			
		||||
            if (selectedRTKStationConnectInfo.baseStationId == 0) {
 | 
			
		||||
                LogUtils.i(TAG, "RTK Station has reconnected and remove scanHandler message")
 | 
			
		||||
                selectedRTKStationConnectInfo = DJIRTKBaseStationConnectInfo(stationId, signalLevel, stationName, RTKStationConnetState.CONNECTED)
 | 
			
		||||
                scanHandler.removeCallbacksAndMessages(null)
 | 
			
		||||
                updateConnectStatus(RTKStationConnetState.CONNECTED)
 | 
			
		||||
                handleStationRTKList(arrayListOf(selectedRTKStationConnectInfo))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,161 @@
 | 
			
		|||
package dji.v5.ux.accessory
 | 
			
		||||
 | 
			
		||||
import dji.sdk.keyvalue.key.FlightControllerKey
 | 
			
		||||
import dji.sdk.keyvalue.key.KeyTools
 | 
			
		||||
import dji.sdk.keyvalue.value.rtkbasestation.RTKStationConnetState
 | 
			
		||||
import dji.sdk.keyvalue.value.rtkbasestation.RTKStationInfo
 | 
			
		||||
import dji.v5.common.callback.CommonCallbacks
 | 
			
		||||
import dji.v5.common.error.IDJIError
 | 
			
		||||
import dji.v5.common.error.RxError
 | 
			
		||||
import dji.v5.manager.aircraft.rtk.station.ConnectedRTKStationInfo
 | 
			
		||||
import dji.v5.manager.aircraft.rtk.station.ConnectedRTKStationInfoListener
 | 
			
		||||
import dji.v5.manager.aircraft.rtk.station.RTKStationConnectStatusListener
 | 
			
		||||
import dji.v5.manager.aircraft.rtk.station.SearchRTKStationListener
 | 
			
		||||
import dji.v5.manager.interfaces.IRTKCenter
 | 
			
		||||
import dji.v5.ux.accessory.data.DJIRTKBaseStationConnectInfo
 | 
			
		||||
import dji.v5.ux.core.base.DJISDKModel
 | 
			
		||||
import dji.v5.ux.core.base.SchedulerProvider
 | 
			
		||||
import dji.v5.ux.core.base.WidgetModel
 | 
			
		||||
import dji.v5.ux.core.communication.ObservableInMemoryKeyedStore
 | 
			
		||||
import dji.v5.ux.core.util.DataProcessor
 | 
			
		||||
import io.reactivex.rxjava3.core.*
 | 
			
		||||
import io.reactivex.rxjava3.processors.BehaviorProcessor
 | 
			
		||||
import io.reactivex.rxjava3.schedulers.Schedulers
 | 
			
		||||
import java.util.ArrayList
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Description :Widget Model for the [RTKStationConnectWidget] used to define
 | 
			
		||||
 * the underlying logic and communication
 | 
			
		||||
 *
 | 
			
		||||
 * @author: Byte.Cai
 | 
			
		||||
 *  date : 2022/9/1
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2022, DJI All Rights Reserved.
 | 
			
		||||
 */
 | 
			
		||||
class RTKStationConnectWidgetModel(
 | 
			
		||||
    djiSdkModel: DJISDKModel,
 | 
			
		||||
    keyedStore: ObservableInMemoryKeyedStore,
 | 
			
		||||
    val rtkCenter: IRTKCenter,
 | 
			
		||||
) : WidgetModel(djiSdkModel, keyedStore) {
 | 
			
		||||
    private val rtkStationManager = rtkCenter.rtkStationManager
 | 
			
		||||
    private val rtkStationConnectStateProcessor: DataProcessor<RTKStationConnetState> =
 | 
			
		||||
        DataProcessor.create(RTKStationConnetState.UNKNOWN)
 | 
			
		||||
 | 
			
		||||
    private val stationListProcessor: DataProcessor<ArrayList<DJIRTKBaseStationConnectInfo>> =
 | 
			
		||||
        DataProcessor.create(arrayListOf())
 | 
			
		||||
 | 
			
		||||
    private val connectedRTKStationInfoProcessor: BehaviorProcessor<ConnectedRTKStationInfo> =
 | 
			
		||||
        BehaviorProcessor.create()
 | 
			
		||||
    private val isMotorOnProcessor: DataProcessor<Boolean> = DataProcessor.create(false)
 | 
			
		||||
    private val stationConnectStatusListener =
 | 
			
		||||
        RTKStationConnectStatusListener { rtkBaseStationConnectState ->
 | 
			
		||||
            rtkStationConnectStateProcessor.onNext(rtkBaseStationConnectState)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    private val connectedRTKStationInfoListener =
 | 
			
		||||
        ConnectedRTKStationInfoListener {
 | 
			
		||||
            connectedRTKStationInfoProcessor.onNext(it)
 | 
			
		||||
        }
 | 
			
		||||
    private val searchStationListener =
 | 
			
		||||
        SearchRTKStationListener { newConnectInfoList ->
 | 
			
		||||
            val convertToDJIRTKBaseStationConnectInfo = convertToDJIRTKBaseStationConnectInfo(newConnectInfoList)
 | 
			
		||||
            stationListProcessor.onNext(convertToDJIRTKBaseStationConnectInfo)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    val isMotorOn: Flowable<Boolean>
 | 
			
		||||
        get() = isMotorOnProcessor.toFlowable()
 | 
			
		||||
 | 
			
		||||
    val stationConnectStatus: Flowable<RTKStationConnetState>
 | 
			
		||||
        get() = rtkStationConnectStateProcessor.toFlowable()
 | 
			
		||||
 | 
			
		||||
    val connectedRTKStationInfo: Flowable<ConnectedRTKStationInfo>
 | 
			
		||||
        get() = connectedRTKStationInfoProcessor.observeOn(SchedulerProvider.computation()).onBackpressureLatest()
 | 
			
		||||
 | 
			
		||||
    val stationList: Flowable<ArrayList<DJIRTKBaseStationConnectInfo>>
 | 
			
		||||
        get() = stationListProcessor.toFlowable()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    override fun inSetup() {
 | 
			
		||||
        bindDataProcessor(
 | 
			
		||||
            KeyTools.createKey(
 | 
			
		||||
                FlightControllerKey.KeyAreMotorsOn), isMotorOnProcessor)
 | 
			
		||||
        rtkStationManager.addRTKStationConnectStatusListener(stationConnectStatusListener)
 | 
			
		||||
        rtkStationManager.addConnectedRTKStationInfoListener(connectedRTKStationInfoListener)
 | 
			
		||||
        rtkStationManager.addSearchRTKStationListener(searchStationListener)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun inCleanup() {
 | 
			
		||||
        rtkStationManager.removeRTKStationConnectStatusListener(stationConnectStatusListener)
 | 
			
		||||
        rtkStationManager.removeConnectedRTKStationInfoListener(connectedRTKStationInfoListener)
 | 
			
		||||
        rtkStationManager.removeSearchRTKStationListener(searchStationListener)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun startConnectToRTKStation(stationId: Int): Single<Boolean> {
 | 
			
		||||
        return Single.create(SingleOnSubscribe<Boolean> {
 | 
			
		||||
            rtkStationManager.startConnectToRTKStation(stationId, object :
 | 
			
		||||
                CommonCallbacks.CompletionCallback {
 | 
			
		||||
                override fun onSuccess() {
 | 
			
		||||
                    it.onSuccess(true)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                override fun onFailure(error: IDJIError) {
 | 
			
		||||
                    it.onError(RxError(error))
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            })
 | 
			
		||||
        }).subscribeOn(Schedulers.computation())
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun startSearchStationRTK(): Single<Boolean> {
 | 
			
		||||
        return Single.create(SingleOnSubscribe<Boolean> {
 | 
			
		||||
            rtkStationManager.startSearchRTKStation(object :
 | 
			
		||||
                CommonCallbacks.CompletionCallback {
 | 
			
		||||
                override fun onSuccess() {
 | 
			
		||||
                    it.onSuccess(true)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                override fun onFailure(error: IDJIError) {
 | 
			
		||||
                    it.onError(RxError(error))
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            })
 | 
			
		||||
        }).subscribeOn(Schedulers.computation())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun stopSearchStationRTK(): Single<Boolean> {
 | 
			
		||||
        return Single.create(SingleOnSubscribe<Boolean> {
 | 
			
		||||
            rtkStationManager.startSearchRTKStation(object :
 | 
			
		||||
                CommonCallbacks.CompletionCallback {
 | 
			
		||||
                override fun onSuccess() {
 | 
			
		||||
                    it.onSuccess(true)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                override fun onFailure(error: IDJIError) {
 | 
			
		||||
                    it.onError(RxError(error))
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            })
 | 
			
		||||
        }).subscribeOn(Schedulers.computation())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private fun convertToDJIRTKBaseStationConnectInfo(list: List<RTKStationInfo>?): ArrayList<DJIRTKBaseStationConnectInfo> {
 | 
			
		||||
        val djiRTKBaseStationConnectInfoList = ArrayList<DJIRTKBaseStationConnectInfo>()
 | 
			
		||||
        list?.let {
 | 
			
		||||
            for (i in list) {
 | 
			
		||||
                val djirtkBaseStationConnectInfo = i.toDJIRTKBaseStationConnectInfo()
 | 
			
		||||
                djiRTKBaseStationConnectInfoList.add(djirtkBaseStationConnectInfo)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return djiRTKBaseStationConnectInfoList
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun RTKStationInfo.toDJIRTKBaseStationConnectInfo(): DJIRTKBaseStationConnectInfo {
 | 
			
		||||
        val baseStationId = this.stationId
 | 
			
		||||
        val name = this.stationName
 | 
			
		||||
        val signalLevel = this.signalLevel
 | 
			
		||||
        return DJIRTKBaseStationConnectInfo(baseStationId, signalLevel, name)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,447 @@
 | 
			
		|||
package dji.v5.ux.accessory
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.text.TextUtils
 | 
			
		||||
import android.util.AttributeSet
 | 
			
		||||
import android.widget.Button
 | 
			
		||||
import android.widget.LinearLayout
 | 
			
		||||
import android.widget.TextView
 | 
			
		||||
import android.widget.Toast
 | 
			
		||||
import dji.rtk.CoordinateSystem
 | 
			
		||||
import dji.sdk.keyvalue.value.rtkbasestation.RTKCustomNetworkSetting
 | 
			
		||||
import dji.sdk.keyvalue.value.rtkbasestation.RTKReferenceStationSource
 | 
			
		||||
import dji.v5.common.callback.CommonCallbacks
 | 
			
		||||
import dji.v5.common.error.IDJIError
 | 
			
		||||
import dji.v5.manager.aircraft.rtk.RTKCenter
 | 
			
		||||
import dji.v5.manager.areacode.AreaCodeManager
 | 
			
		||||
import dji.v5.utils.common.LogUtils
 | 
			
		||||
import dji.v5.utils.common.StringUtils
 | 
			
		||||
import dji.v5.ux.R
 | 
			
		||||
import dji.v5.ux.core.base.DJISDKModel
 | 
			
		||||
import dji.v5.ux.core.base.SchedulerProvider
 | 
			
		||||
import dji.v5.ux.core.base.widget.ConstraintLayoutWidget
 | 
			
		||||
import dji.v5.ux.core.communication.ObservableInMemoryKeyedStore
 | 
			
		||||
import dji.v5.ux.core.extension.getString
 | 
			
		||||
import dji.v5.ux.core.extension.hide
 | 
			
		||||
import dji.v5.ux.core.extension.isFastClick
 | 
			
		||||
import dji.v5.ux.core.extension.show
 | 
			
		||||
import dji.v5.ux.core.util.ViewUtil
 | 
			
		||||
import dji.v5.ux.util.RtkSettingWatcher
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Description :基站启动页
 | 
			
		||||
 *
 | 
			
		||||
 * @author: Byte.Cai
 | 
			
		||||
 *  date : 2022/8/1
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2022, DJI All Rights Reserved.
 | 
			
		||||
 */
 | 
			
		||||
private const val TAG = "RTKTypeSwitchWidget"
 | 
			
		||||
 | 
			
		||||
open class RTKTypeSwitchWidget @JvmOverloads constructor(
 | 
			
		||||
    context: Context,
 | 
			
		||||
    attrs: AttributeSet? = null,
 | 
			
		||||
    defStyleAttr: Int = 0,
 | 
			
		||||
) : ConstraintLayoutWidget<Boolean>(context, attrs, defStyleAttr), RtkSettingWatcher.OnEditTextEmptyChangedListener {
 | 
			
		||||
    private val rtkTypeCell: DescSpinnerCell = findViewById(R.id.cell_rtk_type)
 | 
			
		||||
    private val coordinateSystemCell: DescSpinnerCell = findViewById(R.id.cell_coordinate_system)
 | 
			
		||||
    private val edHost: TextView = findViewById(R.id.net_rtk_ntrip_host)
 | 
			
		||||
    private val edPort: TextView = findViewById(R.id.net_rtk_ntrip_port)
 | 
			
		||||
    private val edUser: TextView = findViewById(R.id.net_rtk_ntrip_user)
 | 
			
		||||
    private val edMountPoint: TextView = findViewById(R.id.net_rtk_ntrip_mountpoint)
 | 
			
		||||
    private val edPassword: TextView = findViewById(R.id.net_rtk_ntrip_pwd)
 | 
			
		||||
    private val btSaveRtkInfo: Button = findViewById(R.id.btn_set_net_rtk_info)
 | 
			
		||||
    private val customSetting: LinearLayout = findViewById(R.id.ll_rtk_custom_detail_view)
 | 
			
		||||
 | 
			
		||||
    private var rtkSourceList: List<RTKReferenceStationSource> = ArrayList()
 | 
			
		||||
    private var coordinateSystemList: List<CoordinateSystem> = arrayListOf()
 | 
			
		||||
    private var isMotorsOn = false
 | 
			
		||||
    private var currentRTKSource: RTKReferenceStationSource = RTKReferenceStationSource.UNKNOWN
 | 
			
		||||
    private val textWatcher = RtkSettingWatcher(this)
 | 
			
		||||
    private val INITIAL_INDEX: Int = -1
 | 
			
		||||
    private var lastSelectedRTKTypeIndex: Int = INITIAL_INDEX
 | 
			
		||||
    private var lastSelectedCoordinateSystemIndex: Int = INITIAL_INDEX
 | 
			
		||||
 | 
			
		||||
    private val widgetModel by lazy {
 | 
			
		||||
        RTKTypeSwitchWidgetModel(
 | 
			
		||||
            DJISDKModel.getInstance(),
 | 
			
		||||
            ObservableInMemoryKeyedStore.getInstance(),
 | 
			
		||||
            AreaCodeManager.getInstance(), RTKCenter.getInstance())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val rtkTypeSelectListener = object : DescSpinnerCell.OnItemSelectedListener {
 | 
			
		||||
        override fun onItemSelected(position: Int) {
 | 
			
		||||
            if (position == lastSelectedRTKTypeIndex) {
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
            // B控不可以打开网络RTK。
 | 
			
		||||
            if (RTKStartServiceHelper.isChannelB() && RTKStartServiceHelper.isNetworkRTK(rtkSourceList[position])) {
 | 
			
		||||
                //回滚之前的选择,并提示用户
 | 
			
		||||
                rtkTypeCell.select(lastSelectedRTKTypeIndex)
 | 
			
		||||
                Toast.makeText(getContext(), getTip(position), Toast.LENGTH_SHORT).show()
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
            //电机起转后,并且选择过服务类型,则不允许再次切换RTK类型
 | 
			
		||||
            if (isMotorsOn && lastSelectedRTKTypeIndex != -1) {
 | 
			
		||||
                rtkTypeCell.select(lastSelectedRTKTypeIndex)
 | 
			
		||||
                val tip = StringUtils.getResStr(R.string.uxsdk_rtk_setting_menu_esc_beeping_tip)
 | 
			
		||||
                Toast.makeText(getContext(), tip, Toast.LENGTH_SHORT).show()
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
            setRTKType(position)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val coordinateSelectListener = object : DescSpinnerCell.OnItemSelectedListener {
 | 
			
		||||
        override fun onItemSelected(position: Int) {
 | 
			
		||||
            if (position == lastSelectedCoordinateSystemIndex || coordinateSystemList.isEmpty() || position >= coordinateSystemList.size || position < 0) {
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
            lastSelectedCoordinateSystemIndex = position
 | 
			
		||||
            val coordinate = coordinateSystemList[position]
 | 
			
		||||
            RTKUtil.saveRTKCoordinateSystem(currentRTKSource, coordinate)
 | 
			
		||||
            LogUtils.i(TAG, "select:$coordinate, reStartRtkService now!(Thread.currentThread().name${Thread.currentThread().name})")
 | 
			
		||||
            RTKStartServiceHelper.startRtkService(true)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        edHost.addTextChangedListener(textWatcher)
 | 
			
		||||
        edUser.addTextChangedListener(textWatcher)
 | 
			
		||||
        edPassword.addTextChangedListener(textWatcher)
 | 
			
		||||
        edMountPoint.addTextChangedListener(textWatcher)
 | 
			
		||||
        edPort.addTextChangedListener(textWatcher)
 | 
			
		||||
        btSaveRtkInfo.setOnClickListener {
 | 
			
		||||
            if (!btSaveRtkInfo.isFastClick()) {
 | 
			
		||||
                saveRtkCustomUserInfo()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        //读取默认的配置,并启动RTK
 | 
			
		||||
        LogUtils.i(TAG, "RTKTypeSwitchWidget init,startRtkService now!(Thread.currentThread().name=${Thread.currentThread().name})")
 | 
			
		||||
        RTKStartServiceHelper.startRtkService()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    override fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
 | 
			
		||||
        inflate(context, R.layout.uxsdk_widget_rtk_type_switch, this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    override fun onAttachedToWindow() {
 | 
			
		||||
        super.onAttachedToWindow()
 | 
			
		||||
        if (!isInEditMode) {
 | 
			
		||||
            widgetModel.setup()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDetachedFromWindow() {
 | 
			
		||||
        if (!isInEditMode) {
 | 
			
		||||
            widgetModel.cleanup()
 | 
			
		||||
        }
 | 
			
		||||
        edHost.removeTextChangedListener(textWatcher)
 | 
			
		||||
        edUser.removeTextChangedListener(textWatcher)
 | 
			
		||||
        edMountPoint.removeTextChangedListener(textWatcher)
 | 
			
		||||
        edPort.removeTextChangedListener(textWatcher)
 | 
			
		||||
        edPassword.removeTextChangedListener(textWatcher)
 | 
			
		||||
        super.onDetachedFromWindow()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun reactToModelChanges() {
 | 
			
		||||
        addReaction(widgetModel.isMotorsOn.subscribe {
 | 
			
		||||
            isMotorsOn = it
 | 
			
		||||
        })
 | 
			
		||||
        addReaction(widgetModel.rtkSource.observeOn(SchedulerProvider.ui()).subscribe {
 | 
			
		||||
            LogUtils.i(TAG, "currentRTKSource=$it")
 | 
			
		||||
            //获取RTK服务类型,并切换UI
 | 
			
		||||
            currentRTKSource = it
 | 
			
		||||
            updateRTKView()
 | 
			
		||||
            initDefaultNetRtkUI()
 | 
			
		||||
        })
 | 
			
		||||
        addReaction(widgetModel.supportReferenceStationList.observeOn(SchedulerProvider.ui()).subscribe {
 | 
			
		||||
            //更新支持的RTK列表
 | 
			
		||||
            if (it.isNotEmpty() && !rtkSourceList.containsAll(it)) {
 | 
			
		||||
                LogUtils.i(TAG, "supportReferenceStationList=$it")
 | 
			
		||||
                rtkSourceList = it
 | 
			
		||||
                val referenceStationSourceNames = getReferenceStationSourceNames(it)
 | 
			
		||||
                rtkTypeCell.setEntries(referenceStationSourceNames)
 | 
			
		||||
                rtkTypeCell.addOnItemSelectedListener(rtkTypeSelectListener)
 | 
			
		||||
                initDefaultNetRtkUI()
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        addReaction(widgetModel.coordinateSystemList.observeOn(SchedulerProvider.ui()).subscribe {
 | 
			
		||||
            LogUtils.i(TAG, "coordinateSystemList=$it")
 | 
			
		||||
            //更新坐标系
 | 
			
		||||
            if (it.isNotEmpty() && !coordinateSystemList.containsAll(it)) {
 | 
			
		||||
                LogUtils.i(TAG, "coordinateSystemList=$it")
 | 
			
		||||
                coordinateSystemList = it
 | 
			
		||||
                coordinateSystemCell.addOnItemSelectedListener(null)
 | 
			
		||||
                coordinateSystemCell.setEntries(getCoordinateSystemName(it))
 | 
			
		||||
                coordinateSystemCell.addOnItemSelectedListener(coordinateSelectListener)
 | 
			
		||||
                initDefaultNetRtkUI()
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        //进入App后初始化用户上次选择
 | 
			
		||||
        initDefaultCustomSetting()
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getIdealDimensionRatioString(): String? {
 | 
			
		||||
        return getString(R.string.uxsdk_widget_rtk_keep_status_ratio)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setRTKType(position: Int) {
 | 
			
		||||
        if (rtkSourceList.isEmpty() || position >= rtkSourceList.size || position < 0) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        val rtkSource: RTKReferenceStationSource = rtkSourceList[position]
 | 
			
		||||
        LogUtils.i(TAG, "selected $rtkSource")
 | 
			
		||||
        rtkTypeCell.isEnabled = false
 | 
			
		||||
 | 
			
		||||
        RTKCenter.getInstance().setRTKReferenceStationSource(rtkSource, object : CommonCallbacks.CompletionCallback {
 | 
			
		||||
            override fun onSuccess() {
 | 
			
		||||
                lastSelectedRTKTypeIndex = position
 | 
			
		||||
                rtkTypeCell.isEnabled = true
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            override fun onFailure(error: IDJIError) {
 | 
			
		||||
                rtkTypeCell.isEnabled = true
 | 
			
		||||
                //切换RTK服务类型失败,回滚到上次选择
 | 
			
		||||
                for ((index, source) in rtkSourceList.withIndex()) {
 | 
			
		||||
                    if (source == currentRTKSource) {
 | 
			
		||||
                        rtkTypeCell.select(index)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private fun updateRTKView() {
 | 
			
		||||
        val rtkSwitchDec = StringUtils.getResStr(R.string.uxsdk_rtk_setting_menu_switch_des_info)
 | 
			
		||||
        var rtkSwitchDecDetail = ""
 | 
			
		||||
        when (currentRTKSource) {
 | 
			
		||||
            RTKReferenceStationSource.BASE_STATION -> {
 | 
			
		||||
                customSetting.hide()
 | 
			
		||||
                coordinateSystemCell.hide()
 | 
			
		||||
                rtkSwitchDecDetail = StringUtils.getResStr(R.string.uxsdk_rtk_setting_menu_base_gps_input_desc)
 | 
			
		||||
            }
 | 
			
		||||
            RTKReferenceStationSource.QX_NETWORK_SERVICE,
 | 
			
		||||
            RTKReferenceStationSource.NTRIP_NETWORK_SERVICE,
 | 
			
		||||
            -> {
 | 
			
		||||
                if (!RTKStartServiceHelper.isChannelB()) {
 | 
			
		||||
                    customSetting.hide()
 | 
			
		||||
                    coordinateSystemCell.show()
 | 
			
		||||
                } else {
 | 
			
		||||
                    customSetting.hide()
 | 
			
		||||
                    coordinateSystemCell.hide()
 | 
			
		||||
                }
 | 
			
		||||
                rtkSwitchDecDetail = StringUtils.getResStr(R.string.uxsdk_rtk_setting_menu_station_net_rtk_desc)
 | 
			
		||||
            }
 | 
			
		||||
            RTKReferenceStationSource.CUSTOM_NETWORK_SERVICE -> {
 | 
			
		||||
                if (!RTKStartServiceHelper.isChannelB()) {
 | 
			
		||||
                    customSetting.show()
 | 
			
		||||
                    coordinateSystemCell.hide()
 | 
			
		||||
                } else {
 | 
			
		||||
                    customSetting.hide()
 | 
			
		||||
                    coordinateSystemCell.hide()
 | 
			
		||||
                }
 | 
			
		||||
                rtkSwitchDecDetail = StringUtils.getResStr(R.string.uxsdk_rtk_setting_menu_station_net_rtk_desc)
 | 
			
		||||
            }
 | 
			
		||||
            else -> {
 | 
			
		||||
                customSetting.hide()
 | 
			
		||||
                coordinateSystemCell.hide()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (TextUtils.isEmpty(rtkSwitchDecDetail)) {
 | 
			
		||||
            rtkTypeCell.setSDescText(rtkSwitchDec)
 | 
			
		||||
        } else {
 | 
			
		||||
            rtkTypeCell.setSDescText(rtkSwitchDec + "\n" + rtkSwitchDecDetail)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getReferenceStationSourceNames(list: List<RTKReferenceStationSource>): List<String> {
 | 
			
		||||
        return list.map {
 | 
			
		||||
            val res = when (it) {
 | 
			
		||||
                RTKReferenceStationSource.BASE_STATION ->
 | 
			
		||||
                    R.string.uxsdk_rtk_setting_menu_type_rtk_station
 | 
			
		||||
                RTKReferenceStationSource.CUSTOM_NETWORK_SERVICE ->
 | 
			
		||||
                    R.string.uxsdk_rtk_setting_menu_type_custom_rtk
 | 
			
		||||
                RTKReferenceStationSource.QX_NETWORK_SERVICE ->
 | 
			
		||||
                    R.string.uxsdk_rtk_setting_menu_type_qx_rtk
 | 
			
		||||
                RTKReferenceStationSource.NTRIP_NETWORK_SERVICE ->
 | 
			
		||||
                    R.string.uxsdk_rtk_setting_menu_type_cmcc_rtk
 | 
			
		||||
                else ->
 | 
			
		||||
                    R.string.uxsdk_rtk_setting_menu_type_rtk_none
 | 
			
		||||
            }
 | 
			
		||||
            StringUtils.getResStr(res)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getCoordinateSystemName(list: List<CoordinateSystem>): List<String> {
 | 
			
		||||
        return list.map {
 | 
			
		||||
            val coordinateName: String = when (it) {
 | 
			
		||||
                CoordinateSystem.CGCS2000 ->
 | 
			
		||||
                    CoordinateSystem.CGCS2000.name
 | 
			
		||||
                CoordinateSystem.WGS84 ->
 | 
			
		||||
                    CoordinateSystem.WGS84.name
 | 
			
		||||
                else -> {
 | 
			
		||||
                    LogUtils.e(TAG, "UnSupport CoordinateSystem:$it")
 | 
			
		||||
                    CoordinateSystem.UNKNOWN.name
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            coordinateName
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getTip(position: Int): String {
 | 
			
		||||
        return if (rtkSourceList[position] == RTKReferenceStationSource.QX_NETWORK_SERVICE || rtkSourceList[position] == RTKReferenceStationSource.NTRIP_NETWORK_SERVICE) {
 | 
			
		||||
            StringUtils.getResStr(R.string.uxsdk_rtk_channel_b_not_support_net_rtk)
 | 
			
		||||
        } else {
 | 
			
		||||
            StringUtils.getResStr(R.string.uxsdk_rtk_channel_b_not_support_net_custom_rtk)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun `isTextEmptyChanged`() {
 | 
			
		||||
        //检查RTK设置项的填写状态,并更新设置button的状态
 | 
			
		||||
        var shouldEnableButton = true
 | 
			
		||||
        if (TextUtils.isEmpty(edUser.text)) {
 | 
			
		||||
            shouldEnableButton = false
 | 
			
		||||
        }
 | 
			
		||||
        if (TextUtils.isEmpty(edHost.text)) {
 | 
			
		||||
            shouldEnableButton = false
 | 
			
		||||
        }
 | 
			
		||||
        if (TextUtils.isEmpty(edMountPoint.text)) {
 | 
			
		||||
            shouldEnableButton = false
 | 
			
		||||
        }
 | 
			
		||||
        if (TextUtils.isEmpty(edPort.text)) {
 | 
			
		||||
            shouldEnableButton = false
 | 
			
		||||
        }
 | 
			
		||||
        if (TextUtils.isEmpty(edPassword.text)) {
 | 
			
		||||
            shouldEnableButton = false
 | 
			
		||||
        }
 | 
			
		||||
        //根据各项填写情况,更新保存按钮的值
 | 
			
		||||
        if (shouldEnableButton) {
 | 
			
		||||
            btSaveRtkInfo.isEnabled = true
 | 
			
		||||
            btSaveRtkInfo.setTextColor(resources.getColor(R.color.uxsdk_setting_menu_rtk_tiny_green))
 | 
			
		||||
        } else {
 | 
			
		||||
            btSaveRtkInfo.isEnabled = false
 | 
			
		||||
            btSaveRtkInfo.setTextColor(resources.getColor(R.color.uxsdk_setting_menu_rtk_txt_gray))
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun saveRtkCustomUserInfo() {
 | 
			
		||||
        // 检查输入是否有效
 | 
			
		||||
        var isHostValid = true
 | 
			
		||||
        var isPortValid = true
 | 
			
		||||
        var isUserValid = true
 | 
			
		||||
        var isPwdValid = true
 | 
			
		||||
        var isMountPointValid = true
 | 
			
		||||
        val host: String = edHost.text.toString()
 | 
			
		||||
        if (TextUtils.isEmpty(host)) {
 | 
			
		||||
            isHostValid = false
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val portStr: String = edPort.text.toString()
 | 
			
		||||
        var port = -1
 | 
			
		||||
        if (!TextUtils.isEmpty(portStr) && TextUtils.isDigitsOnly(portStr)) {
 | 
			
		||||
            port = portStr.toInt()
 | 
			
		||||
            if (port < 0) {
 | 
			
		||||
                isPortValid = false
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            isPortValid = false
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val user: String = edUser.text.toString()
 | 
			
		||||
        if (TextUtils.isEmpty(user)) {
 | 
			
		||||
            isUserValid = false
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val pw: String = edPassword.text.toString()
 | 
			
		||||
        if (TextUtils.isEmpty(pw)) {
 | 
			
		||||
            isPwdValid = false
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val mountPint: String = edMountPoint.text.toString()
 | 
			
		||||
        if (TextUtils.isEmpty(mountPint)) {
 | 
			
		||||
            isMountPointValid = false
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // 参数无效,select设为true时,EditText边框设为红色
 | 
			
		||||
        edHost.isSelected = !isHostValid
 | 
			
		||||
        edPort.isSelected = !isPortValid
 | 
			
		||||
        edUser.isSelected = !isUserValid
 | 
			
		||||
        edPassword.isSelected = !isPwdValid
 | 
			
		||||
        edMountPoint.isSelected = !isMountPointValid
 | 
			
		||||
 | 
			
		||||
        val isParamsValid = (isHostValid && isMountPointValid
 | 
			
		||||
                && isPortValid && isUserValid && isPwdValid)
 | 
			
		||||
        if (!isParamsValid) {
 | 
			
		||||
            ViewUtil.showToast(context, R.string.uxsdk_rtk_setting_menu_customer_rtk_save_failed_tips, Toast.LENGTH_SHORT)
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        startRtkCustomNetwork(host, port, user, pw, mountPint)
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun startRtkCustomNetwork(host: String, port: Int, user: String, pw: String, mountPint: String) {
 | 
			
		||||
        val rtkSetting = RTKCustomNetworkSetting()
 | 
			
		||||
        rtkSetting.serverAddress = host
 | 
			
		||||
        rtkSetting.port = port
 | 
			
		||||
        rtkSetting.userName = user
 | 
			
		||||
        rtkSetting.password = pw
 | 
			
		||||
        rtkSetting.mountPoint = mountPint
 | 
			
		||||
        RTKUtil.saveRtkCustomNetworkSetting(rtkSetting)
 | 
			
		||||
        LogUtils.i(TAG, "rtkSetting=$rtkSetting,startRtkCustomNetwork now!(Thread.currentThread().name${Thread.currentThread().name})")
 | 
			
		||||
        RTKStartServiceHelper.startRtkService(true)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 初始化用户设置的自定义网络RTK设置的信息
 | 
			
		||||
     */
 | 
			
		||||
    private fun initDefaultCustomSetting() {
 | 
			
		||||
        RTKUtil.getRtkCustomNetworkSetting()?.run {
 | 
			
		||||
            LogUtils.i(TAG, "getRtkCustomNetworkSetting=$this")
 | 
			
		||||
            edMountPoint.text = mountPoint
 | 
			
		||||
            edHost.text = serverAddress
 | 
			
		||||
            edPassword.text = password
 | 
			
		||||
            edUser.text = userName
 | 
			
		||||
            edPort.text = port.toString()
 | 
			
		||||
        }
 | 
			
		||||
        isTextEmptyChanged()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun initDefaultNetRtkUI() {
 | 
			
		||||
        //初始化用户上次选择的RTK服务类型
 | 
			
		||||
        if (currentRTKSource != RTKReferenceStationSource.UNKNOWN && rtkSourceList.isNotEmpty()) {
 | 
			
		||||
            for ((index, rtkSource) in rtkSourceList.withIndex()) {
 | 
			
		||||
                if (rtkSource == currentRTKSource) {
 | 
			
		||||
                    lastSelectedRTKTypeIndex = index
 | 
			
		||||
                    rtkTypeCell.select(index)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //初始化用户上次选择的坐标系
 | 
			
		||||
        val netRTKCoordinateSystem = RTKUtil.getNetRTKCoordinateSystem(currentRTKSource)
 | 
			
		||||
        for ((index, coordinate) in coordinateSystemList.withIndex()) {
 | 
			
		||||
            if (netRTKCoordinateSystem == coordinate) {
 | 
			
		||||
                lastSelectedCoordinateSystemIndex = index
 | 
			
		||||
                coordinateSystemCell.select(index)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,201 @@
 | 
			
		|||
package dji.v5.ux.accessory
 | 
			
		||||
 | 
			
		||||
import dji.rtk.CoordinateSystem
 | 
			
		||||
import dji.sdk.keyvalue.key.FlightControllerKey
 | 
			
		||||
import dji.sdk.keyvalue.key.KeyTools
 | 
			
		||||
import dji.sdk.keyvalue.key.ProductKey
 | 
			
		||||
import dji.sdk.keyvalue.utils.ProductUtil
 | 
			
		||||
import dji.sdk.keyvalue.value.product.ProductType
 | 
			
		||||
import dji.sdk.keyvalue.value.rtkbasestation.RTKReferenceStationSource
 | 
			
		||||
import dji.v5.common.utils.RxUtil
 | 
			
		||||
import dji.v5.manager.KeyManager
 | 
			
		||||
import dji.v5.manager.aircraft.rtk.RTKSystemStateListener
 | 
			
		||||
import dji.v5.manager.areacode.AreaCode
 | 
			
		||||
import dji.v5.manager.areacode.AreaCodeChangeListener
 | 
			
		||||
import dji.v5.manager.areacode.AreaCodeManager
 | 
			
		||||
import dji.v5.manager.interfaces.IAreaCodeManager
 | 
			
		||||
import dji.v5.manager.interfaces.INetworkRTKManager
 | 
			
		||||
import dji.v5.manager.interfaces.IRTKCenter
 | 
			
		||||
import dji.v5.utils.common.LogUtils
 | 
			
		||||
import dji.v5.ux.core.base.DJISDKModel
 | 
			
		||||
import dji.v5.ux.core.base.WidgetModel
 | 
			
		||||
import dji.v5.ux.core.communication.ObservableInMemoryKeyedStore
 | 
			
		||||
import dji.v5.ux.core.util.DataProcessor
 | 
			
		||||
import io.reactivex.rxjava3.core.Flowable
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Description :
 | 
			
		||||
 *
 | 
			
		||||
 * @author: Byte.Cai
 | 
			
		||||
 *  date : 2022/8/15
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2022, DJI All Rights Reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
class RTKTypeSwitchWidgetModel(
 | 
			
		||||
    djiSdkModel: DJISDKModel,
 | 
			
		||||
    uxKeyManager: ObservableInMemoryKeyedStore,
 | 
			
		||||
    val areaCodeManager: IAreaCodeManager,
 | 
			
		||||
    val rtkCenter: IRTKCenter,
 | 
			
		||||
) : WidgetModel(djiSdkModel, uxKeyManager) {
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private const val TAG = "RTKTypeSwitchWidgetModel"
 | 
			
		||||
        private const val CUSTOM_RTK_SETTING_CACHE = "customRTKSettingCache"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val supportReferenceStationListProcessor: DataProcessor<List<RTKReferenceStationSource>> = DataProcessor.create(ArrayList())
 | 
			
		||||
    private val areaCodeProcessor: DataProcessor<String> = DataProcessor.create(AreaCode.UNKNOWN.code)
 | 
			
		||||
    private val productTypeProcessor: DataProcessor<ProductType> = DataProcessor.create(ProductType.UNKNOWN)
 | 
			
		||||
    private val rtkSourceProcessor: DataProcessor<RTKReferenceStationSource> = DataProcessor.create(RTKReferenceStationSource.UNKNOWN)
 | 
			
		||||
    private val coordinateSystemListProcessor: DataProcessor<List<CoordinateSystem>> = DataProcessor.create(arrayListOf())
 | 
			
		||||
    private val isMotorOnProcessor: DataProcessor<Boolean> = DataProcessor.create(false)
 | 
			
		||||
    private var qxRTKManager: INetworkRTKManager? = null
 | 
			
		||||
    private var customNetworkRTKManager: INetworkRTKManager? = null
 | 
			
		||||
    private var currentRtkSource: RTKReferenceStationSource = RTKReferenceStationSource.UNKNOWN
 | 
			
		||||
    private val areaCodeChangeListener = AreaCodeChangeListener { _, result ->
 | 
			
		||||
        areaCodeProcessor.onNext(result.areaCode)
 | 
			
		||||
        updateSupportReferenceStationList()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private val rtkSystemStateListener = RTKSystemStateListener {
 | 
			
		||||
        val rtkSource = it.rtkReferenceStationSource
 | 
			
		||||
        var coordinateSystemList: List<CoordinateSystem> = arrayListOf()
 | 
			
		||||
        when (rtkSource) {
 | 
			
		||||
            RTKReferenceStationSource.QX_NETWORK_SERVICE,
 | 
			
		||||
            RTKReferenceStationSource.NTRIP_NETWORK_SERVICE,
 | 
			
		||||
            -> {
 | 
			
		||||
                coordinateSystemList = arrayListOf(CoordinateSystem.WGS84, CoordinateSystem.CGCS2000)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            else -> {
 | 
			
		||||
                //Do Nothing
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (currentRtkSource != rtkSource) {
 | 
			
		||||
            currentRtkSource = rtkSource
 | 
			
		||||
            rtkSourceProcessor.onNext(rtkSource)
 | 
			
		||||
            coordinateSystemListProcessor.onNext(coordinateSystemList)
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    val isMotorsOn: Flowable<Boolean>
 | 
			
		||||
        get() = isMotorOnProcessor.toFlowable()
 | 
			
		||||
 | 
			
		||||
    val rtkSource: Flowable<RTKReferenceStationSource>
 | 
			
		||||
        get() = rtkSourceProcessor.toFlowable()
 | 
			
		||||
 | 
			
		||||
    val coordinateSystemList: Flowable<List<CoordinateSystem>>
 | 
			
		||||
        get() = coordinateSystemListProcessor.toFlowable()
 | 
			
		||||
 | 
			
		||||
    val supportReferenceStationList: Flowable<List<RTKReferenceStationSource>>
 | 
			
		||||
        get() = supportReferenceStationListProcessor.toFlowable()
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        qxRTKManager = rtkCenter.qxrtkManager
 | 
			
		||||
        customNetworkRTKManager = rtkCenter.customRTKManager
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun inSetup() {
 | 
			
		||||
        bindDataProcessor(
 | 
			
		||||
            KeyTools.createKey(
 | 
			
		||||
                FlightControllerKey.KeyAreMotorsOn
 | 
			
		||||
            ), isMotorOnProcessor
 | 
			
		||||
        )
 | 
			
		||||
        addDisposable(RxUtil.addListener(
 | 
			
		||||
            KeyTools.createKey(
 | 
			
		||||
                ProductKey.KeyProductType
 | 
			
		||||
            ), this
 | 
			
		||||
        ).subscribe {
 | 
			
		||||
            productTypeProcessor.onNext(it)
 | 
			
		||||
            updateSupportReferenceStationList()
 | 
			
		||||
        })
 | 
			
		||||
        areaCodeManager.addAreaCodeChangeListener(areaCodeChangeListener)
 | 
			
		||||
        rtkCenter.addRTKSystemStateListener(rtkSystemStateListener)
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun inCleanup() {
 | 
			
		||||
        areaCodeManager.removeAreaCodeChangeListener(areaCodeChangeListener)
 | 
			
		||||
        rtkCenter.removeRTKSystemStateListener(rtkSystemStateListener)
 | 
			
		||||
        KeyManager.getInstance().cancelListen(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun updateSupportReferenceStationList() {
 | 
			
		||||
        if (ProductType.DJI_MAVIC_3_ENTERPRISE_SERIES == productTypeProcessor.value) {
 | 
			
		||||
            when (areaCodeProcessor.value) {
 | 
			
		||||
                AreaCode.CHINA.code -> {
 | 
			
		||||
                    supportReferenceStationListProcessor.onNext(getSupportReferenceStationSource(true))
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                else -> {
 | 
			
		||||
                    supportReferenceStationListProcessor.onNext(getSupportReferenceStationSource(false))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            supportReferenceStationListProcessor.onNext(getSupportReferenceStationSource(isInChina()))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取支持的差分数据源
 | 
			
		||||
     */
 | 
			
		||||
    private fun getSupportReferenceStationSource(supportNetworkRTK: Boolean): List<RTKReferenceStationSource> {
 | 
			
		||||
        return if (ProductUtil.isM3EProduct() || ProductUtil.isM4EProduct() || ProductUtil.isM4DProduct()) {
 | 
			
		||||
            getMavicSupportReferenceStationSource(supportNetworkRTK)
 | 
			
		||||
        } else {
 | 
			
		||||
            getDefaultSupportReferenceStationSource(supportNetworkRTK)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private fun getMavicSupportReferenceStationSource(supportNetworkRTK: Boolean): MutableList<RTKReferenceStationSource> {
 | 
			
		||||
        return if (supportNetworkRTK) {
 | 
			
		||||
            mutableListOf(
 | 
			
		||||
                RTKReferenceStationSource.NONE,
 | 
			
		||||
                RTKReferenceStationSource.BASE_STATION,
 | 
			
		||||
                RTKReferenceStationSource.NTRIP_NETWORK_SERVICE,
 | 
			
		||||
                RTKReferenceStationSource.CUSTOM_NETWORK_SERVICE
 | 
			
		||||
            )
 | 
			
		||||
        } else {
 | 
			
		||||
            mutableListOf(
 | 
			
		||||
                RTKReferenceStationSource.NONE,
 | 
			
		||||
                RTKReferenceStationSource.BASE_STATION,
 | 
			
		||||
                RTKReferenceStationSource.CUSTOM_NETWORK_SERVICE
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getDefaultSupportReferenceStationSource(supportNetworkRTK: Boolean): MutableList<RTKReferenceStationSource> {
 | 
			
		||||
        LogUtils.i(TAG, "supportNetworkRTK=$supportNetworkRTK")
 | 
			
		||||
        return if (supportNetworkRTK) {
 | 
			
		||||
            mutableListOf(
 | 
			
		||||
                RTKReferenceStationSource.NONE,
 | 
			
		||||
                RTKReferenceStationSource.BASE_STATION,
 | 
			
		||||
                RTKReferenceStationSource.QX_NETWORK_SERVICE,
 | 
			
		||||
                RTKReferenceStationSource.NTRIP_NETWORK_SERVICE,
 | 
			
		||||
                RTKReferenceStationSource.CUSTOM_NETWORK_SERVICE
 | 
			
		||||
            )
 | 
			
		||||
        } else {
 | 
			
		||||
            mutableListOf(
 | 
			
		||||
                RTKReferenceStationSource.NONE,
 | 
			
		||||
                RTKReferenceStationSource.BASE_STATION,
 | 
			
		||||
                RTKReferenceStationSource.CUSTOM_NETWORK_SERVICE
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否在中国
 | 
			
		||||
     */
 | 
			
		||||
    private fun isInChina(): Boolean {
 | 
			
		||||
        val countryCode = AreaCodeManager.getInstance().areaCode.areaCode
 | 
			
		||||
        return AreaCode.CHINA.code.equals(countryCode)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,129 @@
 | 
			
		|||
package dji.v5.ux.accessory
 | 
			
		||||
 | 
			
		||||
import android.text.TextUtils
 | 
			
		||||
import android.view.View
 | 
			
		||||
import dji.rtk.CoordinateSystem
 | 
			
		||||
import dji.sdk.keyvalue.value.rtkbasestation.RTKCustomNetworkSetting
 | 
			
		||||
import dji.sdk.keyvalue.value.rtkbasestation.RTKReferenceStationSource
 | 
			
		||||
import dji.sdk.keyvalue.value.rtkmobilestation.RTKPositioningSolution
 | 
			
		||||
import dji.v5.manager.aircraft.rtk.RTKCenter
 | 
			
		||||
import dji.v5.utils.common.ContextUtil
 | 
			
		||||
import dji.v5.utils.common.DjiSharedPreferencesManager
 | 
			
		||||
import dji.v5.utils.common.JsonUtil
 | 
			
		||||
import dji.v5.utils.common.LogUtils
 | 
			
		||||
import dji.v5.ux.R
 | 
			
		||||
import dji.v5.ux.core.extension.getString
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Description :
 | 
			
		||||
 *
 | 
			
		||||
 * @author: Byte.Cai
 | 
			
		||||
 *  date : 2022/5/24
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2022, DJI All Rights Reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
object RTKUtil {
 | 
			
		||||
    private const val TAG = "RTKUtil"
 | 
			
		||||
    private const val USER_RTK_NETWORK_SERVICE_SETTINGS = "user_rtk_network_service_settings"
 | 
			
		||||
    private const val USER_RTK_REFERENCE_SOURCE = "user_rtk_reference_source"
 | 
			
		||||
    private const val USER_RTK_NETWORK_COORDINATE_SYSTEM_CMCC = "user_rtk_network_coordinate_system_cmcc"
 | 
			
		||||
    private const val USER_RTK_NETWORK_COORDINATE_SYSTEM_QX = "user_rtk_network_coordinate_system_qx"
 | 
			
		||||
    fun getRTKTypeName(view: View, rtkReferenceStationSource: RTKReferenceStationSource?): String {
 | 
			
		||||
        view.run {
 | 
			
		||||
            if (rtkReferenceStationSource == null) {
 | 
			
		||||
                return getString(R.string.uxsdk_rtk_type_unknown_rtk);
 | 
			
		||||
            }
 | 
			
		||||
            return when (rtkReferenceStationSource) {
 | 
			
		||||
                RTKReferenceStationSource.QX_NETWORK_SERVICE -> getString(R.string.uxsdk_rtk_type_nrtk)
 | 
			
		||||
                RTKReferenceStationSource.BASE_STATION -> getString(R.string.uxsdk_rtk_type_rtk_mobile_station)
 | 
			
		||||
                RTKReferenceStationSource.CUSTOM_NETWORK_SERVICE -> getString(R.string.uxsdk_rtk_type_custom_rtk)
 | 
			
		||||
                RTKReferenceStationSource.NTRIP_NETWORK_SERVICE -> getString(R.string.uxsdk_rtk_type_cmcc_rtk)
 | 
			
		||||
                else -> getString(R.string.uxsdk_rtk_type_unknown_rtk)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getRTKStatusName(view: View, positioningSolution: RTKPositioningSolution?): String {
 | 
			
		||||
        view.run {
 | 
			
		||||
            if (positioningSolution == null) {
 | 
			
		||||
                return getString(R.string.uxsdk_rtk_solution_unknown)
 | 
			
		||||
            }
 | 
			
		||||
            return when (positioningSolution) {
 | 
			
		||||
                RTKPositioningSolution.NONE -> getString(R.string.uxsdk_rtk_solution_none)
 | 
			
		||||
                RTKPositioningSolution.SINGLE_POINT -> getString(R.string.uxsdk_rtk_solution_single)
 | 
			
		||||
                RTKPositioningSolution.FLOAT -> getString(R.string.uxsdk_rtk_solution_float)
 | 
			
		||||
                RTKPositioningSolution.FIXED_POINT -> getString(R.string.uxsdk_rtk_solution_fixed)
 | 
			
		||||
                RTKPositioningSolution.UNKNOWN -> getString(R.string.uxsdk_rtk_solution_unknown)
 | 
			
		||||
                else -> getString(R.string.uxsdk_rtk_solution_unknown)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getNetRTKCoordinateSystem(rtkReferenceStationSource: RTKReferenceStationSource): CoordinateSystem? {
 | 
			
		||||
        var coordinateSystem = ""
 | 
			
		||||
        when (rtkReferenceStationSource) {
 | 
			
		||||
            RTKReferenceStationSource.NTRIP_NETWORK_SERVICE -> {
 | 
			
		||||
                coordinateSystem = DjiSharedPreferencesManager.getString(ContextUtil.getContext(),
 | 
			
		||||
                    USER_RTK_NETWORK_COORDINATE_SYSTEM_CMCC, "")
 | 
			
		||||
            }
 | 
			
		||||
            RTKReferenceStationSource.QX_NETWORK_SERVICE -> {
 | 
			
		||||
                coordinateSystem = DjiSharedPreferencesManager.getString(ContextUtil.getContext(), USER_RTK_NETWORK_COORDINATE_SYSTEM_QX, "")
 | 
			
		||||
            }
 | 
			
		||||
            else -> {
 | 
			
		||||
                LogUtils.e(TAG, "getNetRTKCoordinateSystem error,unSupport rtkReferenceStationSource:$rtkReferenceStationSource")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return if (TextUtils.isEmpty(coordinateSystem)) null else getCoordinateName(coordinateSystem)
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun saveRTKCoordinateSystem(rtkReferenceStationSource: RTKReferenceStationSource, coordinateSystem: CoordinateSystem) {
 | 
			
		||||
        when (rtkReferenceStationSource) {
 | 
			
		||||
            RTKReferenceStationSource.QX_NETWORK_SERVICE -> {
 | 
			
		||||
                DjiSharedPreferencesManager.putString(ContextUtil.getContext(), USER_RTK_NETWORK_COORDINATE_SYSTEM_QX, coordinateSystem.name)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            RTKReferenceStationSource.NTRIP_NETWORK_SERVICE -> {
 | 
			
		||||
                DjiSharedPreferencesManager.putString(ContextUtil.getContext(), USER_RTK_NETWORK_COORDINATE_SYSTEM_CMCC, coordinateSystem.name)
 | 
			
		||||
            }
 | 
			
		||||
            else -> {
 | 
			
		||||
                LogUtils.e(TAG, "saveRTKCoordinateSystem error,unSupport rtkReferenceStationSource:$rtkReferenceStationSource")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    fun saveRtkCustomNetworkSetting(settings: RTKCustomNetworkSetting) {
 | 
			
		||||
        DjiSharedPreferencesManager.putString(ContextUtil.getContext(), USER_RTK_NETWORK_SERVICE_SETTINGS, settings.toString())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getRtkCustomNetworkSetting(): RTKCustomNetworkSetting? {
 | 
			
		||||
        val localSetting = DjiSharedPreferencesManager.getString(ContextUtil.getContext(), USER_RTK_NETWORK_SERVICE_SETTINGS, "")
 | 
			
		||||
        return if (!TextUtils.isEmpty(localSetting)) {
 | 
			
		||||
            JsonUtil.toBean(localSetting, RTKCustomNetworkSetting::class.java)
 | 
			
		||||
        } else RTKCenter.getInstance().customRTKManager.customNetworkRTKSettings
 | 
			
		||||
            ?: RTKCustomNetworkSetting(
 | 
			
		||||
                "",
 | 
			
		||||
                0,
 | 
			
		||||
                "",
 | 
			
		||||
                "",
 | 
			
		||||
                ""
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getCoordinateName(value: String): CoordinateSystem {
 | 
			
		||||
        return when (value) {
 | 
			
		||||
            CoordinateSystem.CGCS2000.name ->
 | 
			
		||||
                CoordinateSystem.CGCS2000
 | 
			
		||||
            CoordinateSystem.WGS84.name ->
 | 
			
		||||
                CoordinateSystem.WGS84
 | 
			
		||||
            else -> {
 | 
			
		||||
                //如果未设置过坐标系,则默认使用CGCS2000作为启动的坐标系
 | 
			
		||||
                CoordinateSystem.CGCS2000
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,183 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.accessory
 | 
			
		||||
 | 
			
		||||
import android.annotation.SuppressLint
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.res.ColorStateList
 | 
			
		||||
import android.graphics.drawable.Drawable
 | 
			
		||||
import android.util.AttributeSet
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.widget.TextView
 | 
			
		||||
import androidx.annotation.ColorInt
 | 
			
		||||
import androidx.annotation.DrawableRes
 | 
			
		||||
import androidx.annotation.StyleRes
 | 
			
		||||
import androidx.core.content.res.use
 | 
			
		||||
import dji.v5.utils.common.LogUtils
 | 
			
		||||
import dji.v5.ux.R
 | 
			
		||||
import io.reactivex.rxjava3.core.Flowable
 | 
			
		||||
import dji.v5.ux.core.base.DJISDKModel
 | 
			
		||||
import dji.v5.ux.core.base.SchedulerProvider
 | 
			
		||||
import dji.v5.ux.core.base.widget.ConstraintLayoutWidget
 | 
			
		||||
import dji.v5.ux.core.communication.ObservableInMemoryKeyedStore
 | 
			
		||||
import dji.v5.ux.core.extension.*
 | 
			
		||||
 | 
			
		||||
private const val TAG = "RTKWidget"
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This widget contains multiple widgets to control and get information related to RTK.
 | 
			
		||||
 */
 | 
			
		||||
open class RTKWidget @JvmOverloads constructor(
 | 
			
		||||
    context: Context,
 | 
			
		||||
    attrs: AttributeSet? = null,
 | 
			
		||||
    defStyleAttr: Int = 0
 | 
			
		||||
) : ConstraintLayoutWidget<RTKWidget.ModelState>(context, attrs, defStyleAttr) {
 | 
			
		||||
    //region Fields
 | 
			
		||||
    private val rtkDialogSeparator: View = findViewById(R.id.rtk_dialog_separator)
 | 
			
		||||
 | 
			
		||||
    private val widgetModel by lazy {
 | 
			
		||||
        RTKWidgetModel(
 | 
			
		||||
            DJISDKModel.getInstance(),
 | 
			
		||||
            ObservableInMemoryKeyedStore.getInstance()
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the RTK Enabled Widget so it can be customized.
 | 
			
		||||
     */
 | 
			
		||||
    @get:JvmName("getRTKEnabledWidget")
 | 
			
		||||
    val rtkEnabledWidget: RTKEnabledWidget = findViewById(R.id.widget_rtk_enabled)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the RTK Satellite Status Widget so it can be customized.
 | 
			
		||||
     */
 | 
			
		||||
    @get:JvmName("getRTKSatelliteStatusWidget")
 | 
			
		||||
    val rtkSatelliteStatusWidget: RTKSatelliteStatusWidget = findViewById(R.id.widget_rtk_satellite_status)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the RTK Type Switch Widget so it can be customized.
 | 
			
		||||
     */
 | 
			
		||||
    @get:JvmName("getRTKTypeSwitchWidget")
 | 
			
		||||
    val rtkTypeSwitchWidget: RTKTypeSwitchWidget = findViewById(R.id.widget_rtk_type_switch)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The color for the separator line views
 | 
			
		||||
     */
 | 
			
		||||
    var rtkSeparatorsColor: Int
 | 
			
		||||
        @JvmName("getRTKSeparatorsColor")
 | 
			
		||||
        @ColorInt
 | 
			
		||||
        get() = rtkSatelliteStatusWidget.rtkSeparatorsColor
 | 
			
		||||
        @JvmName("setRTKSeparatorsColor")
 | 
			
		||||
        set(@ColorInt color) {
 | 
			
		||||
            rtkSatelliteStatusWidget.rtkSeparatorsColor = color
 | 
			
		||||
            rtkDialogSeparator.setBackgroundColor(color)
 | 
			
		||||
        }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Constructor
 | 
			
		||||
    override fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
 | 
			
		||||
        inflate(context, R.layout.uxsdk_widget_rtk, this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        LogUtils.i(TAG, "init")
 | 
			
		||||
        attrs?.let { initAttributes(context, it) }
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Lifecycle
 | 
			
		||||
    override fun onAttachedToWindow() {
 | 
			
		||||
        super.onAttachedToWindow()
 | 
			
		||||
        LogUtils.i(TAG, "onAttachedToWindow")
 | 
			
		||||
 | 
			
		||||
        if (!isInEditMode) {
 | 
			
		||||
            widgetModel.setup()
 | 
			
		||||
            LogUtils.i(TAG, "widgetModel.setup()")
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDetachedFromWindow() {
 | 
			
		||||
        if (!isInEditMode) {
 | 
			
		||||
            widgetModel.cleanup()
 | 
			
		||||
        }
 | 
			
		||||
        super.onDetachedFromWindow()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun reactToModelChanges() {
 | 
			
		||||
        //do nothing
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    //region Customization
 | 
			
		||||
    override fun getIdealDimensionRatioString(): String {
 | 
			
		||||
        return resources.getString(R.string.uxsdk_widget_rtk_ratio)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    //Initialize all customizable attributes
 | 
			
		||||
    @SuppressLint("Recycle")
 | 
			
		||||
    private fun initAttributes(context: Context, attrs: AttributeSet) {
 | 
			
		||||
        context.obtainStyledAttributes(attrs, R.styleable.RTKWidget).use { typedArray ->
 | 
			
		||||
            typedArray.getColorAndUse(R.styleable.RTKWidget_uxsdk_rtkSeparatorsColor) {
 | 
			
		||||
                rtkSeparatorsColor = it
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the [ModelState] updates
 | 
			
		||||
     */
 | 
			
		||||
    @SuppressWarnings
 | 
			
		||||
    override fun getWidgetStateUpdate(): Flowable<ModelState> {
 | 
			
		||||
        return super.getWidgetStateUpdate()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Class defines the widget state updates
 | 
			
		||||
     */
 | 
			
		||||
    sealed class ModelState {
 | 
			
		||||
        /**
 | 
			
		||||
         * Product connection update
 | 
			
		||||
         */
 | 
			
		||||
        data class ProductConnected(val isConnected: Boolean) : ModelState()
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * RTK enabled update
 | 
			
		||||
         */
 | 
			
		||||
        data class RTKEnabledUpdated(val isRTKEnabled: Boolean) : ModelState()
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,74 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.accessory
 | 
			
		||||
 | 
			
		||||
import dji.sdk.keyvalue.key.DJIKey
 | 
			
		||||
import dji.sdk.keyvalue.key.KeyTools
 | 
			
		||||
import dji.sdk.keyvalue.key.RtkMobileStationKey
 | 
			
		||||
import dji.v5.ux.core.base.DJISDKModel
 | 
			
		||||
import dji.v5.ux.core.base.WidgetModel
 | 
			
		||||
import dji.v5.ux.core.communication.ObservableInMemoryKeyedStore
 | 
			
		||||
import dji.v5.ux.core.util.DataProcessor
 | 
			
		||||
import io.reactivex.rxjava3.core.Flowable
 | 
			
		||||
 | 
			
		||||
private const val TAG = "RTKWidgetModel"
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Widget Model for the [RTKWidget] used to define
 | 
			
		||||
 * the underlying logic and communication
 | 
			
		||||
 */
 | 
			
		||||
class RTKWidgetModel(
 | 
			
		||||
    djiSdkModel: DJISDKModel,
 | 
			
		||||
    uxKeyManager: ObservableInMemoryKeyedStore
 | 
			
		||||
) : WidgetModel(djiSdkModel, uxKeyManager) {
 | 
			
		||||
 | 
			
		||||
    //region Fields
 | 
			
		||||
    private val rtkEnabledProcessor: DataProcessor<Boolean> = DataProcessor.create(false)
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Data
 | 
			
		||||
    /**
 | 
			
		||||
     * Get whether the RTK is enabled.
 | 
			
		||||
     */
 | 
			
		||||
    val rtkEnabled: Flowable<Boolean>
 | 
			
		||||
        @JvmName("getRTKEnabled")
 | 
			
		||||
        get() = rtkEnabledProcessor.toFlowable()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    //region Lifecycle
 | 
			
		||||
    override fun inSetup() {
 | 
			
		||||
        val rtkEnabledKey: DJIKey<Boolean> = KeyTools.createKey(
 | 
			
		||||
            RtkMobileStationKey.KeyRTKEnable)
 | 
			
		||||
        bindDataProcessor(rtkEnabledKey, rtkEnabledProcessor)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun inCleanup() {
 | 
			
		||||
        // do nothing
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun updateStates() {
 | 
			
		||||
        // Nothing to update
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,26 @@
 | 
			
		|||
package dji.v5.ux.accessory.data
 | 
			
		||||
 | 
			
		||||
import dji.sdk.keyvalue.value.rtkbasestation.BaseStationDeviceType
 | 
			
		||||
import dji.sdk.keyvalue.value.rtkbasestation.RTKStationInfo
 | 
			
		||||
import dji.sdk.keyvalue.value.rtkbasestation.RTKStationConnetState
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Description :驱动UI的数据模型,基于RTKBaseStationConnectInfo新增一个连接状态属性
 | 
			
		||||
 *
 | 
			
		||||
 * @author: Byte.Cai
 | 
			
		||||
 *  date : 2022/3/6
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2022, DJI All Rights Reserved.
 | 
			
		||||
 */
 | 
			
		||||
class DJIRTKBaseStationConnectInfo(
 | 
			
		||||
    var baseStationId: Int,
 | 
			
		||||
    var signalLevel: Int,
 | 
			
		||||
    var rtkStationName: String,
 | 
			
		||||
    var connectStatus: RTKStationConnetState = RTKStationConnetState.IDLE
 | 
			
		||||
) : RTKStationInfo(baseStationId, signalLevel, rtkStationName, BaseStationDeviceType.BS_RTK2) {
 | 
			
		||||
    constructor() : this(0, 0, "")
 | 
			
		||||
 | 
			
		||||
    override fun toString(): String {
 | 
			
		||||
        return super.toString()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,149 @@
 | 
			
		|||
package dji.v5.ux.accessory.data
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.view.LayoutInflater
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import android.widget.ImageView
 | 
			
		||||
import android.widget.TextView
 | 
			
		||||
import androidx.annotation.DrawableRes
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import dji.sdk.keyvalue.value.rtkbasestation.RTKStationConnetState
 | 
			
		||||
import dji.v5.utils.common.LogUtils
 | 
			
		||||
import dji.v5.ux.R
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Description :基站RTK的Adapter,展示扫码到的基站情况
 | 
			
		||||
 *
 | 
			
		||||
 * @author: Byte.Cai
 | 
			
		||||
 *  date : 2022/3/6
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2022, DJI All Rights Reserved.
 | 
			
		||||
 */
 | 
			
		||||
class RtkStationScanAdapter(val context: Context, list: List<DJIRTKBaseStationConnectInfo>?) :
 | 
			
		||||
    RecyclerView.Adapter<RtkStationScanAdapter.RtkViewHolder>() {
 | 
			
		||||
    private val TAG = "RtkStationScanAdapter"
 | 
			
		||||
 | 
			
		||||
    private val LEVEL_0 = 0
 | 
			
		||||
    private val LEVEL_1 = 1
 | 
			
		||||
    private val LEVEL_2 = 2
 | 
			
		||||
    private val LEVEL_3 = 3
 | 
			
		||||
    private val LEVEL_4 = 4
 | 
			
		||||
    private var baseStationInfoList: List<DJIRTKBaseStationConnectInfo>? = list
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RtkViewHolder {
 | 
			
		||||
        val view: View = LayoutInflater.from(context).inflate(R.layout.uxsdk_widget_rtk_connect_status_item, parent, false)
 | 
			
		||||
        return RtkViewHolder(view)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBindViewHolder(holder: RtkViewHolder, position: Int) {
 | 
			
		||||
        baseStationInfoList?.get(position)?.let { info ->
 | 
			
		||||
            holder.mRtkStationNameTv.text = info.rtkStationName
 | 
			
		||||
            holder.mConnectSignalIv.setBackgroundResource(getSignalLevelDrawable(info.signalLevel))
 | 
			
		||||
            holder.itemView.setOnClickListener {
 | 
			
		||||
                val pos = holder.layoutPosition
 | 
			
		||||
                val isConnecting = checkConnecting()
 | 
			
		||||
                val hasConnected = checkConnected(pos)
 | 
			
		||||
                if (!isConnecting && !hasConnected) {
 | 
			
		||||
                    //上一笔连接还没结束或者基站已连接则不响应新的连接请求
 | 
			
		||||
                    mOnItemClickListener?.onItemClick(holder.itemView, pos)
 | 
			
		||||
                } else if (checkConnecting()) {
 | 
			
		||||
                    LogUtils.e(TAG, "The station is currently connecting, please try to connect later!")
 | 
			
		||||
                } else {
 | 
			
		||||
                    LogUtils.e(TAG, "The station has connected!")
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            when (info.connectStatus) {
 | 
			
		||||
                RTKStationConnetState.CONNECTING -> {
 | 
			
		||||
                    holder.mConnectStatusIv.visible()
 | 
			
		||||
                    holder.mConnectStatusIv.setImageResource(R.drawable.uxsdk_rotate_progress_circle)
 | 
			
		||||
                }
 | 
			
		||||
                RTKStationConnetState.CONNECTED -> {
 | 
			
		||||
                    holder.mConnectStatusIv.visible()
 | 
			
		||||
                    holder.mConnectStatusIv.setImageResource(R.drawable.uxsdk_ic_confirm)
 | 
			
		||||
                }
 | 
			
		||||
                else -> {
 | 
			
		||||
                    holder.mConnectStatusIv.gone()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getItemId(position: Int): Long {
 | 
			
		||||
        return position.toLong()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getItemCount(): Int {
 | 
			
		||||
        return if (baseStationInfoList == null) 0 else baseStationInfoList!!.size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    interface OnItemClickListener {
 | 
			
		||||
        fun onItemClick(view: View?, position: Int)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private var mOnItemClickListener: OnItemClickListener? = null
 | 
			
		||||
 | 
			
		||||
    fun setOnItemClickListener(onItemClickListener: OnItemClickListener?) {
 | 
			
		||||
        mOnItemClickListener = onItemClickListener
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @DrawableRes
 | 
			
		||||
    fun getSignalLevelDrawable(signalLevel: Int): Int {
 | 
			
		||||
        LogUtils.i(TAG, "getSignalLevelDrawable,signalLevel=$signalLevel")
 | 
			
		||||
        return when (signalLevel) {
 | 
			
		||||
            LEVEL_0 -> R.drawable.uxsdk_ic_topbar_signal_level_0
 | 
			
		||||
            LEVEL_1 -> R.drawable.uxsdk_ic_topbar_signal_level_1
 | 
			
		||||
            LEVEL_2 -> R.drawable.uxsdk_ic_topbar_signal_level_2
 | 
			
		||||
            LEVEL_3 -> R.drawable.uxsdk_ic_topbar_signal_level_3
 | 
			
		||||
            LEVEL_4 -> R.drawable.uxsdk_ic_topbar_signal_level_4
 | 
			
		||||
            else -> R.drawable.uxsdk_ic_topbar_signal_level_5
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    class RtkViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
 | 
			
		||||
        var mConnectStatusIv: ImageView
 | 
			
		||||
        var mRtkStationNameTv: TextView
 | 
			
		||||
        var mConnectSignalIv: ImageView
 | 
			
		||||
 | 
			
		||||
        init {
 | 
			
		||||
            mConnectStatusIv = itemView.findViewById(R.id.connect_status_iv)
 | 
			
		||||
            mRtkStationNameTv = itemView.findViewById(R.id.station_name_tv)
 | 
			
		||||
            mConnectSignalIv = itemView.findViewById(R.id.connect_signal_iv)
 | 
			
		||||
            mConnectStatusIv.visibility = View.GONE
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun View.visible() {
 | 
			
		||||
        this.visibility = View.VISIBLE
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun View.gone() {
 | 
			
		||||
        this.visibility = View.GONE
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun checkConnecting(): Boolean {
 | 
			
		||||
        baseStationInfoList?.let {
 | 
			
		||||
            for (station in it) {
 | 
			
		||||
                if (station.connectStatus == RTKStationConnetState.CONNECTING) {
 | 
			
		||||
                    return true
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return false
 | 
			
		||||
        }
 | 
			
		||||
        return false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun checkConnected(position: Int): Boolean {
 | 
			
		||||
        baseStationInfoList?.run {
 | 
			
		||||
            val stationInfo = get(position)
 | 
			
		||||
            if (stationInfo.connectStatus == RTKStationConnetState.CONNECTED) {
 | 
			
		||||
                return true
 | 
			
		||||
            }
 | 
			
		||||
            return false
 | 
			
		||||
        }
 | 
			
		||||
        return false
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,332 @@
 | 
			
		|||
package dji.v5.ux.accessory.item;
 | 
			
		||||
 | 
			
		||||
import android.animation.Animator;
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.util.AttributeSet;
 | 
			
		||||
import android.view.Gravity;
 | 
			
		||||
import android.view.LayoutInflater;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.view.ViewGroup;
 | 
			
		||||
import android.widget.Button;
 | 
			
		||||
import android.widget.ImageView;
 | 
			
		||||
import android.widget.PopupWindow;
 | 
			
		||||
import android.widget.TextView;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.ColorRes;
 | 
			
		||||
import androidx.annotation.IntDef;
 | 
			
		||||
import androidx.constraintlayout.widget.ConstraintLayout;
 | 
			
		||||
import androidx.constraintlayout.widget.Constraints;
 | 
			
		||||
 | 
			
		||||
import com.airbnb.lottie.LottieAnimationView;
 | 
			
		||||
 | 
			
		||||
import java.lang.annotation.Retention;
 | 
			
		||||
import java.lang.annotation.RetentionPolicy;
 | 
			
		||||
 | 
			
		||||
import dji.v5.utils.common.LogUtils;
 | 
			
		||||
import dji.v5.utils.common.StringUtils;
 | 
			
		||||
import dji.v5.ux.R;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
public class RtkGuidanceView extends ConstraintLayout implements View.OnClickListener {
 | 
			
		||||
 | 
			
		||||
    @IntDef({
 | 
			
		||||
            GuidanceMode.STEP_RESET,
 | 
			
		||||
            GuidanceMode.STEP_FIRST,
 | 
			
		||||
            GuidanceMode.STEP_SECOND,
 | 
			
		||||
            GuidanceMode.STEP_THIRD,
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    @Retention(RetentionPolicy.SOURCE)
 | 
			
		||||
    private @interface GuidanceMode {
 | 
			
		||||
        int STEP_RESET = 0;
 | 
			
		||||
        int STEP_FIRST = 1;
 | 
			
		||||
        int STEP_SECOND = 2;
 | 
			
		||||
        int STEP_THIRD = 3;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //顶部导航栏控件
 | 
			
		||||
    private View mNavigationLayout;
 | 
			
		||||
    private TextView mStepFirstTv;
 | 
			
		||||
    private TextView mStepSecondTv;
 | 
			
		||||
    private TextView mStepThirdTv;
 | 
			
		||||
    private ImageView mStepFirstIv;
 | 
			
		||||
    private ImageView mStepSecondIv;
 | 
			
		||||
    private View mStepStartDivider;
 | 
			
		||||
    private View mStepEndDivider;
 | 
			
		||||
    //左边动画控件
 | 
			
		||||
    private LottieAnimationView mLottieAnimationView;
 | 
			
		||||
    private View mReplayView;
 | 
			
		||||
    private Button mReplayBtn;
 | 
			
		||||
    private TextView mLottieTip;
 | 
			
		||||
    //左边文字控件
 | 
			
		||||
    private TextView mTitleTv;
 | 
			
		||||
    private TextView mContentTv;
 | 
			
		||||
    private View mImageView;
 | 
			
		||||
    private TextView mImageDescTv;
 | 
			
		||||
    private Button mPreviousBtn;
 | 
			
		||||
    private Button mNextBtn;
 | 
			
		||||
 | 
			
		||||
    private PopupWindow mPopupWindow;
 | 
			
		||||
 | 
			
		||||
    private int mStep; //0——重置、1——步骤1、2——步骤2、3——步骤3
 | 
			
		||||
    private String[] mTitleStr;
 | 
			
		||||
    private String[] mContentStr;
 | 
			
		||||
    private int[] mAnimationRes;
 | 
			
		||||
    private String[] mImageAssetsFolderStr;
 | 
			
		||||
    private boolean mStartAnimation;
 | 
			
		||||
    private Runnable mTipRunnable;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public RtkGuidanceView(Context context) {
 | 
			
		||||
        this(context, null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public RtkGuidanceView(Context context, AttributeSet attrs) {
 | 
			
		||||
        this(context, attrs, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public RtkGuidanceView(Context context, AttributeSet attrs, int defStyleAttr) {
 | 
			
		||||
        super(context, attrs, defStyleAttr);
 | 
			
		||||
        init(context);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void init(Context context) {
 | 
			
		||||
        LayoutInflater.from(context).inflate(R.layout.uxsdk_rtk_guidance_layout, this, true);
 | 
			
		||||
        initView();
 | 
			
		||||
        initWidgetParams();
 | 
			
		||||
        initParams(context);
 | 
			
		||||
        initListener();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initView() {
 | 
			
		||||
        //顶部导航栏控件
 | 
			
		||||
        mNavigationLayout = findViewById(R.id.rtk_guidance_step_navigation);
 | 
			
		||||
        mStepFirstTv = findViewById(R.id.rtk_guidance_step_first_number);
 | 
			
		||||
        mStepSecondTv = findViewById(R.id.rtk_guidance_step_second_number);
 | 
			
		||||
        mStepThirdTv = findViewById(R.id.rtk_guidance_step_third_number);
 | 
			
		||||
        mStepFirstIv = findViewById(R.id.rtk_guidance_step_first_image);
 | 
			
		||||
        mStepSecondIv = findViewById(R.id.rtk_guidance_step_second_image);
 | 
			
		||||
        mStepStartDivider = findViewById(R.id.rtk_guidance_start_divider);
 | 
			
		||||
        mStepEndDivider = findViewById(R.id.rtk_guidance_end_divider);
 | 
			
		||||
 | 
			
		||||
        //左边动画控件
 | 
			
		||||
        mLottieAnimationView = findViewById(R.id.rtk_guidance_lottie_animation);
 | 
			
		||||
        mReplayView = findViewById(R.id.rtk_guidance_replay_view);
 | 
			
		||||
        mReplayBtn = findViewById(R.id.rtk_guidance_replay_btn);
 | 
			
		||||
        mLottieTip = findViewById(R.id.rtk_guidance_lottie_tip);
 | 
			
		||||
 | 
			
		||||
        //左边文字控件
 | 
			
		||||
        mTitleTv = findViewById(R.id.rtk_guidance_step_title);
 | 
			
		||||
        mContentTv = findViewById(R.id.rtk_guidance_step_content);
 | 
			
		||||
        mImageView = findViewById(R.id.rtk_guidance_step_image);
 | 
			
		||||
        mImageDescTv = findViewById(R.id.rtk_guidance_step_image_desc);
 | 
			
		||||
        mPreviousBtn = findViewById(R.id.rtk_guidance_step_previous);
 | 
			
		||||
        mNextBtn = findViewById(R.id.rtk_guidance_step_next);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initListener() {
 | 
			
		||||
        findViewById(R.id.rtk_guidance_close).setOnClickListener(this);
 | 
			
		||||
        findViewById(R.id.rtk_guidance_step_previous).setOnClickListener(this);
 | 
			
		||||
        findViewById(R.id.rtk_guidance_step_next).setOnClickListener(this);
 | 
			
		||||
        findViewById(R.id.rtk_guidance_replay_btn).setOnClickListener(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initWidgetParams() {
 | 
			
		||||
        setLayoutParams(new Constraints.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
 | 
			
		||||
                ViewGroup.LayoutParams.MATCH_PARENT));
 | 
			
		||||
        setBackgroundResource(R.color.uxsdk_white);
 | 
			
		||||
        mLottieAnimationView.addAnimatorListener(new Animator.AnimatorListener() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onAnimationStart(Animator animation) {
 | 
			
		||||
                updateReplayView(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onAnimationEnd(Animator animation) {
 | 
			
		||||
                updateReplayView(true);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onAnimationCancel(Animator animation) {
 | 
			
		||||
                //动画取消,无需操作
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onAnimationRepeat(Animator animation) {
 | 
			
		||||
                updateReplayView(false);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initParams(Context context) {
 | 
			
		||||
        mStep = GuidanceMode.STEP_FIRST;
 | 
			
		||||
        mTitleStr = context.getResources().getStringArray(R.array.uxsdk_rtk_guidance_title);
 | 
			
		||||
        mContentStr = context.getResources().getStringArray(R.array.uxsdk_rtk_guidance_content);
 | 
			
		||||
        mAnimationRes = new int[]{
 | 
			
		||||
                R.raw.uxsdk_rtk_guidance_reset,
 | 
			
		||||
                R.raw.uxsdk_rtk_guidance_step1,
 | 
			
		||||
                R.raw.uxsdk_rtk_guidance_step2,
 | 
			
		||||
                R.raw.uxsdk_rtk_guidance_step3
 | 
			
		||||
        };
 | 
			
		||||
        mImageAssetsFolderStr = new String[]{
 | 
			
		||||
                "rtk_guidance_reset_images",
 | 
			
		||||
                "rtk_guidance_step1_images",
 | 
			
		||||
                "rtk_guidance_step2_images",
 | 
			
		||||
                "rtk_guidance_step3_images"
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void updateView() {
 | 
			
		||||
        //更新视图可见性
 | 
			
		||||
        updateVisibility();
 | 
			
		||||
        //更新顶部导航栏
 | 
			
		||||
        if (mStep != GuidanceMode.STEP_RESET) {
 | 
			
		||||
            mStepFirstTv.setVisibility(mStep <= GuidanceMode.STEP_FIRST ? VISIBLE : GONE);
 | 
			
		||||
            mStepSecondTv.setVisibility(mStep <= GuidanceMode.STEP_SECOND ? VISIBLE : GONE);
 | 
			
		||||
 | 
			
		||||
            updateSelectedTopView(mStepSecondTv, mStep < GuidanceMode.STEP_SECOND);
 | 
			
		||||
            updateSelectedTopView(mStepThirdTv, mStep < GuidanceMode.STEP_THIRD);
 | 
			
		||||
 | 
			
		||||
            mStepFirstIv.setVisibility(mStep > GuidanceMode.STEP_FIRST ? VISIBLE : GONE);
 | 
			
		||||
            mStepSecondIv.setVisibility(mStep > GuidanceMode.STEP_SECOND ? VISIBLE : GONE);
 | 
			
		||||
 | 
			
		||||
            mStepStartDivider.setBackgroundResource(mStep == GuidanceMode.STEP_FIRST ? R.color.uxsdk_gray : R.color.uxsdk_blue);
 | 
			
		||||
            mStepEndDivider.setBackgroundResource(mStep <= GuidanceMode.STEP_SECOND ? R.color.uxsdk_gray : R.color.uxsdk_blue);
 | 
			
		||||
        }
 | 
			
		||||
        //更新左边动画
 | 
			
		||||
        mLottieAnimationView.setImageAssetsFolder(mImageAssetsFolderStr[mStep]);
 | 
			
		||||
        mLottieAnimationView.setAnimation(mAnimationRes[mStep]);
 | 
			
		||||
        mLottieAnimationView.playAnimation();
 | 
			
		||||
        //在步骤2中更新右边动画中 tip 视图的显示
 | 
			
		||||
        updateLottieTipView();
 | 
			
		||||
        //更新右边文案
 | 
			
		||||
        mTitleTv.setText(mTitleStr[mStep]);
 | 
			
		||||
        mContentTv.setText(mContentStr[mStep]);
 | 
			
		||||
        mNextBtn.setText(StringUtils.getResStr(mStep == GuidanceMode.STEP_RESET
 | 
			
		||||
                || mStep == GuidanceMode.STEP_THIRD ? R.string.uxsdk_rtk_guidance_btn_finish : R.string.uxsdk_rtk_guidance_btn_next));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void updateVisibility() {
 | 
			
		||||
        mNavigationLayout.setVisibility(mStep == GuidanceMode.STEP_RESET ? INVISIBLE : VISIBLE);
 | 
			
		||||
        mImageView.setVisibility(mStep == GuidanceMode.STEP_SECOND ? VISIBLE : INVISIBLE);
 | 
			
		||||
        mImageDescTv.setVisibility(mStep == GuidanceMode.STEP_SECOND ? VISIBLE : INVISIBLE);
 | 
			
		||||
        mPreviousBtn.setVisibility(mStep == GuidanceMode.STEP_RESET
 | 
			
		||||
                || mStep == GuidanceMode.STEP_FIRST ? GONE : VISIBLE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新顶部步骤view
 | 
			
		||||
     *
 | 
			
		||||
     * @param textView   步骤view
 | 
			
		||||
     * @param isSelected 是否被选中
 | 
			
		||||
     */
 | 
			
		||||
    private void updateSelectedTopView(TextView textView, boolean isSelected) {
 | 
			
		||||
        textView.setBackgroundResource(isSelected ?
 | 
			
		||||
                R.drawable.uxsdk_bg_rtk_guidance_step_oval_gray :
 | 
			
		||||
                R.drawable.uxsdk_bg_rtk_guidance_step_oval_blue_solid);
 | 
			
		||||
        textView.setTextColor(isSelected ?
 | 
			
		||||
                getResColor(R.color.uxsdk_black) :
 | 
			
		||||
                getResColor(R.color.uxsdk_white));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新动画上方提示语
 | 
			
		||||
     */
 | 
			
		||||
    private void updateLottieTipView() {
 | 
			
		||||
        removeCallbacks(mTipRunnable);
 | 
			
		||||
        mLottieTip.setVisibility(GONE);
 | 
			
		||||
        if (mStep == GuidanceMode.STEP_RESET) {
 | 
			
		||||
            delayedUpdateResetTipView();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 延迟更新动画上方提示语——重置成功
 | 
			
		||||
     */
 | 
			
		||||
    private void delayedUpdateResetTipView() {
 | 
			
		||||
        mTipRunnable = () -> {
 | 
			
		||||
            mLottieTip.setText(R.string.uxsdk_rtk_guidance_reset_tip);
 | 
			
		||||
            mLottieTip.setVisibility(mStartAnimation ? VISIBLE : GONE);
 | 
			
		||||
            if (mStartAnimation) {
 | 
			
		||||
                mStartAnimation = false;
 | 
			
		||||
                postDelayed(mTipRunnable, 2500);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        mStartAnimation = true;
 | 
			
		||||
        //重置在14秒后显示重置成功
 | 
			
		||||
        postDelayed(mTipRunnable, 14000);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void updateReplayView(boolean isShow) {
 | 
			
		||||
        int visible = isShow ? VISIBLE : GONE;
 | 
			
		||||
        mReplayView.setVisibility(visible);
 | 
			
		||||
        mReplayBtn.setVisibility(visible);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onDetachedFromWindow() {
 | 
			
		||||
        removeCallbacks(mTipRunnable);
 | 
			
		||||
        mStep = GuidanceMode.STEP_FIRST;
 | 
			
		||||
        //停止动画
 | 
			
		||||
        if (mLottieAnimationView != null && mLottieAnimationView.isAnimating()) {
 | 
			
		||||
            mLottieAnimationView.cancelAnimation();
 | 
			
		||||
        }
 | 
			
		||||
        super.onDetachedFromWindow();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onClick(View v) {
 | 
			
		||||
        int id = v.getId();
 | 
			
		||||
        if (id == R.id.rtk_guidance_close) {
 | 
			
		||||
            closePopupWindow();
 | 
			
		||||
        } else if (id == R.id.rtk_guidance_step_previous) {
 | 
			
		||||
            mStep--;
 | 
			
		||||
            updateView();
 | 
			
		||||
        } else if (id == R.id.rtk_guidance_step_next) {
 | 
			
		||||
            if (mStep != GuidanceMode.STEP_RESET && mStep != GuidanceMode.STEP_THIRD) {
 | 
			
		||||
                mStep++;
 | 
			
		||||
                updateView();
 | 
			
		||||
            } else {
 | 
			
		||||
                closePopupWindow();
 | 
			
		||||
            }
 | 
			
		||||
        } else if (id == R.id.rtk_guidance_replay_btn) {
 | 
			
		||||
            mLottieAnimationView.playAnimation();
 | 
			
		||||
            updateLottieTipView();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private void closePopupWindow() {
 | 
			
		||||
        if (mPopupWindow != null) {
 | 
			
		||||
            mPopupWindow.dismiss();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void showPopupWindow(View parent) {
 | 
			
		||||
        showPopupWindow(parent, GuidanceMode.STEP_FIRST);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void showPopupWindow(View parent, int guidanceMode) {
 | 
			
		||||
        if (guidanceMode < GuidanceMode.STEP_RESET || guidanceMode > GuidanceMode.STEP_THIRD) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        mStep = guidanceMode;
 | 
			
		||||
        if (mPopupWindow == null) {
 | 
			
		||||
            mPopupWindow = new PopupWindow(this, ViewGroup.LayoutParams.MATCH_PARENT,
 | 
			
		||||
                    ViewGroup.LayoutParams.MATCH_PARENT, true);
 | 
			
		||||
        }
 | 
			
		||||
        updateView();
 | 
			
		||||
        mPopupWindow.setClippingEnabled(false);
 | 
			
		||||
        mPopupWindow.showAtLocation(parent, Gravity.NO_GRAVITY, 0, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public int getResColor(@ColorRes int resId) {
 | 
			
		||||
        if (getContext() != null) {
 | 
			
		||||
            return getContext().getResources().getColor(resId);
 | 
			
		||||
        }
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,68 @@
 | 
			
		|||
package dji.v5.ux.accessory.item
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.util.AttributeSet
 | 
			
		||||
import android.widget.FrameLayout
 | 
			
		||||
import android.widget.TextView
 | 
			
		||||
import dji.v5.ux.R
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Description :垂直显示标题和值的控件
 | 
			
		||||
 *
 | 
			
		||||
 * @author: Byte.Cai
 | 
			
		||||
 *  date : 2022/9/7
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2022, DJI All Rights Reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
class TitleValueCell @JvmOverloads constructor(
 | 
			
		||||
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0,
 | 
			
		||||
) : FrameLayout(context, attrs, defStyleAttr) {
 | 
			
		||||
 | 
			
		||||
    private var tvTitle: TextView
 | 
			
		||||
    private var tvValue: TextView
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        val ta = context.obtainStyledAttributes(attrs, R.styleable.FpvTitleValueCell, defStyleAttr, 0)
 | 
			
		||||
        val title = ta.getString(R.styleable.FpvTitleValueCell_uxsdk_title)
 | 
			
		||||
        val value = ta.getString(R.styleable.FpvTitleValueCell_uxsdk_value)
 | 
			
		||||
        val layoutId = ta.getResourceId(R.styleable.FpvTitleValueCell_uxsdk_layout, R.layout.uxsdk_fpv_top_bar_popover_title_value_cell)
 | 
			
		||||
        if (layoutId == 0) {
 | 
			
		||||
            throw IllegalArgumentException("layout can not be null.")
 | 
			
		||||
        }
 | 
			
		||||
        inflate(context, layoutId, this)
 | 
			
		||||
 | 
			
		||||
        ta.recycle()
 | 
			
		||||
 | 
			
		||||
        tvTitle = findViewById(R.id.tv_title)
 | 
			
		||||
        tvValue = findViewById(R.id.tv_value)
 | 
			
		||||
        tvTitle.text = title
 | 
			
		||||
        tvValue.text = value
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var title: CharSequence?
 | 
			
		||||
        get() = tvTitle.text
 | 
			
		||||
        set(value) {
 | 
			
		||||
            if (value != tvTitle.text) {
 | 
			
		||||
                tvTitle.text = value
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    var value: CharSequence?
 | 
			
		||||
        get() = tvValue.text
 | 
			
		||||
        set(value) {
 | 
			
		||||
            if (value != tvValue.text) {
 | 
			
		||||
                tvValue.text = value
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    fun setValueTextColor(color: Int) {
 | 
			
		||||
        tvValue.setTextColor(color)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun setTitleTextColor(color: Int) {
 | 
			
		||||
        tvTitle.setTextColor(color)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,182 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.ui;
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.graphics.Canvas;
 | 
			
		||||
import android.graphics.Color;
 | 
			
		||||
import android.graphics.Paint;
 | 
			
		||||
import android.graphics.RectF;
 | 
			
		||||
import android.graphics.Shader;
 | 
			
		||||
import android.graphics.SweepGradient;
 | 
			
		||||
import android.util.AttributeSet;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.view.animation.Animation;
 | 
			
		||||
import android.view.animation.AnimationUtils;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.ColorInt;
 | 
			
		||||
 | 
			
		||||
import dji.v5.ux.R;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Ring progress bar.
 | 
			
		||||
 * The view shows circular animation to indicate progress.
 | 
			
		||||
 */
 | 
			
		||||
public class ProgressRingView extends View {
 | 
			
		||||
 | 
			
		||||
    private RectF boundaries;
 | 
			
		||||
    private Paint paint;
 | 
			
		||||
    private Animation indeterminateAnimation;
 | 
			
		||||
    private Shader progressGradient;
 | 
			
		||||
    private boolean indeterminate;
 | 
			
		||||
    private int ringColor = Color.WHITE;
 | 
			
		||||
    private int height;
 | 
			
		||||
    private int width;
 | 
			
		||||
 | 
			
		||||
    public ProgressRingView(Context context) {
 | 
			
		||||
        super(context, null, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ProgressRingView(Context context, AttributeSet attrs) {
 | 
			
		||||
        this(context, attrs, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ProgressRingView(Context context, AttributeSet attrs, int defStyleAttr) {
 | 
			
		||||
        super(context, attrs, defStyleAttr);
 | 
			
		||||
        initView(context);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initView(Context context) {
 | 
			
		||||
        boundaries = new RectF();
 | 
			
		||||
 | 
			
		||||
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
 | 
			
		||||
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
 | 
			
		||||
        paint.setStyle(Paint.Style.STROKE);
 | 
			
		||||
        paint.setStrokeCap(Paint.Cap.SQUARE);
 | 
			
		||||
        paint.setColor(ringColor);
 | 
			
		||||
 | 
			
		||||
        if (!isInEditMode()) {
 | 
			
		||||
            indeterminateAnimation = AnimationUtils.loadAnimation(context, R.anim.uxsdk_anim_rotate);
 | 
			
		||||
            indeterminateAnimation.setAnimationListener(new Animation.AnimationListener() {
 | 
			
		||||
 | 
			
		||||
                @Override
 | 
			
		||||
                public void onAnimationStart(Animation animation) {
 | 
			
		||||
                    indeterminate = true;
 | 
			
		||||
                    if (progressGradient != null) {
 | 
			
		||||
                        paint.setShader(progressGradient);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                @Override
 | 
			
		||||
                public void onAnimationRepeat(Animation animation) {
 | 
			
		||||
                    // Nothing to do here
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                @Override
 | 
			
		||||
                public void onAnimationEnd(Animation animation) {
 | 
			
		||||
                    paint.setShader(null);
 | 
			
		||||
                    invalidate();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
 | 
			
		||||
        super.onSizeChanged(w, h, oldw, oldh);
 | 
			
		||||
 | 
			
		||||
        // StrokeWidth percentage
 | 
			
		||||
        // Color
 | 
			
		||||
 | 
			
		||||
        float strokeWidth = w * .08f;
 | 
			
		||||
        paint.setStrokeWidth(strokeWidth / 2);
 | 
			
		||||
        width = w;
 | 
			
		||||
        height = h;
 | 
			
		||||
        initProgressGradient(w, h);
 | 
			
		||||
        float padding = strokeWidth / 2;
 | 
			
		||||
        boundaries.top = padding;
 | 
			
		||||
        boundaries.left = padding;
 | 
			
		||||
        boundaries.bottom = getMeasuredWidth() - padding;
 | 
			
		||||
        boundaries.right = getMeasuredHeight() - padding;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onDraw(Canvas canvas) {
 | 
			
		||||
        super.onDraw(canvas);
 | 
			
		||||
        canvas.drawArc(boundaries, 0, 360, false, paint);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the color of the progress ring.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color integer value
 | 
			
		||||
     */
 | 
			
		||||
    public void setRingColor(@ColorInt int color) {
 | 
			
		||||
        ringColor = color;
 | 
			
		||||
        paint.setColor(color);
 | 
			
		||||
        initProgressGradient(width, height);
 | 
			
		||||
        invalidate();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if the ring is currently animating.
 | 
			
		||||
     *
 | 
			
		||||
     * @return boolean value
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isIndeterminate() {
 | 
			
		||||
        return indeterminate;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Start/Stop the ring animation.
 | 
			
		||||
     *
 | 
			
		||||
     * @param indeterminate boolean value
 | 
			
		||||
     *                      true - to start animation
 | 
			
		||||
     *                      false - to stop animation
 | 
			
		||||
     */
 | 
			
		||||
    public void setIndeterminate(boolean indeterminate) {
 | 
			
		||||
        if (indeterminate == this.indeterminate) return;
 | 
			
		||||
 | 
			
		||||
        this.indeterminate = indeterminate;
 | 
			
		||||
        if (indeterminate) {
 | 
			
		||||
            startAnimation(indeterminateAnimation);
 | 
			
		||||
        } else {
 | 
			
		||||
            clearAnimation();
 | 
			
		||||
            indeterminateAnimation.cancel();
 | 
			
		||||
            indeterminateAnimation.reset();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initProgressGradient(int w, int h) {
 | 
			
		||||
        progressGradient = null;
 | 
			
		||||
        float cx = w / 2f;
 | 
			
		||||
        float cy = h / 2f;
 | 
			
		||||
        int[] colors = {ringColor, Color.TRANSPARENT, Color.TRANSPARENT};
 | 
			
		||||
        float[] positions = {0, 0.7f, 1};
 | 
			
		||||
 | 
			
		||||
        progressGradient = new SweepGradient(cx, cy, colors, positions);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,201 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.util;
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import dji.v5.ux.R;
 | 
			
		||||
import dji.v5.ux.core.util.AudioUtil;
 | 
			
		||||
import io.reactivex.rxjava3.disposables.Disposable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class used as a util for playing sounds of
 | 
			
		||||
 * capture photo and record video
 | 
			
		||||
 */
 | 
			
		||||
public class CameraActionSound {
 | 
			
		||||
    private final Context context;
 | 
			
		||||
    private ShutterSoundCount shutterCount;
 | 
			
		||||
 | 
			
		||||
    public CameraActionSound(Context con) {
 | 
			
		||||
        context = con;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private int shutterCountSound(ShutterSoundCount value) {
 | 
			
		||||
        int soundId;
 | 
			
		||||
        switch (value) {
 | 
			
		||||
            case ONE:
 | 
			
		||||
                soundId = R.raw.uxsdk_shutter_1;
 | 
			
		||||
                break;
 | 
			
		||||
            case THREE:
 | 
			
		||||
                soundId = R.raw.uxsdk_shutter_3;
 | 
			
		||||
                break;
 | 
			
		||||
            case FIVE:
 | 
			
		||||
                soundId = R.raw.uxsdk_shutter_5;
 | 
			
		||||
                break;
 | 
			
		||||
            case SEVEN:
 | 
			
		||||
                soundId = R.raw.uxsdk_shutter_7;
 | 
			
		||||
                break;
 | 
			
		||||
            case TEN:
 | 
			
		||||
                soundId = R.raw.uxsdk_shutter_10;
 | 
			
		||||
                break;
 | 
			
		||||
            case FOURTEEN:
 | 
			
		||||
                soundId = R.raw.uxsdk_shutter_14;
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case UNKNOWN:
 | 
			
		||||
            default:
 | 
			
		||||
                soundId = R.raw.uxsdk_shutter_3;
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
        return soundId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the count of photos based on current mode
 | 
			
		||||
     *
 | 
			
		||||
     * @param count int value
 | 
			
		||||
     */
 | 
			
		||||
    public void setShutterCount(@NonNull ShutterSoundCount count) {
 | 
			
		||||
        shutterCount = count;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Play sound for Capture Photo
 | 
			
		||||
     *
 | 
			
		||||
     * @return Disposable
 | 
			
		||||
     */
 | 
			
		||||
    @NonNull
 | 
			
		||||
    public Disposable playCapturePhoto() {
 | 
			
		||||
        return AudioUtil.playSoundInBackground(context, shutterCountSound(shutterCount));
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Play sound for start record video
 | 
			
		||||
     *
 | 
			
		||||
     * @return Disposable
 | 
			
		||||
     */
 | 
			
		||||
    @NonNull
 | 
			
		||||
    public Disposable playStartRecordVideo() {
 | 
			
		||||
        return AudioUtil.playSoundInBackground(context, R.raw.uxsdk_video_voice);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Play sound for stop record video
 | 
			
		||||
     *
 | 
			
		||||
     * @return Disposable
 | 
			
		||||
     */
 | 
			
		||||
    @NonNull
 | 
			
		||||
    public Disposable playStopRecordVideo() {
 | 
			
		||||
        return AudioUtil.playSoundInBackground(context, R.raw.uxsdk_end_video_record);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Enum for shutter sound count
 | 
			
		||||
     */
 | 
			
		||||
    public enum ShutterSoundCount {
 | 
			
		||||
        /**
 | 
			
		||||
         * Single photo shutter sound
 | 
			
		||||
         */
 | 
			
		||||
        ONE(1),
 | 
			
		||||
        /**
 | 
			
		||||
         * Three photos shutter sound
 | 
			
		||||
         */
 | 
			
		||||
        THREE(3),
 | 
			
		||||
        /**
 | 
			
		||||
         * Five photos shutter sound
 | 
			
		||||
         */
 | 
			
		||||
        FIVE(5),
 | 
			
		||||
        /**
 | 
			
		||||
         * Seven photos shutter sound
 | 
			
		||||
         */
 | 
			
		||||
        SEVEN(7),
 | 
			
		||||
        /**
 | 
			
		||||
         * Ten photos shutter sound
 | 
			
		||||
         */
 | 
			
		||||
        TEN(10),
 | 
			
		||||
        /**
 | 
			
		||||
         * Fourteen photos shutter sound
 | 
			
		||||
         */
 | 
			
		||||
        FOURTEEN(14),
 | 
			
		||||
        /**
 | 
			
		||||
         * Will default to one shutter sound
 | 
			
		||||
         */
 | 
			
		||||
        UNKNOWN(1);
 | 
			
		||||
 | 
			
		||||
        private int value;
 | 
			
		||||
 | 
			
		||||
        ShutterSoundCount(int value) {
 | 
			
		||||
            this.value = value;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Returns the enum constant of this type with the input integer value.
 | 
			
		||||
         *
 | 
			
		||||
         * @param value The input integer value
 | 
			
		||||
         * @return The enum constant of this type
 | 
			
		||||
         */
 | 
			
		||||
        public static ShutterSoundCount find(int value) {
 | 
			
		||||
            ShutterSoundCount result = UNKNOWN;
 | 
			
		||||
            for (int i = 0; i < getValues().length; i++) {
 | 
			
		||||
                if (getValues()[i]._equals(value)) {
 | 
			
		||||
                    result = getValues()[i];
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Returns the real value of an enum value.
 | 
			
		||||
         *
 | 
			
		||||
         * @return integer The real value.
 | 
			
		||||
         */
 | 
			
		||||
        public int value() {
 | 
			
		||||
            return this.value;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Compares the input integer value with the real value of an enum value.
 | 
			
		||||
         *
 | 
			
		||||
         * @param b The input integer value.
 | 
			
		||||
         * @return boolean The compared result.
 | 
			
		||||
         */
 | 
			
		||||
        private boolean _equals(int b) {
 | 
			
		||||
            return value == b;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static ShutterSoundCount[] values;
 | 
			
		||||
 | 
			
		||||
        public static ShutterSoundCount[] getValues() {
 | 
			
		||||
            if (values == null) {
 | 
			
		||||
                values = values();
 | 
			
		||||
            }
 | 
			
		||||
            return values;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,139 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.util;
 | 
			
		||||
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraShootPhotoMode;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.PhotoPanoramaMode;
 | 
			
		||||
import dji.v5.ux.R;
 | 
			
		||||
 | 
			
		||||
public class CameraResource {
 | 
			
		||||
 | 
			
		||||
    public static int getPhotoModeImgResId(final int mode, final int value) {
 | 
			
		||||
        if (CameraShootPhotoMode.HDR.value() == mode) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_hdr;
 | 
			
		||||
        } else if (CameraShootPhotoMode.BURST.value() == mode) {
 | 
			
		||||
            return getImgResIdFromBurstMode(value);
 | 
			
		||||
        } else if (CameraShootPhotoMode.RAW_BURST.value() == mode) {
 | 
			
		||||
            return getImgResIdFromRawBurstMode(value);
 | 
			
		||||
        } else if (CameraShootPhotoMode.AEB.value() == mode) {
 | 
			
		||||
            return getImgResIdFromAebMode(value);
 | 
			
		||||
        } else if (CameraShootPhotoMode.INTERVAL.value() == mode) {
 | 
			
		||||
            return getImgResIdFromIntervalMode(value);
 | 
			
		||||
        } else if (CameraShootPhotoMode.VISION_BOKEH.value() == mode) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_shallow_focus;
 | 
			
		||||
        } else if (CameraShootPhotoMode.VISION_PANO.value() == mode) {
 | 
			
		||||
            return getImgResIdFromVisionPanoMode(value);
 | 
			
		||||
        } else if (CameraShootPhotoMode.EHDR.value() == mode) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_nor;
 | 
			
		||||
        } else if (CameraShootPhotoMode.HYPER_LIGHT.value() == mode) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_nor;
 | 
			
		||||
        } else {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_nor;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static int getImgResIdFromBurstMode(int value) {
 | 
			
		||||
        if (value == 14) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_continuous_14;
 | 
			
		||||
        } else if (value == 10) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_continuous_10;
 | 
			
		||||
        } else if (value == 7) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_continuous_7;
 | 
			
		||||
        } else if (value == 5) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_continuous_5;
 | 
			
		||||
        } else {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_continuous_3;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static int getImgResIdFromRawBurstMode(int value) {
 | 
			
		||||
        if (value == 255) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_raw_burst_infinity;
 | 
			
		||||
        } else if (value == 14) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_raw_burst_14;
 | 
			
		||||
        } else if (value == 10) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_raw_burst_10;
 | 
			
		||||
        } else if (value == 7) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_raw_burst_7;
 | 
			
		||||
        } else if (value == 5) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_raw_burst_5;
 | 
			
		||||
        } else {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_raw_burst_3;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static int getImgResIdFromAebMode(int value) {
 | 
			
		||||
        if (value == 7) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_aeb_continuous_7;
 | 
			
		||||
        } else if (value == 5) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_aeb_continuous_5;
 | 
			
		||||
        } else {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_aeb_continuous_3;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static int getImgResIdFromIntervalMode(int value) {
 | 
			
		||||
        if (value == 60) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_timepause_60s;
 | 
			
		||||
        } else if (value == 30) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_timepause_30s;
 | 
			
		||||
        } else if (value == 20) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_timepause_20s;
 | 
			
		||||
        } else if (value == 15) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_timepause_15s;
 | 
			
		||||
        } else if (value == 10) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_timepause_10s;
 | 
			
		||||
        } else if (value == 7) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_timepause_7s;
 | 
			
		||||
        } else if (value == 4) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_timepause_4s;
 | 
			
		||||
        } else if (value == 3) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_timepause_3s;
 | 
			
		||||
        } else if (value == 2) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_timepause_2s;
 | 
			
		||||
        } else {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_timepause_5s;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static int getImgResIdFromVisionPanoMode(int value) {
 | 
			
		||||
        if (PhotoPanoramaMode.MODE_3x1.value() == value) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_pano_3x1;
 | 
			
		||||
        } else if (PhotoPanoramaMode.MODE_1x3.value() == value) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_pano_180;
 | 
			
		||||
        } else if (PhotoPanoramaMode.MODE_3x3.value() == value) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_pano_3x3;
 | 
			
		||||
        } else if (PhotoPanoramaMode.MODE_SUPER_RESOLUTION.value() == value) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_nor;
 | 
			
		||||
        } else if (PhotoPanoramaMode.MODE_SPHERE.value() == value) {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_pano_sphere;
 | 
			
		||||
        } else {
 | 
			
		||||
            return R.drawable.uxsdk_ic_photo_mode_pano_180;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private CameraResource() {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,467 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.widget.autoexposurelock;
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.content.res.ColorStateList;
 | 
			
		||||
import android.content.res.TypedArray;
 | 
			
		||||
import android.graphics.drawable.Drawable;
 | 
			
		||||
import android.util.AttributeSet;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.widget.ImageView;
 | 
			
		||||
import android.widget.TextView;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.ColorInt;
 | 
			
		||||
import androidx.annotation.DrawableRes;
 | 
			
		||||
import androidx.annotation.MainThread;
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.annotation.Nullable;
 | 
			
		||||
import androidx.annotation.StyleRes;
 | 
			
		||||
import dji.sdk.keyvalue.value.common.CameraLensType;
 | 
			
		||||
import dji.sdk.keyvalue.value.common.ComponentIndexType;
 | 
			
		||||
import dji.v5.ux.R;
 | 
			
		||||
import dji.v5.ux.core.base.DJISDKModel;
 | 
			
		||||
import dji.v5.ux.core.base.ICameraIndex;
 | 
			
		||||
import dji.v5.ux.core.base.SchedulerProvider;
 | 
			
		||||
import dji.v5.ux.core.base.widget.ConstraintLayoutWidget;
 | 
			
		||||
import dji.v5.ux.core.communication.ObservableInMemoryKeyedStore;
 | 
			
		||||
import dji.v5.ux.core.util.UxErrorHandle;
 | 
			
		||||
import dji.v5.ux.core.util.ViewUtil;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Auto Exposure Lock Widget will display the current state of exposure lock.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * When locked the exposure of the camera will remain constant.
 | 
			
		||||
 * Changing the exposure parameters manually will release the lock.
 | 
			
		||||
 */
 | 
			
		||||
public class AutoExposureLockWidget extends ConstraintLayoutWidget<Object> implements View.OnClickListener, ICameraIndex {
 | 
			
		||||
 | 
			
		||||
    //region Fields
 | 
			
		||||
    private static final String TAG = "AutoExposureLockWidget";
 | 
			
		||||
    private ImageView foregroundImageView;
 | 
			
		||||
    private TextView titleTextView;
 | 
			
		||||
    private AutoExposureLockWidgetModel widgetModel;
 | 
			
		||||
    private Drawable autoExposureLockDrawable;
 | 
			
		||||
    private Drawable autoExposureUnlockDrawable;
 | 
			
		||||
    private ColorStateList lockDrawableTint;
 | 
			
		||||
    private ColorStateList unlockDrawableTint;
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Lifecycle
 | 
			
		||||
    public AutoExposureLockWidget(@NonNull Context context) {
 | 
			
		||||
        super(context);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public AutoExposureLockWidget(@NonNull Context context, @Nullable AttributeSet attrs) {
 | 
			
		||||
        super(context, attrs);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public AutoExposureLockWidget(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
 | 
			
		||||
        super(context, attrs, defStyleAttr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void initView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
 | 
			
		||||
        inflate(context, R.layout.uxsdk_widget_auto_exposure_lock, this);
 | 
			
		||||
        if (getBackground() == null) {
 | 
			
		||||
            setBackgroundResource(R.drawable.uxsdk_background_black_rectangle);
 | 
			
		||||
        }
 | 
			
		||||
        foregroundImageView = findViewById(R.id.auto_exposure_lock_widget_foreground_image_view);
 | 
			
		||||
        titleTextView = findViewById(R.id.auto_exposure_lock_widget_title_text_view);
 | 
			
		||||
        if (!isInEditMode()) {
 | 
			
		||||
            widgetModel =
 | 
			
		||||
                    new AutoExposureLockWidgetModel(DJISDKModel.getInstance(),
 | 
			
		||||
                            ObservableInMemoryKeyedStore.getInstance());
 | 
			
		||||
        }
 | 
			
		||||
        initDefaults();
 | 
			
		||||
        if (attrs != null) {
 | 
			
		||||
            initAttributes(context, attrs);
 | 
			
		||||
        }
 | 
			
		||||
        setOnClickListener(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void reactToModelChanges() {
 | 
			
		||||
        addReaction(widgetModel.isAutoExposureLockOn().observeOn(SchedulerProvider.ui()).subscribe(this::onAELockChange));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onClick(View v) {
 | 
			
		||||
        if (v == this) {
 | 
			
		||||
            setAutoExposureLock();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onAttachedToWindow() {
 | 
			
		||||
        super.onAttachedToWindow();
 | 
			
		||||
        if (!isInEditMode()) {
 | 
			
		||||
            widgetModel.setup();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onDetachedFromWindow() {
 | 
			
		||||
        if (!isInEditMode()) {
 | 
			
		||||
            widgetModel.cleanup();
 | 
			
		||||
        }
 | 
			
		||||
        super.onDetachedFromWindow();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region private methods
 | 
			
		||||
    @MainThread
 | 
			
		||||
    private void onAELockChange(boolean isLocked) {
 | 
			
		||||
        if (isLocked) {
 | 
			
		||||
            foregroundImageView.setImageDrawable(autoExposureLockDrawable);
 | 
			
		||||
            if (lockDrawableTint != null) ViewUtil.tintImage(foregroundImageView, lockDrawableTint);
 | 
			
		||||
        } else {
 | 
			
		||||
            foregroundImageView.setImageDrawable(autoExposureUnlockDrawable);
 | 
			
		||||
            if (unlockDrawableTint != null)
 | 
			
		||||
                ViewUtil.tintImage(foregroundImageView, unlockDrawableTint);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void setAutoExposureLock() {
 | 
			
		||||
        addDisposable(widgetModel.toggleAutoExposureLock()
 | 
			
		||||
                .observeOn(SchedulerProvider.ui())
 | 
			
		||||
                .subscribe(() -> {
 | 
			
		||||
                    // Do nothing
 | 
			
		||||
                }, UxErrorHandle.logErrorConsumer(TAG, "set auto exposure lock: ")));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void checkAndUpdateAELock() {
 | 
			
		||||
        if (!isInEditMode()) {
 | 
			
		||||
            addDisposable(widgetModel.isAutoExposureLockOn().firstOrError()
 | 
			
		||||
                    .observeOn(SchedulerProvider.ui())
 | 
			
		||||
                    .subscribe(this::onAELockChange, UxErrorHandle.logErrorConsumer(TAG, "Update AE Lock ")));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initDefaults() {
 | 
			
		||||
        autoExposureLockDrawable = getResources().getDrawable(R.drawable.uxsdk_ic_auto_exposure_lock);
 | 
			
		||||
        autoExposureUnlockDrawable = getResources().getDrawable(R.drawable.uxsdk_ic_auto_exposure_unlock);
 | 
			
		||||
        setTitleTextColor(getResources().getColorStateList(R.color.uxsdk_color_selector_auto_exposure_lock));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initAttributes(@NonNull Context context, @NonNull AttributeSet attrs) {
 | 
			
		||||
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.AutoExposureLockWidget);
 | 
			
		||||
        if (!isInEditMode()) {
 | 
			
		||||
            updateCameraSource(ComponentIndexType.find(typedArray.getInt(R.styleable.AutoExposureLockWidget_uxsdk_cameraIndex, 0)), CameraLensType.find(typedArray.getInt(R.styleable.AutoExposureLockWidget_uxsdk_lensType, 0)));
 | 
			
		||||
        }
 | 
			
		||||
        ColorStateList colorStateList = typedArray.getColorStateList(R.styleable.AutoExposureLockWidget_uxsdk_widgetTitleTextColor);
 | 
			
		||||
        if (colorStateList != null) {
 | 
			
		||||
            setTitleTextColor(colorStateList);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int colorResource = typedArray.getColor(R.styleable.AutoExposureLockWidget_uxsdk_widgetTitleTextColor, INVALID_COLOR);
 | 
			
		||||
        if (colorResource != INVALID_COLOR) {
 | 
			
		||||
            setTitleTextColor(colorResource);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        colorStateList = typedArray.getColorStateList(R.styleable.AutoExposureLockWidget_uxsdk_autoExposureUnlockDrawableTint);
 | 
			
		||||
        if (colorStateList != null) {
 | 
			
		||||
            setAutoExposureUnlockIconTint(colorStateList);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        colorResource = typedArray.getColor(R.styleable.AutoExposureLockWidget_uxsdk_autoExposureUnlockDrawableTint, INVALID_COLOR);
 | 
			
		||||
        if (colorResource != INVALID_COLOR) {
 | 
			
		||||
            setAutoExposureUnlockIconTint(colorResource);
 | 
			
		||||
        }
 | 
			
		||||
        colorStateList = typedArray.getColorStateList(R.styleable.AutoExposureLockWidget_uxsdk_autoExposureLockDrawableTint);
 | 
			
		||||
        if (colorStateList != null) {
 | 
			
		||||
            setAutoExposureLockIconTint(colorStateList);
 | 
			
		||||
        }
 | 
			
		||||
        colorResource = typedArray.getColor(R.styleable.AutoExposureLockWidget_uxsdk_autoExposureLockDrawableTint, INVALID_COLOR);
 | 
			
		||||
        if (colorResource != INVALID_COLOR) {
 | 
			
		||||
            setAutoExposureLockIconTint(colorResource);
 | 
			
		||||
        }
 | 
			
		||||
        int textAppearance = typedArray.getResourceId(R.styleable.AutoExposureLockWidget_uxsdk_widgetTitleTextAppearance, INVALID_RESOURCE);
 | 
			
		||||
        if (textAppearance != INVALID_RESOURCE) {
 | 
			
		||||
            setTitleTextAppearance(textAppearance);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.AutoExposureLockWidget_uxsdk_autoExposureLockDrawable) != null) {
 | 
			
		||||
            autoExposureLockDrawable = typedArray.getDrawable(R.styleable.AutoExposureLockWidget_uxsdk_autoExposureLockDrawable);
 | 
			
		||||
        }
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.AutoExposureLockWidget_uxsdk_autoExposureUnlockDrawable) != null) {
 | 
			
		||||
            autoExposureUnlockDrawable = typedArray.getDrawable(R.styleable.AutoExposureLockWidget_uxsdk_autoExposureUnlockDrawable);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        typedArray.recycle();
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region customization
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getIdealDimensionRatioString() {
 | 
			
		||||
        return getResources().getString(R.string.uxsdk_widget_auto_exposure_lock_ratio);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    public ComponentIndexType getCameraIndex() {
 | 
			
		||||
        return widgetModel.getCameraIndex();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public CameraLensType getLensType() {
 | 
			
		||||
        return widgetModel.getLensType();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void updateCameraSource(@NonNull ComponentIndexType cameraIndex, @NonNull CameraLensType lensType) {
 | 
			
		||||
        widgetModel.updateCameraSource(cameraIndex, lensType);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set drawable for auto exposure lock in locked state
 | 
			
		||||
     *
 | 
			
		||||
     * @param resourceId to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setAutoExposureLockIcon(@DrawableRes int resourceId) {
 | 
			
		||||
        setAutoExposureLockIcon(getResources().getDrawable(resourceId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set drawable for auto exposure lock in locked state
 | 
			
		||||
     *
 | 
			
		||||
     * @param drawable to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setAutoExposureLockIcon(@Nullable Drawable drawable) {
 | 
			
		||||
        this.autoExposureLockDrawable = drawable;
 | 
			
		||||
        checkAndUpdateAELock();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get current drawable resource for auto exposure lock in locked state
 | 
			
		||||
     *
 | 
			
		||||
     * @return Drawable
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public Drawable getAutoExposureLockDrawable() {
 | 
			
		||||
        return autoExposureLockDrawable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set resource for auto exposure lock in unlocked state
 | 
			
		||||
     *
 | 
			
		||||
     * @param resourceId to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setAutoExposureUnlockIcon(@DrawableRes int resourceId) {
 | 
			
		||||
        setAutoExposureUnlockIcon(getResources().getDrawable(resourceId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set drawable for auto exposure lock in unlocked state
 | 
			
		||||
     *
 | 
			
		||||
     * @param drawable to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setAutoExposureUnlockIcon(@Nullable Drawable drawable) {
 | 
			
		||||
        this.autoExposureUnlockDrawable = drawable;
 | 
			
		||||
        checkAndUpdateAELock();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get current drawable resource for auto exposure lock in unlocked state
 | 
			
		||||
     *
 | 
			
		||||
     * @return Drawable
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public Drawable getAutoExposureUnlockDrawable() {
 | 
			
		||||
        return autoExposureUnlockDrawable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get current text color state list of widget title
 | 
			
		||||
     *
 | 
			
		||||
     * @return ColorStateList used
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public ColorStateList getTitleTextColors() {
 | 
			
		||||
        return titleTextView.getTextColors();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the current color of title text
 | 
			
		||||
     *
 | 
			
		||||
     * @return integer value representing color
 | 
			
		||||
     */
 | 
			
		||||
    @ColorInt
 | 
			
		||||
    public int getTitleTextColor() {
 | 
			
		||||
        return titleTextView.getCurrentTextColor();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set text color state list to the widget title
 | 
			
		||||
     *
 | 
			
		||||
     * @param colorStateList to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setTitleTextColor(@Nullable ColorStateList colorStateList) {
 | 
			
		||||
        titleTextView.setTextColor(colorStateList);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the color of title text
 | 
			
		||||
     *
 | 
			
		||||
     * @param color integer value
 | 
			
		||||
     */
 | 
			
		||||
    public void setTitleTextColor(@ColorInt int color) {
 | 
			
		||||
        titleTextView.setTextColor(color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set text appearance of the widget title
 | 
			
		||||
     *
 | 
			
		||||
     * @param textAppearance to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setTitleTextAppearance(@StyleRes int textAppearance) {
 | 
			
		||||
        titleTextView.setTextAppearance(getContext(), textAppearance);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get current background of icon
 | 
			
		||||
     *
 | 
			
		||||
     * @return Drawable
 | 
			
		||||
     */
 | 
			
		||||
    @NonNull
 | 
			
		||||
    public Drawable getIconBackground() {
 | 
			
		||||
        return foregroundImageView.getBackground();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set background to icon
 | 
			
		||||
     *
 | 
			
		||||
     * @param resourceId to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setIconBackground(@DrawableRes int resourceId) {
 | 
			
		||||
        setIconBackground(getResources().getDrawable(resourceId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set background to icon
 | 
			
		||||
     *
 | 
			
		||||
     * @param drawable to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setIconBackground(@Nullable Drawable drawable) {
 | 
			
		||||
        foregroundImageView.setBackground(drawable);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get current background of title text
 | 
			
		||||
     *
 | 
			
		||||
     * @return Drawable
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public Drawable getTitleBackground() {
 | 
			
		||||
        return titleTextView.getBackground();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set background to title text
 | 
			
		||||
     *
 | 
			
		||||
     * @param resourceId to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setTitleBackground(@DrawableRes int resourceId) {
 | 
			
		||||
        setTitleBackground(getResources().getDrawable(resourceId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set background to title text
 | 
			
		||||
     *
 | 
			
		||||
     * @param drawable to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setTitleBackground(@Nullable Drawable drawable) {
 | 
			
		||||
        titleTextView.setBackground(drawable);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setEnabled(boolean enabled) {
 | 
			
		||||
        titleTextView.setEnabled(enabled);
 | 
			
		||||
        foregroundImageView.setEnabled(enabled);
 | 
			
		||||
        super.setEnabled(enabled);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the color tint for exposure settings unlocked icon
 | 
			
		||||
     *
 | 
			
		||||
     * @return ColorStateList used as color tint
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public ColorStateList getAutoExposureUnlockIconTint() {
 | 
			
		||||
        return unlockDrawableTint;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the color of tint for exposure settings unlocked icon
 | 
			
		||||
     *
 | 
			
		||||
     * @param color int value
 | 
			
		||||
     */
 | 
			
		||||
    public void setAutoExposureUnlockIconTint(@ColorInt int color) {
 | 
			
		||||
        setAutoExposureUnlockIconTint(ColorStateList.valueOf(color));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the color of tint for the exposure settings unlocked icon
 | 
			
		||||
     *
 | 
			
		||||
     * @param colorStateList to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setAutoExposureUnlockIconTint(@Nullable ColorStateList colorStateList) {
 | 
			
		||||
        unlockDrawableTint = colorStateList;
 | 
			
		||||
        checkAndUpdateAELock();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the color tint for exposure settings locked icon
 | 
			
		||||
     *
 | 
			
		||||
     * @return ColorStateList used as color tint
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public ColorStateList getAutoExposureLockIconTint() {
 | 
			
		||||
        return lockDrawableTint;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the color of tint for exposure settings locked icon
 | 
			
		||||
     *
 | 
			
		||||
     * @param color int value
 | 
			
		||||
     */
 | 
			
		||||
    public void setAutoExposureLockIconTint(@ColorInt int color) {
 | 
			
		||||
        setAutoExposureLockIconTint(ColorStateList.valueOf(color));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the color of tint for the exposure settings locked icon
 | 
			
		||||
     *
 | 
			
		||||
     * @param colorStateList to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setAutoExposureLockIconTint(@Nullable ColorStateList colorStateList) {
 | 
			
		||||
        lockDrawableTint = colorStateList;
 | 
			
		||||
        checkAndUpdateAELock();
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,119 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.widget.autoexposurelock;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import dji.sdk.keyvalue.key.CameraKey;
 | 
			
		||||
import dji.sdk.keyvalue.key.KeyTools;
 | 
			
		||||
import dji.sdk.keyvalue.value.common.CameraLensType;
 | 
			
		||||
import dji.sdk.keyvalue.value.common.ComponentIndexType;
 | 
			
		||||
import dji.v5.ux.core.base.DJISDKModel;
 | 
			
		||||
import dji.v5.ux.core.base.ICameraIndex;
 | 
			
		||||
import dji.v5.ux.core.base.WidgetModel;
 | 
			
		||||
import dji.v5.ux.core.communication.ObservableInMemoryKeyedStore;
 | 
			
		||||
import dji.v5.ux.core.util.DataProcessor;
 | 
			
		||||
import io.reactivex.rxjava3.core.Completable;
 | 
			
		||||
import io.reactivex.rxjava3.core.Flowable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Auto Exposure Lock Widget Model
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Widget Model for the {@link AutoExposureLockWidget} used to define the
 | 
			
		||||
 * underlying logic and communication
 | 
			
		||||
 */
 | 
			
		||||
public class AutoExposureLockWidgetModel extends WidgetModel implements ICameraIndex {
 | 
			
		||||
    //region Fields
 | 
			
		||||
    private final DataProcessor<Boolean> autoExposureLockBooleanProcessor;
 | 
			
		||||
    private ComponentIndexType cameraIndex = ComponentIndexType.LEFT_OR_MAIN;
 | 
			
		||||
    private CameraLensType lensType = CameraLensType.CAMERA_LENS_ZOOM;
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    public AutoExposureLockWidgetModel(@NonNull DJISDKModel djiSdkModel,
 | 
			
		||||
                                       @NonNull ObservableInMemoryKeyedStore uxKeyManager) {
 | 
			
		||||
        super(djiSdkModel, uxKeyManager);
 | 
			
		||||
        autoExposureLockBooleanProcessor = DataProcessor.create(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //region Data
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if the auto exposure lock is enabled
 | 
			
		||||
     *
 | 
			
		||||
     * @return Flowable with boolean true - enabled  false - disabled
 | 
			
		||||
     */
 | 
			
		||||
    public Flowable<Boolean> isAutoExposureLockOn() {
 | 
			
		||||
        return autoExposureLockBooleanProcessor.toFlowable();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Actions
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    public ComponentIndexType getCameraIndex() {
 | 
			
		||||
        return cameraIndex;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public CameraLensType getLensType() {
 | 
			
		||||
        return lensType;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void updateCameraSource(@NonNull ComponentIndexType cameraIndex, @NonNull CameraLensType lensType) {
 | 
			
		||||
        this.cameraIndex = cameraIndex;
 | 
			
		||||
        this.lensType = lensType;
 | 
			
		||||
        restart();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set auto exposure lock the opposite of its current state
 | 
			
		||||
     *
 | 
			
		||||
     * @return Completable representing success and failure of action
 | 
			
		||||
     */
 | 
			
		||||
    public Completable toggleAutoExposureLock() {
 | 
			
		||||
        return djiSdkModel.setValue(KeyTools.createCameraKey(CameraKey.KeyAELockEnabled, cameraIndex, lensType), !autoExposureLockBooleanProcessor.getValue());
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Lifecycle
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void inSetup() {
 | 
			
		||||
        KeyTools.createCameraKey(CameraKey.KeyAELockEnabled, cameraIndex, lensType);
 | 
			
		||||
        bindDataProcessor(KeyTools.createCameraKey(CameraKey.KeyAELockEnabled, cameraIndex, lensType), autoExposureLockBooleanProcessor);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void inCleanup() {
 | 
			
		||||
        // nothing to clean
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void updateStates() {
 | 
			
		||||
        // No States
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //endregion
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,224 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.widget.cameracapture;
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.util.AttributeSet;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.view.ViewGroup;
 | 
			
		||||
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.annotation.Nullable;
 | 
			
		||||
import androidx.constraintlayout.widget.ConstraintLayout;
 | 
			
		||||
import androidx.constraintlayout.widget.Constraints;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraMode;
 | 
			
		||||
import dji.sdk.keyvalue.value.common.CameraLensType;
 | 
			
		||||
import dji.sdk.keyvalue.value.common.ComponentIndexType;
 | 
			
		||||
import dji.v5.ux.R;
 | 
			
		||||
import dji.v5.ux.cameracore.widget.cameracapture.recordvideo.RecordVideoWidget;
 | 
			
		||||
import dji.v5.ux.cameracore.widget.cameracapture.shootphoto.ShootPhotoWidget;
 | 
			
		||||
import dji.v5.ux.core.base.DJISDKModel;
 | 
			
		||||
import dji.v5.ux.core.base.ICameraIndex;
 | 
			
		||||
import dji.v5.ux.core.base.SchedulerProvider;
 | 
			
		||||
import dji.v5.ux.core.base.widget.ConstraintLayoutWidget;
 | 
			
		||||
import dji.v5.ux.core.communication.ObservableInMemoryKeyedStore;
 | 
			
		||||
import dji.v5.ux.core.util.UxErrorHandle;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Camera Capture Widget
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Widget can be used to shoot photo and record video. It reacts to change in {@link CameraMode}
 | 
			
		||||
 * It encloses {@link ShootPhotoWidget} and {@link RecordVideoWidget} for respective modes
 | 
			
		||||
 */
 | 
			
		||||
public class CameraCaptureWidget extends ConstraintLayoutWidget<Object> implements ICameraIndex {
 | 
			
		||||
 | 
			
		||||
    //region Fields
 | 
			
		||||
    private static final String TAG = "CameraCaptureWidget";
 | 
			
		||||
    private CameraCaptureWidgetModel widgetModel;
 | 
			
		||||
    private Map<CameraMode, View> widgetMap;
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Lifecycle
 | 
			
		||||
    public CameraCaptureWidget(Context context) {
 | 
			
		||||
        super(context);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public CameraCaptureWidget(Context context, AttributeSet attrs) {
 | 
			
		||||
        super(context, attrs);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public CameraCaptureWidget(Context context, AttributeSet attrs, int defStyleAttr) {
 | 
			
		||||
        super(context, attrs, defStyleAttr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void initView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
 | 
			
		||||
        widgetMap = new HashMap<>();
 | 
			
		||||
        if (!isInEditMode()) {
 | 
			
		||||
            addViewByMode(CameraMode.PHOTO_NORMAL, new ShootPhotoWidget(context));
 | 
			
		||||
            addViewByMode(CameraMode.VIDEO_NORMAL, new RecordVideoWidget(context));
 | 
			
		||||
            widgetModel = new CameraCaptureWidgetModel(DJISDKModel.getInstance(), ObservableInMemoryKeyedStore.getInstance());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onAttachedToWindow() {
 | 
			
		||||
        super.onAttachedToWindow();
 | 
			
		||||
        if (!isInEditMode()) {
 | 
			
		||||
            widgetModel.setup();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onDetachedFromWindow() {
 | 
			
		||||
        if (!isInEditMode()) {
 | 
			
		||||
            widgetModel.cleanup();
 | 
			
		||||
        }
 | 
			
		||||
        super.onDetachedFromWindow();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void reactToModelChanges() {
 | 
			
		||||
        addReaction(
 | 
			
		||||
                widgetModel.getCameraMode()
 | 
			
		||||
                        .observeOn(SchedulerProvider.ui())
 | 
			
		||||
                        .subscribe(
 | 
			
		||||
                                this::onCameraModeChange,
 | 
			
		||||
                                UxErrorHandle.logErrorConsumer(TAG, "Camera Mode Change: ")));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getIdealDimensionRatioString() {
 | 
			
		||||
        return getResources().getString(R.string.uxsdk_widget_default_ratio);
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    public ComponentIndexType getCameraIndex() {
 | 
			
		||||
        return widgetModel.getCameraIndex();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public CameraLensType getLensType() {
 | 
			
		||||
        return widgetModel.getLensType();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void updateCameraSource(@NonNull ComponentIndexType cameraIndex, @NonNull CameraLensType lensType) {
 | 
			
		||||
        widgetModel.updateCameraSource(cameraIndex, lensType);
 | 
			
		||||
        ShootPhotoWidget shootPhotoWidget = getShootPhotoWidget();
 | 
			
		||||
        if (shootPhotoWidget != null) {
 | 
			
		||||
            shootPhotoWidget.updateCameraSource(cameraIndex, lensType);
 | 
			
		||||
        }
 | 
			
		||||
        RecordVideoWidget recordVideoWidget = getRecordVideoWidget();
 | 
			
		||||
        if (recordVideoWidget != null) {
 | 
			
		||||
            recordVideoWidget.updateCameraSource(cameraIndex, lensType);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //region private helpers
 | 
			
		||||
    private void onCameraModeChange(CameraMode cameraMode) {
 | 
			
		||||
        for (View view : widgetMap.values()) {
 | 
			
		||||
            if (view != null) view.setVisibility(INVISIBLE);
 | 
			
		||||
        }
 | 
			
		||||
        View currentView = widgetMap.get(cameraMode);
 | 
			
		||||
        if (currentView != null) {
 | 
			
		||||
            widgetMap.get(cameraMode).setVisibility(VISIBLE);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region customizations
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add view to be shown based on camera mode
 | 
			
		||||
     *
 | 
			
		||||
     * @param cameraMode instance of camera mode
 | 
			
		||||
     * @param view       the view to be shown for camera mode
 | 
			
		||||
     */
 | 
			
		||||
    public void addViewByMode(@NonNull CameraMode cameraMode, @NonNull View view) {
 | 
			
		||||
        if (widgetMap.get(cameraMode) != null) {
 | 
			
		||||
            removeView(widgetMap.get(cameraMode));
 | 
			
		||||
        }
 | 
			
		||||
        widgetMap.put(cameraMode, view);
 | 
			
		||||
        view.setVisibility(INVISIBLE);
 | 
			
		||||
        addView(view);
 | 
			
		||||
        ConstraintLayout.LayoutParams lp = new Constraints.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
 | 
			
		||||
        view.setLayoutParams(lp);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove the view based on camera mode
 | 
			
		||||
     *
 | 
			
		||||
     * @param cameraMode for which the view should be removed
 | 
			
		||||
     */
 | 
			
		||||
    public void removeViewByMode(@NonNull CameraMode cameraMode) {
 | 
			
		||||
        if (widgetMap.get(cameraMode) == null) return;
 | 
			
		||||
        removeView(widgetMap.get(cameraMode));
 | 
			
		||||
        widgetMap.remove(cameraMode);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the view that will be shown based on camera mode
 | 
			
		||||
     *
 | 
			
		||||
     * @param cameraMode for which the view is shown
 | 
			
		||||
     * @return View for the mode
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public View getViewByMode(@NonNull CameraMode cameraMode) {
 | 
			
		||||
        return widgetMap.get(cameraMode);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get shoot photo widget
 | 
			
		||||
     *
 | 
			
		||||
     * @return instance of {@link ShootPhotoWidget}
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public ShootPhotoWidget getShootPhotoWidget() {
 | 
			
		||||
        if (widgetMap.get(CameraMode.PHOTO_NORMAL) == null || !(widgetMap.get(CameraMode.PHOTO_NORMAL) instanceof ShootPhotoWidget)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        return (ShootPhotoWidget) widgetMap.get(CameraMode.PHOTO_NORMAL);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get record video widget
 | 
			
		||||
     *
 | 
			
		||||
     * @return instance of {@link RecordVideoWidget}
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public RecordVideoWidget getRecordVideoWidget() {
 | 
			
		||||
        if (widgetMap.get(CameraMode.VIDEO_NORMAL) == null || !(widgetMap.get(CameraMode.VIDEO_NORMAL) instanceof RecordVideoWidget)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        return (RecordVideoWidget) widgetMap.get(CameraMode.VIDEO_NORMAL);
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,103 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.widget.cameracapture;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraMode;
 | 
			
		||||
import dji.sdk.keyvalue.value.common.CameraLensType;
 | 
			
		||||
import dji.sdk.keyvalue.value.common.ComponentIndexType;
 | 
			
		||||
import dji.v5.ux.core.base.DJISDKModel;
 | 
			
		||||
import dji.v5.ux.core.base.ICameraIndex;
 | 
			
		||||
import dji.v5.ux.core.base.WidgetModel;
 | 
			
		||||
import dji.v5.ux.core.communication.ObservableInMemoryKeyedStore;
 | 
			
		||||
import dji.v5.ux.core.module.FlatCameraModule;
 | 
			
		||||
import io.reactivex.rxjava3.core.Flowable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Camera Capture Widget Model
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Widget Model for {@link CameraCaptureWidget} used to define underlying logic
 | 
			
		||||
 * and communication
 | 
			
		||||
 */
 | 
			
		||||
public class CameraCaptureWidgetModel extends WidgetModel implements ICameraIndex {
 | 
			
		||||
 | 
			
		||||
    //region Fields
 | 
			
		||||
    private FlatCameraModule flatCameraModule;
 | 
			
		||||
    private ComponentIndexType cameraIndex = ComponentIndexType.LEFT_OR_MAIN;
 | 
			
		||||
    private CameraLensType lensType = CameraLensType.CAMERA_LENS_ZOOM;
 | 
			
		||||
 | 
			
		||||
    //region Lifecycle
 | 
			
		||||
    public CameraCaptureWidgetModel(@NonNull DJISDKModel djiSdkModel,
 | 
			
		||||
                                    @NonNull ObservableInMemoryKeyedStore keyedStore) {
 | 
			
		||||
        super(djiSdkModel, keyedStore);
 | 
			
		||||
        flatCameraModule = new FlatCameraModule();
 | 
			
		||||
        addModule(flatCameraModule);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void inSetup() {
 | 
			
		||||
        // do nothing
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void inCleanup() {
 | 
			
		||||
        // do nothing
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void updateCameraSource(@NonNull ComponentIndexType cameraIndex, @NonNull CameraLensType lensType) {
 | 
			
		||||
        this.cameraIndex = cameraIndex;
 | 
			
		||||
        this.lensType = lensType;
 | 
			
		||||
        flatCameraModule.updateCameraSource(cameraIndex,lensType);
 | 
			
		||||
        restart();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    public ComponentIndexType getCameraIndex() {
 | 
			
		||||
        return cameraIndex;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    public CameraLensType getLensType() {
 | 
			
		||||
        return lensType;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void updateStates() {
 | 
			
		||||
        // Empty function
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Data
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the current camera Mode
 | 
			
		||||
     *
 | 
			
		||||
     * @return Flowable with {@link CameraMode} instance
 | 
			
		||||
     */
 | 
			
		||||
    public Flowable<CameraMode> getCameraMode() {
 | 
			
		||||
        return flatCameraModule.getCameraModeDataProcessor().toFlowable();
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,65 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.widget.cameracapture.recordvideo;
 | 
			
		||||
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraSDCardState;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraStorageLocation;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.SSDOperationState;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class represents storage state for SD card and internal storage
 | 
			
		||||
 * when camera is in record video mode
 | 
			
		||||
 */
 | 
			
		||||
public class CameraSDVideoStorageState extends CameraVideoStorageState {
 | 
			
		||||
 | 
			
		||||
    private CameraSDCardState sdCardOperationState;
 | 
			
		||||
 | 
			
		||||
    public CameraSDVideoStorageState(CameraStorageLocation storageLocation, int availableRecordingTimeInSeconds, CameraSDCardState sdCardOperationState) {
 | 
			
		||||
        super(storageLocation, availableRecordingTimeInSeconds);
 | 
			
		||||
        this.sdCardOperationState = sdCardOperationState;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the operation state of current storage
 | 
			
		||||
     *
 | 
			
		||||
     * @return instance of {@link SSDOperationState} representing state for SDCard or internal Storage
 | 
			
		||||
     */
 | 
			
		||||
    public CameraSDCardState getSdCardOperationState() {
 | 
			
		||||
        return sdCardOperationState;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object o) {
 | 
			
		||||
        if (this == o) return true;
 | 
			
		||||
        if (!(o instanceof CameraSDVideoStorageState)) return false;
 | 
			
		||||
        if (!super.equals(o)) return false;
 | 
			
		||||
        CameraSDVideoStorageState that = (CameraSDVideoStorageState) o;
 | 
			
		||||
        return getSdCardOperationState() == that.getSdCardOperationState();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int hashCode() {
 | 
			
		||||
        return 31 * getSdCardOperationState().value();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,64 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.widget.cameracapture.recordvideo;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraStorageLocation;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.SSDOperationState;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class represents SSD Storage state for Record Video Mode
 | 
			
		||||
 */
 | 
			
		||||
public class CameraSSDVideoStorageState extends CameraVideoStorageState {
 | 
			
		||||
 | 
			
		||||
    private SSDOperationState ssdOperationState;
 | 
			
		||||
 | 
			
		||||
    public CameraSSDVideoStorageState(CameraStorageLocation storageLocation, int availableRecordingTimeInSeconds, SSDOperationState ssdOperationState) {
 | 
			
		||||
        super(storageLocation, availableRecordingTimeInSeconds);
 | 
			
		||||
        this.ssdOperationState = ssdOperationState;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get operation state of SSD
 | 
			
		||||
     *
 | 
			
		||||
     * @return {@link SSDOperationState}
 | 
			
		||||
     */
 | 
			
		||||
    public SSDOperationState getSsdOperationState() {
 | 
			
		||||
        return ssdOperationState;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object o) {
 | 
			
		||||
        if (this == o) return true;
 | 
			
		||||
        if (!(o instanceof CameraSSDVideoStorageState)) return false;
 | 
			
		||||
        if (!super.equals(o)) return false;
 | 
			
		||||
        CameraSSDVideoStorageState that = (CameraSSDVideoStorageState) o;
 | 
			
		||||
        return getSsdOperationState() == that.getSsdOperationState();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int hashCode() {
 | 
			
		||||
        return 31 * getSsdOperationState().value();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,75 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.widget.cameracapture.recordvideo;
 | 
			
		||||
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraStorageLocation;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class represents the storage state when camera is in Record Video Mode
 | 
			
		||||
 * The state determines the storage location and the available space
 | 
			
		||||
 * in terms of duration in seconds
 | 
			
		||||
 */
 | 
			
		||||
public abstract class CameraVideoStorageState {
 | 
			
		||||
    private final CameraStorageLocation storageLocation;
 | 
			
		||||
    private final int availableRecordingTimeInSeconds;
 | 
			
		||||
 | 
			
		||||
    protected CameraVideoStorageState(CameraStorageLocation storageLocation, int availableRecordingTimeInSeconds) {
 | 
			
		||||
        this.storageLocation = storageLocation;
 | 
			
		||||
        this.availableRecordingTimeInSeconds = availableRecordingTimeInSeconds;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the current storage location
 | 
			
		||||
     *
 | 
			
		||||
     * @return instance of CameraStorageLocation
 | 
			
		||||
     */
 | 
			
		||||
    public CameraStorageLocation getStorageLocation() {
 | 
			
		||||
        return storageLocation;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the duration in seconds that can be recorded before running out of storage
 | 
			
		||||
     *
 | 
			
		||||
     * @return integer value representing seconds
 | 
			
		||||
     */
 | 
			
		||||
    public int getAvailableRecordingTimeInSeconds() {
 | 
			
		||||
        return availableRecordingTimeInSeconds;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object o) {
 | 
			
		||||
        if (this == o) return true;
 | 
			
		||||
        if (!(o instanceof CameraVideoStorageState)) return false;
 | 
			
		||||
        CameraVideoStorageState that = (CameraVideoStorageState) o;
 | 
			
		||||
        return getAvailableRecordingTimeInSeconds() == that.getAvailableRecordingTimeInSeconds() &&
 | 
			
		||||
                getStorageLocation() == that.getStorageLocation();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int hashCode() {
 | 
			
		||||
        int result = 31 * getStorageLocation().value();
 | 
			
		||||
        result = result + 31 * getAvailableRecordingTimeInSeconds();
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,669 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.widget.cameracapture.recordvideo;
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.content.res.ColorStateList;
 | 
			
		||||
import android.content.res.TypedArray;
 | 
			
		||||
import android.graphics.Color;
 | 
			
		||||
import android.graphics.drawable.Drawable;
 | 
			
		||||
import android.util.AttributeSet;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.view.View.OnClickListener;
 | 
			
		||||
import android.widget.ImageView;
 | 
			
		||||
import android.widget.TextView;
 | 
			
		||||
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.ColorInt;
 | 
			
		||||
import androidx.annotation.Dimension;
 | 
			
		||||
import androidx.annotation.DrawableRes;
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.annotation.Nullable;
 | 
			
		||||
import androidx.annotation.StyleRes;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraSDCardState;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraStorageLocation;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.SSDOperationState;
 | 
			
		||||
import dji.sdk.keyvalue.value.common.CameraLensType;
 | 
			
		||||
import dji.sdk.keyvalue.value.common.ComponentIndexType;
 | 
			
		||||
import dji.v5.ux.R;
 | 
			
		||||
import dji.v5.ux.cameracore.util.CameraActionSound;
 | 
			
		||||
import dji.v5.ux.cameracore.widget.cameracapture.recordvideo.RecordVideoWidgetModel.RecordingState;
 | 
			
		||||
import dji.v5.ux.cameracore.widget.cameracontrols.RemoteControllerButtonDownModel;
 | 
			
		||||
import dji.v5.ux.core.base.DJISDKModel;
 | 
			
		||||
import dji.v5.ux.core.base.ICameraIndex;
 | 
			
		||||
import dji.v5.ux.core.base.SchedulerProvider;
 | 
			
		||||
import dji.v5.ux.core.base.widget.ConstraintLayoutWidget;
 | 
			
		||||
import dji.v5.ux.core.communication.ObservableInMemoryKeyedStore;
 | 
			
		||||
import dji.v5.ux.core.util.CameraUtil;
 | 
			
		||||
import dji.v5.ux.core.util.UxErrorHandle;
 | 
			
		||||
import io.reactivex.rxjava3.core.Completable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Record Video Widget
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Widget can be used for recording video. The widget displays the current video mode. It also
 | 
			
		||||
 * displays the storage state and errors associated with it.
 | 
			
		||||
 */
 | 
			
		||||
public class RecordVideoWidget extends ConstraintLayoutWidget<Object> implements OnClickListener, ICameraIndex {
 | 
			
		||||
 | 
			
		||||
    //region Fields
 | 
			
		||||
    private static final String TAG = "RecordVideoWidget";
 | 
			
		||||
    private RecordVideoWidgetModel widgetModel;
 | 
			
		||||
    private RemoteControllerButtonDownModel buttonDownModel;
 | 
			
		||||
    private ImageView centerImageView;
 | 
			
		||||
    private TextView videoTimerTextView;
 | 
			
		||||
    private ImageView storageStatusOverlayImageView;
 | 
			
		||||
    private Map<StorageIconState, Drawable> storageInternalIconMap;
 | 
			
		||||
    private Map<StorageIconState, Drawable> storageSSDIconMap;
 | 
			
		||||
    private Map<StorageIconState, Drawable> storageSDCardIconMap;
 | 
			
		||||
    private Drawable recordVideoStartDrawable;
 | 
			
		||||
    private Drawable recordVideoStopDrawable;
 | 
			
		||||
    private CameraActionSound cameraActionSound;
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Lifecycle
 | 
			
		||||
    public RecordVideoWidget(Context context) {
 | 
			
		||||
        super(context);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public RecordVideoWidget(Context context, AttributeSet attrs) {
 | 
			
		||||
        super(context, attrs);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public RecordVideoWidget(Context context, AttributeSet attrs, int defStyleAttr) {
 | 
			
		||||
        super(context, attrs, defStyleAttr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void initView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
 | 
			
		||||
        inflate(context, R.layout.uxsdk_widget_record_video, this);
 | 
			
		||||
        centerImageView = findViewById(R.id.image_view_center);
 | 
			
		||||
        videoTimerTextView = findViewById(R.id.text_view_video_record_time);
 | 
			
		||||
        storageStatusOverlayImageView = findViewById(R.id.image_view_storage_status_overlay);
 | 
			
		||||
        storageInternalIconMap = new HashMap<>();
 | 
			
		||||
        storageSSDIconMap = new HashMap<>();
 | 
			
		||||
        storageSDCardIconMap = new HashMap<>();
 | 
			
		||||
        centerImageView.setOnClickListener(this);
 | 
			
		||||
        cameraActionSound = new CameraActionSound(context);
 | 
			
		||||
        if (!isInEditMode()) {
 | 
			
		||||
            widgetModel = new RecordVideoWidgetModel(DJISDKModel.getInstance(), ObservableInMemoryKeyedStore.getInstance());
 | 
			
		||||
            buttonDownModel = new RemoteControllerButtonDownModel(DJISDKModel.getInstance(), ObservableInMemoryKeyedStore.getInstance());
 | 
			
		||||
        }
 | 
			
		||||
        initDefaults();
 | 
			
		||||
        if (attrs != null) {
 | 
			
		||||
            initAttributes(context, attrs);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onAttachedToWindow() {
 | 
			
		||||
        super.onAttachedToWindow();
 | 
			
		||||
        if (!isInEditMode()) {
 | 
			
		||||
            widgetModel.setup();
 | 
			
		||||
            buttonDownModel.setup();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onDetachedFromWindow() {
 | 
			
		||||
        if (!isInEditMode()) {
 | 
			
		||||
            widgetModel.cleanup();
 | 
			
		||||
            buttonDownModel.cleanup();
 | 
			
		||||
        }
 | 
			
		||||
        super.onDetachedFromWindow();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void reactToModelChanges() {
 | 
			
		||||
        addReaction(widgetModel.getRecordingTimeInSeconds()
 | 
			
		||||
                .observeOn(SchedulerProvider.ui())
 | 
			
		||||
                .subscribe(this::updateRecordingTime, UxErrorHandle.logErrorConsumer(TAG, "record time: ")));
 | 
			
		||||
        addReaction(widgetModel.getRecordingState()
 | 
			
		||||
                .observeOn(SchedulerProvider.ui())
 | 
			
		||||
                .subscribe(recordingState -> onIsRecordingVideoChange(recordingState, true), UxErrorHandle.logErrorConsumer(TAG, "is recording: ")));
 | 
			
		||||
        addReaction(widgetModel.getCameraVideoStorageState()
 | 
			
		||||
                .observeOn(SchedulerProvider.ui())
 | 
			
		||||
                .subscribe(this::updateCameraForegroundResource, UxErrorHandle.logErrorConsumer(TAG, "camera storage update: ")));
 | 
			
		||||
        addReaction(buttonDownModel.isRecordButtonDownProcessor().toFlowable()
 | 
			
		||||
                .observeOn(SchedulerProvider.ui())
 | 
			
		||||
                .subscribe(aBoolean -> {
 | 
			
		||||
                    if (aBoolean == Boolean.TRUE) {
 | 
			
		||||
                        actionOnRecording();
 | 
			
		||||
                    }
 | 
			
		||||
                }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getIdealDimensionRatioString() {
 | 
			
		||||
        return getResources().getString(R.string.uxsdk_widget_default_ratio);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    public ComponentIndexType getCameraIndex() {
 | 
			
		||||
        return widgetModel.getCameraIndex();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public CameraLensType getLensType() {
 | 
			
		||||
        return widgetModel.getLensType();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void updateCameraSource(@NonNull ComponentIndexType cameraIndex, @NonNull CameraLensType lensType) {
 | 
			
		||||
        if (!isInEditMode()) {
 | 
			
		||||
            widgetModel.updateCameraSource(cameraIndex, lensType);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onClick(View v) {
 | 
			
		||||
        if (v.equals(centerImageView)) {
 | 
			
		||||
            actionOnRecording();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void actionOnRecording() {
 | 
			
		||||
        if (!widgetModel.isVideoMode()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        widgetModel.getRecordingState().firstOrError().flatMapCompletable(recordingState -> {
 | 
			
		||||
            if (recordingState == RecordingState.RECORDING_IN_PROGRESS) {
 | 
			
		||||
                return widgetModel.stopRecordVideo();
 | 
			
		||||
            } else if (recordingState == RecordingState.RECORDING_STOPPED || recordingState == RecordingState.RECORDING_NOT_STARED) {
 | 
			
		||||
                return widgetModel.startRecordVideo();
 | 
			
		||||
            } else {
 | 
			
		||||
                return Completable.complete();
 | 
			
		||||
            }
 | 
			
		||||
        }).observeOn(SchedulerProvider.ui()).subscribe(() -> {
 | 
			
		||||
        }, UxErrorHandle.logErrorConsumer(TAG, "START STOP VIDEO"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //region private helpers
 | 
			
		||||
    private void initDefaults() {
 | 
			
		||||
        recordVideoStartDrawable = getResources().getDrawable(R.drawable.uxsdk_selector_start_record_video);
 | 
			
		||||
        recordVideoStopDrawable = getResources().getDrawable(R.drawable.uxsdk_selector_stop_record_video);
 | 
			
		||||
        setInternalStorageIcon(StorageIconState.NOT_INSERTED, R.drawable.uxsdk_ic_internal_storage_not_inserted);
 | 
			
		||||
        setInternalStorageIcon(StorageIconState.SLOW, R.drawable.uxsdk_ic_internal_storage_slow);
 | 
			
		||||
        setInternalStorageIcon(StorageIconState.FULL, R.drawable.uxsdk_ic_internal_storage_full);
 | 
			
		||||
        setSDCardStorageIcon(StorageIconState.NOT_INSERTED, R.drawable.uxsdk_ic_sdcard_not_inserted);
 | 
			
		||||
        setSDCardStorageIcon(StorageIconState.SLOW, R.drawable.uxsdk_ic_sdcard_slow);
 | 
			
		||||
        setSDCardStorageIcon(StorageIconState.FULL, R.drawable.uxsdk_ic_sdcard_full);
 | 
			
		||||
        setSSDStorageIcon(StorageIconState.NOT_INSERTED, R.drawable.uxsdk_ic_ssd_not_inserted);
 | 
			
		||||
        setSSDStorageIcon(StorageIconState.FULL, R.drawable.uxsdk_ic_ssd_full);
 | 
			
		||||
        setVideoTimerTextColor(Color.WHITE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initAttributes(@NonNull Context context, @NonNull AttributeSet attrs) {
 | 
			
		||||
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RecordVideoWidget);
 | 
			
		||||
        updateCameraSource(ComponentIndexType.find(typedArray.getInt(R.styleable.RecordVideoWidget_uxsdk_cameraIndex, 0)),
 | 
			
		||||
                CameraLensType.find(typedArray.getInt(R.styleable.RecordVideoWidget_uxsdk_lensType, 0)));
 | 
			
		||||
 | 
			
		||||
        int textAppearance = typedArray.getResourceId(R.styleable.RecordVideoWidget_uxsdk_timerTextAppearance, INVALID_RESOURCE);
 | 
			
		||||
        if (textAppearance != INVALID_RESOURCE) {
 | 
			
		||||
            setTimerTextAppearance(textAppearance);
 | 
			
		||||
        }
 | 
			
		||||
        setVideoTimerTextColor(typedArray.getColor(R.styleable.RecordVideoWidget_uxsdk_timerTextColor, Color.WHITE));
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_timerTextBackground) != null) {
 | 
			
		||||
            setVideoTimerTextBackground(typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_timerTextBackground));
 | 
			
		||||
        }
 | 
			
		||||
        setVideoTimerTextSize(typedArray.getDimension(R.styleable.RecordVideoWidget_uxsdk_timerTextSize, 12));
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_foregroundIconBackground) != null) {
 | 
			
		||||
            setForegroundIconBackground(typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_foregroundIconBackground));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        initRecordVideoDrawable(typedArray);
 | 
			
		||||
        initInternalStorageIcon(typedArray);
 | 
			
		||||
        initSDCardStorageIcon(typedArray);
 | 
			
		||||
        initSSDStorageIcon(typedArray);
 | 
			
		||||
        typedArray.recycle();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initSSDStorageIcon(TypedArray typedArray) {
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_ssdNotInsertedIcon) != null) {
 | 
			
		||||
            setSSDStorageIcon(StorageIconState.NOT_INSERTED, typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_ssdNotInsertedIcon));
 | 
			
		||||
        }
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_ssdFullIcon) != null) {
 | 
			
		||||
            setSSDStorageIcon(StorageIconState.FULL, typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_ssdFullIcon));
 | 
			
		||||
        }
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_ssdSlowIcon) != null) {
 | 
			
		||||
            setSSDStorageIcon(StorageIconState.SLOW, typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_ssdSlowIcon));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initSDCardStorageIcon(TypedArray typedArray) {
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_sdCardNotInsertedIcon) != null) {
 | 
			
		||||
            setSDCardStorageIcon(StorageIconState.NOT_INSERTED, typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_sdCardNotInsertedIcon));
 | 
			
		||||
        }
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_sdCardFullIcon) != null) {
 | 
			
		||||
            setSDCardStorageIcon(StorageIconState.FULL, typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_sdCardFullIcon));
 | 
			
		||||
        }
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_sdCardSlowIcon) != null) {
 | 
			
		||||
            setSDCardStorageIcon(StorageIconState.SLOW, typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_sdCardSlowIcon));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initInternalStorageIcon(TypedArray typedArray) {
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_internalStorageNotInsertedIcon) != null) {
 | 
			
		||||
            setInternalStorageIcon(StorageIconState.NOT_INSERTED, typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_internalStorageNotInsertedIcon));
 | 
			
		||||
        }
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_internalStorageFullIcon) != null) {
 | 
			
		||||
            setInternalStorageIcon(StorageIconState.FULL, typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_internalStorageFullIcon));
 | 
			
		||||
        }
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_internalStorageSlowIcon) != null) {
 | 
			
		||||
            setInternalStorageIcon(StorageIconState.SLOW, typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_internalStorageSlowIcon));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initRecordVideoDrawable(TypedArray typedArray) {
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_recordStartIcon) != null) {
 | 
			
		||||
            recordVideoStartDrawable = typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_recordStartIcon);
 | 
			
		||||
        }
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_recordStopIcon) != null) {
 | 
			
		||||
            recordVideoStopDrawable = typedArray.getDrawable(R.styleable.RecordVideoWidget_uxsdk_recordStopIcon);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void updateCameraForegroundResource(CameraVideoStorageState cameraVideoStorageState) {
 | 
			
		||||
        Drawable foregroundResource = null;
 | 
			
		||||
        if (cameraVideoStorageState instanceof CameraSDVideoStorageState) {
 | 
			
		||||
            if (cameraVideoStorageState.getStorageLocation() == CameraStorageLocation.SDCARD) {
 | 
			
		||||
                foregroundResource = updateResourceWithStorageInSDCard(cameraVideoStorageState);
 | 
			
		||||
            } else if (cameraVideoStorageState.getStorageLocation() == CameraStorageLocation.INTERNAL) {
 | 
			
		||||
                foregroundResource = updateResourceWithStorageInternal(cameraVideoStorageState);
 | 
			
		||||
            }
 | 
			
		||||
        } else if (cameraVideoStorageState instanceof CameraSSDVideoStorageState) {
 | 
			
		||||
            if (((CameraSSDVideoStorageState) cameraVideoStorageState).getSsdOperationState()
 | 
			
		||||
                    == SSDOperationState.NOT_FOUND) {
 | 
			
		||||
                foregroundResource = getSSDStorageIcon(StorageIconState.NOT_INSERTED);
 | 
			
		||||
            } else if (((CameraSSDVideoStorageState) cameraVideoStorageState).getSsdOperationState()
 | 
			
		||||
                    == SSDOperationState.FULL) {
 | 
			
		||||
                foregroundResource = getSSDStorageIcon(StorageIconState.FULL);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        storageStatusOverlayImageView.setImageDrawable(foregroundResource);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Drawable updateResourceWithStorageInSDCard(CameraVideoStorageState cameraVideoStorageState) {
 | 
			
		||||
        Drawable foregroundResource = null;
 | 
			
		||||
        if (((CameraSDVideoStorageState) cameraVideoStorageState).getSdCardOperationState()
 | 
			
		||||
                == CameraSDCardState.NOT_INSERTED) {
 | 
			
		||||
            foregroundResource = getSDCardStorageIcon(StorageIconState.NOT_INSERTED);
 | 
			
		||||
        } else if (((CameraSDVideoStorageState) cameraVideoStorageState).getSdCardOperationState()
 | 
			
		||||
                == CameraSDCardState.FULL) {
 | 
			
		||||
            foregroundResource = getSDCardStorageIcon(StorageIconState.FULL);
 | 
			
		||||
        } else if (((CameraSDVideoStorageState) cameraVideoStorageState).getSdCardOperationState()
 | 
			
		||||
                == CameraSDCardState.SLOW) {
 | 
			
		||||
            foregroundResource = getSDCardStorageIcon(StorageIconState.SLOW);
 | 
			
		||||
        }
 | 
			
		||||
        return foregroundResource;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Drawable updateResourceWithStorageInternal(CameraVideoStorageState cameraVideoStorageState) {
 | 
			
		||||
        Drawable foregroundResource = null;
 | 
			
		||||
        if (((CameraSDVideoStorageState) cameraVideoStorageState).getSdCardOperationState()
 | 
			
		||||
                == CameraSDCardState.NOT_INSERTED) {
 | 
			
		||||
            foregroundResource = getInternalStorageIcon(StorageIconState.NOT_INSERTED);
 | 
			
		||||
        } else if (((CameraSDVideoStorageState) cameraVideoStorageState).getSdCardOperationState()
 | 
			
		||||
                == CameraSDCardState.FULL) {
 | 
			
		||||
            foregroundResource = getInternalStorageIcon(StorageIconState.FULL);
 | 
			
		||||
        } else if (((CameraSDVideoStorageState) cameraVideoStorageState).getSdCardOperationState()
 | 
			
		||||
                == CameraSDCardState.SLOW) {
 | 
			
		||||
            foregroundResource = getInternalStorageIcon(StorageIconState.SLOW);
 | 
			
		||||
        }
 | 
			
		||||
        return foregroundResource;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void updateRecordingTime(int seconds) {
 | 
			
		||||
        videoTimerTextView.setText(CameraUtil.formatVideoTime(getResources(), seconds));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void onIsRecordingVideoChange(RecordingState recordingState, boolean playSound) {
 | 
			
		||||
        boolean isRecordingVideo = recordingState == RecordingState.RECORDING_IN_PROGRESS;
 | 
			
		||||
        Drawable recordStart = recordVideoStartDrawable;
 | 
			
		||||
        Drawable recordStop = recordVideoStopDrawable;
 | 
			
		||||
        centerImageView.setImageDrawable(isRecordingVideo ? recordStop : recordStart);
 | 
			
		||||
        videoTimerTextView.setVisibility(isRecordingVideo ? View.VISIBLE : View.INVISIBLE);
 | 
			
		||||
        storageStatusOverlayImageView.setVisibility(isRecordingVideo ? View.GONE : View.VISIBLE);
 | 
			
		||||
        if (playSound) {
 | 
			
		||||
            if (recordingState == RecordingState.RECORDING_IN_PROGRESS) {
 | 
			
		||||
                cameraActionSound.playStartRecordVideo();
 | 
			
		||||
            } else if (recordingState == RecordingState.RECORDING_STOPPED) {
 | 
			
		||||
                cameraActionSound.playStopRecordVideo();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void checkAndUpdateCameraForegroundResource() {
 | 
			
		||||
        if (!isInEditMode()) {
 | 
			
		||||
            addDisposable(widgetModel.getCameraVideoStorageState().firstOrError()
 | 
			
		||||
                    .observeOn(SchedulerProvider.ui())
 | 
			
		||||
                    .subscribe(this::updateCameraForegroundResource,
 | 
			
		||||
                            UxErrorHandle.logErrorConsumer(TAG, "check and update camera foreground resource: ")));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void checkAndUpdateCenterImageView() {
 | 
			
		||||
        if (!isInEditMode()) {
 | 
			
		||||
            addDisposable(widgetModel.getRecordingState().firstOrError()
 | 
			
		||||
                    .observeOn(SchedulerProvider.ui())
 | 
			
		||||
                    .subscribe(recordingState -> onIsRecordingVideoChange(recordingState, false),
 | 
			
		||||
                            UxErrorHandle.logErrorConsumer(TAG, "check and update camera foreground resource: ")));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region customizations
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the current start recording video icon
 | 
			
		||||
     *
 | 
			
		||||
     * @return Drawable currently used
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public Drawable getRecordVideoStartDrawable() {
 | 
			
		||||
        return recordVideoStartDrawable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the start record video icon
 | 
			
		||||
     *
 | 
			
		||||
     * @param resourceId to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setRecordVideoStartDrawable(@DrawableRes int resourceId) {
 | 
			
		||||
        setRecordVideoStartDrawable(getResources().getDrawable(resourceId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the start record video icon
 | 
			
		||||
     *
 | 
			
		||||
     * @param drawable to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setRecordVideoStartDrawable(@Nullable Drawable drawable) {
 | 
			
		||||
        recordVideoStartDrawable = drawable;
 | 
			
		||||
        checkAndUpdateCenterImageView();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the current stop video recording icon
 | 
			
		||||
     *
 | 
			
		||||
     * @return Drawable currently used
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public Drawable getRecordVideoStopDrawable() {
 | 
			
		||||
        return recordVideoStopDrawable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set stop video recording icon
 | 
			
		||||
     *
 | 
			
		||||
     * @param resourceId to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setRecordVideoStopDrawable(@DrawableRes int resourceId) {
 | 
			
		||||
        setRecordVideoStopDrawable(getResources().getDrawable(resourceId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set stop video recording icon
 | 
			
		||||
     *
 | 
			
		||||
     * @param drawable to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setRecordVideoStopDrawable(@Nullable Drawable drawable) {
 | 
			
		||||
        recordVideoStopDrawable = drawable;
 | 
			
		||||
        checkAndUpdateCenterImageView();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the icon for internal storage based on storage icon state
 | 
			
		||||
     *
 | 
			
		||||
     * @param storageIconState for which icon is used
 | 
			
		||||
     * @param resourceId       to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setInternalStorageIcon(@NonNull StorageIconState storageIconState, @DrawableRes int resourceId) {
 | 
			
		||||
        setInternalStorageIcon(storageIconState, getResources().getDrawable(resourceId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the icon for internal storage based on storage icon state
 | 
			
		||||
     *
 | 
			
		||||
     * @param storageIconState for which icon is used
 | 
			
		||||
     * @param drawable         to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setInternalStorageIcon(@NonNull StorageIconState storageIconState, @Nullable Drawable drawable) {
 | 
			
		||||
        storageInternalIconMap.put(storageIconState, drawable);
 | 
			
		||||
        checkAndUpdateCameraForegroundResource();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the icon for internal storage based on storage icon state
 | 
			
		||||
     *
 | 
			
		||||
     * @param storageIconState for which icon is used
 | 
			
		||||
     * @return Drawable currently used
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public Drawable getInternalStorageIcon(@NonNull StorageIconState storageIconState) {
 | 
			
		||||
        return storageInternalIconMap.get(storageIconState);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the icon for SD card storage based on storage icon state
 | 
			
		||||
     *
 | 
			
		||||
     * @param storageIconState for which icon is used
 | 
			
		||||
     * @param resourceId       to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setSDCardStorageIcon(@NonNull StorageIconState storageIconState, @DrawableRes int resourceId) {
 | 
			
		||||
        setSDCardStorageIcon(storageIconState, getResources().getDrawable(resourceId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the icon for SD card storage based on storage icon state
 | 
			
		||||
     *
 | 
			
		||||
     * @param storageIconState for which icon is used
 | 
			
		||||
     * @param drawable         to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setSDCardStorageIcon(@NonNull StorageIconState storageIconState, @Nullable Drawable drawable) {
 | 
			
		||||
        storageSDCardIconMap.put(storageIconState, drawable);
 | 
			
		||||
        checkAndUpdateCameraForegroundResource();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the icon for SD card storage based on storage icon state
 | 
			
		||||
     *
 | 
			
		||||
     * @param storageIconState for which icon is used
 | 
			
		||||
     * @return Drawable currently used
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public Drawable getSDCardStorageIcon(@NonNull StorageIconState storageIconState) {
 | 
			
		||||
        return storageSDCardIconMap.get(storageIconState);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the icon for SSD storage based on storage icon state
 | 
			
		||||
     *
 | 
			
		||||
     * @param storageIconState for which icon should be used
 | 
			
		||||
     * @param resourceId       to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setSSDStorageIcon(@NonNull StorageIconState storageIconState, @DrawableRes int resourceId) {
 | 
			
		||||
        setSSDStorageIcon(storageIconState, getResources().getDrawable(resourceId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the icon for SSD storage based on storage icon state
 | 
			
		||||
     *
 | 
			
		||||
     * @param storageIconState for which icon should be used
 | 
			
		||||
     * @param drawable         Drawable to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setSSDStorageIcon(@NonNull StorageIconState storageIconState, @Nullable Drawable drawable) {
 | 
			
		||||
        storageSSDIconMap.put(storageIconState, drawable);
 | 
			
		||||
        checkAndUpdateCameraForegroundResource();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the icon for SSD storage based on storage icon state
 | 
			
		||||
     *
 | 
			
		||||
     * @param storageIconState for which icon is used
 | 
			
		||||
     * @return Drawable currently used
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public Drawable getSSDStorageIcon(@NonNull StorageIconState storageIconState) {
 | 
			
		||||
        return storageSSDIconMap.get(storageIconState);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the background of the foreground icon
 | 
			
		||||
     *
 | 
			
		||||
     * @param resourceId to be used as background
 | 
			
		||||
     */
 | 
			
		||||
    public void setForegroundIconBackground(@DrawableRes int resourceId) {
 | 
			
		||||
        storageStatusOverlayImageView.setBackgroundResource(resourceId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set background of the foreground icon
 | 
			
		||||
     *
 | 
			
		||||
     * @param drawable to be used as background
 | 
			
		||||
     */
 | 
			
		||||
    public void setForegroundIconBackground(@Nullable Drawable drawable) {
 | 
			
		||||
        storageStatusOverlayImageView.setBackground(drawable);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get current background of foreground icon
 | 
			
		||||
     *
 | 
			
		||||
     * @return Drawable being used
 | 
			
		||||
     */
 | 
			
		||||
    public Drawable getForegroundIconBackground() {
 | 
			
		||||
        return storageStatusOverlayImageView.getBackground();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the background of the video record duration text
 | 
			
		||||
     *
 | 
			
		||||
     * @param resourceId to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setVideoTimerTextBackground(@DrawableRes int resourceId) {
 | 
			
		||||
        videoTimerTextView.setBackgroundResource(resourceId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the background of the video record duration text
 | 
			
		||||
     *
 | 
			
		||||
     * @param drawable to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setVideoTimerTextBackground(@Nullable Drawable drawable) {
 | 
			
		||||
        videoTimerTextView.setBackground(drawable);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the color state list currently used for video record duration text
 | 
			
		||||
     *
 | 
			
		||||
     * @return ColorSateList
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public ColorStateList getVideoTimerTextColors() {
 | 
			
		||||
        return videoTimerTextView.getTextColors();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the color state list for video record duration text
 | 
			
		||||
     *
 | 
			
		||||
     * @param colorStateList to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setVideoTimerTextColors(@Nullable ColorStateList colorStateList) {
 | 
			
		||||
        videoTimerTextView.setTextColor(colorStateList);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the current text color of video record duration text
 | 
			
		||||
     *
 | 
			
		||||
     * @return integer value representing color
 | 
			
		||||
     */
 | 
			
		||||
    @ColorInt
 | 
			
		||||
    public int getVideoTimerTextColor() {
 | 
			
		||||
        return videoTimerTextView.getCurrentTextColor();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the text color of video record duration text
 | 
			
		||||
     *
 | 
			
		||||
     * @param color integer value representing color
 | 
			
		||||
     */
 | 
			
		||||
    public void setVideoTimerTextColor(@ColorInt int color) {
 | 
			
		||||
        videoTimerTextView.setTextColor(color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the current text size of video record duration text
 | 
			
		||||
     *
 | 
			
		||||
     * @return float value representing text size
 | 
			
		||||
     */
 | 
			
		||||
    @Dimension
 | 
			
		||||
    public float getVideoTimerTextSize() {
 | 
			
		||||
        return videoTimerTextView.getTextSize();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the text size of video record duration text
 | 
			
		||||
     *
 | 
			
		||||
     * @param textSize float value
 | 
			
		||||
     */
 | 
			
		||||
    public void setVideoTimerTextSize(@Dimension float textSize) {
 | 
			
		||||
        videoTimerTextView.setTextSize(textSize);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the text appearance for video record duration text
 | 
			
		||||
     *
 | 
			
		||||
     * @param textAppearance to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setTimerTextAppearance(@StyleRes int textAppearance) {
 | 
			
		||||
        videoTimerTextView.setTextAppearance(getContext(), textAppearance);
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Enum indicating storage error state
 | 
			
		||||
     */
 | 
			
		||||
    public enum StorageIconState {
 | 
			
		||||
        /**
 | 
			
		||||
         * The storage is slow
 | 
			
		||||
         */
 | 
			
		||||
        SLOW,
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The storage is full
 | 
			
		||||
         */
 | 
			
		||||
        FULL,
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The storage is not inserted
 | 
			
		||||
         */
 | 
			
		||||
        NOT_INSERTED
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,330 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.widget.cameracapture.recordvideo;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import dji.sdk.keyvalue.key.CameraKey;
 | 
			
		||||
import dji.sdk.keyvalue.key.KeyTools;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraSDCardState;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraStorageInfo;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraStorageInfos;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraStorageLocation;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraType;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.SSDOperationState;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.VideoFrameRate;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.VideoResolution;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.VideoResolutionFrameRate;
 | 
			
		||||
import dji.sdk.keyvalue.value.common.CameraLensType;
 | 
			
		||||
import dji.sdk.keyvalue.value.common.ComponentIndexType;
 | 
			
		||||
import dji.v5.ux.core.base.DJISDKModel;
 | 
			
		||||
import dji.v5.ux.core.base.ICameraIndex;
 | 
			
		||||
import dji.v5.ux.core.base.WidgetModel;
 | 
			
		||||
import dji.v5.ux.core.communication.ObservableInMemoryKeyedStore;
 | 
			
		||||
import dji.v5.ux.core.module.FlatCameraModule;
 | 
			
		||||
import dji.v5.ux.core.util.DataProcessor;
 | 
			
		||||
import io.reactivex.rxjava3.core.Completable;
 | 
			
		||||
import io.reactivex.rxjava3.core.Flowable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Record Video Widget Model
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Widget Model for {@link RecordVideoWidget} used to define underlying
 | 
			
		||||
 * logic and communication
 | 
			
		||||
 */
 | 
			
		||||
public class RecordVideoWidgetModel extends WidgetModel implements ICameraIndex {
 | 
			
		||||
 | 
			
		||||
    private static final int INVALID_AVAILABLE_RECORDING_TIME = -1;
 | 
			
		||||
    private static final int MAX_VIDEO_TIME_THRESHOLD_MINUTES = 29;
 | 
			
		||||
    private static final int SECONDS_PER_MIN = 60;
 | 
			
		||||
 | 
			
		||||
    private final DataProcessor<CameraVideoStorageState> cameraVideoStorageState;
 | 
			
		||||
    private final DataProcessor<Boolean> isRecording;
 | 
			
		||||
    private final DataProcessor<String> cameraDisplayName;
 | 
			
		||||
    private final DataProcessor<CameraType> cameraType;
 | 
			
		||||
    private final DataProcessor<Integer> recordingTimeInSeconds;
 | 
			
		||||
    private final DataProcessor<VideoResolutionFrameRate> recordedVideoParameters;
 | 
			
		||||
    private final DataProcessor<VideoResolutionFrameRate> nonSSDRecordedVideoParameters;
 | 
			
		||||
    private final DataProcessor<VideoResolutionFrameRate> ssdRecordedVideoParameters;
 | 
			
		||||
    private final DataProcessor<CameraStorageLocation> storageLocation;
 | 
			
		||||
    private final DataProcessor<CameraSDCardState> sdCardState;
 | 
			
		||||
    private final DataProcessor<CameraSDCardState> storageState;
 | 
			
		||||
    private final DataProcessor<CameraSDCardState> innerStorageState;
 | 
			
		||||
    private final DataProcessor<SSDOperationState> ssdState;
 | 
			
		||||
    private final DataProcessor<CameraStorageInfos> cameraStorageInfos;
 | 
			
		||||
    private final DataProcessor<RecordingState> recordingStateProcessor;
 | 
			
		||||
    private final FlatCameraModule flatCameraModule;
 | 
			
		||||
    private ComponentIndexType cameraIndex = ComponentIndexType.LEFT_OR_MAIN;
 | 
			
		||||
    private CameraLensType lensType = CameraLensType.CAMERA_LENS_ZOOM;
 | 
			
		||||
    private boolean lastIsRecording = false;
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Constructor
 | 
			
		||||
    public RecordVideoWidgetModel(@NonNull DJISDKModel djiSdkModel,
 | 
			
		||||
                                  @NonNull ObservableInMemoryKeyedStore uxKeyManager) {
 | 
			
		||||
        super(djiSdkModel, uxKeyManager);
 | 
			
		||||
        CameraSDVideoStorageState cameraSDVideoStorageState = new CameraSDVideoStorageState(
 | 
			
		||||
                CameraStorageLocation.SDCARD,
 | 
			
		||||
                0,
 | 
			
		||||
                CameraSDCardState.NOT_INSERTED);
 | 
			
		||||
        cameraVideoStorageState = DataProcessor.create(cameraSDVideoStorageState);
 | 
			
		||||
        isRecording = DataProcessor.create(false);
 | 
			
		||||
        cameraDisplayName = DataProcessor.create("");
 | 
			
		||||
        cameraType = DataProcessor.create(CameraType.NOT_SUPPORTED);
 | 
			
		||||
        recordingTimeInSeconds = DataProcessor.create(0);
 | 
			
		||||
        VideoResolutionFrameRate resolutionAndFrameRate = new VideoResolutionFrameRate(
 | 
			
		||||
                VideoResolution.UNKNOWN,
 | 
			
		||||
                VideoFrameRate.UNKNOWN);
 | 
			
		||||
        recordedVideoParameters = DataProcessor.create(resolutionAndFrameRate);
 | 
			
		||||
        nonSSDRecordedVideoParameters = DataProcessor.create(resolutionAndFrameRate);
 | 
			
		||||
        ssdRecordedVideoParameters = DataProcessor.create(resolutionAndFrameRate);
 | 
			
		||||
        storageLocation = DataProcessor.create(CameraStorageLocation.SDCARD);
 | 
			
		||||
        sdCardState = DataProcessor.create(CameraSDCardState.UNKNOWN_ERROR);
 | 
			
		||||
        storageState = DataProcessor.create(CameraSDCardState.NORMAL);
 | 
			
		||||
        innerStorageState = DataProcessor.create(CameraSDCardState.UNKNOWN_ERROR);
 | 
			
		||||
        ssdState = DataProcessor.create(SSDOperationState.UNKNOWN);
 | 
			
		||||
        cameraStorageInfos = DataProcessor.create(new CameraStorageInfos(CameraStorageLocation.UNKNOWN, new ArrayList<>()));
 | 
			
		||||
        recordingStateProcessor = DataProcessor.create(RecordingState.UNKNOWN);
 | 
			
		||||
        flatCameraModule = new FlatCameraModule();
 | 
			
		||||
        addModule(flatCameraModule);
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Lifecycle
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void inSetup() {
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeyIsRecording, cameraIndex), isRecording, newValue -> {
 | 
			
		||||
            if (newValue == null) {
 | 
			
		||||
                recordingStateProcessor.onNext(RecordingState.UNKNOWN);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if (lastIsRecording && !newValue) { //只有从ture变化为false时,才发stopped
 | 
			
		||||
                recordingStateProcessor.onNext(RecordingState.RECORDING_STOPPED);
 | 
			
		||||
            } else if (newValue) {
 | 
			
		||||
                recordingStateProcessor.onNext(RecordingState.RECORDING_IN_PROGRESS);
 | 
			
		||||
            } else {
 | 
			
		||||
                recordingStateProcessor.onNext(RecordingState.RECORDING_NOT_STARED);
 | 
			
		||||
            }
 | 
			
		||||
            lastIsRecording = newValue;
 | 
			
		||||
        });
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeyRecordingTime, cameraIndex), recordingTimeInSeconds);
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeyCameraType, cameraIndex), cameraType, type -> cameraDisplayName.onNext(type.name()));
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeyCameraStorageLocation, cameraIndex), storageLocation);
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeyCameraSDCardState, cameraIndex), sdCardState);
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeyInternalStorageState, cameraIndex), innerStorageState);
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeySSDOperationState, cameraIndex), ssdState);
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeyCameraStorageInfos, cameraIndex), cameraStorageInfos);
 | 
			
		||||
 | 
			
		||||
        // Resolution and Frame Rates
 | 
			
		||||
        bindDataProcessor(KeyTools.createCameraKey(CameraKey.KeyVideoResolutionFrameRate, cameraIndex, lensType), nonSSDRecordedVideoParameters);
 | 
			
		||||
        bindDataProcessor(KeyTools.createCameraKey(CameraKey.KeySSDVideoResolutionFrameRate, cameraIndex, lensType), ssdRecordedVideoParameters);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void inCleanup() {
 | 
			
		||||
        // Do nothing
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void updateStates() {
 | 
			
		||||
        updateVideoStorageState();
 | 
			
		||||
        if (isRecording.getValue()) {
 | 
			
		||||
            checkIsOverRecordTime(recordingTimeInSeconds.getValue());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onProductConnectionChanged(boolean isConnected) {
 | 
			
		||||
        super.onProductConnectionChanged(isConnected);
 | 
			
		||||
        if (!isConnected) {
 | 
			
		||||
            recordingStateProcessor.onNext(RecordingState.UNKNOWN);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isVideoMode() {
 | 
			
		||||
        return flatCameraModule.getCameraModeDataProcessor().getValue().isVideoMode();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the current camera video storage state
 | 
			
		||||
     *
 | 
			
		||||
     * @return Flowable with {@link CameraVideoStorageState} instance
 | 
			
		||||
     */
 | 
			
		||||
    public Flowable<CameraVideoStorageState> getCameraVideoStorageState() {
 | 
			
		||||
        return cameraVideoStorageState.toFlowable();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check the recording state of the camera
 | 
			
		||||
     *
 | 
			
		||||
     * @return Flowable with {@link RecordingState} value
 | 
			
		||||
     */
 | 
			
		||||
    public Flowable<RecordingState> getRecordingState() {
 | 
			
		||||
        return recordingStateProcessor.toFlowable();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the display name of the camera which the model is reacting to
 | 
			
		||||
     *
 | 
			
		||||
     * @return Flowable with string of camera name
 | 
			
		||||
     */
 | 
			
		||||
    public Flowable<String> getCameraDisplayName() {
 | 
			
		||||
        return cameraDisplayName.toFlowable();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the duration of on going video recording in seconds
 | 
			
		||||
     *
 | 
			
		||||
     * @return Flowable with integer value representing seconds
 | 
			
		||||
     */
 | 
			
		||||
    public Flowable<Integer> getRecordingTimeInSeconds() {
 | 
			
		||||
        return recordingTimeInSeconds.toFlowable();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    public ComponentIndexType getCameraIndex() {
 | 
			
		||||
        return cameraIndex;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public CameraLensType getLensType() {
 | 
			
		||||
        return lensType;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void updateCameraSource(@NonNull ComponentIndexType cameraIndex, @NonNull CameraLensType lensType) {
 | 
			
		||||
        this.cameraIndex = cameraIndex;
 | 
			
		||||
        this.lensType = lensType;
 | 
			
		||||
        flatCameraModule.updateCameraSource(cameraIndex,lensType);
 | 
			
		||||
        restart();
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Actions
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Start video recording
 | 
			
		||||
     *
 | 
			
		||||
     * @return Completable to determine the status of the action
 | 
			
		||||
     */
 | 
			
		||||
    public Completable startRecordVideo() {
 | 
			
		||||
        if (isRecording.getValue()) {
 | 
			
		||||
            return Completable.complete();
 | 
			
		||||
        }
 | 
			
		||||
        return djiSdkModel.performActionWithOutResult(KeyTools.createKey(CameraKey.KeyStartRecord, cameraIndex));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Stop video recording
 | 
			
		||||
     *
 | 
			
		||||
     * @return Completable to determine the status of the action
 | 
			
		||||
     */
 | 
			
		||||
    public Completable stopRecordVideo() {
 | 
			
		||||
        if (!isRecording.getValue()) {
 | 
			
		||||
            return Completable.complete();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return djiSdkModel.performActionWithOutResult(KeyTools.createKey(CameraKey.KeyStopRecord, cameraIndex));
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Helpers
 | 
			
		||||
    private void updateVideoStorageState() {
 | 
			
		||||
        CameraStorageLocation currentStorageLocation = storageLocation.getValue();
 | 
			
		||||
        if (CameraStorageLocation.UNKNOWN.equals(currentStorageLocation)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int availableRecordingTime = getAvailableRecordingTime();
 | 
			
		||||
 | 
			
		||||
        CameraVideoStorageState newCameraVideoStorageState = null;
 | 
			
		||||
        if (CameraStorageLocation.SDCARD.equals(currentStorageLocation)) {
 | 
			
		||||
            if (!CameraSDCardState.UNKNOWN_ERROR.equals(sdCardState.getValue())) {
 | 
			
		||||
                newCameraVideoStorageState = new CameraSDVideoStorageState(currentStorageLocation, availableRecordingTime, sdCardState.getValue());
 | 
			
		||||
            } else if (!CameraSDCardState.UNKNOWN_ERROR.equals(storageState.getValue())) {
 | 
			
		||||
                newCameraVideoStorageState = new CameraSDVideoStorageState(currentStorageLocation, availableRecordingTime, storageState.getValue());
 | 
			
		||||
            }
 | 
			
		||||
            recordedVideoParameters.onNext(nonSSDRecordedVideoParameters.getValue());
 | 
			
		||||
        } else if (CameraStorageLocation.INTERNAL.equals(currentStorageLocation)) {
 | 
			
		||||
            newCameraVideoStorageState = new CameraSDVideoStorageState(currentStorageLocation, availableRecordingTime, innerStorageState.getValue());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (newCameraVideoStorageState != null) {
 | 
			
		||||
            cameraVideoStorageState.onNext(newCameraVideoStorageState);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private int getAvailableRecordingTime() {
 | 
			
		||||
        CameraStorageInfo info = cameraStorageInfos.getValue().getCurrentCameraStorageInfo();
 | 
			
		||||
        if (info == null) {
 | 
			
		||||
            return INVALID_AVAILABLE_RECORDING_TIME;
 | 
			
		||||
        }
 | 
			
		||||
        Integer availableVideoDuration = info.getAvailableVideoDuration();
 | 
			
		||||
        return availableVideoDuration == null ? INVALID_AVAILABLE_RECORDING_TIME : availableVideoDuration;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determine if the time is exceeded, and close the video in
 | 
			
		||||
     * {@link RecordVideoWidgetModel#MAX_VIDEO_TIME_THRESHOLD_MINUTES} minutes.
 | 
			
		||||
     *
 | 
			
		||||
     * @param recordTime The time to check.
 | 
			
		||||
     */
 | 
			
		||||
    private void checkIsOverRecordTime(int recordTime) {
 | 
			
		||||
        if (recordTime > (MAX_VIDEO_TIME_THRESHOLD_MINUTES * SECONDS_PER_MIN)) {
 | 
			
		||||
            stopRecordVideo();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Classes
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The recording state of the camera.
 | 
			
		||||
     */
 | 
			
		||||
    public enum RecordingState {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * No product is connected, or the recording state is unknown.
 | 
			
		||||
         */
 | 
			
		||||
        UNKNOWN,
 | 
			
		||||
 | 
			
		||||
        RECORDING_NOT_STARED,
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The camera is recording video.
 | 
			
		||||
         */
 | 
			
		||||
        RECORDING_IN_PROGRESS,
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The camera is not recording video.
 | 
			
		||||
         */
 | 
			
		||||
        RECORDING_STOPPED
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,68 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.widget.cameracapture.shootphoto;
 | 
			
		||||
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraShootPhotoMode;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.PhotoAEBPhotoCount;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Camera AEB Photo State
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Class represents camera photo state. It will be returned when device is in
 | 
			
		||||
 * CameraShootPhotoMode AEB mode.
 | 
			
		||||
 * It will also return the aeb count
 | 
			
		||||
 */
 | 
			
		||||
public class CameraAEBPhotoState extends CameraPhotoState {
 | 
			
		||||
 | 
			
		||||
    private PhotoAEBPhotoCount photoAEBCount;
 | 
			
		||||
 | 
			
		||||
    public CameraAEBPhotoState(CameraShootPhotoMode shootPhotoMode, PhotoAEBPhotoCount photoAEBCount) {
 | 
			
		||||
        super(shootPhotoMode);
 | 
			
		||||
        this.photoAEBCount = photoAEBCount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get AEB photo count
 | 
			
		||||
     */
 | 
			
		||||
    public PhotoAEBPhotoCount getPhotoAEBCount() {
 | 
			
		||||
        return photoAEBCount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object obj) {
 | 
			
		||||
        if (obj instanceof CameraAEBPhotoState) {
 | 
			
		||||
            return ((CameraAEBPhotoState) obj).getShootPhotoMode() == this.getShootPhotoMode()
 | 
			
		||||
                    && ((CameraAEBPhotoState) obj).getPhotoAEBCount() == this.getPhotoAEBCount();
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int hashCode() {
 | 
			
		||||
        int result = 31 * getShootPhotoMode().value();
 | 
			
		||||
        result = result + 31 * getPhotoAEBCount().value();
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,71 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.widget.cameracapture.shootphoto;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraShootPhotoMode;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.PhotoBurstCount;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Camera Burst Photo State
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Class represents camera photo state. It will be returned when device is in
 | 
			
		||||
 * CameraShootPhotoMode photo burst mode.
 | 
			
		||||
 * It will also return the burst count.
 | 
			
		||||
 */
 | 
			
		||||
public class CameraBurstPhotoState extends CameraPhotoState {
 | 
			
		||||
 | 
			
		||||
    private PhotoBurstCount photoBurstCount;
 | 
			
		||||
 | 
			
		||||
    public CameraBurstPhotoState(@NonNull CameraShootPhotoMode shootPhotoMode, @NonNull PhotoBurstCount photoBurstCount) {
 | 
			
		||||
        super(shootPhotoMode);
 | 
			
		||||
        this.photoBurstCount = photoBurstCount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the burst count
 | 
			
		||||
     *
 | 
			
		||||
     * @return PhotoBurstCount
 | 
			
		||||
     */
 | 
			
		||||
    public PhotoBurstCount getPhotoBurstCount() {
 | 
			
		||||
        return photoBurstCount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object obj) {
 | 
			
		||||
        if (obj instanceof CameraBurstPhotoState) {
 | 
			
		||||
            return ((CameraBurstPhotoState) obj).getShootPhotoMode() == this.getShootPhotoMode()
 | 
			
		||||
                    && ((CameraBurstPhotoState) obj).getPhotoBurstCount() == this.getPhotoBurstCount();
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int hashCode() {
 | 
			
		||||
        int result = 31 * getShootPhotoMode().value();
 | 
			
		||||
        result = result + 31 * getPhotoBurstCount().value();
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,83 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.widget.cameracapture.shootphoto;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraShootPhotoMode;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Camera Interval Photo State
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Class represents camera photo state. It will be returned when device is in
 | 
			
		||||
 * CameraShootPhotoMode interval mode.
 | 
			
		||||
 * It will also return the capture count and interval duration in seconds
 | 
			
		||||
 */
 | 
			
		||||
public class CameraIntervalPhotoState extends CameraPhotoState {
 | 
			
		||||
 | 
			
		||||
    private int captureCount;
 | 
			
		||||
    private int timeIntervalInSeconds;
 | 
			
		||||
 | 
			
		||||
    public CameraIntervalPhotoState(@NonNull CameraShootPhotoMode shootPhotoMode, int captureCount, int timeIntervalInSeconds) {
 | 
			
		||||
        super(shootPhotoMode);
 | 
			
		||||
        this.captureCount = captureCount;
 | 
			
		||||
        this.timeIntervalInSeconds = timeIntervalInSeconds;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get capture count
 | 
			
		||||
     *
 | 
			
		||||
     * @return int value
 | 
			
		||||
     */
 | 
			
		||||
    public int getCaptureCount() {
 | 
			
		||||
        return captureCount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get time interval in seconds
 | 
			
		||||
     *
 | 
			
		||||
     * @return int value
 | 
			
		||||
     */
 | 
			
		||||
    public int getTimeIntervalInSeconds() {
 | 
			
		||||
        return timeIntervalInSeconds;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object obj) {
 | 
			
		||||
        if (obj instanceof CameraIntervalPhotoState) {
 | 
			
		||||
            return ((CameraIntervalPhotoState) obj).getShootPhotoMode() == this.getShootPhotoMode()
 | 
			
		||||
                    && ((CameraIntervalPhotoState) obj).getCaptureCount() == this.getCaptureCount()
 | 
			
		||||
                    && ((CameraIntervalPhotoState) obj).getTimeIntervalInSeconds() == this.getTimeIntervalInSeconds();
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int hashCode() {
 | 
			
		||||
        int result = 31 * getShootPhotoMode().value();
 | 
			
		||||
        result = result + 31 * getCaptureCount();
 | 
			
		||||
        result = result + 31 * getTimeIntervalInSeconds();
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,70 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.widget.cameracapture.shootphoto;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraShootPhotoMode;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.PhotoPanoramaMode;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Camera Panorama Photo State
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Class represents camera photo state. It will be returned when device is in
 | 
			
		||||
 * CameraShootPhotoMode panorama mode.
 | 
			
		||||
 * It will also return the value of PhotoPanoramaMode
 | 
			
		||||
 */
 | 
			
		||||
public class CameraPanoramaPhotoState extends CameraPhotoState {
 | 
			
		||||
    private PhotoPanoramaMode photoPanoramaMode;
 | 
			
		||||
 | 
			
		||||
    public CameraPanoramaPhotoState(@NonNull CameraShootPhotoMode shootPhotoMode, @NonNull PhotoPanoramaMode photoPanoramaMode) {
 | 
			
		||||
        super(shootPhotoMode);
 | 
			
		||||
        this.photoPanoramaMode = photoPanoramaMode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the current panorama mode value
 | 
			
		||||
     *
 | 
			
		||||
     * @return instance of PhotoPanoramaMode
 | 
			
		||||
     */
 | 
			
		||||
    public PhotoPanoramaMode getPhotoPanoramaMode() {
 | 
			
		||||
        return photoPanoramaMode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object obj) {
 | 
			
		||||
        if (obj instanceof CameraPanoramaPhotoState) {
 | 
			
		||||
            return ((CameraPanoramaPhotoState) obj).getShootPhotoMode() == this.getShootPhotoMode()
 | 
			
		||||
                    && ((CameraPanoramaPhotoState) obj).getPhotoPanoramaMode() == this.getPhotoPanoramaMode();
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int hashCode() {
 | 
			
		||||
        int result = 31 * getShootPhotoMode().value();
 | 
			
		||||
        result = result + 31 * getPhotoPanoramaMode().value();
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,63 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.widget.cameracapture.shootphoto;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraShootPhotoMode;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Camera Photo State
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Class will be returned representing CameraShootPhotoMode
 | 
			
		||||
 */
 | 
			
		||||
public class CameraPhotoState {
 | 
			
		||||
    private CameraShootPhotoMode shootPhotoMode;
 | 
			
		||||
 | 
			
		||||
    public CameraPhotoState(CameraShootPhotoMode shootPhotoMode) {
 | 
			
		||||
        this.shootPhotoMode = shootPhotoMode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the current shoot photo phone
 | 
			
		||||
     *
 | 
			
		||||
     * @return instance of CameraShootPhotoMode
 | 
			
		||||
     */
 | 
			
		||||
    @NonNull
 | 
			
		||||
    public CameraShootPhotoMode getShootPhotoMode() {
 | 
			
		||||
        return shootPhotoMode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object obj) {
 | 
			
		||||
        if (obj instanceof CameraPhotoState) {
 | 
			
		||||
            return ((CameraPhotoState) obj).getShootPhotoMode() == this.getShootPhotoMode();
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int hashCode() {
 | 
			
		||||
        return 31 * shootPhotoMode.value();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,76 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.widget.cameracapture.shootphoto;
 | 
			
		||||
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraStorageLocation;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class represents the storage state when camera is in Shoot Photo Mode
 | 
			
		||||
 * The state determines the storage location and the available space
 | 
			
		||||
 * in terms of capture count
 | 
			
		||||
 */
 | 
			
		||||
public abstract class CameraPhotoStorageState {
 | 
			
		||||
    private final CameraStorageLocation storageLocation;
 | 
			
		||||
    private final long availableCaptureCount;
 | 
			
		||||
 | 
			
		||||
    protected CameraPhotoStorageState(CameraStorageLocation storageLocation, long availableCaptureCount) {
 | 
			
		||||
        this.storageLocation = storageLocation;
 | 
			
		||||
        this.availableCaptureCount = availableCaptureCount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *  Get the current storage location
 | 
			
		||||
     *
 | 
			
		||||
     * @return instance of CameraStorageLocation
 | 
			
		||||
     */
 | 
			
		||||
    public CameraStorageLocation getStorageLocation() {
 | 
			
		||||
        return storageLocation;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *  Get the number of photos that can be clicked before running out of storage
 | 
			
		||||
     *
 | 
			
		||||
     * @return long value representing count of photos
 | 
			
		||||
     */
 | 
			
		||||
    public long getAvailableCaptureCount() {
 | 
			
		||||
        return availableCaptureCount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object o) {
 | 
			
		||||
        if (this == o) return true;
 | 
			
		||||
        if (!(o instanceof CameraPhotoStorageState)) return false;
 | 
			
		||||
        CameraPhotoStorageState that = (CameraPhotoStorageState) o;
 | 
			
		||||
        return availableCaptureCount == that.availableCaptureCount &&
 | 
			
		||||
                storageLocation == that.storageLocation;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int hashCode() {
 | 
			
		||||
        int result = 31 * storageLocation.value();
 | 
			
		||||
        result = result + 31 * (int) availableCaptureCount;
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,69 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.widget.cameracapture.shootphoto;
 | 
			
		||||
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraStorageLocation;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.SDCardLoadState;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *  Class represents storage state for SD card and internal storage
 | 
			
		||||
 *  when camera is in shoot photo mode
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
public class CameraSDPhotoStorageState extends CameraPhotoStorageState {
 | 
			
		||||
    private SDCardLoadState storageOperationState;
 | 
			
		||||
 | 
			
		||||
    public CameraSDPhotoStorageState(CameraStorageLocation storageLocation, long availableCaptureCount, SDCardLoadState storageOperationState) {
 | 
			
		||||
        super(storageLocation, availableCaptureCount);
 | 
			
		||||
        this.storageOperationState = storageOperationState;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the operation state of current storage
 | 
			
		||||
     *
 | 
			
		||||
     * @return instance of CameraSDCardState
 | 
			
		||||
     *  representing state for SDCard or internal Storage
 | 
			
		||||
     */
 | 
			
		||||
    public SDCardLoadState getStorageOperationState() {
 | 
			
		||||
        return storageOperationState;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object o) {
 | 
			
		||||
        if (this == o) return true;
 | 
			
		||||
        if (!(o instanceof CameraSDPhotoStorageState)) return false;
 | 
			
		||||
        if (!super.equals(o)) return false;
 | 
			
		||||
        CameraSDPhotoStorageState that = (CameraSDPhotoStorageState) o;
 | 
			
		||||
        return storageOperationState == that.storageOperationState;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int hashCode() {
 | 
			
		||||
        int result = 31 * storageOperationState.value();
 | 
			
		||||
        result = result + 31 * getStorageLocation().value();
 | 
			
		||||
        result = result + 31 * (int) getAvailableCaptureCount();
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,62 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.widget.cameracapture.shootphoto;
 | 
			
		||||
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraStorageLocation;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.SSDOperationState;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class represents SSD Storage state for Shoot Photo Mode
 | 
			
		||||
 */
 | 
			
		||||
public class CameraSSDPhotoStorageState extends CameraPhotoStorageState {
 | 
			
		||||
    private SSDOperationState storageOperationState;
 | 
			
		||||
 | 
			
		||||
    public CameraSSDPhotoStorageState(CameraStorageLocation storageLocation, long availableCaptureCount, SSDOperationState storageOperationState) {
 | 
			
		||||
        super(storageLocation, availableCaptureCount);
 | 
			
		||||
        this.storageOperationState = storageOperationState;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get operation state of SSD
 | 
			
		||||
     *
 | 
			
		||||
     * @return {@link SSDOperationState}
 | 
			
		||||
     */
 | 
			
		||||
    public SSDOperationState getStorageOperationState() {
 | 
			
		||||
        return storageOperationState;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object o) {
 | 
			
		||||
        if (this == o) return true;
 | 
			
		||||
        if (!(o instanceof CameraSSDPhotoStorageState)) return false;
 | 
			
		||||
        if (!super.equals(o)) return false;
 | 
			
		||||
        CameraSSDPhotoStorageState that = (CameraSSDPhotoStorageState) o;
 | 
			
		||||
        return storageOperationState == that.storageOperationState;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int hashCode() {
 | 
			
		||||
        return 31 * storageOperationState.value();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,641 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.widget.cameracapture.shootphoto;
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.content.res.TypedArray;
 | 
			
		||||
import android.graphics.Color;
 | 
			
		||||
import android.graphics.drawable.Drawable;
 | 
			
		||||
import android.util.AttributeSet;
 | 
			
		||||
import android.util.Pair;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.widget.ImageView;
 | 
			
		||||
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.ColorInt;
 | 
			
		||||
import androidx.annotation.DrawableRes;
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.annotation.Nullable;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraShootPhotoMode;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraStorageLocation;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.SDCardLoadState;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.SSDOperationState;
 | 
			
		||||
import dji.sdk.keyvalue.value.common.CameraLensType;
 | 
			
		||||
import dji.sdk.keyvalue.value.common.ComponentIndexType;
 | 
			
		||||
import dji.v5.ux.R;
 | 
			
		||||
import dji.v5.ux.cameracore.ui.ProgressRingView;
 | 
			
		||||
import dji.v5.ux.cameracore.util.CameraActionSound;
 | 
			
		||||
import dji.v5.ux.cameracore.util.CameraResource;
 | 
			
		||||
import dji.v5.ux.cameracore.widget.cameracontrols.RemoteControllerButtonDownModel;
 | 
			
		||||
import dji.v5.ux.core.base.DJISDKModel;
 | 
			
		||||
import dji.v5.ux.core.base.ICameraIndex;
 | 
			
		||||
import dji.v5.ux.core.base.SchedulerProvider;
 | 
			
		||||
import dji.v5.ux.core.base.widget.ConstraintLayoutWidget;
 | 
			
		||||
import dji.v5.ux.core.communication.ObservableInMemoryKeyedStore;
 | 
			
		||||
import dji.v5.ux.core.util.UxErrorHandle;
 | 
			
		||||
import io.reactivex.rxjava3.core.Completable;
 | 
			
		||||
import io.reactivex.rxjava3.core.Flowable;
 | 
			
		||||
import io.reactivex.rxjava3.core.Single;
 | 
			
		||||
import io.reactivex.rxjava3.disposables.Disposable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Shoot Photo Widget
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Widget can be used for shooting photo. The widget displays the current photo mode. It also
 | 
			
		||||
 * displays the storage state and errors associated with it.
 | 
			
		||||
 */
 | 
			
		||||
public class ShootPhotoWidget extends ConstraintLayoutWidget<Object> implements View.OnClickListener, ICameraIndex {
 | 
			
		||||
    //region Fields
 | 
			
		||||
    private static final String TAG = "ShootPhotoWidget";
 | 
			
		||||
    private ShootPhotoWidgetModel widgetModel;
 | 
			
		||||
    private RemoteControllerButtonDownModel buttonDownModel;
 | 
			
		||||
    private ProgressRingView borderProgressRingView;
 | 
			
		||||
    private ImageView centerImageView;
 | 
			
		||||
    private ImageView storageStatusOverlayImageView;
 | 
			
		||||
    private Drawable startShootPhotoDrawable;
 | 
			
		||||
    private Drawable stopShootPhotoDrawable;
 | 
			
		||||
    @ColorInt
 | 
			
		||||
    private int progressRingColor;
 | 
			
		||||
    private Map<StorageIconState, Drawable> storageInternalIconMap;
 | 
			
		||||
    private Map<StorageIconState, Drawable> storageSSDIconMap;
 | 
			
		||||
    private Map<StorageIconState, Drawable> storageSDCardIconMap;
 | 
			
		||||
    private CameraActionSound cameraActionSound;
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Lifecycle
 | 
			
		||||
    public ShootPhotoWidget(Context context) {
 | 
			
		||||
        super(context);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ShootPhotoWidget(Context context, AttributeSet attrs) {
 | 
			
		||||
        super(context, attrs);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ShootPhotoWidget(Context context, AttributeSet attrs, int defStyleAttr) {
 | 
			
		||||
        super(context, attrs, defStyleAttr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void initView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
 | 
			
		||||
        inflate(context, R.layout.uxsdk_widget_shoot_photo, this);
 | 
			
		||||
        borderProgressRingView = findViewById(R.id.progress_ring_view_border);
 | 
			
		||||
        centerImageView = findViewById(R.id.image_view_center);
 | 
			
		||||
        storageStatusOverlayImageView = findViewById(R.id.image_view_storage_status_overlay);
 | 
			
		||||
        storageInternalIconMap = new HashMap<>();
 | 
			
		||||
        storageSSDIconMap = new HashMap<>();
 | 
			
		||||
        storageSDCardIconMap = new HashMap<>();
 | 
			
		||||
        cameraActionSound = new CameraActionSound(context);
 | 
			
		||||
        if (!isInEditMode()) {
 | 
			
		||||
            centerImageView.setOnClickListener(this);
 | 
			
		||||
            widgetModel = new ShootPhotoWidgetModel(DJISDKModel.getInstance(), ObservableInMemoryKeyedStore.getInstance());
 | 
			
		||||
            buttonDownModel = new RemoteControllerButtonDownModel(DJISDKModel.getInstance(), ObservableInMemoryKeyedStore.getInstance());
 | 
			
		||||
        }
 | 
			
		||||
        initDefaults();
 | 
			
		||||
        if (attrs != null) {
 | 
			
		||||
            initAttributes(context, attrs);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onAttachedToWindow() {
 | 
			
		||||
        super.onAttachedToWindow();
 | 
			
		||||
        if (!isInEditMode()) {
 | 
			
		||||
            widgetModel.setup();
 | 
			
		||||
            buttonDownModel.setup();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onDetachedFromWindow() {
 | 
			
		||||
        if (!isInEditMode()) {
 | 
			
		||||
            widgetModel.cleanup();
 | 
			
		||||
            buttonDownModel.cleanup();
 | 
			
		||||
        }
 | 
			
		||||
        super.onDetachedFromWindow();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void reactToModelChanges() {
 | 
			
		||||
        addReaction(widgetModel.isShootingPhoto().observeOn(SchedulerProvider.ui())
 | 
			
		||||
                .subscribe(this::onIsShootingPhotoChange, UxErrorHandle.logErrorConsumer(TAG, "isShootingPhoto: ")));
 | 
			
		||||
        addReaction(reactToCanStartOrStopShootingPhoto());
 | 
			
		||||
        addReaction(reactToPhotoStateAndPhotoStorageState());
 | 
			
		||||
        addReaction(buttonDownModel.isShutterButtonDownProcessor().toFlowable()
 | 
			
		||||
                .observeOn(SchedulerProvider.ui())
 | 
			
		||||
                .subscribe(aBoolean -> {
 | 
			
		||||
                    if (aBoolean == Boolean.TRUE) {
 | 
			
		||||
                        actionOnShootingPhoto();
 | 
			
		||||
                    }
 | 
			
		||||
                }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getIdealDimensionRatioString() {
 | 
			
		||||
        return getContext().getResources().getString(R.string.uxsdk_widget_default_ratio);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    public ComponentIndexType getCameraIndex() {
 | 
			
		||||
        return widgetModel.getCameraIndex();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public CameraLensType getLensType() {
 | 
			
		||||
        return widgetModel.getLensType();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void updateCameraSource(@NonNull ComponentIndexType cameraIndex, @NonNull CameraLensType lensType) {
 | 
			
		||||
        if (!isInEditMode()) {
 | 
			
		||||
            widgetModel.updateCameraSource(cameraIndex, lensType);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onClick(View v) {
 | 
			
		||||
        if (v.equals(centerImageView)) {
 | 
			
		||||
            actionOnShootingPhoto();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void actionOnShootingPhoto() {
 | 
			
		||||
        if (!widgetModel.isPhotoMode()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Single<Boolean> stop = widgetModel.canStopShootingPhoto().firstOrError();
 | 
			
		||||
        Single<Boolean> start = widgetModel.canStartShootingPhoto().firstOrError();
 | 
			
		||||
 | 
			
		||||
        addDisposable(Single.zip(stop, start, Pair::new).flatMapCompletable(pairs -> {
 | 
			
		||||
                    if (pairs.first) {
 | 
			
		||||
                        return widgetModel.stopShootPhoto();
 | 
			
		||||
                    } else if (pairs.second) {
 | 
			
		||||
                        return widgetModel.startShootPhoto();
 | 
			
		||||
                    }
 | 
			
		||||
                    return Completable.complete();
 | 
			
		||||
                }).observeOn(SchedulerProvider.ui())
 | 
			
		||||
                .subscribe(() -> {
 | 
			
		||||
                }, UxErrorHandle.logErrorConsumer(TAG, "Start Stop Shoot Photo")));
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region private helpers
 | 
			
		||||
    private Drawable getCameraResourceDrawable(int resourceId) {
 | 
			
		||||
        return getResources().getDrawable(resourceId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void updateCameraForegroundResource(@NonNull CameraPhotoState cameraPhotoState, @NonNull CameraPhotoStorageState cameraPhotoStorageState) {
 | 
			
		||||
        Drawable foregroundDrawable = updateCameraActionSound(cameraPhotoState);
 | 
			
		||||
 | 
			
		||||
        if (cameraPhotoStorageState instanceof CameraSDPhotoStorageState) {
 | 
			
		||||
            CameraSDPhotoStorageState sdStorageState = (CameraSDPhotoStorageState) cameraPhotoStorageState;
 | 
			
		||||
            if (cameraPhotoStorageState.getStorageLocation() == CameraStorageLocation.SDCARD) {
 | 
			
		||||
                foregroundDrawable = updateResourceWithStorageInSDCard(sdStorageState);
 | 
			
		||||
            } else if (cameraPhotoStorageState.getStorageLocation() == CameraStorageLocation.INTERNAL) {
 | 
			
		||||
                foregroundDrawable = updateResourceWithStorageInternal(sdStorageState);
 | 
			
		||||
            }
 | 
			
		||||
        } else if (cameraPhotoStorageState instanceof CameraSSDPhotoStorageState) {
 | 
			
		||||
            CameraSSDPhotoStorageState ssdStorageState = (CameraSSDPhotoStorageState) cameraPhotoStorageState;
 | 
			
		||||
            if (ssdStorageState.getStorageOperationState() == SSDOperationState.NOT_FOUND) {
 | 
			
		||||
                foregroundDrawable = getSSDStorageIcon(StorageIconState.NOT_INSERTED);
 | 
			
		||||
            } else if (ssdStorageState.getStorageOperationState() == SSDOperationState.FULL) {
 | 
			
		||||
                foregroundDrawable = getSSDStorageIcon(StorageIconState.FULL);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        storageStatusOverlayImageView.setImageDrawable(foregroundDrawable);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Drawable updateResourceWithStorageInternal(CameraSDPhotoStorageState sdStorageState) {
 | 
			
		||||
        Drawable foregroundDrawable = null;
 | 
			
		||||
        if (sdStorageState.getStorageOperationState() == SDCardLoadState.NOT_INSERTED) {
 | 
			
		||||
            foregroundDrawable = getInternalStorageIcon(StorageIconState.NOT_INSERTED);
 | 
			
		||||
        }
 | 
			
		||||
        return foregroundDrawable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Drawable updateResourceWithStorageInSDCard(CameraSDPhotoStorageState sdStorageState) {
 | 
			
		||||
        Drawable foregroundDrawable = null;
 | 
			
		||||
        if (sdStorageState.getStorageOperationState() == SDCardLoadState.NOT_INSERTED) {
 | 
			
		||||
            foregroundDrawable = getSDCardStorageIcon(StorageIconState.NOT_INSERTED);
 | 
			
		||||
        }
 | 
			
		||||
        return foregroundDrawable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Drawable updateCameraActionSound(CameraPhotoState cameraPhotoState) {
 | 
			
		||||
        Drawable foregroundDrawable = null;
 | 
			
		||||
        if (cameraPhotoState instanceof CameraPanoramaPhotoState) {
 | 
			
		||||
            foregroundDrawable = getCameraResourceDrawable(CameraResource.getPhotoModeImgResId(cameraPhotoState.getShootPhotoMode().value(),
 | 
			
		||||
                    ((CameraPanoramaPhotoState) cameraPhotoState).getPhotoPanoramaMode().value()));
 | 
			
		||||
            cameraActionSound.setShutterCount(CameraActionSound.ShutterSoundCount.ONE);
 | 
			
		||||
        } else if (cameraPhotoState instanceof CameraAEBPhotoState) {
 | 
			
		||||
            int photoCount = ((CameraAEBPhotoState) cameraPhotoState).getPhotoAEBCount().value();
 | 
			
		||||
            foregroundDrawable = getCameraResourceDrawable(CameraResource.getPhotoModeImgResId(cameraPhotoState.getShootPhotoMode().value(),
 | 
			
		||||
                    photoCount));
 | 
			
		||||
            cameraActionSound.setShutterCount(CameraActionSound.ShutterSoundCount.find(photoCount));
 | 
			
		||||
        } else if (cameraPhotoState instanceof CameraBurstPhotoState) {
 | 
			
		||||
            int photoCount = ((CameraBurstPhotoState) cameraPhotoState).getPhotoBurstCount().value();
 | 
			
		||||
            foregroundDrawable = getCameraResourceDrawable(CameraResource.getPhotoModeImgResId(cameraPhotoState.getShootPhotoMode().value(),
 | 
			
		||||
                    photoCount));
 | 
			
		||||
            cameraActionSound.setShutterCount(CameraActionSound.ShutterSoundCount.find(photoCount));
 | 
			
		||||
        } else if (cameraPhotoState instanceof CameraIntervalPhotoState) {
 | 
			
		||||
            foregroundDrawable = getCameraResourceDrawable(CameraResource.getPhotoModeImgResId(cameraPhotoState.getShootPhotoMode().value(),
 | 
			
		||||
                    ((CameraIntervalPhotoState) cameraPhotoState).getTimeIntervalInSeconds()));
 | 
			
		||||
            cameraActionSound.setShutterCount(CameraActionSound.ShutterSoundCount.ONE);
 | 
			
		||||
        } else {
 | 
			
		||||
            if (cameraPhotoState.getShootPhotoMode() != CameraShootPhotoMode.NORMAL) {
 | 
			
		||||
                foregroundDrawable = getCameraResourceDrawable(CameraResource.getPhotoModeImgResId(cameraPhotoState.getShootPhotoMode().value(),
 | 
			
		||||
                        0));
 | 
			
		||||
                cameraActionSound.setShutterCount(CameraActionSound.ShutterSoundCount.ONE);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return foregroundDrawable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void onIsShootingPhotoChange(boolean isShootingPhoto) {
 | 
			
		||||
 | 
			
		||||
        borderProgressRingView.setIndeterminate(isShootingPhoto);
 | 
			
		||||
        if (isShootingPhoto) {
 | 
			
		||||
            cameraActionSound.playCapturePhoto();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Disposable reactToPhotoStateAndPhotoStorageState() {
 | 
			
		||||
        return Flowable.combineLatest(widgetModel.getCameraPhotoState(), widgetModel.getCameraStorageState(), Pair::new)
 | 
			
		||||
                .observeOn(SchedulerProvider.ui())
 | 
			
		||||
                .subscribe(values -> updateCameraForegroundResource(values.first, values.second),
 | 
			
		||||
                        UxErrorHandle.logErrorConsumer(TAG, "reactToPhotoStateAndPhotoStorageState "));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Disposable reactToCanStartOrStopShootingPhoto() {
 | 
			
		||||
        return Flowable.combineLatest(widgetModel.canStartShootingPhoto(), widgetModel.canStopShootingPhoto(), Pair::new)
 | 
			
		||||
                .observeOn(SchedulerProvider.ui())
 | 
			
		||||
                .subscribe(values -> updateImages(values.first, values.second),
 | 
			
		||||
                        UxErrorHandle.logErrorConsumer(TAG, "reactToCanStartOrStopShootingPhoto: "));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void checkAndUpdatePhotoStateAndPhotoStorageState() {
 | 
			
		||||
        if (!isInEditMode()) {
 | 
			
		||||
            addDisposable(Flowable.combineLatest(widgetModel.getCameraPhotoState(),
 | 
			
		||||
                            widgetModel.getCameraStorageState(),
 | 
			
		||||
                            Pair::new)
 | 
			
		||||
                    .firstOrError()
 | 
			
		||||
                    .observeOn(SchedulerProvider.ui())
 | 
			
		||||
                    .subscribe(values -> updateCameraForegroundResource(values.first, values.second),
 | 
			
		||||
                            UxErrorHandle.logErrorConsumer(TAG, "checkAndUpdatePhotoStateAndPhotoStorageState ")));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void checkAndUpdateCanStartOrStopShootingPhoto() {
 | 
			
		||||
        if (!isInEditMode()) {
 | 
			
		||||
            addDisposable(Flowable.combineLatest(
 | 
			
		||||
                            widgetModel.canStartShootingPhoto(),
 | 
			
		||||
                            widgetModel.canStopShootingPhoto(),
 | 
			
		||||
                            Pair::new)
 | 
			
		||||
                    .firstOrError()
 | 
			
		||||
                    .observeOn(SchedulerProvider.ui())
 | 
			
		||||
                    .subscribe(values -> updateImages(values.first, values.second),
 | 
			
		||||
                            UxErrorHandle.logErrorConsumer(TAG, "checkAndUpdateCanStartOrStopShootingPhoto: ")));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void updateImages(boolean canStartShootingPhoto, boolean canStopShootingPhoto) {
 | 
			
		||||
        if (!canStopShootingPhoto) {
 | 
			
		||||
            enableAction(canStartShootingPhoto);
 | 
			
		||||
            centerImageView.setImageDrawable(startShootPhotoDrawable);
 | 
			
		||||
        } else {
 | 
			
		||||
            enableAction(true);
 | 
			
		||||
            centerImageView.setImageDrawable(stopShootPhotoDrawable);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        storageStatusOverlayImageView.setVisibility(canStopShootingPhoto ? View.GONE : View.VISIBLE);
 | 
			
		||||
        borderProgressRingView.setRingColor(progressRingColor);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void enableAction(boolean isEnabled) {
 | 
			
		||||
        centerImageView.setEnabled(isEnabled);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initDefaults() {
 | 
			
		||||
        startShootPhotoDrawable = getResources().getDrawable(R.drawable.uxsdk_shape_circle);
 | 
			
		||||
        stopShootPhotoDrawable = getResources().getDrawable(R.drawable.uxsdk_ic_shutter_stop);
 | 
			
		||||
        setProgressRingColor(Color.WHITE);
 | 
			
		||||
        setInternalStorageIcon(StorageIconState.NOT_INSERTED, R.drawable.uxsdk_ic_internal_storage_not_inserted);
 | 
			
		||||
        setInternalStorageIcon(StorageIconState.SLOW, R.drawable.uxsdk_ic_internal_storage_slow);
 | 
			
		||||
        setInternalStorageIcon(StorageIconState.FULL, R.drawable.uxsdk_ic_internal_storage_full);
 | 
			
		||||
        setSDCardStorageIcon(StorageIconState.NOT_INSERTED, R.drawable.uxsdk_ic_sdcard_not_inserted);
 | 
			
		||||
        setSDCardStorageIcon(StorageIconState.SLOW, R.drawable.uxsdk_ic_sdcard_slow);
 | 
			
		||||
        setSDCardStorageIcon(StorageIconState.FULL, R.drawable.uxsdk_ic_sdcard_full);
 | 
			
		||||
        setSSDStorageIcon(StorageIconState.NOT_INSERTED, R.drawable.uxsdk_ic_ssd_not_inserted);
 | 
			
		||||
        setSSDStorageIcon(StorageIconState.FULL, R.drawable.uxsdk_ic_ssd_full);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initAttributes(Context context, AttributeSet attrs) {
 | 
			
		||||
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ShootPhotoWidget);
 | 
			
		||||
        updateCameraSource(ComponentIndexType.find(typedArray.getInt(R.styleable.ShootPhotoWidget_uxsdk_cameraIndex, 0)), CameraLensType.UNKNOWN);
 | 
			
		||||
 | 
			
		||||
        setForegroundIconBackground(typedArray.getDrawable(R.styleable.ShootPhotoWidget_uxsdk_foregroundIconBackground));
 | 
			
		||||
        setProgressRingColor(typedArray.getColor(R.styleable.ShootPhotoWidget_uxsdk_progressRingColor, Color.WHITE));
 | 
			
		||||
 | 
			
		||||
        initShootPhotoDrawable(typedArray);
 | 
			
		||||
        initInternalStorageIcon(typedArray);
 | 
			
		||||
        initSSDStorageIcon(typedArray);
 | 
			
		||||
        initSDCardStorageIcon(typedArray);
 | 
			
		||||
        typedArray.recycle();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initSSDStorageIcon(TypedArray typedArray) {
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.ShootPhotoWidget_uxsdk_ssdNotInsertedIcon) != null) {
 | 
			
		||||
            setSSDStorageIcon(StorageIconState.NOT_INSERTED, typedArray.getDrawable(R.styleable.ShootPhotoWidget_uxsdk_ssdNotInsertedIcon));
 | 
			
		||||
        }
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.ShootPhotoWidget_uxsdk_ssdFullIcon) != null) {
 | 
			
		||||
            setSSDStorageIcon(StorageIconState.FULL, typedArray.getDrawable(R.styleable.ShootPhotoWidget_uxsdk_ssdFullIcon));
 | 
			
		||||
        }
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.ShootPhotoWidget_uxsdk_ssdSlowIcon) != null) {
 | 
			
		||||
            setSSDStorageIcon(StorageIconState.SLOW, typedArray.getDrawable(R.styleable.ShootPhotoWidget_uxsdk_ssdSlowIcon));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initSDCardStorageIcon(TypedArray typedArray) {
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.ShootPhotoWidget_uxsdk_sdCardNotInsertedIcon) != null) {
 | 
			
		||||
            setSDCardStorageIcon(StorageIconState.NOT_INSERTED, typedArray.getDrawable(R.styleable.ShootPhotoWidget_uxsdk_sdCardNotInsertedIcon));
 | 
			
		||||
        }
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.ShootPhotoWidget_uxsdk_sdCardFullIcon) != null) {
 | 
			
		||||
            setSDCardStorageIcon(StorageIconState.FULL, typedArray.getDrawable(R.styleable.ShootPhotoWidget_uxsdk_sdCardFullIcon));
 | 
			
		||||
        }
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.ShootPhotoWidget_uxsdk_sdCardSlowIcon) != null) {
 | 
			
		||||
            setSDCardStorageIcon(StorageIconState.SLOW, typedArray.getDrawable(R.styleable.ShootPhotoWidget_uxsdk_sdCardSlowIcon));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initInternalStorageIcon(TypedArray typedArray) {
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.ShootPhotoWidget_uxsdk_internalStorageNotInsertedIcon) != null) {
 | 
			
		||||
            setInternalStorageIcon(StorageIconState.NOT_INSERTED, typedArray.getDrawable(R.styleable.ShootPhotoWidget_uxsdk_internalStorageNotInsertedIcon));
 | 
			
		||||
        }
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.ShootPhotoWidget_uxsdk_internalStorageFullIcon) != null) {
 | 
			
		||||
            setInternalStorageIcon(StorageIconState.FULL, typedArray.getDrawable(R.styleable.ShootPhotoWidget_uxsdk_internalStorageFullIcon));
 | 
			
		||||
        }
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.ShootPhotoWidget_uxsdk_internalStorageSlowIcon) != null) {
 | 
			
		||||
            setInternalStorageIcon(StorageIconState.SLOW, typedArray.getDrawable(R.styleable.ShootPhotoWidget_uxsdk_internalStorageSlowIcon));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initShootPhotoDrawable(TypedArray typedArray) {
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.ShootPhotoWidget_uxsdk_shootPhotoStartIcon) != null) {
 | 
			
		||||
            startShootPhotoDrawable = typedArray.getDrawable(R.styleable.ShootPhotoWidget_uxsdk_shootPhotoStartIcon);
 | 
			
		||||
        }
 | 
			
		||||
        if (typedArray.getDrawable(R.styleable.ShootPhotoWidget_uxsdk_shootPhotoStopIcon) != null) {
 | 
			
		||||
            stopShootPhotoDrawable = typedArray.getDrawable(R.styleable.ShootPhotoWidget_uxsdk_shootPhotoStopIcon);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region customizations
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the current start shooting photo icon
 | 
			
		||||
     *
 | 
			
		||||
     * @return Drawable currently used
 | 
			
		||||
     */
 | 
			
		||||
    public Drawable getStartShootPhotoDrawable() {
 | 
			
		||||
        return startShootPhotoDrawable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the start shoot photo icon
 | 
			
		||||
     *
 | 
			
		||||
     * @param resourceId to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setStartShootPhotoDrawable(@DrawableRes int resourceId) {
 | 
			
		||||
        setStartShootPhotoDrawable(getResources().getDrawable(resourceId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the start shoot photo icon
 | 
			
		||||
     *
 | 
			
		||||
     * @param drawable to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setStartShootPhotoDrawable(@Nullable Drawable drawable) {
 | 
			
		||||
        startShootPhotoDrawable = drawable;
 | 
			
		||||
        checkAndUpdateCanStartOrStopShootingPhoto();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the current stop shooting photo icon
 | 
			
		||||
     *
 | 
			
		||||
     * @return Drawable currently used
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public Drawable getStopShootPhotoDrawable() {
 | 
			
		||||
        return stopShootPhotoDrawable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set stop shoot photo icon
 | 
			
		||||
     *
 | 
			
		||||
     * @param resourceId to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setStopShootPhotoDrawable(@DrawableRes int resourceId) {
 | 
			
		||||
        setStopShootPhotoDrawable(getResources().getDrawable(resourceId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set stop shoot photo icon
 | 
			
		||||
     *
 | 
			
		||||
     * @param drawable to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setStopShootPhotoDrawable(@Nullable Drawable drawable) {
 | 
			
		||||
        stopShootPhotoDrawable = drawable;
 | 
			
		||||
        checkAndUpdateCanStartOrStopShootingPhoto();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the icon for internal storage based on storage icon state
 | 
			
		||||
     *
 | 
			
		||||
     * @param storageIconState for which icon is used
 | 
			
		||||
     * @param resourceId       to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setInternalStorageIcon(@NonNull StorageIconState storageIconState, @DrawableRes int resourceId) {
 | 
			
		||||
        setInternalStorageIcon(storageIconState, getResources().getDrawable(resourceId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the icon for internal storage based on storage icon state
 | 
			
		||||
     *
 | 
			
		||||
     * @param storageIconState for which icon is used
 | 
			
		||||
     * @param drawable         to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setInternalStorageIcon(@NonNull StorageIconState storageIconState, @Nullable Drawable drawable) {
 | 
			
		||||
        storageInternalIconMap.put(storageIconState, drawable);
 | 
			
		||||
        checkAndUpdatePhotoStateAndPhotoStorageState();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the icon for internal storage based on storage icon state
 | 
			
		||||
     *
 | 
			
		||||
     * @param storageIconState for which icon is used
 | 
			
		||||
     * @return Drawable currently used
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public Drawable getInternalStorageIcon(@NonNull StorageIconState storageIconState) {
 | 
			
		||||
        return storageInternalIconMap.get(storageIconState);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the icon for SD card storage based on storage icon state
 | 
			
		||||
     *
 | 
			
		||||
     * @param storageIconState for which icon is used
 | 
			
		||||
     * @param resourceId       to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setSDCardStorageIcon(@NonNull StorageIconState storageIconState, @DrawableRes int resourceId) {
 | 
			
		||||
        setSDCardStorageIcon(storageIconState, getResources().getDrawable(resourceId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the icon for SD card storage based on storage icon state
 | 
			
		||||
     *
 | 
			
		||||
     * @param storageIconState for which icon is used
 | 
			
		||||
     * @param drawable         to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setSDCardStorageIcon(@NonNull StorageIconState storageIconState, @Nullable Drawable drawable) {
 | 
			
		||||
        storageSDCardIconMap.put(storageIconState, drawable);
 | 
			
		||||
        checkAndUpdatePhotoStateAndPhotoStorageState();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the icon for SD card storage based on storage icon state
 | 
			
		||||
     *
 | 
			
		||||
     * @param storageIconState for which icon is used
 | 
			
		||||
     * @return Drawable currently used
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public Drawable getSDCardStorageIcon(@NonNull StorageIconState storageIconState) {
 | 
			
		||||
        return storageSDCardIconMap.get(storageIconState);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the icon for SSD storage based on storage icon state
 | 
			
		||||
     *
 | 
			
		||||
     * @param storageIconState for which icon should be used
 | 
			
		||||
     * @param resourceId       to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setSSDStorageIcon(@NonNull StorageIconState storageIconState, @DrawableRes int resourceId) {
 | 
			
		||||
        setSSDStorageIcon(storageIconState, getResources().getDrawable(resourceId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the icon for SSD storage based on storage icon state
 | 
			
		||||
     *
 | 
			
		||||
     * @param storageIconState for which icon should be used
 | 
			
		||||
     * @param drawable         Drawable to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setSSDStorageIcon(@NonNull StorageIconState storageIconState, @Nullable Drawable drawable) {
 | 
			
		||||
        storageSSDIconMap.put(storageIconState, drawable);
 | 
			
		||||
        checkAndUpdatePhotoStateAndPhotoStorageState();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the icon for SSD storage based on storage icon state
 | 
			
		||||
     *
 | 
			
		||||
     * @param storageIconState for which icon is used
 | 
			
		||||
     * @return Drawable currently used
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public Drawable getSSDStorageIcon(@NonNull StorageIconState storageIconState) {
 | 
			
		||||
        return storageSSDIconMap.get(storageIconState);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get current background of foreground icon
 | 
			
		||||
     *
 | 
			
		||||
     * @return Drawable being used
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public Drawable getForegroundIconBackground() {
 | 
			
		||||
        return storageStatusOverlayImageView.getBackground();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the background of the foreground icon
 | 
			
		||||
     *
 | 
			
		||||
     * @param resourceId to be used as background
 | 
			
		||||
     */
 | 
			
		||||
    public void setForegroundIconBackground(@DrawableRes int resourceId) {
 | 
			
		||||
        storageStatusOverlayImageView.setBackgroundResource(resourceId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set background of the foreground icon
 | 
			
		||||
     *
 | 
			
		||||
     * @param drawable to be used as background
 | 
			
		||||
     */
 | 
			
		||||
    public void setForegroundIconBackground(@Nullable Drawable drawable) {
 | 
			
		||||
        storageStatusOverlayImageView.setBackground(drawable);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the color of the progress ring
 | 
			
		||||
     *
 | 
			
		||||
     * @return integer representing color
 | 
			
		||||
     */
 | 
			
		||||
    @ColorInt
 | 
			
		||||
    public int getProgressRingColor() {
 | 
			
		||||
        return progressRingColor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the color of the progress ring
 | 
			
		||||
     *
 | 
			
		||||
     * @param color integer value
 | 
			
		||||
     */
 | 
			
		||||
    public void setProgressRingColor(@ColorInt int color) {
 | 
			
		||||
        progressRingColor = color;
 | 
			
		||||
        checkAndUpdateCanStartOrStopShootingPhoto();
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Enum indicating storage error state
 | 
			
		||||
     */
 | 
			
		||||
    public enum StorageIconState {
 | 
			
		||||
        /**
 | 
			
		||||
         * The storage is slow
 | 
			
		||||
         */
 | 
			
		||||
        SLOW,
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The storage is full
 | 
			
		||||
         */
 | 
			
		||||
        FULL,
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The storage is not inserted
 | 
			
		||||
         */
 | 
			
		||||
        NOT_INSERTED
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,458 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.widget.cameracapture.shootphoto;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import dji.sdk.keyvalue.key.CameraKey;
 | 
			
		||||
import dji.sdk.keyvalue.key.KeyTools;
 | 
			
		||||
import dji.sdk.keyvalue.utils.ProductUtil;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraMode;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraShootPhotoMode;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraStorageInfo;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraStorageInfos;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraStorageLocation;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.CameraType;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.PhotoAEBPhotoCount;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.PhotoAEBSettings;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.PhotoBurstCount;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.PhotoIntervalShootSettings;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.PhotoPanoramaMode;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.SDCardLoadState;
 | 
			
		||||
import dji.sdk.keyvalue.value.camera.SSDOperationState;
 | 
			
		||||
import dji.sdk.keyvalue.value.common.CameraLensType;
 | 
			
		||||
import dji.sdk.keyvalue.value.common.ComponentIndexType;
 | 
			
		||||
import dji.v5.manager.KeyManager;
 | 
			
		||||
import dji.v5.ux.core.base.DJISDKModel;
 | 
			
		||||
import dji.v5.ux.core.base.ICameraIndex;
 | 
			
		||||
import dji.v5.ux.core.base.WidgetModel;
 | 
			
		||||
import dji.v5.ux.core.communication.ObservableInMemoryKeyedStore;
 | 
			
		||||
import dji.v5.ux.core.module.FlatCameraModule;
 | 
			
		||||
import dji.v5.ux.core.util.CameraUtil;
 | 
			
		||||
import dji.v5.ux.core.util.DataProcessor;
 | 
			
		||||
import io.reactivex.rxjava3.core.Completable;
 | 
			
		||||
import io.reactivex.rxjava3.core.Flowable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Shoot Photo Widget Model
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Widget Model for {@link ShootPhotoWidget} used to define underlying
 | 
			
		||||
 * logic and communication
 | 
			
		||||
 */
 | 
			
		||||
public class ShootPhotoWidgetModel extends WidgetModel implements ICameraIndex {
 | 
			
		||||
 | 
			
		||||
    //region Constants
 | 
			
		||||
    private static final int INVALID_AVAILABLE_CAPTURE_COUNT = -1;
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Public data
 | 
			
		||||
    private final DataProcessor<CameraPhotoState> cameraPhotoState;
 | 
			
		||||
    private final DataProcessor<CameraPhotoStorageState> cameraStorageState;
 | 
			
		||||
    private final DataProcessor<Boolean> isShootingPhoto;
 | 
			
		||||
    private final DataProcessor<Boolean> isStoringPhoto;
 | 
			
		||||
    private final DataProcessor<Boolean> canStartShootingPhoto;
 | 
			
		||||
    private final DataProcessor<Boolean> shootPhotoNotAllowed;
 | 
			
		||||
    private final DataProcessor<Boolean> canStopShootingPhoto;
 | 
			
		||||
    private final DataProcessor<String> cameraDisplayName;
 | 
			
		||||
    private final DataProcessor<Boolean> isShootingInterval;
 | 
			
		||||
    private final DataProcessor<CameraType> cameraType;
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Internal data
 | 
			
		||||
    private final DataProcessor<Boolean> isShootingPanorama;
 | 
			
		||||
    private final DataProcessor<PhotoAEBSettings> aebSettings;
 | 
			
		||||
    private final DataProcessor<PhotoAEBPhotoCount> aebCount;
 | 
			
		||||
    private final DataProcessor<PhotoBurstCount> burstCount;
 | 
			
		||||
    private final DataProcessor<PhotoBurstCount> rawBurstCount;
 | 
			
		||||
    private final DataProcessor<PhotoIntervalShootSettings> timeIntervalSettings;
 | 
			
		||||
    private final DataProcessor<PhotoPanoramaMode> panoramaMode;
 | 
			
		||||
    private final DataProcessor<CameraStorageInfos> storageInfosProcessor;
 | 
			
		||||
    private final DataProcessor<CameraStorageLocation> storageLocation;
 | 
			
		||||
    private final DataProcessor<SDCardLoadState> sdCardState;
 | 
			
		||||
    private final DataProcessor<SDCardLoadState> innerStorageState;
 | 
			
		||||
    private final DataProcessor<SSDOperationState> ssdState;
 | 
			
		||||
    private final DataProcessor<Integer> sdAvailableCaptureCount;
 | 
			
		||||
    private final DataProcessor<Integer> innerStorageAvailableCaptureCount;
 | 
			
		||||
    private final DataProcessor<Integer> rawPhotoBurstCaptureCount;
 | 
			
		||||
    private final DataProcessor<Boolean> isProductConnected;
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Other fields
 | 
			
		||||
    private final PhotoIntervalShootSettings defaultIntervalSettings;
 | 
			
		||||
    private ComponentIndexType cameraIndex = ComponentIndexType.LEFT_OR_MAIN;
 | 
			
		||||
    private CameraLensType lensType = CameraLensType.UNKNOWN;
 | 
			
		||||
    private FlatCameraModule flatCameraModule;
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Constructor
 | 
			
		||||
    public ShootPhotoWidgetModel(@NonNull DJISDKModel djiSdkModel,
 | 
			
		||||
                                 @NonNull ObservableInMemoryKeyedStore keyedStore) {
 | 
			
		||||
        super(djiSdkModel, keyedStore);
 | 
			
		||||
        defaultIntervalSettings = new PhotoIntervalShootSettings();
 | 
			
		||||
        CameraPhotoState photoState = new CameraPhotoState(CameraShootPhotoMode.UNKNOWN);
 | 
			
		||||
 | 
			
		||||
        this.cameraPhotoState = DataProcessor.create(photoState);
 | 
			
		||||
        CameraSDPhotoStorageState cameraSDStorageState = new CameraSDPhotoStorageState(
 | 
			
		||||
                CameraStorageLocation.SDCARD, 0, SDCardLoadState.NOT_INSERTED);
 | 
			
		||||
        cameraStorageState = DataProcessor.create(cameraSDStorageState);
 | 
			
		||||
        canStartShootingPhoto = DataProcessor.create(false);
 | 
			
		||||
        shootPhotoNotAllowed = DataProcessor.create(true);
 | 
			
		||||
        canStopShootingPhoto = DataProcessor.create(false);
 | 
			
		||||
        cameraDisplayName = DataProcessor.create("");
 | 
			
		||||
        aebSettings = DataProcessor.create(new PhotoAEBSettings());
 | 
			
		||||
        aebCount = DataProcessor.create(PhotoAEBPhotoCount.UNKNOWN);
 | 
			
		||||
        burstCount = DataProcessor.create(PhotoBurstCount.UNKNOWN);
 | 
			
		||||
        rawBurstCount = DataProcessor.create(PhotoBurstCount.UNKNOWN);
 | 
			
		||||
        timeIntervalSettings = DataProcessor.create(defaultIntervalSettings);
 | 
			
		||||
        panoramaMode = DataProcessor.create(PhotoPanoramaMode.UNKNOWN);
 | 
			
		||||
        isShootingPhoto = DataProcessor.create(false);
 | 
			
		||||
        isShootingInterval = DataProcessor.create(false);
 | 
			
		||||
        cameraType = DataProcessor.create(CameraType.NOT_SUPPORTED);
 | 
			
		||||
        isShootingPanorama = DataProcessor.create(false);
 | 
			
		||||
        isStoringPhoto = DataProcessor.create(false);
 | 
			
		||||
        storageLocation = DataProcessor.create(CameraStorageLocation.SDCARD);
 | 
			
		||||
        sdCardState = DataProcessor.create(SDCardLoadState.UNKNOWN);
 | 
			
		||||
        innerStorageState = DataProcessor.create(SDCardLoadState.UNKNOWN);
 | 
			
		||||
        ssdState = DataProcessor.create(SSDOperationState.UNKNOWN);
 | 
			
		||||
        sdAvailableCaptureCount = DataProcessor.create(INVALID_AVAILABLE_CAPTURE_COUNT);
 | 
			
		||||
        innerStorageAvailableCaptureCount = DataProcessor.create(INVALID_AVAILABLE_CAPTURE_COUNT);
 | 
			
		||||
        rawPhotoBurstCaptureCount = DataProcessor.create(INVALID_AVAILABLE_CAPTURE_COUNT);
 | 
			
		||||
        isProductConnected = DataProcessor.create(false);
 | 
			
		||||
        storageInfosProcessor = DataProcessor.create(new CameraStorageInfos(CameraStorageLocation.UNKNOWN, new ArrayList<>()));
 | 
			
		||||
        flatCameraModule = new FlatCameraModule();
 | 
			
		||||
        addModule(flatCameraModule);
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Data
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the current shoot photo mode
 | 
			
		||||
     *
 | 
			
		||||
     * @return Flowable with {@link CameraPhotoState} instance
 | 
			
		||||
     */
 | 
			
		||||
    public Flowable<CameraPhotoState> getCameraPhotoState() {
 | 
			
		||||
        return cameraPhotoState.toFlowable();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the current camera photo storage location
 | 
			
		||||
     *
 | 
			
		||||
     * @return Flowable with {@link CameraPhotoStorageState} instance
 | 
			
		||||
     */
 | 
			
		||||
    public Flowable<CameraPhotoStorageState> getCameraStorageState() {
 | 
			
		||||
        return cameraStorageState.toFlowable();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if the device is currently shooting photo
 | 
			
		||||
     *
 | 
			
		||||
     * @return Flowable with boolean value
 | 
			
		||||
     * true - if camera is shooting photo false - camera is not shooting photo
 | 
			
		||||
     */
 | 
			
		||||
    public Flowable<Boolean> isShootingPhoto() {
 | 
			
		||||
        return isShootingPhoto.toFlowable();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if the device is currently in the process of storing photo
 | 
			
		||||
     *
 | 
			
		||||
     * @return Flowable with boolean value
 | 
			
		||||
     * true - if device is storing photo false - device is not storing photo
 | 
			
		||||
     */
 | 
			
		||||
    public Flowable<Boolean> isStoringPhoto() {
 | 
			
		||||
        return isStoringPhoto.toFlowable();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if the device is ready to shoot photo.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Flowable with boolean value
 | 
			
		||||
     * true - device ready  false - device not ready
 | 
			
		||||
     */
 | 
			
		||||
    public Flowable<Boolean> canStartShootingPhoto() {
 | 
			
		||||
        return canStartShootingPhoto.toFlowable();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if the device is currently shooting photo and is ready to stop
 | 
			
		||||
     *
 | 
			
		||||
     * @return Flowable with boolean value
 | 
			
		||||
     * true - can stop shooting false - can not stop shooting photo
 | 
			
		||||
     */
 | 
			
		||||
    public Flowable<Boolean> canStopShootingPhoto() {
 | 
			
		||||
        return canStopShootingPhoto.toFlowable();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    public ComponentIndexType getCameraIndex() {
 | 
			
		||||
        return cameraIndex;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public CameraLensType getLensType() {
 | 
			
		||||
        return lensType;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void updateCameraSource(@NonNull ComponentIndexType cameraIndex, @NonNull CameraLensType lensType) {
 | 
			
		||||
        this.cameraIndex = cameraIndex;
 | 
			
		||||
        this.lensType = lensType;
 | 
			
		||||
        flatCameraModule.updateCameraSource(cameraIndex,lensType);
 | 
			
		||||
        restart();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the display name of the camera which the model is reacting to
 | 
			
		||||
     *
 | 
			
		||||
     * @return Flowable with string of camera name
 | 
			
		||||
     */
 | 
			
		||||
    public Flowable<String> getCameraDisplayName() {
 | 
			
		||||
        return cameraDisplayName.toFlowable();
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Actions
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Start shooting photo
 | 
			
		||||
     *
 | 
			
		||||
     * @return Completable to determine the status of the action
 | 
			
		||||
     */
 | 
			
		||||
    public Completable startShootPhoto() {
 | 
			
		||||
        if (!canStartShootingPhoto.getValue()) {
 | 
			
		||||
            return Completable.complete();
 | 
			
		||||
        }
 | 
			
		||||
        return djiSdkModel.performActionWithOutResult(KeyTools.createKey(CameraKey.KeyStartShootPhoto, cameraIndex));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Stop shooting photo
 | 
			
		||||
     *
 | 
			
		||||
     * @return Completable to determine the status of the action
 | 
			
		||||
     */
 | 
			
		||||
    public Completable stopShootPhoto() {
 | 
			
		||||
        if (!canStopShootingPhoto.getValue()) {
 | 
			
		||||
            return Completable.complete();
 | 
			
		||||
        }
 | 
			
		||||
        return djiSdkModel.performActionWithOutResult(KeyTools.createKey(CameraKey.KeyStopShootPhoto, cameraIndex));
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Lifecycle
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void inSetup() {
 | 
			
		||||
        // Product connection
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeyConnection, cameraIndex), isProductConnected, newValue -> onCameraConnected((boolean) newValue));
 | 
			
		||||
 | 
			
		||||
        // Photo mode
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeyAEBSettings, cameraIndex), aebSettings, photoAEBSettings -> {
 | 
			
		||||
            aebCount.onNext(photoAEBSettings.getCount());
 | 
			
		||||
        });
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeyPhotoBurstCount, cameraIndex), burstCount);
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeyRawBurstCount, cameraIndex), rawBurstCount);
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeyPhotoIntervalShootSettings, cameraIndex), timeIntervalSettings);
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeyPhotoPanoramaMode, cameraIndex), panoramaMode);
 | 
			
		||||
 | 
			
		||||
        // Is shooting photo state
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeyIsShootingPhoto, cameraIndex), isShootingPhoto);
 | 
			
		||||
 | 
			
		||||
        // Is storing photo state
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeyCameraStoringFile, cameraIndex), isStoringPhoto);
 | 
			
		||||
 | 
			
		||||
        // Can start shooting photo
 | 
			
		||||
        // can't take photo when product is not connected
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeyShootPhotoNotAllowed, cameraIndex), shootPhotoNotAllowed, aBoolean -> {
 | 
			
		||||
            canStartShootingPhoto.onNext(!aBoolean);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Can stop shooting photo
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeyCameraShootingContinuousPhotos, cameraIndex), isShootingInterval, this::onCanStopShootingPhoto);
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeyIsShootingPhotoPanorama, cameraIndex), isShootingPanorama, newValue -> onCanStopShootingPhoto((boolean) newValue));
 | 
			
		||||
 | 
			
		||||
        // Display name
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeyCameraType, cameraIndex), cameraType, type -> cameraDisplayName.onNext(type.name()));
 | 
			
		||||
 | 
			
		||||
        // Storage
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeyCameraStorageInfos, cameraIndex), storageInfosProcessor, cameraStorageInfos -> {
 | 
			
		||||
            storageLocation.onNext(cameraStorageInfos.getCurrentStorageType());
 | 
			
		||||
 | 
			
		||||
            CameraStorageInfo internalInfo = cameraStorageInfos.getCameraStorageInfoByLocation(CameraStorageLocation.INTERNAL);
 | 
			
		||||
            if (internalInfo != null) {
 | 
			
		||||
                innerStorageState.onNext(internalInfo.getStorageState());
 | 
			
		||||
                sdAvailableCaptureCount.onNext(internalInfo.getAvailablePhotoCount());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            CameraStorageInfo sdcardInfo = cameraStorageInfos.getCameraStorageInfoByLocation(CameraStorageLocation.SDCARD);
 | 
			
		||||
            if (sdcardInfo != null) {
 | 
			
		||||
                sdCardState.onNext(sdcardInfo.getStorageState());
 | 
			
		||||
                innerStorageAvailableCaptureCount.onNext(sdcardInfo.getAvailablePhotoCount());
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeySSDOperationState, cameraIndex), ssdState);
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeySDCardAvailablePhotoCount, cameraIndex), sdAvailableCaptureCount);
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeyInternalStorageAvailablePhotoCount, cameraIndex), innerStorageAvailableCaptureCount);
 | 
			
		||||
        bindDataProcessor(KeyTools.createKey(CameraKey.KeyH1PhotoBurstCount, cameraIndex), rawPhotoBurstCaptureCount);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void inCleanup() {
 | 
			
		||||
        // do nothing
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void updateStates() {
 | 
			
		||||
        updateCameraPhotoState();
 | 
			
		||||
        updateCameraStorageState();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isPhotoMode(){
 | 
			
		||||
        return flatCameraModule.getCameraModeDataProcessor().getValue().isPhotoMode()
 | 
			
		||||
                || (flatCameraModule.getCameraModeDataProcessor().getValue() == CameraMode.VIDEO_NORMAL && isSupportShootPhoto());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean isSupportShootPhoto(){
 | 
			
		||||
        CameraType cameraType = KeyManager.getInstance().getValue(KeyTools.createKey(CameraKey.KeyCameraType ,cameraIndex) ,CameraType.NOT_SUPPORTED);
 | 
			
		||||
        return cameraType == CameraType.M3T || cameraType == CameraType.M3E || cameraType == CameraType.M3TA || cameraType == CameraType.M3M || cameraType == CameraType.ZENMUSE_H20T;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //region Helpers
 | 
			
		||||
    private void updateCameraPhotoState() {
 | 
			
		||||
        CameraPhotoState state = null;
 | 
			
		||||
        CameraShootPhotoMode shootPhotoMode = flatCameraModule.getShootPhotoModeProcessor().getValue();
 | 
			
		||||
        switch (shootPhotoMode) {
 | 
			
		||||
            case NORMAL:
 | 
			
		||||
            case HDR:
 | 
			
		||||
            case HYPER_LIGHT:
 | 
			
		||||
                //case SHALLOW_FOCUS:
 | 
			
		||||
            case EHDR:
 | 
			
		||||
                state = new CameraPhotoState(shootPhotoMode);
 | 
			
		||||
                break;
 | 
			
		||||
            case BURST:
 | 
			
		||||
                if (!PhotoBurstCount.UNKNOWN.equals(burstCount.getValue())) {
 | 
			
		||||
                    state = new CameraBurstPhotoState(
 | 
			
		||||
                            shootPhotoMode,
 | 
			
		||||
                            burstCount.getValue());
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case RAW_BURST:
 | 
			
		||||
                if (!PhotoBurstCount.UNKNOWN.equals(rawBurstCount.getValue())) {
 | 
			
		||||
                    state = new CameraBurstPhotoState(
 | 
			
		||||
                            shootPhotoMode,
 | 
			
		||||
                            rawBurstCount.getValue()
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case AEB:
 | 
			
		||||
                if (!PhotoAEBPhotoCount.UNKNOWN.equals(aebCount.getValue())) {
 | 
			
		||||
                    state = new CameraAEBPhotoState(
 | 
			
		||||
                            shootPhotoMode,
 | 
			
		||||
                            aebCount.getValue()
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case INTERVAL:
 | 
			
		||||
                PhotoIntervalShootSettings intervalSettings = timeIntervalSettings.getValue();
 | 
			
		||||
                if (!defaultIntervalSettings.equals(timeIntervalSettings.getValue())) {
 | 
			
		||||
                    state = new CameraIntervalPhotoState(
 | 
			
		||||
                            shootPhotoMode,
 | 
			
		||||
                            intervalSettings.getCount(),
 | 
			
		||||
                            intervalSettings.getInterval().intValue()
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case PANO_APP:
 | 
			
		||||
                if (!PhotoPanoramaMode.UNKNOWN.equals(panoramaMode.getValue())) {
 | 
			
		||||
                    state = new CameraPanoramaPhotoState(
 | 
			
		||||
                            shootPhotoMode,
 | 
			
		||||
                            panoramaMode.getValue()
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (state != null) {
 | 
			
		||||
            this.cameraPhotoState.onNext(state);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void updateCameraStorageState() {
 | 
			
		||||
        CameraStorageLocation currentStorageLocation = storageLocation.getValue();
 | 
			
		||||
        if (CameraStorageLocation.UNKNOWN.equals(currentStorageLocation)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        CameraShootPhotoMode currentShootPhotoMode = flatCameraModule.getShootPhotoModeProcessor().getValue();
 | 
			
		||||
        long availableCaptureCount = getAvailableCaptureCount(currentStorageLocation, currentShootPhotoMode);
 | 
			
		||||
        CameraPhotoStorageState newCameraPhotoStorageState = null;
 | 
			
		||||
        if (currentShootPhotoMode == CameraShootPhotoMode.RAW_BURST) {
 | 
			
		||||
            newCameraPhotoStorageState = new CameraSSDPhotoStorageState(CameraStorageLocation.UNKNOWN, availableCaptureCount, ssdState.getValue());
 | 
			
		||||
        } else if (CameraStorageLocation.SDCARD.equals(currentStorageLocation)) {
 | 
			
		||||
            if (!SDCardLoadState.UNKNOWN.equals(sdCardState.getValue())) {
 | 
			
		||||
                newCameraPhotoStorageState = new CameraSDPhotoStorageState(currentStorageLocation, availableCaptureCount, sdCardState.getValue());
 | 
			
		||||
            }
 | 
			
		||||
        } else if (CameraStorageLocation.INTERNAL.equals(currentStorageLocation)) {
 | 
			
		||||
            newCameraPhotoStorageState = new CameraSDPhotoStorageState(currentStorageLocation, availableCaptureCount, innerStorageState.getValue());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (newCameraPhotoStorageState != null) {
 | 
			
		||||
            cameraStorageState.onNext(newCameraPhotoStorageState);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private long getAvailableCaptureCount(CameraStorageLocation storageLocation,
 | 
			
		||||
                                          CameraShootPhotoMode shootPhotoMode) {
 | 
			
		||||
        if (shootPhotoMode == CameraShootPhotoMode.RAW_BURST) {
 | 
			
		||||
            return rawPhotoBurstCaptureCount.getValue();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        switch (storageLocation) {
 | 
			
		||||
            case SDCARD:
 | 
			
		||||
                return sdAvailableCaptureCount.getValue();
 | 
			
		||||
            case INTERNAL:
 | 
			
		||||
                return innerStorageAvailableCaptureCount.getValue();
 | 
			
		||||
            case UNKNOWN:
 | 
			
		||||
            default:
 | 
			
		||||
                return INVALID_AVAILABLE_CAPTURE_COUNT;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void onCanStopShootingPhoto(boolean canStopShootingPhoto) {
 | 
			
		||||
        this.canStopShootingPhoto.onNext(canStopShootingPhoto);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void onCameraConnected(boolean isCameraConnected) {
 | 
			
		||||
        if (!isCameraConnected) {
 | 
			
		||||
            // Reset storage state
 | 
			
		||||
            sdCardState.onNext(SDCardLoadState.UNKNOWN);
 | 
			
		||||
            innerStorageState.onNext(SDCardLoadState.UNKNOWN);
 | 
			
		||||
            ssdState.onNext(SSDOperationState.UNKNOWN);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,221 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.widget.cameracontrols;
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.util.AttributeSet;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.annotation.Nullable;
 | 
			
		||||
import dji.sdk.keyvalue.value.common.CameraLensType;
 | 
			
		||||
import dji.sdk.keyvalue.value.common.ComponentIndexType;
 | 
			
		||||
import dji.v5.ux.R;
 | 
			
		||||
import dji.v5.ux.cameracore.widget.cameracapture.CameraCaptureWidget;
 | 
			
		||||
import dji.v5.ux.cameracore.widget.cameracontrols.camerasettingsindicator.CameraSettingsMenuIndicatorWidget;
 | 
			
		||||
import dji.v5.ux.cameracore.widget.cameracontrols.exposuresettingsindicator.ExposureSettingsIndicatorWidget;
 | 
			
		||||
import dji.v5.ux.cameracore.widget.cameracontrols.photovideoswitch.PhotoVideoSwitchWidget;
 | 
			
		||||
import dji.v5.ux.core.base.ICameraIndex;
 | 
			
		||||
import dji.v5.ux.core.base.widget.ConstraintLayoutWidget;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Compound widget which combines the state and interaction related to camera.
 | 
			
		||||
 * It includes {@link CameraSettingsMenuIndicatorWidget}, {@link ExposureSettingsIndicatorWidget},
 | 
			
		||||
 * {@link PhotoVideoSwitchWidget} and {@link CameraCaptureWidget}.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * The widget gives access to all the child widgets.
 | 
			
		||||
 */
 | 
			
		||||
public class CameraControlsWidget extends ConstraintLayoutWidget<Object> implements ICameraIndex {
 | 
			
		||||
 | 
			
		||||
    //region Fields
 | 
			
		||||
    private CameraSettingsMenuIndicatorWidget cameraSettingsMenuIndicatorWidget;
 | 
			
		||||
    private PhotoVideoSwitchWidget photoVideoSwitchWidget;
 | 
			
		||||
    private CameraCaptureWidget cameraCaptureWidget;
 | 
			
		||||
    private ExposureSettingsIndicatorWidget exposureSettingsIndicatorWidget;
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Lifecycle
 | 
			
		||||
    public CameraControlsWidget(@NonNull Context context) {
 | 
			
		||||
        super(context);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public CameraControlsWidget(@NonNull Context context, @Nullable AttributeSet attrs) {
 | 
			
		||||
        super(context, attrs);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public CameraControlsWidget(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
 | 
			
		||||
        super(context, attrs, defStyleAttr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void initView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
 | 
			
		||||
        inflate(context, R.layout.uxsdk_widget_camera_controls, this);
 | 
			
		||||
        setBackgroundResource(R.drawable.uxsdk_background_black_rectangle);
 | 
			
		||||
        cameraCaptureWidget = findViewById(R.id.widget_camera_control_camera_capture);
 | 
			
		||||
        photoVideoSwitchWidget = findViewById(R.id.widget_camera_control_photo_video_switch);
 | 
			
		||||
        exposureSettingsIndicatorWidget = findViewById(R.id.widget_camera_control_camera_exposure_settings);
 | 
			
		||||
        cameraSettingsMenuIndicatorWidget = findViewById(R.id.widget_camera_control_camera_settings_menu);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void reactToModelChanges() {
 | 
			
		||||
        //Do Nothing
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getIdealDimensionRatioString() {
 | 
			
		||||
        return getResources().getString(R.string.uxsdk_widget_camera_controls_ratio);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public ComponentIndexType getCameraIndex() {
 | 
			
		||||
        return cameraCaptureWidget.getCameraIndex();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public CameraLensType getLensType() {
 | 
			
		||||
        return cameraCaptureWidget.getLensType();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void updateCameraSource(@NonNull ComponentIndexType cameraIndex, @NonNull CameraLensType lensType) {
 | 
			
		||||
        cameraCaptureWidget.updateCameraSource(cameraIndex, lensType);
 | 
			
		||||
        photoVideoSwitchWidget.updateCameraSource(cameraIndex, lensType);
 | 
			
		||||
        exposureSettingsIndicatorWidget.updateCameraSource(cameraIndex, lensType);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the camera capture widget
 | 
			
		||||
     *
 | 
			
		||||
     * @return {@link CameraCaptureWidget}
 | 
			
		||||
     */
 | 
			
		||||
    @NonNull
 | 
			
		||||
    public CameraCaptureWidget getCameraCaptureWidget() {
 | 
			
		||||
        return cameraCaptureWidget;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the camera settings menu widget
 | 
			
		||||
     *
 | 
			
		||||
     * @return {@link CameraSettingsMenuIndicatorWidget}
 | 
			
		||||
     */
 | 
			
		||||
    @NonNull
 | 
			
		||||
    public CameraSettingsMenuIndicatorWidget getCameraSettingsMenuIndicatorWidget() {
 | 
			
		||||
        return cameraSettingsMenuIndicatorWidget;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the exposure settings widget
 | 
			
		||||
     *
 | 
			
		||||
     * @return {@link ExposureSettingsIndicatorWidget}
 | 
			
		||||
     */
 | 
			
		||||
    @NonNull
 | 
			
		||||
    public ExposureSettingsIndicatorWidget getExposureSettingsIndicatorWidget() {
 | 
			
		||||
        return exposureSettingsIndicatorWidget;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the photo video switch widget
 | 
			
		||||
     *
 | 
			
		||||
     * @return {@link PhotoVideoSwitchWidget}
 | 
			
		||||
     */
 | 
			
		||||
    @NonNull
 | 
			
		||||
    public PhotoVideoSwitchWidget getPhotoVideoSwitchWidget() {
 | 
			
		||||
        return photoVideoSwitchWidget;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show/Hide {@link CameraSettingsMenuIndicatorWidget}
 | 
			
		||||
     *
 | 
			
		||||
     * @param isVisible boolean true - visible  false - gone
 | 
			
		||||
     */
 | 
			
		||||
    public void setCameraSettingsMenuIndicatorWidgetVisibility(boolean isVisible) {
 | 
			
		||||
        cameraSettingsMenuIndicatorWidget.setVisibility(isVisible ? VISIBLE : GONE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if {@link CameraSettingsMenuIndicatorWidget} is visible
 | 
			
		||||
     *
 | 
			
		||||
     * @return boolean true - visible  false - gone
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isCameraSettingsMenuIndicatorWidgetVisible() {
 | 
			
		||||
        return cameraSettingsMenuIndicatorWidget.getVisibility() == VISIBLE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show/Hide {@link PhotoVideoSwitchWidget}
 | 
			
		||||
     *
 | 
			
		||||
     * @param isVisible boolean true - visible false - gone
 | 
			
		||||
     */
 | 
			
		||||
    public void setPhotoVideoSwitchWidgetVisibility(boolean isVisible) {
 | 
			
		||||
        photoVideoSwitchWidget.setVisibility(isVisible ? VISIBLE : GONE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if {@link PhotoVideoSwitchWidget} is Visible
 | 
			
		||||
     *
 | 
			
		||||
     * @return boolean  true - visible  false - gone
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isPhotoVideoSwitchWidgetVisible() {
 | 
			
		||||
        return photoVideoSwitchWidget.getVisibility() == VISIBLE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show/Hide {@link CameraCaptureWidget}
 | 
			
		||||
     *
 | 
			
		||||
     * @param isVisible boolean  true - visible  false - gone
 | 
			
		||||
     */
 | 
			
		||||
    public void setCameraCaptureWidgetVisibility(boolean isVisible) {
 | 
			
		||||
        cameraCaptureWidget.setVisibility(isVisible ? VISIBLE : GONE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if {@link CameraCaptureWidget} is visible
 | 
			
		||||
     *
 | 
			
		||||
     * @return boolean  true - visible  false - gone
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isCameraCaptureWidgetVisible() {
 | 
			
		||||
        return cameraCaptureWidget.getVisibility() == VISIBLE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show/Hide {@link ExposureSettingsIndicatorWidget}
 | 
			
		||||
     *
 | 
			
		||||
     * @param isVisible boolean  true - visible  false - gone
 | 
			
		||||
     */
 | 
			
		||||
    public void setExposureSettingsIndicatorWidgetVisibility(boolean isVisible) {
 | 
			
		||||
        exposureSettingsIndicatorWidget.setVisibility(isVisible ? VISIBLE : GONE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if {@link ExposureSettingsIndicatorWidget}
 | 
			
		||||
     *
 | 
			
		||||
     * @return boolean  true - visible  false - gone
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isExposureSettingsIndicatorWidgetVisible() {
 | 
			
		||||
        return exposureSettingsIndicatorWidget.getVisibility() == VISIBLE;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
package dji.v5.ux.cameracore.widget.cameracontrols
 | 
			
		||||
 | 
			
		||||
import dji.sdk.keyvalue.key.RemoteControllerKey
 | 
			
		||||
import dji.v5.et.create
 | 
			
		||||
import dji.v5.ux.core.base.DJISDKModel
 | 
			
		||||
import dji.v5.ux.core.base.WidgetModel
 | 
			
		||||
import dji.v5.ux.core.communication.ObservableInMemoryKeyedStore
 | 
			
		||||
import dji.v5.ux.core.util.DataProcessor
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class Description
 | 
			
		||||
 *
 | 
			
		||||
 * @author Hoker
 | 
			
		||||
 * @date 2021/12/13
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2021, DJI All Rights Reserved.
 | 
			
		||||
 */
 | 
			
		||||
open class RemoteControllerButtonDownModel constructor(
 | 
			
		||||
    djiSdkModel: DJISDKModel,
 | 
			
		||||
    keyedStore: ObservableInMemoryKeyedStore
 | 
			
		||||
) : WidgetModel(djiSdkModel, keyedStore) {
 | 
			
		||||
 | 
			
		||||
    val isShutterButtonDownProcessor: DataProcessor<Boolean> = DataProcessor.create(false)
 | 
			
		||||
    val isRecordButtonDownProcessor: DataProcessor<Boolean> = DataProcessor.create(false)
 | 
			
		||||
 | 
			
		||||
    override fun inSetup() {
 | 
			
		||||
        bindDataProcessor(RemoteControllerKey.KeyShutterButtonDown.create(), isShutterButtonDownProcessor)
 | 
			
		||||
        bindDataProcessor(RemoteControllerKey.KeyRecordButtonDown.create(), isRecordButtonDownProcessor)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun inCleanup() {
 | 
			
		||||
        //暂未实现
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,212 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018-2020 DJI
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
 * copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
 * SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dji.v5.ux.cameracore.widget.cameracontrols.camerasettingsindicator;
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.content.res.TypedArray;
 | 
			
		||||
import android.graphics.Color;
 | 
			
		||||
import android.graphics.drawable.Drawable;
 | 
			
		||||
import android.util.AttributeSet;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.widget.TextView;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.ColorInt;
 | 
			
		||||
import androidx.annotation.Dimension;
 | 
			
		||||
import androidx.annotation.DrawableRes;
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.annotation.Nullable;
 | 
			
		||||
import dji.v5.ux.R;
 | 
			
		||||
import dji.v5.ux.cameracore.widget.cameracontrols.CameraControlsWidget;
 | 
			
		||||
import dji.v5.ux.core.base.widget.FrameLayoutWidget;
 | 
			
		||||
import dji.v5.ux.core.communication.OnStateChangeCallback;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Camera Settings Menu Indicator Widget
 | 
			
		||||
 * <p>
 | 
			
		||||
 * The widget is part of the {@link CameraControlsWidget}.
 | 
			
		||||
 * Tapping the widget can be used to open the camera settings panel
 | 
			
		||||
 */
 | 
			
		||||
public class CameraSettingsMenuIndicatorWidget extends FrameLayoutWidget<Object> implements View.OnClickListener {
 | 
			
		||||
 | 
			
		||||
    //region Fields
 | 
			
		||||
    private TextView foregroundTextView;
 | 
			
		||||
    private OnStateChangeCallback<Object> stateChangeCallback = null;
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region Lifecycle
 | 
			
		||||
    public CameraSettingsMenuIndicatorWidget(@NonNull Context context) {
 | 
			
		||||
        super(context);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public CameraSettingsMenuIndicatorWidget(@NonNull Context context, @Nullable AttributeSet attrs) {
 | 
			
		||||
        super(context, attrs);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public CameraSettingsMenuIndicatorWidget(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
 | 
			
		||||
        super(context, attrs, defStyleAttr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void initView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
 | 
			
		||||
        inflate(context, R.layout.uxsdk_widget_camera_settings_menu_indicator, this);
 | 
			
		||||
        foregroundTextView = findViewById(R.id.text_view_menu);
 | 
			
		||||
        setOnClickListener(this);
 | 
			
		||||
 | 
			
		||||
        if (attrs != null) {
 | 
			
		||||
            initAttributes(context, attrs);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void reactToModelChanges() {
 | 
			
		||||
        // No Code
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getIdealDimensionRatioString() {
 | 
			
		||||
        return getResources().getString(R.string.uxsdk_widget_default_ratio);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onClick(View v) {
 | 
			
		||||
        if (stateChangeCallback != null) {
 | 
			
		||||
            stateChangeCallback.onStateChange(null);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onAttachedToWindow() {
 | 
			
		||||
        super.onAttachedToWindow();
 | 
			
		||||
        initializeListener();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onDetachedFromWindow() {
 | 
			
		||||
        destroyListener();
 | 
			
		||||
        super.onDetachedFromWindow();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region private methods
 | 
			
		||||
 | 
			
		||||
    private void initializeListener() {
 | 
			
		||||
        //暂未实现
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void destroyListener() {
 | 
			
		||||
        stateChangeCallback = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initAttributes(Context context, AttributeSet attrs) {
 | 
			
		||||
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CameraSettingsMenuIndicatorWidget);
 | 
			
		||||
 | 
			
		||||
        setLabelTextSize(typedArray.getDimension(R.styleable.CameraSettingsMenuIndicatorWidget_uxsdk_settingsTextSize, 12));
 | 
			
		||||
        setLabelTextColor(typedArray.getColor(R.styleable.CameraSettingsMenuIndicatorWidget_uxsdk_settingsTextColor, Color.WHITE));
 | 
			
		||||
        setLabelTextBackground(typedArray.getDrawable(R.styleable.CameraSettingsMenuIndicatorWidget_uxsdk_settingsTextBackground));
 | 
			
		||||
        typedArray.recycle();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    //region customizations
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set callback for when the widget is tapped.
 | 
			
		||||
     * This can be used to link the widget to //TODO camera settings menu panel
 | 
			
		||||
     *
 | 
			
		||||
     * @param stateChangeCallback listener to handheld callback
 | 
			
		||||
     */
 | 
			
		||||
    public void setStateChangeCallback(@NonNull OnStateChangeCallback<Object> stateChangeCallback) {
 | 
			
		||||
        this.stateChangeCallback = stateChangeCallback;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the color of the widget label text
 | 
			
		||||
     *
 | 
			
		||||
     * @return integer color value
 | 
			
		||||
     */
 | 
			
		||||
    @ColorInt
 | 
			
		||||
    public int getLabelTextColor() {
 | 
			
		||||
        return foregroundTextView.getCurrentTextColor();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the color of the widget label text
 | 
			
		||||
     *
 | 
			
		||||
     * @param color integer value to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setLabelTextColor(@ColorInt int color) {
 | 
			
		||||
        foregroundTextView.setTextColor(color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the background of the widget label
 | 
			
		||||
     *
 | 
			
		||||
     * @param resourceId to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setLabelTextBackground(@DrawableRes int resourceId) {
 | 
			
		||||
        foregroundTextView.setBackgroundResource(resourceId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the background of the widget label
 | 
			
		||||
     *
 | 
			
		||||
     * @param drawable to be used
 | 
			
		||||
     */
 | 
			
		||||
    public void setLabelTextBackground(@Nullable Drawable drawable) {
 | 
			
		||||
        foregroundTextView.setBackground(drawable);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the background of the widget label
 | 
			
		||||
     *
 | 
			
		||||
     * @return Drawable
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public Drawable getLabelBackground() {
 | 
			
		||||
        return foregroundTextView.getBackground();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the text size of the widget label
 | 
			
		||||
     *
 | 
			
		||||
     * @return float value representing text size
 | 
			
		||||
     */
 | 
			
		||||
    @Dimension
 | 
			
		||||
    public float getLabelTextSize() {
 | 
			
		||||
        return foregroundTextView.getTextSize();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the text size of the widget label
 | 
			
		||||
     *
 | 
			
		||||
     * @param textSize float value representing text size
 | 
			
		||||
     */
 | 
			
		||||
    public void setLabelTextSize(@Dimension float textSize) {
 | 
			
		||||
        foregroundTextView.setTextSize(textSize);
 | 
			
		||||
    }
 | 
			
		||||
    //endregion
 | 
			
		||||
}
 | 
			
		||||