diff --git a/.travis.yml b/.travis.yml index 5f3545ca..b9e1da97 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,8 +38,8 @@ before_script: - chmod +x gradlew script: - - ./gradlew clean build - - ./gradlew test + - travis_wait 30 ./gradlew clean build + - travis_wait 30 ./gradlew test after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/README.md b/README.md index e8dc13a2..ba818665 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ You can also post questions, keep up to date on DJI developer news and contribut ## Future Plans -We are eager to give you a sneak peek, and are very interested in receiving your feedback and suggestions. Please refer to the wiki or the release notes for the full list of elements that the Beta 4 version makes available. +We are eager to give you a sneak peek, and are very interested in receiving your feedback and suggestions. Please refer to the wiki or the release notes for the full list of elements that the Beta 5 version makes available. Our long-term plan is for this framework to reach feature parity with the current UX SDK 4.x release. The core team is currently working on porting the remaining widgets and other APIs to the new architecture and we will open source them in additional future Beta releases as they are completed. We welcome your feedback on the architecture and your ideas for additional widgets, including those not included in prior UX SDK releases, as well as your contributions and PRs for any ***currently open-sourced features***. diff --git a/android-uxsdk-beta-hardwareaccessory/build.gradle b/android-uxsdk-beta-accessory/build.gradle similarity index 96% rename from android-uxsdk-beta-hardwareaccessory/build.gradle rename to android-uxsdk-beta-accessory/build.gradle index 6c2455af..4d40f66f 100644 --- a/android-uxsdk-beta-hardwareaccessory/build.gradle +++ b/android-uxsdk-beta-accessory/build.gradle @@ -69,7 +69,7 @@ android { dependencies { api project(path: ':android-uxsdk-beta-core') - implementation ('com.dji:dji-sdk:4.13.1', { + implementation ('com.dji:dji-sdk:4.14-trial1', { /** * Comment the "library-anti-distortion" if your app needs Anti Distortion for Mavic 2 Pro * and Mavic 2 Zoom. @@ -81,7 +81,7 @@ dependencies { exclude module: 'library-anti-distortion' exclude module: 'fly-safe-database' }) - compileOnly ('com.dji:dji-sdk-provided:4.13.1') + compileOnly ('com.dji:dji-sdk-provided:4.14-trial1') implementation 'androidx.multidex:multidex:2.0.0' implementation 'androidx.appcompat:appcompat:1.0.0' diff --git a/android-uxsdk-beta-hardwareaccessory/proguard-rules.pro b/android-uxsdk-beta-accessory/proguard-rules.pro similarity index 100% rename from android-uxsdk-beta-hardwareaccessory/proguard-rules.pro rename to android-uxsdk-beta-accessory/proguard-rules.pro diff --git a/android-uxsdk-beta-hardwareaccessory/proguard-test-rules.pro b/android-uxsdk-beta-accessory/proguard-test-rules.pro similarity index 100% rename from android-uxsdk-beta-hardwareaccessory/proguard-test-rules.pro rename to android-uxsdk-beta-accessory/proguard-test-rules.pro diff --git a/android-uxsdk-beta-intelligentflight/src/main/AndroidManifest.xml b/android-uxsdk-beta-accessory/src/main/AndroidManifest.xml similarity index 95% rename from android-uxsdk-beta-intelligentflight/src/main/AndroidManifest.xml rename to android-uxsdk-beta-accessory/src/main/AndroidManifest.xml index 17cb7967..719fb363 100644 --- a/android-uxsdk-beta-intelligentflight/src/main/AndroidManifest.xml +++ b/android-uxsdk-beta-accessory/src/main/AndroidManifest.xml @@ -21,4 +21,4 @@ ~ --> - + diff --git a/android-uxsdk-beta-hardwareaccessory/src/main/java/dji/ux/beta/hardwareaccessory/widget/rtk/RTKEnabledWidget.kt b/android-uxsdk-beta-accessory/src/main/java/dji/ux/beta/accessory/widget/rtk/RTKEnabledWidget.kt similarity index 84% rename from android-uxsdk-beta-hardwareaccessory/src/main/java/dji/ux/beta/hardwareaccessory/widget/rtk/RTKEnabledWidget.kt rename to android-uxsdk-beta-accessory/src/main/java/dji/ux/beta/accessory/widget/rtk/RTKEnabledWidget.kt index 8c4e18b7..20dcf579 100644 --- a/android-uxsdk-beta-hardwareaccessory/src/main/java/dji/ux/beta/hardwareaccessory/widget/rtk/RTKEnabledWidget.kt +++ b/android-uxsdk-beta-accessory/src/main/java/dji/ux/beta/accessory/widget/rtk/RTKEnabledWidget.kt @@ -18,10 +18,10 @@ * 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.ux.beta.hardwareaccessory.widget.rtk +package dji.ux.beta.accessory.widget.rtk import android.annotation.SuppressLint import android.content.Context @@ -39,17 +39,17 @@ import dji.thirdparty.io.reactivex.Flowable import dji.thirdparty.io.reactivex.disposables.Disposable import dji.thirdparty.io.reactivex.functions.Consumer import dji.thirdparty.io.reactivex.processors.PublishProcessor -import dji.ux.beta.core.base.ConstraintLayoutWidget +import dji.ux.beta.accessory.R +import dji.ux.beta.accessory.widget.rtk.RTKEnabledWidget.ModelState +import dji.ux.beta.accessory.widget.rtk.RTKEnabledWidget.ModelState.ProductConnected +import dji.ux.beta.accessory.widget.rtk.RTKEnabledWidget.ModelState.RTKEnabledUpdated +import dji.ux.beta.accessory.widget.rtk.RTKEnabledWidget.UIState.SwitchChanged import dji.ux.beta.core.base.DJISDKModel import dji.ux.beta.core.base.SchedulerProvider -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.* import dji.ux.beta.core.util.DisplayUtil -import dji.ux.beta.hardwareaccessory.R -import dji.ux.beta.hardwareaccessory.widget.rtk.RTKEnabledWidget.RTKEnabledWidgetState -import dji.ux.beta.hardwareaccessory.widget.rtk.RTKEnabledWidget.RTKEnabledWidgetState.ProductConnected -import dji.ux.beta.hardwareaccessory.widget.rtk.RTKEnabledWidget.RTKEnabledWidgetState.RTKEnabledUpdate -import dji.ux.beta.hardwareaccessory.widget.rtk.RTKEnabledWidget.RTKEnabledWidgetUIState.RTKEnabledSwitchCheckChanged private const val TAG = "RTKEnabledWidget" @@ -60,19 +60,18 @@ open class RTKEnabledWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ConstraintLayoutWidget(context, attrs, defStyleAttr), CompoundButton.OnCheckedChangeListener { +) : ConstraintLayoutWidget(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 = PublishProcessor.create() + private val uiUpdateStateProcessor: PublishProcessor = PublishProcessor.create() private val widgetModel by lazy { RTKEnabledWidgetModel( DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - SchedulerProvider.getInstance()) + ObservableInMemoryKeyedStore.getInstance()) } /** @@ -169,7 +168,7 @@ open class RTKEnabledWidget @JvmOverloads constructor( } //endregion - //region Constructors + //region Constructor override fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { inflate(context, R.layout.uxsdk_widget_rtk_enabled, this) } @@ -197,7 +196,7 @@ open class RTKEnabledWidget @JvmOverloads constructor( override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) { addDisposable(widgetModel.canEnableRTK.firstOrError() - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { canEnableRTK: Boolean -> if (!canEnableRTK) { setRTKSwitch(!isChecked) @@ -206,15 +205,15 @@ open class RTKEnabledWidget @JvmOverloads constructor( setRTKEnabled(isChecked) } }, logErrorConsumer(TAG, "canEnableRTK: "))) - uiUpdateStateProcessor.onNext(RTKEnabledSwitchCheckChanged(isChecked)) + uiUpdateStateProcessor.onNext(SwitchChanged(isChecked)) } override fun reactToModelChanges() { addReaction(widgetModel.rtkEnabled - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { updateUIForRTKEnabled(it) }) addReaction(widgetModel.productConnection - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) } //endregion @@ -222,13 +221,13 @@ open class RTKEnabledWidget @JvmOverloads constructor( //region Reaction helpers private fun updateUIForRTKEnabled(rtkEnabled: Boolean) { setRTKSwitch(rtkEnabled) - widgetStateDataProcessor.onNext(RTKEnabledUpdate(rtkEnabled)) + widgetStateDataProcessor.onNext(RTKEnabledUpdated(rtkEnabled)) } private fun setRTKEnabled(enabled: Boolean) { addDisposable(widgetModel.rtkEnabled .firstOrError() - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { rtkEnabled: Boolean -> if (rtkEnabled != enabled) { addDisposable(toggleRTK(enabled)) @@ -238,7 +237,7 @@ open class RTKEnabledWidget @JvmOverloads constructor( private fun toggleRTK(enabled: Boolean): Disposable { return widgetModel.setRTKEnabled(enabled) - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe({} ) { throwable: Throwable -> setRTKSwitch(!enabled) @@ -333,42 +332,43 @@ open class RTKEnabledWidget @JvmOverloads constructor( //region Hooks /** - * Get the [RTKEnabledWidgetUIState] updates + * Get the [UIState] updates */ - fun getUIStateUpdates(): Flowable { - return uiUpdateStateProcessor + fun getUIStateUpdates(): Flowable { + return uiUpdateStateProcessor.onBackpressureBuffer() } /** * Widget UI update State */ - sealed class RTKEnabledWidgetUIState { + sealed class UIState { /** * RTK enabled switch check changed update */ - data class RTKEnabledSwitchCheckChanged(val isChecked: Boolean) : RTKEnabledWidgetUIState() + data class SwitchChanged(val isChecked: Boolean) : UIState() } /** - * Get the [RTKEnabledWidgetState] updates + * Get the [ModelState] updates */ - override fun getWidgetStateUpdate(): Flowable { + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { return super.getWidgetStateUpdate() } /** * Class defines the widget state updates */ - sealed class RTKEnabledWidgetState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : RTKEnabledWidgetState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * RTK enabled update */ - data class RTKEnabledUpdate(val isRTKEnabled: Boolean) : RTKEnabledWidgetState() + data class RTKEnabledUpdated(val isRTKEnabled: Boolean) : ModelState() } //endregion } \ No newline at end of file diff --git a/android-uxsdk-beta-hardwareaccessory/src/main/java/dji/ux/beta/hardwareaccessory/widget/rtk/RTKEnabledWidgetModel.kt b/android-uxsdk-beta-accessory/src/main/java/dji/ux/beta/accessory/widget/rtk/RTKEnabledWidgetModel.kt similarity index 86% rename from android-uxsdk-beta-hardwareaccessory/src/main/java/dji/ux/beta/hardwareaccessory/widget/rtk/RTKEnabledWidgetModel.kt rename to android-uxsdk-beta-accessory/src/main/java/dji/ux/beta/accessory/widget/rtk/RTKEnabledWidgetModel.kt index 12dea042..3beff70d 100644 --- a/android-uxsdk-beta-hardwareaccessory/src/main/java/dji/ux/beta/hardwareaccessory/widget/rtk/RTKEnabledWidgetModel.kt +++ b/android-uxsdk-beta-accessory/src/main/java/dji/ux/beta/accessory/widget/rtk/RTKEnabledWidgetModel.kt @@ -18,28 +18,27 @@ * 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.ux.beta.hardwareaccessory.widget.rtk +package dji.ux.beta.accessory.widget.rtk import dji.keysdk.DJIKey import dji.keysdk.FlightControllerKey import dji.thirdparty.io.reactivex.Completable import dji.thirdparty.io.reactivex.Flowable import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.SchedulerProviderInterface import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.util.DataProcessor /** * Widget Model for the [RTKEnabledWidget] used to define * the underlying logic and communication */ -class RTKEnabledWidgetModel(djiSdkModel: DJISDKModel, - keyedStore: ObservableInMemoryKeyedStore, - private val schedulerProvider: SchedulerProviderInterface +class RTKEnabledWidgetModel( + djiSdkModel: DJISDKModel, + keyedStore: ObservableInMemoryKeyedStore ) : WidgetModel(djiSdkModel, keyedStore) { //region Fields @@ -90,7 +89,7 @@ class RTKEnabledWidgetModel(djiSdkModel: DJISDKModel, //region User interaction fun setRTKEnabled(enabled: Boolean): Completable { - return djiSdkModel.setValue(isRTKEnabledKey, enabled).subscribeOn(schedulerProvider.io()) + return djiSdkModel.setValue(isRTKEnabledKey, enabled) } //endregion } @@ -123,15 +122,12 @@ enum class HomePointDataSourceType(@get:JvmName("value") val value: Int) { UNKNOWN(255); companion object { + @JvmStatic + val values = values() + @JvmStatic fun find(value: Int): HomePointDataSourceType { - val values = values() - for (item in values) { - if (item.value == value) { - return item - } - } - return UNKNOWN + return values.find { it.value == value } ?: UNKNOWN } } } \ No newline at end of file diff --git a/android-uxsdk-beta-hardwareaccessory/src/main/java/dji/ux/beta/hardwareaccessory/widget/rtk/RTKSatelliteStatusWidget.kt b/android-uxsdk-beta-accessory/src/main/java/dji/ux/beta/accessory/widget/rtk/RTKSatelliteStatusWidget.kt similarity index 90% rename from android-uxsdk-beta-hardwareaccessory/src/main/java/dji/ux/beta/hardwareaccessory/widget/rtk/RTKSatelliteStatusWidget.kt rename to android-uxsdk-beta-accessory/src/main/java/dji/ux/beta/accessory/widget/rtk/RTKSatelliteStatusWidget.kt index 88977fc1..b4ed79e3 100644 --- a/android-uxsdk-beta-hardwareaccessory/src/main/java/dji/ux/beta/hardwareaccessory/widget/rtk/RTKSatelliteStatusWidget.kt +++ b/android-uxsdk-beta-accessory/src/main/java/dji/ux/beta/accessory/widget/rtk/RTKSatelliteStatusWidget.kt @@ -18,10 +18,10 @@ * 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.ux.beta.hardwareaccessory.widget.rtk +package dji.ux.beta.accessory.widget.rtk import android.annotation.SuppressLint import android.content.Context @@ -45,18 +45,18 @@ import dji.common.flightcontroller.rtk.NetworkServiceChannelState import dji.common.product.Model import dji.thirdparty.io.reactivex.Flowable import dji.thirdparty.io.reactivex.functions.Consumer -import dji.ux.beta.core.base.ConstraintLayoutWidget +import dji.ux.beta.accessory.R +import dji.ux.beta.accessory.widget.rtk.RTKSatelliteStatusWidget.ModelState +import dji.ux.beta.accessory.widget.rtk.RTKSatelliteStatusWidget.ModelState.* +import dji.ux.beta.accessory.widget.rtk.RTKSatelliteStatusWidgetModel.* import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.GlobalPreferencesManager import dji.ux.beta.core.base.SchedulerProvider -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget +import dji.ux.beta.core.communication.GlobalPreferencesManager +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.* import dji.ux.beta.core.util.DisplayUtil import dji.ux.beta.core.util.UnitConversionUtil -import dji.ux.beta.hardwareaccessory.R -import dji.ux.beta.hardwareaccessory.widget.rtk.RTKSatelliteStatusWidget.RTKSatelliteStatusWidgetState -import dji.ux.beta.hardwareaccessory.widget.rtk.RTKSatelliteStatusWidget.RTKSatelliteStatusWidgetState.* -import dji.ux.beta.hardwareaccessory.widget.rtk.RTKSatelliteStatusWidgetModel.* import java.util.* private const val TAG = "RTKStatusWidget" @@ -64,13 +64,13 @@ private const val TAG = "RTKStatusWidget" /** * This widget shows all the information related to RTK. This includes coordinates and altitude * of the aircraft and base station, course angle, GLONASS, Beidou, Galileo, and GPS satellite - * counts for both antennas and the base station, and overall status of the RTK system. + * counts for both antennas and the base station, and overall state of the RTK system. */ open class RTKSatelliteStatusWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ConstraintLayoutWidget(context, attrs, defStyleAttr) { +) : ConstraintLayoutWidget(context, attrs, defStyleAttr) { //region Fields private val rtkStatusTitleTextView: TextView = findViewById(R.id.textview_rtk_status_title) private val rtkStatusTextView: TextView = findViewById(R.id.textview_rtk_status) @@ -118,11 +118,11 @@ open class RTKSatelliteStatusWidget @JvmOverloads constructor( private val rtkOrientationPositioningSeparator: View = findViewById(R.id.rtk_orientation_positioning_separator) private val rtkLocationSeparator: View = findViewById(R.id.rtk_location_separator) private val rtkSatelliteCountSeparator: View = findViewById(R.id.rtk_satellite_count_separator) - private val connectionStatusTextColorMap: MutableMap = + private val connectionStateTextColorMap: MutableMap = mutableMapOf( - RTKBaseStationStatus.CONNECTED_IN_USE to getColor(R.color.uxsdk_rtk_status_connected_in_use), - RTKBaseStationStatus.CONNECTED_NOT_IN_USE to getColor(R.color.uxsdk_rtk_status_connected_not_in_use), - RTKBaseStationStatus.DISCONNECTED to getColor(R.color.uxsdk_rtk_status_disconnected) + RTKBaseStationState.CONNECTED_IN_USE to getColor(R.color.uxsdk_rtk_status_connected_in_use), + RTKBaseStationState.CONNECTED_NOT_IN_USE to getColor(R.color.uxsdk_rtk_status_connected_not_in_use), + RTKBaseStationState.DISCONNECTED to getColor(R.color.uxsdk_rtk_status_disconnected) ) private val widgetModel by lazy { @@ -132,7 +132,7 @@ open class RTKSatelliteStatusWidget @JvmOverloads constructor( } /** - * The text size of the RTK connection status title. + * The text size of the RTK connection state title. */ var rtkConnectionStatusTitleTextSize: Float @JvmName("getRTKConnectionStatusTitleTextSize") @@ -144,7 +144,7 @@ open class RTKSatelliteStatusWidget @JvmOverloads constructor( } /** - * The text color of the RTK connection status title. + * The text color of the RTK connection state title. */ var rtkConnectionStatusTitleTextColor: Int @JvmName("getRTKConnectionStatusTitleTextColor") @@ -156,7 +156,7 @@ open class RTKSatelliteStatusWidget @JvmOverloads constructor( } /** - * The background of the RTK connection status title. + * The background of the RTK connection state title. */ var rtkConnectionStatusTitleTextBackground: Drawable? @JvmName("getRTKConnectionStatusTitleTextBackground") @@ -167,7 +167,7 @@ open class RTKSatelliteStatusWidget @JvmOverloads constructor( } /** - * The text size of the RTK connection status. + * The text size of the RTK connection state. */ var rtkConnectionStatusTextSize: Float @JvmName("getRTKConnectionStatusTextSize") @@ -179,7 +179,7 @@ open class RTKSatelliteStatusWidget @JvmOverloads constructor( } /** - * The background of the RTK connection status. + * The background of the RTK connection state. */ var rtkConnectionStatusTextBackground: Drawable? @JvmName("getRTKConnectionStatusTextBackground") @@ -521,7 +521,7 @@ open class RTKSatelliteStatusWidget @JvmOverloads constructor( } /** - * The image displayed behind the satellite status table. + * The image displayed behind the satellite state table. */ var tableBackground: Drawable? get() = tableBackgroundImageView.imageDrawable @@ -530,7 +530,7 @@ open class RTKSatelliteStatusWidget @JvmOverloads constructor( } //endregion - //region Constructors + //region Constructor override fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { inflate(context, R.layout.uxsdk_widget_rtk_satellite_status, this) } @@ -558,28 +558,28 @@ open class RTKSatelliteStatusWidget @JvmOverloads constructor( override fun reactToModelChanges() { addReaction(widgetModel.isRTKConnected - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { updateUIForIsRTKConnected(it) }) addReaction(widgetModel.rtkState - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { updateRTKStateUI(it) }) addReaction(widgetModel.model - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { updateModel(it) }) addReaction(widgetModel.rtkSignal - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { updateBaseStationTitle(it) }) - addReaction(widgetModel.rtkBaseStationStatus - .observeOn(SchedulerProvider.getInstance().ui()) + addReaction(widgetModel.rtkBaseStationState + .observeOn(SchedulerProvider.ui()) .subscribe { updateBaseStationStatus(it) }) - addReaction(widgetModel.rtkNetworkServiceStatus - .observeOn(SchedulerProvider.getInstance().ui()) + addReaction(widgetModel.rtkNetworkServiceState + .observeOn(SchedulerProvider.ui()) .subscribe { updateNetworkServiceStatus(it) }) addReaction(widgetModel.standardDeviation - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { updateStandardDeviation(it) }) addReaction(widgetModel.productConnection - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) } @@ -613,14 +613,14 @@ open class RTKSatelliteStatusWidget @JvmOverloads constructor( updateBeidouSatelliteDisplay(rtkState) updateGLONASSSatelliteDisplay(rtkState) updateGalileoSatelliteDisplay(rtkState) - widgetStateDataProcessor.onNext(RTKStateUpdate(rtkState)) + widgetStateDataProcessor.onNext(RTKStateUpdated(rtkState)) } private fun updateUIForIsRTKConnected(isRTKConnected: Boolean) { if (!isRTKConnected) { initItemValues() } - widgetStateDataProcessor.onNext(RTKConnectedUpdate(isRTKConnected)) + widgetStateDataProcessor.onNext(RTKConnectionUpdated(isRTKConnected)) } private fun updateModel(model: Model) { @@ -640,7 +640,7 @@ open class RTKSatelliteStatusWidget @JvmOverloads constructor( beiDouAntenna2TextView.visibility = if (!isPhantom4RTK(model) && isBeiDouSatelliteInfoVisible) View.VISIBLE else View.GONE glonassAntenna2TextView.visibility = if (!isPhantom4RTK(model) && isGLONASSSatelliteInfoVisible) View.VISIBLE else View.GONE galileoAntenna2TextView.visibility = if (!isPhantom4RTK(model) && isGalileoSatelliteInfoVisible) View.VISIBLE else View.GONE - widgetStateDataProcessor.onNext(ModelUpdate(model)) + widgetStateDataProcessor.onNext(ModelUpdated(model)) } private fun isPhantom4RTK(model: Model): Boolean { @@ -718,7 +718,7 @@ open class RTKSatelliteStatusWidget @JvmOverloads constructor( + resources.getString(resourceString, String.format(Locale.US, "%s", standardDeviation.longitude)) + "\n" + resources.getString(resourceString, String.format(Locale.US, "%s", standardDeviation.altitude))) standardDeviationTextView.text = standardDeviationStr - widgetStateDataProcessor.onNext(StandardDeviationUpdate(standardDeviation)) + widgetStateDataProcessor.onNext(StandardDeviationUpdated(standardDeviation)) } private fun updateBeidouSatelliteDisplay(rtkState: RTKState) { @@ -772,16 +772,16 @@ open class RTKSatelliteStatusWidget @JvmOverloads constructor( } } - private fun updateNetworkServiceStatus(networkServiceStatus: RTKNetworkServiceStatus) { + private fun updateNetworkServiceStatus(networkServiceState: RTKNetworkServiceState) { val rtkStatusStr: String - var rtkStatusColor: Int = getRTKConnectionStatusLabelTextColor(RTKBaseStationStatus.DISCONNECTED) - when (networkServiceStatus.state) { + var rtkStatusColor: Int = getRTKConnectionStatusLabelTextColor(RTKBaseStationState.DISCONNECTED) + when (networkServiceState.state) { NetworkServiceChannelState.TRANSMITTING -> - if (networkServiceStatus.isRTKBeingUsed) { - rtkStatusColor = getRTKConnectionStatusLabelTextColor(RTKBaseStationStatus.CONNECTED_IN_USE) + if (networkServiceState.isRTKBeingUsed) { + rtkStatusColor = getRTKConnectionStatusLabelTextColor(RTKBaseStationState.CONNECTED_IN_USE) rtkStatusStr = getString(R.string.uxsdk_rtk_state_connect) } else { - rtkStatusColor = getRTKConnectionStatusLabelTextColor(RTKBaseStationStatus.CONNECTED_NOT_IN_USE) + rtkStatusColor = getRTKConnectionStatusLabelTextColor(RTKBaseStationState.CONNECTED_NOT_IN_USE) rtkStatusStr = getString(R.string.uxsdk_rtk_state_connect_not_healthy) } NetworkServiceChannelState.SERVICE_SUSPENSION -> rtkStatusStr = getString(R.string.uxsdk_rtk_state_pause) @@ -789,8 +789,8 @@ open class RTKSatelliteStatusWidget @JvmOverloads constructor( NetworkServiceChannelState.NETWORK_NOT_REACHABLE -> rtkStatusStr = getString(R.string.uxsdk_rtk_state_network_err) NetworkServiceChannelState.LOGIN_FAILURE -> rtkStatusStr = getString(R.string.uxsdk_rtk_state_auth_failed) NetworkServiceChannelState.UNKNOWN -> rtkStatusStr = - if (networkServiceStatus.isNetworkServiceOpen) { - resources.getString(R.string.uxsdk_rtk_nrtk_state_inner_error, getRTKTypeName(networkServiceStatus.rtkSignal)) + if (networkServiceState.isNetworkServiceOpen) { + resources.getString(R.string.uxsdk_rtk_nrtk_state_inner_error, getRTKTypeName(networkServiceState.rtkSignal)) } else { getString(R.string.uxsdk_rtk_nrtk_state_unknown) } @@ -802,32 +802,32 @@ open class RTKSatelliteStatusWidget @JvmOverloads constructor( } rtkStatusTextView.text = rtkStatusStr rtkStatusTextView.setTextColor(rtkStatusColor) - widgetStateDataProcessor.onNext(RTKNetworkServiceStatusUpdate(networkServiceStatus)) + widgetStateDataProcessor.onNext(RTKNetworkServiceStateUpdated(networkServiceState)) } - private fun updateBaseStationStatus(connectionState: RTKBaseStationStatus) { + private fun updateBaseStationStatus(connectionState: RTKBaseStationState) { when (connectionState) { - RTKBaseStationStatus.CONNECTED_IN_USE -> { + RTKBaseStationState.CONNECTED_IN_USE -> { rtkStatusTextView.setText(R.string.uxsdk_rtk_state_connect) - rtkStatusTextView.setTextColor(getRTKConnectionStatusLabelTextColor(RTKBaseStationStatus.CONNECTED_IN_USE)) + rtkStatusTextView.setTextColor(getRTKConnectionStatusLabelTextColor(RTKBaseStationState.CONNECTED_IN_USE)) } - RTKBaseStationStatus.CONNECTED_NOT_IN_USE -> { + RTKBaseStationState.CONNECTED_NOT_IN_USE -> { rtkStatusTextView.setText(R.string.uxsdk_rtk_state_connect_not_healthy) - rtkStatusTextView.setTextColor(getRTKConnectionStatusLabelTextColor(RTKBaseStationStatus.CONNECTED_NOT_IN_USE)) + rtkStatusTextView.setTextColor(getRTKConnectionStatusLabelTextColor(RTKBaseStationState.CONNECTED_NOT_IN_USE)) } - RTKBaseStationStatus.DISCONNECTED -> { + RTKBaseStationState.DISCONNECTED -> { rtkStatusTextView.setText(R.string.uxsdk_rtk_state_disconnect) - rtkStatusTextView.textColor = getRTKConnectionStatusLabelTextColor(RTKBaseStationStatus.DISCONNECTED) + rtkStatusTextView.textColor = getRTKConnectionStatusLabelTextColor(RTKBaseStationState.DISCONNECTED) } } - widgetStateDataProcessor.onNext(RTKBaseStationStatusUpdate(connectionState)) + widgetStateDataProcessor.onNext(RTKBaseStationStateUpdated(connectionState)) } private fun updateBaseStationTitle(rtkSignal: RTKSignal) { val name = getRTKTypeName(rtkSignal) baseStationCoordinatesTitleTextView.text = name rtkStatusTitleTextView.text = resources.getString(R.string.uxsdk_rtk_status_desc, name) - widgetStateDataProcessor.onNext(RTKSignalUpdate(rtkSignal)) + widgetStateDataProcessor.onNext(RTKSignalUpdated(rtkSignal)) } private fun getRTKTypeName(rtkSignal: RTKSignal): String { @@ -865,7 +865,7 @@ open class RTKSatelliteStatusWidget @JvmOverloads constructor( if (!isInEditMode) { addDisposable(widgetModel.rtkState .firstOrError() - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { updateOrientationStatus(it.isHeadingValid, it.headingSolution) }, logErrorConsumer(TAG, "updateOrientation"))) } @@ -875,7 +875,7 @@ open class RTKSatelliteStatusWidget @JvmOverloads constructor( if (!isInEditMode) { addDisposable(widgetModel.model .firstOrError() - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { updateModel(it) }, logErrorConsumer(TAG, "updateModel"))) } @@ -889,7 +889,7 @@ open class RTKSatelliteStatusWidget @JvmOverloads constructor( } /** - * Set the text appearance of the RTK connection status title. + * Set the text appearance of the RTK connection state title. * * @param textAppearance Style resource for text appearance */ @@ -898,7 +898,7 @@ open class RTKSatelliteStatusWidget @JvmOverloads constructor( } /** - * Set the text appearance of the RTK connection status. + * Set the text appearance of the RTK connection state. * * @param textAppearance Style resource for text appearance */ @@ -907,27 +907,27 @@ open class RTKSatelliteStatusWidget @JvmOverloads constructor( } /** - * Set the text color of the RTK connection status when the RTKBaseStationStatus is the given + * Set the text color of the RTK connection state when the RTKBaseStationState is the given * value. * - * @param status The status for which to set the text color. + * @param state The state for which to set the text color. * @param color The color of the text */ - fun setRTKConnectionStatusLabelTextColor(status: RTKBaseStationStatus, @ColorInt color: Int) { - connectionStatusTextColorMap[status] = color - widgetModel.updateRTKConnectionStatus() + fun setRTKConnectionStatusLabelTextColor(state: RTKBaseStationState, @ColorInt color: Int) { + connectionStateTextColorMap[state] = color + widgetModel.updateRTKConnectionState() } /** - * Get the text color of the RTK connection status when the RTKBaseStationStatus is the given + * Get the text color of the RTK connection state when the RTKBaseStationState is the given * value. * - * @param status The status for which to get the text color. + * @param state The state for which to get the text color. * @return The color of the text */ @ColorInt - fun getRTKConnectionStatusLabelTextColor(status: RTKBaseStationStatus): Int { - return (connectionStatusTextColorMap[status]?.let { it } + fun getRTKConnectionStatusLabelTextColor(state: RTKBaseStationState): Int { + return (connectionStateTextColorMap[state]?.let { it } ?: getColor(R.color.uxsdk_rtk_status_disconnected)) } @@ -994,7 +994,7 @@ open class RTKSatelliteStatusWidget @JvmOverloads constructor( } /** - * Set the image displayed behind the satellite status table. + * Set the image displayed behind the satellite state table. * * @param resourceId Integer ID of the drawable resource */ @@ -1025,13 +1025,13 @@ open class RTKSatelliteStatusWidget @JvmOverloads constructor( rtkConnectionStatusTextSize = DisplayUtil.pxToSp(context, it) } typedArray.getColorAndUse(R.styleable.RTKSatelliteStatusWidget_uxsdk_rtkConnectionStatusConnectedInUseTextColor) { - setRTKConnectionStatusLabelTextColor(RTKBaseStationStatus.CONNECTED_IN_USE, it) + setRTKConnectionStatusLabelTextColor(RTKBaseStationState.CONNECTED_IN_USE, it) } typedArray.getColorAndUse(R.styleable.RTKSatelliteStatusWidget_uxsdk_rtkConnectionStatusConnectedNotInUseTextColor) { - setRTKConnectionStatusLabelTextColor(RTKBaseStationStatus.CONNECTED_NOT_IN_USE, it) + setRTKConnectionStatusLabelTextColor(RTKBaseStationState.CONNECTED_NOT_IN_USE, it) } typedArray.getColorAndUse(R.styleable.RTKSatelliteStatusWidget_uxsdk_rtkConnectionStatusDisconnectedTextColor) { - setRTKConnectionStatusLabelTextColor(RTKBaseStationStatus.DISCONNECTED, it) + setRTKConnectionStatusLabelTextColor(RTKBaseStationState.DISCONNECTED, it) } typedArray.getDrawableAndUse(R.styleable.RTKSatelliteStatusWidget_uxsdk_rtkConnectionStatusBackgroundDrawable) { rtkConnectionStatusTextBackground = it @@ -1083,56 +1083,58 @@ open class RTKSatelliteStatusWidget @JvmOverloads constructor( //endregion //region Hooks + /** - * Get the [RTKSatelliteStatusWidgetState] updates + * Get the [ModelState] updates */ - override fun getWidgetStateUpdate(): Flowable { + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { return super.getWidgetStateUpdate() } /** * Class defines the widget state updates */ - sealed class RTKSatelliteStatusWidgetState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : RTKSatelliteStatusWidgetState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * RTK connection update */ - data class RTKConnectedUpdate(val isConnected: Boolean) : RTKSatelliteStatusWidgetState() + data class RTKConnectionUpdated(val isConnected: Boolean) : ModelState() /** * RTK state update */ - data class RTKStateUpdate(val rtkState: RTKState) : RTKSatelliteStatusWidgetState() + data class RTKStateUpdated(val rtkState: RTKState) : ModelState() /** * Model update */ - data class ModelUpdate(val model: Model) : RTKSatelliteStatusWidgetState() + data class ModelUpdated(val model: Model) : ModelState() /** * RTK signal update */ - data class RTKSignalUpdate(val source: RTKSignal) : RTKSatelliteStatusWidgetState() + data class RTKSignalUpdated(val source: RTKSignal) : ModelState() /** * Standard deviation update */ - data class StandardDeviationUpdate(val standardDeviation: StandardDeviation) : RTKSatelliteStatusWidgetState() + data class StandardDeviationUpdated(val standardDeviation: StandardDeviation) : ModelState() /** - * RTK base station status update + * RTK base station state update */ - data class RTKBaseStationStatusUpdate(val status: RTKBaseStationStatus) : RTKSatelliteStatusWidgetState() + data class RTKBaseStationStateUpdated(val state: RTKBaseStationState) : ModelState() /** - * RTK network service status update + * RTK network service state update */ - data class RTKNetworkServiceStatusUpdate(val status: RTKNetworkServiceStatus) : RTKSatelliteStatusWidgetState() + data class RTKNetworkServiceStateUpdated(val state: RTKNetworkServiceState) : ModelState() } //endregion } \ No newline at end of file diff --git a/android-uxsdk-beta-hardwareaccessory/src/main/java/dji/ux/beta/hardwareaccessory/widget/rtk/RTKSatelliteStatusWidgetModel.kt b/android-uxsdk-beta-accessory/src/main/java/dji/ux/beta/accessory/widget/rtk/RTKSatelliteStatusWidgetModel.kt similarity index 81% rename from android-uxsdk-beta-hardwareaccessory/src/main/java/dji/ux/beta/hardwareaccessory/widget/rtk/RTKSatelliteStatusWidgetModel.kt rename to android-uxsdk-beta-accessory/src/main/java/dji/ux/beta/accessory/widget/rtk/RTKSatelliteStatusWidgetModel.kt index 058575ef..7108c488 100644 --- a/android-uxsdk-beta-hardwareaccessory/src/main/java/dji/ux/beta/hardwareaccessory/widget/rtk/RTKSatelliteStatusWidgetModel.kt +++ b/android-uxsdk-beta-accessory/src/main/java/dji/ux/beta/accessory/widget/rtk/RTKSatelliteStatusWidgetModel.kt @@ -18,10 +18,10 @@ * 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.ux.beta.hardwareaccessory.widget.rtk +package dji.ux.beta.accessory.widget.rtk import dji.common.flightcontroller.RTKState import dji.common.flightcontroller.rtk.NetworkServiceChannelState @@ -35,11 +35,11 @@ import dji.sdk.network.RTKNetworkServiceProvider import dji.thirdparty.io.reactivex.Flowable import dji.thirdparty.io.reactivex.functions.BiFunction import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.GlobalPreferencesInterface import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.GlobalPreferenceKeys -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.base.uxsdkkeys.UXKeys +import dji.ux.beta.core.communication.GlobalPreferenceKeys +import dji.ux.beta.core.communication.GlobalPreferencesInterface +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.UXKeys import dji.ux.beta.core.util.DataProcessor import dji.ux.beta.core.util.UnitConversionUtil @@ -62,9 +62,9 @@ class RTKSatelliteStatusWidgetModel(djiSdkModel: DJISDKModel, private val unitTypeProcessor: DataProcessor = DataProcessor.create(UnitConversionUtil.UnitType.METRIC) private val networkServiceStateProcessor: DataProcessor = DataProcessor.create(NetworkServiceChannelState.UNKNOWN) - private val rtkBaseStationStatusProcessor: DataProcessor = DataProcessor.create(RTKBaseStationStatus.DISCONNECTED) - private val rtkNetworkServiceStatusProcessor: DataProcessor = DataProcessor.create( - RTKNetworkServiceStatus(NetworkServiceChannelState.UNKNOWN, + private val rtkBaseStationStateProcessor: DataProcessor = DataProcessor.create(RTKBaseStationState.DISCONNECTED) + private val rtkNetworkServiceStateProcessor: DataProcessor = DataProcessor.create( + RTKNetworkServiceState(NetworkServiceChannelState.UNKNOWN, isRTKBeingUsed = false, isNetworkServiceOpen = false, rtkSignal = RTKSignal.BASE_STATION)) @@ -111,27 +111,27 @@ class RTKSatelliteStatusWidgetModel(djiSdkModel: DJISDKModel, get() = standardDeviationProcessor.toFlowable() /** - * Get the status of the RTK base station. + * Get the state of the RTK base station. */ - @get:JvmName("getRTKBaseStationStatus") - val rtkBaseStationStatus: Flowable - get() = rtkBaseStationStatusProcessor.toFlowable() + @get:JvmName("getRTKBaseStationState") + val rtkBaseStationState: Flowable + get() = rtkBaseStationStateProcessor.toFlowable() /** - * Get the status of the network service. + * Get the state of the network service. */ - @get:JvmName("getRTKNetworkServiceStatus") - val rtkNetworkServiceStatus: Flowable - get() = rtkNetworkServiceStatusProcessor.toFlowable() + @get:JvmName("getRTKNetworkServiceState") + val rtkNetworkServiceState: Flowable + get() = rtkNetworkServiceStateProcessor.toFlowable() /** - * Sends the latest network service status or base station status to the corresponding flowable. + * Sends the latest network service state or base station state to the corresponding flowable. */ - fun updateRTKConnectionStatus() { + fun updateRTKConnectionState() { if (isNetworkServiceOpen(referenceStationSourceProcessor.value)) { - updateNetworkServiceStatus() + updateNetworkServiceState() } else { - updateBaseStationStatus() + updateBaseStationState() } } //endregion @@ -182,7 +182,7 @@ class RTKSatelliteStatusWidgetModel(djiSdkModel: DJISDKModel, } override fun updateStates() { - updateRTKConnectionStatus() + updateRTKConnectionState() var stdLatitude = 0f var stdLongitude = 0f @@ -209,29 +209,29 @@ class RTKSatelliteStatusWidgetModel(djiSdkModel: DJISDKModel, override fun onNetworkServiceStateUpdate(networkServiceState: NetworkServiceState?) { if (networkServiceState != null && networkServiceStateProcessor.value != networkServiceState.channelState) { networkServiceStateProcessor.onNext(networkServiceState.channelState) - updateNetworkServiceStatus() + updateNetworkServiceState() } } //endregion //region Helper methods - private fun updateNetworkServiceStatus() { - rtkNetworkServiceStatusProcessor.onNext(RTKNetworkServiceStatus(networkServiceStateProcessor.value, + private fun updateNetworkServiceState() { + rtkNetworkServiceStateProcessor.onNext(RTKNetworkServiceState(networkServiceStateProcessor.value, rtkStateProcessor.value.isRTKBeingUsed, isNetworkServiceOpen(referenceStationSourceProcessor.value), rtkSignalProcessor.value)) } - private fun updateBaseStationStatus() { + private fun updateBaseStationState() { if (isRTKConnectedProcessor.value && productConnectionProcessor.value) { if (rtkStateProcessor.value.isRTKBeingUsed) { - rtkBaseStationStatusProcessor.onNext(RTKBaseStationStatus.CONNECTED_IN_USE) + rtkBaseStationStateProcessor.onNext(RTKBaseStationState.CONNECTED_IN_USE) } else { - rtkBaseStationStatusProcessor.onNext(RTKBaseStationStatus.CONNECTED_NOT_IN_USE) + rtkBaseStationStateProcessor.onNext(RTKBaseStationState.CONNECTED_NOT_IN_USE) } } else { - rtkBaseStationStatusProcessor.onNext(RTKBaseStationStatus.DISCONNECTED) + rtkBaseStationStateProcessor.onNext(RTKBaseStationState.DISCONNECTED) } } @@ -245,9 +245,9 @@ class RTKSatelliteStatusWidgetModel(djiSdkModel: DJISDKModel, //region Classes /** - * The status of the RTK base station + * The state of the RTK base station */ - enum class RTKBaseStationStatus { + enum class RTKBaseStationState { /** * The RTK base station is connected and in use. */ @@ -265,12 +265,12 @@ class RTKSatelliteStatusWidgetModel(djiSdkModel: DJISDKModel, } /** - * The status of the network service. + * The state of the network service. */ - data class RTKNetworkServiceStatus(val state: NetworkServiceChannelState, - val isRTKBeingUsed: Boolean, - val isNetworkServiceOpen: Boolean, - @get:JvmName("getRTKSignal") + data class RTKNetworkServiceState(val state: NetworkServiceChannelState, + val isRTKBeingUsed: Boolean, + val isNetworkServiceOpen: Boolean, + @get:JvmName("getRTKSignal") val rtkSignal: RTKSignal) /** diff --git a/android-uxsdk-beta-hardwareaccessory/src/main/java/dji/ux/beta/hardwareaccessory/widget/rtk/RTKWidget.kt b/android-uxsdk-beta-accessory/src/main/java/dji/ux/beta/accessory/widget/rtk/RTKWidget.kt similarity index 86% rename from android-uxsdk-beta-hardwareaccessory/src/main/java/dji/ux/beta/hardwareaccessory/widget/rtk/RTKWidget.kt rename to android-uxsdk-beta-accessory/src/main/java/dji/ux/beta/accessory/widget/rtk/RTKWidget.kt index 29463492..38da7b70 100644 --- a/android-uxsdk-beta-hardwareaccessory/src/main/java/dji/ux/beta/hardwareaccessory/widget/rtk/RTKWidget.kt +++ b/android-uxsdk-beta-accessory/src/main/java/dji/ux/beta/accessory/widget/rtk/RTKWidget.kt @@ -18,10 +18,10 @@ * 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.ux.beta.hardwareaccessory.widget.rtk +package dji.ux.beta.accessory.widget.rtk import android.annotation.SuppressLint import android.content.Context @@ -37,17 +37,19 @@ import androidx.core.content.res.use import dji.thirdparty.io.reactivex.Flowable import dji.thirdparty.io.reactivex.functions.Consumer import dji.thirdparty.io.reactivex.processors.PublishProcessor -import dji.ux.beta.core.base.ConstraintLayoutWidget +import dji.ux.beta.accessory.R +import dji.ux.beta.accessory.widget.rtk.RTKWidget.ModelState +import dji.ux.beta.accessory.widget.rtk.RTKWidget.ModelState.ProductConnected +import dji.ux.beta.accessory.widget.rtk.RTKWidget.ModelState.RTKEnabledUpdated +import dji.ux.beta.accessory.widget.rtk.RTKWidget.UIState.DialogActionDismissed +import dji.ux.beta.accessory.widget.rtk.RTKWidget.UIState.VisibilityUpdated import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.OnStateChangeCallback import dji.ux.beta.core.base.SchedulerProvider -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.OnStateChangeCallback import dji.ux.beta.core.extension.* import dji.ux.beta.core.util.DisplayUtil -import dji.ux.beta.hardwareaccessory.R -import dji.ux.beta.hardwareaccessory.widget.rtk.RTKWidget.RTKWidgetState -import dji.ux.beta.hardwareaccessory.widget.rtk.RTKWidget.RTKWidgetState.* -import dji.ux.beta.hardwareaccessory.widget.rtk.RTKWidget.RTKWidgetUIState.DialogOKTap private const val TAG = "RTKWidget" @@ -58,19 +60,18 @@ open class RTKWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ConstraintLayoutWidget(context, attrs, defStyleAttr), View.OnClickListener, +) : ConstraintLayoutWidget(context, attrs, defStyleAttr), View.OnClickListener, OnStateChangeCallback { //region Fields private val dialogOkTextView: TextView = findViewById(R.id.textview_ok) private val rtkDescriptionTextView: TextView = findViewById(R.id.textview_rtk_description) private val rtkDialogSeparator: View = findViewById(R.id.rtk_dialog_separator) - private val uiUpdateStateProcessor: PublishProcessor = PublishProcessor.create() + private val uiUpdateStateProcessor: PublishProcessor = PublishProcessor.create() private val widgetModel by lazy { RTKWidgetModel( DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - SchedulerProvider.getInstance()) + ObservableInMemoryKeyedStore.getInstance()) } /** @@ -211,7 +212,7 @@ open class RTKWidget @JvmOverloads constructor( override fun onClick(v: View) { if (v.id == R.id.textview_ok) { visibility = View.GONE - uiUpdateStateProcessor.onNext(DialogOKTap) + uiUpdateStateProcessor.onNext(DialogActionDismissed) } } @@ -221,10 +222,10 @@ open class RTKWidget @JvmOverloads constructor( override fun reactToModelChanges() { addReaction(widgetModel.rtkEnabled - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { rtkEnabled: Boolean -> updateUIForRTKEnabled(rtkEnabled) }) addReaction(widgetModel.productConnection - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) } //endregion @@ -238,7 +239,7 @@ open class RTKWidget @JvmOverloads constructor( rtkSatelliteStatusWidget.visibility = View.GONE rtkDescriptionTextView.visibility = View.VISIBLE } - widgetStateDataProcessor.onNext(RTKEnabledUpdate(rtkEnabled)) + widgetStateDataProcessor.onNext(RTKEnabledUpdated(rtkEnabled)) } //endregion @@ -246,7 +247,7 @@ open class RTKWidget @JvmOverloads constructor( private fun toggleVisibility() { addDisposable(widgetModel.rtkSupported .firstOrError() - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { rtkSupported: Boolean -> if (rtkSupported) { visibility = if (visibility == View.VISIBLE) { @@ -254,7 +255,7 @@ open class RTKWidget @JvmOverloads constructor( } else { View.VISIBLE } - widgetStateDataProcessor.onNext(VisibilityUpdate(visibility == View.VISIBLE)) + uiUpdateStateProcessor.onNext(VisibilityUpdated(visibility == View.VISIBLE)) } }, logErrorConsumer(TAG, "getRTKSupported: "))) } @@ -338,47 +339,49 @@ open class RTKWidget @JvmOverloads constructor( //region Hooks /** - * Get the [RTKWidgetUIState] updates + * Get the [UIState] updates + */ + fun getUIStateUpdates(): Flowable { + return uiUpdateStateProcessor.onBackpressureBuffer() + } + + /** + * Get the [ModelState] updates */ - fun getUIStateUpdates(): Flowable { - return uiUpdateStateProcessor + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { + return super.getWidgetStateUpdate() } /** * Widget UI update State */ - sealed class RTKWidgetUIState { + sealed class UIState { /** * OK button tapped */ - object DialogOKTap : RTKWidgetUIState() - } + object DialogActionDismissed : UIState() - /** - * Get the [RTKWidgetState] updates - */ - override fun getWidgetStateUpdate(): Flowable { - return super.getWidgetStateUpdate() + /** + * Widget visibility update + */ + data class VisibilityUpdated(val isVisible: Boolean) : UIState() } /** * Class defines the widget state updates */ - sealed class RTKWidgetState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : RTKWidgetState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * RTK enabled update */ - data class RTKEnabledUpdate(val isRTKEnabled: Boolean) : RTKWidgetState() + data class RTKEnabledUpdated(val isRTKEnabled: Boolean) : ModelState() - /** - * Widget visibility update - */ - data class VisibilityUpdate(val isVisible: Boolean) : RTKWidgetState() } //endregion } \ No newline at end of file diff --git a/android-uxsdk-beta-hardwareaccessory/src/main/java/dji/ux/beta/hardwareaccessory/widget/rtk/RTKWidgetModel.kt b/android-uxsdk-beta-accessory/src/main/java/dji/ux/beta/accessory/widget/rtk/RTKWidgetModel.kt similarity index 89% rename from android-uxsdk-beta-hardwareaccessory/src/main/java/dji/ux/beta/hardwareaccessory/widget/rtk/RTKWidgetModel.kt rename to android-uxsdk-beta-accessory/src/main/java/dji/ux/beta/accessory/widget/rtk/RTKWidgetModel.kt index c4742578..80a72ccc 100644 --- a/android-uxsdk-beta-hardwareaccessory/src/main/java/dji/ux/beta/hardwareaccessory/widget/rtk/RTKWidgetModel.kt +++ b/android-uxsdk-beta-accessory/src/main/java/dji/ux/beta/accessory/widget/rtk/RTKWidgetModel.kt @@ -18,19 +18,19 @@ * 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.ux.beta.hardwareaccessory.widget.rtk +package dji.ux.beta.accessory.widget.rtk import dji.keysdk.DJIKey import dji.keysdk.FlightControllerKey import dji.thirdparty.io.reactivex.Flowable import dji.thirdparty.io.reactivex.functions.Consumer import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.SchedulerProviderInterface +import dji.ux.beta.core.base.SchedulerProvider import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.util.DataProcessor private const val TAG = "RTKWidgetModel" @@ -40,8 +40,7 @@ private const val TAG = "RTKWidgetModel" * the underlying logic and communication */ class RTKWidgetModel(djiSdkModel: DJISDKModel, - uxKeyManager: ObservableInMemoryKeyedStore, - private val schedulerProvider: SchedulerProviderInterface + uxKeyManager: ObservableInMemoryKeyedStore ) : WidgetModel(djiSdkModel, uxKeyManager) { //region Fields @@ -75,7 +74,7 @@ class RTKWidgetModel(djiSdkModel: DJISDKModel, val isRTKConnectedKey: DJIKey = FlightControllerKey.createRTKKey(FlightControllerKey.IS_RTK_CONNECTED) bindDataProcessor(isRTKConnectedKey, isRTKConnectedProcessor) { addDisposable(djiSdkModel.getValue(rtkEnabledKey) - .observeOn(schedulerProvider.io()) + .observeOn(SchedulerProvider.io()) .subscribe(Consumer { }, logErrorConsumer(TAG, "isRTKEnabled: "))) } } diff --git a/android-uxsdk-beta-hardwareaccessory/src/main/res/drawable/uxsdk_ic_rtk_status.xml b/android-uxsdk-beta-accessory/src/main/res/drawable/uxsdk_ic_rtk_status.xml similarity index 100% rename from android-uxsdk-beta-hardwareaccessory/src/main/res/drawable/uxsdk_ic_rtk_status.xml rename to android-uxsdk-beta-accessory/src/main/res/drawable/uxsdk_ic_rtk_status.xml diff --git a/android-uxsdk-beta-hardwareaccessory/src/main/res/drawable/uxsdk_rtk_status_bg.xml b/android-uxsdk-beta-accessory/src/main/res/drawable/uxsdk_rtk_status_bg.xml similarity index 100% rename from android-uxsdk-beta-hardwareaccessory/src/main/res/drawable/uxsdk_rtk_status_bg.xml rename to android-uxsdk-beta-accessory/src/main/res/drawable/uxsdk_rtk_status_bg.xml diff --git a/android-uxsdk-beta-hardwareaccessory/src/main/res/layout/uxsdk_widget_rtk.xml b/android-uxsdk-beta-accessory/src/main/res/layout/uxsdk_widget_rtk.xml similarity index 95% rename from android-uxsdk-beta-hardwareaccessory/src/main/res/layout/uxsdk_widget_rtk.xml rename to android-uxsdk-beta-accessory/src/main/res/layout/uxsdk_widget_rtk.xml index b9f7f23e..bfe42389 100644 --- a/android-uxsdk-beta-hardwareaccessory/src/main/res/layout/uxsdk_widget_rtk.xml +++ b/android-uxsdk-beta-accessory/src/main/res/layout/uxsdk_widget_rtk.xml @@ -27,7 +27,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:parentTag="dji.ux.beta.hardwareaccessory.widget.rtk.RTKWidget"> + tools:parentTag="dji.ux.beta.accessory.widget.rtk.RTKWidget"> - - + tools:parentTag="dji.ux.beta.accessory.widget.rtk.RTKEnabledWidget"> + tools:parentTag="dji.ux.beta.accessory.widget.rtk.RTKSatelliteStatusWidget"> + Antenna 1 Antenna 2 + Base Station + Satellites# GPS: BeiDou: GLONASS: @@ -56,14 +59,15 @@ RTK connected. RTK data in use RTK connected. RTK data not in use Not connected - Suspending account... + Suspending account… Time verification failed Network unavailable Verification failed + Unknown error %sInternal error Unknown error Account error - Connecting to server... + Connecting to server… Request rejected by server Connecting to server failed Network RTK @@ -71,4 +75,5 @@ Base Station Custom Network RTK %1$s Status: + diff --git a/android-uxsdk-beta-hardwareaccessory/src/main/res/values/strings_dimension_ratios.xml b/android-uxsdk-beta-accessory/src/main/res/values/strings_dimension_ratios.xml similarity index 100% rename from android-uxsdk-beta-hardwareaccessory/src/main/res/values/strings_dimension_ratios.xml rename to android-uxsdk-beta-accessory/src/main/res/values/strings_dimension_ratios.xml diff --git a/android-uxsdk-beta-hardwareaccessory/src/main/res/values/styles.xml b/android-uxsdk-beta-accessory/src/main/res/values/styles.xml similarity index 100% rename from android-uxsdk-beta-hardwareaccessory/src/main/res/values/styles.xml rename to android-uxsdk-beta-accessory/src/main/res/values/styles.xml diff --git a/android-uxsdk-beta-cameracore/build.gradle b/android-uxsdk-beta-cameracore/build.gradle index 8b1e9dd9..6befbc9c 100644 --- a/android-uxsdk-beta-cameracore/build.gradle +++ b/android-uxsdk-beta-cameracore/build.gradle @@ -69,7 +69,7 @@ android { dependencies { api project(path: ':android-uxsdk-beta-core') - implementation ('com.dji:dji-sdk:4.13.1', { + implementation ('com.dji:dji-sdk:4.14-trial1', { /** * Comment the "library-anti-distortion" if your app needs Anti Distortion for Mavic 2 Pro * and Mavic 2 Zoom. @@ -81,7 +81,7 @@ dependencies { exclude module: 'library-anti-distortion' exclude module: 'fly-safe-database' }) - compileOnly ('com.dji:dji-sdk-provided:4.13.1') + compileOnly ('com.dji:dji-sdk-provided:4.14-trial1') implementation 'androidx.multidex:multidex:2.0.0' implementation 'androidx.appcompat:appcompat:1.0.0' diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/ui/ProgressRingView.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/ui/ProgressRingView.java index b68523b2..7c05e3f9 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/ui/ProgressRingView.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/ui/ProgressRingView.java @@ -39,6 +39,10 @@ import dji.ux.beta.cameracore.R; +/** + * Ring progress bar. + * The view shows circular animation to indicate progress. + */ public class ProgressRingView extends View { private RectF boundaries; @@ -125,6 +129,11 @@ protected void onDraw(Canvas 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); @@ -132,10 +141,22 @@ public void setRingColor(@ColorInt int color) { 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; diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/util/CameraActionSound.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/util/CameraActionSound.java index b5a253f4..0b8ce6d2 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/util/CameraActionSound.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/util/CameraActionSound.java @@ -29,7 +29,6 @@ import dji.thirdparty.io.reactivex.disposables.Disposable; import dji.ux.beta.cameracore.R; -import dji.ux.beta.core.base.SchedulerProvider; import dji.ux.beta.core.util.AudioUtil; /** @@ -48,27 +47,27 @@ private int shutterCountSound(ShutterSoundCount value) { int soundId; switch (value) { case ONE: - soundId = R.raw.shutter_1; + soundId = R.raw.uxsdk_shutter_1; break; case THREE: - soundId = R.raw.shutter_3; + soundId = R.raw.uxsdk_shutter_3; break; case FIVE: - soundId = R.raw.shutter_5; + soundId = R.raw.uxsdk_shutter_5; break; case SEVEN: - soundId = R.raw.shutter_7; + soundId = R.raw.uxsdk_shutter_7; break; case TEN: - soundId = R.raw.shutter_10; + soundId = R.raw.uxsdk_shutter_10; break; case FOURTEEN: - soundId = R.raw.shutter_14; + soundId = R.raw.uxsdk_shutter_14; break; case UNKNOWN: default: - soundId = R.raw.shutter_3; + soundId = R.raw.uxsdk_shutter_3; break; } return soundId; @@ -86,35 +85,32 @@ public void setShutterCount(@NonNull ShutterSoundCount count) { /** * Play sound for Capture Photo * - * @param schedulerProvider instance of scheduler * @return Disposable */ @NonNull - public Disposable playCapturePhoto(@NonNull SchedulerProvider schedulerProvider) { - return AudioUtil.playSoundInBackground(schedulerProvider, context, shutterCountSound(shutterCount)); + public Disposable playCapturePhoto() { + return AudioUtil.playSoundInBackground(context, shutterCountSound(shutterCount)); } /** * Play sound for start record video * - * @param schedulerProvider instance of scheduler * @return Disposable */ @NonNull - public Disposable playStartRecordVideo(@NonNull SchedulerProvider schedulerProvider) { - return AudioUtil.playSoundInBackground(schedulerProvider, context, R.raw.video_voice); + public Disposable playStartRecordVideo() { + return AudioUtil.playSoundInBackground(context, R.raw.uxsdk_video_voice); } /** * Play sound for stop record video * - * @param schedulerProvider instance of scheduler * @return Disposable */ @NonNull - public Disposable playStopRecordVideo(@NonNull SchedulerProvider schedulerProvider) { - return AudioUtil.playSoundInBackground(schedulerProvider, context, R.raw.end_video_record); + public Disposable playStopRecordVideo() { + return AudioUtil.playSoundInBackground(context, R.raw.uxsdk_end_video_record); } /** @@ -164,9 +160,9 @@ public enum ShutterSoundCount { */ public static ShutterSoundCount find(int value) { ShutterSoundCount result = UNKNOWN; - for (int i = 0; i < values().length; i++) { - if (values()[i]._equals(value)) { - result = values()[i]; + for (int i = 0; i < getValues().length; i++) { + if (getValues()[i]._equals(value)) { + result = getValues()[i]; break; } } @@ -192,6 +188,15 @@ private boolean _equals(int b) { return value == b; } + private static ShutterSoundCount[] values; + + public static ShutterSoundCount[] getValues() { + if (values == null) { + values = values(); + } + return values; + } + } } diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/autoexposurelock/AutoExposureLockWidget.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/autoexposurelock/AutoExposureLockWidget.java index 45b78b4f..eeb41bc5 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/autoexposurelock/AutoExposureLockWidget.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/autoexposurelock/AutoExposureLockWidget.java @@ -39,12 +39,12 @@ import androidx.annotation.Nullable; import androidx.annotation.StyleRes; -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers; +import dji.common.camera.SettingsDefinitions; import dji.ux.beta.cameracore.R; -import dji.ux.beta.core.base.ConstraintLayoutWidget; import dji.ux.beta.core.base.DJISDKModel; import dji.ux.beta.core.base.SchedulerProvider; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; import dji.ux.beta.core.util.SettingDefinitions.CameraIndex; import dji.ux.beta.core.util.ViewUtil; @@ -63,12 +63,11 @@ public class AutoExposureLockWidget extends ConstraintLayoutWidget implements Vi private AutoExposureLockWidgetModel widgetModel; private Drawable autoExposureLockDrawable; private Drawable autoExposureUnlockDrawable; - private SchedulerProvider schedulerProvider; private ColorStateList lockDrawableTint; private ColorStateList unlockDrawableTint; //endregion - //region lifecycle + //region Lifecycle public AutoExposureLockWidget(@NonNull Context context) { super(context); } @@ -87,14 +86,12 @@ protected void initView(@NonNull Context context, @Nullable AttributeSet attrs, if (getBackground() == null) { setBackgroundResource(R.drawable.uxsdk_background_black_rectangle); } - schedulerProvider = SchedulerProvider.getInstance(); 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(), - schedulerProvider); + ObservableInMemoryKeyedStore.getInstance()); } initDefaults(); if (attrs != null) { @@ -106,7 +103,7 @@ protected void initView(@NonNull Context context, @Nullable AttributeSet attrs, @Override protected void reactToModelChanges() { - addReaction(widgetModel.isAutoExposureLockOn().observeOn(AndroidSchedulers.mainThread()).subscribe(this::onAELockChange)); + addReaction(widgetModel.isAutoExposureLockOn().observeOn(SchedulerProvider.ui()).subscribe(this::onAELockChange)); } @Override @@ -149,7 +146,7 @@ private void onAELockChange(boolean isLocked) { private void setAutoExposureLock() { addDisposable(widgetModel.toggleAutoExposureLock() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(() -> { // Do nothing }, logErrorConsumer(TAG, "set auto exposure lock: "))); @@ -158,7 +155,7 @@ private void setAutoExposureLock() { private void checkAndUpdateAELock() { if (!isInEditMode()) { addDisposable(widgetModel.isAutoExposureLockOn().firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::onAELockChange, logErrorConsumer(TAG, "Update AE Lock "))); } } @@ -173,6 +170,7 @@ private void initDefaults() { private void initAttributes(@NonNull Context context, @NonNull AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.AutoExposureLockWidget); setCameraIndex(CameraIndex.find(typedArray.getInt(R.styleable.AutoExposureLockWidget_uxsdk_cameraIndex, 0))); + setLensType(SettingsDefinitions.LensType.find(typedArray.getInt(R.styleable.AutoExposureLockWidget_uxsdk_lensType, 0))); ColorStateList colorStateList = typedArray.getColorStateList(R.styleable.AutoExposureLockWidget_uxsdk_widgetTitleTextColor); if (colorStateList != null) { setTitleTextColor(colorStateList); @@ -244,6 +242,27 @@ public void setCameraIndex(@NonNull CameraIndex cameraIndex) { } } + /** + * Get the current type of the lens the widget is reacting to + * + * @return current lens type + */ + @NonNull + public SettingsDefinitions.LensType getLensType() { + return widgetModel.getLensType(); + } + + /** + * Set the type of the lens for which the widget should react + * + * @param lensType lens type + */ + public void setLensType(@NonNull SettingsDefinitions.LensType lensType) { + if (!isInEditMode()) { + widgetModel.setLensType(lensType); + } + } + /** * Set drawable for auto exposure lock in locked state * diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/autoexposurelock/AutoExposureLockWidgetModel.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/autoexposurelock/AutoExposureLockWidgetModel.java index 9173995d..d8f61123 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/autoexposurelock/AutoExposureLockWidgetModel.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/autoexposurelock/AutoExposureLockWidgetModel.java @@ -25,14 +25,14 @@ import androidx.annotation.NonNull; +import dji.common.camera.SettingsDefinitions; import dji.keysdk.CameraKey; import dji.keysdk.DJIKey; import dji.thirdparty.io.reactivex.Completable; import dji.thirdparty.io.reactivex.Flowable; import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.SchedulerProviderInterface; import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; import dji.ux.beta.core.util.DataProcessor; import dji.ux.beta.core.util.SettingDefinitions.CameraIndex; @@ -47,15 +47,13 @@ public class AutoExposureLockWidgetModel extends WidgetModel { private final DataProcessor autoExposureLockBooleanProcessor; private DJIKey autoExposureLockKey; private int cameraIndex = CameraIndex.CAMERA_INDEX_0.getIndex(); - private SchedulerProviderInterface schedulerProvider; + private SettingsDefinitions.LensType lensType = SettingsDefinitions.LensType.ZOOM; //endregion public AutoExposureLockWidgetModel(@NonNull DJISDKModel djiSdkModel, - @NonNull ObservableInMemoryKeyedStore uxKeyManager, - @NonNull SchedulerProviderInterface schedulerProvider) { + @NonNull ObservableInMemoryKeyedStore uxKeyManager) { super(djiSdkModel, uxKeyManager); autoExposureLockBooleanProcessor = DataProcessor.create(false); - this.schedulerProvider = schedulerProvider; } //region Data @@ -93,21 +91,40 @@ public void setCameraIndex(@NonNull CameraIndex cameraIndex) { restart(); } + /** + * Get the current type of the lens the widget model is reacting to + * + * @return current lens type + */ + @NonNull + public SettingsDefinitions.LensType getLensType() { + return lensType; + } + + /** + * Set the type of the lens for which the widget model should react + * + * @param lensType lens type + */ + public void setLensType(@NonNull SettingsDefinitions.LensType lensType) { + 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(autoExposureLockKey, !autoExposureLockBooleanProcessor.getValue()) - .subscribeOn(schedulerProvider.io()); + return djiSdkModel.setValue(autoExposureLockKey, !autoExposureLockBooleanProcessor.getValue()); } //endregion - //region lifecycle + //region Lifecycle @Override protected void inSetup() { - autoExposureLockKey = CameraKey.create(CameraKey.AE_LOCK, cameraIndex); + autoExposureLockKey = djiSdkModel.createLensKey(CameraKey.AE_LOCK, cameraIndex, lensType.value()); bindDataProcessor(autoExposureLockKey, autoExposureLockBooleanProcessor); } diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/CameraCaptureWidget.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/CameraCaptureWidget.java index 558b5e67..d6a12ec3 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/CameraCaptureWidget.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/CameraCaptureWidget.java @@ -37,13 +37,13 @@ import java.util.Map; import dji.common.camera.SettingsDefinitions.CameraMode; -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers; import dji.ux.beta.cameracore.R; import dji.ux.beta.cameracore.widget.cameracapture.recordvideo.RecordVideoWidget; import dji.ux.beta.cameracore.widget.cameracapture.shootphoto.ShootPhotoWidget; -import dji.ux.beta.core.base.ConstraintLayoutWidget; import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.base.SchedulerProvider; +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; /** * Camera Capture Widget @@ -53,13 +53,13 @@ */ public class CameraCaptureWidget extends ConstraintLayoutWidget { - //region fields + //region Fields private static final String TAG = "CameraCaptureWidget"; private CameraCaptureWidgetModel widgetModel; private Map widgetMap; //endregion - //region lifecycle + //region Lifecycle public CameraCaptureWidget(Context context) { super(context); } @@ -104,7 +104,7 @@ protected void onDetachedFromWindow() { protected void reactToModelChanges() { addReaction( widgetModel.getCameraMode() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe( this::onCameraModeChange, logErrorConsumer(TAG, "Camera Mode Change: "))); diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/CameraCaptureWidgetModel.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/CameraCaptureWidgetModel.java index 9563ced1..6932aaec 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/CameraCaptureWidgetModel.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/CameraCaptureWidgetModel.java @@ -26,13 +26,11 @@ import androidx.annotation.NonNull; import dji.common.camera.SettingsDefinitions.CameraMode; -import dji.keysdk.CameraKey; -import dji.keysdk.DJIKey; import dji.thirdparty.io.reactivex.Flowable; import dji.ux.beta.core.base.DJISDKModel; import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; -import dji.ux.beta.core.util.DataProcessor; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.module.FlatCameraModule; /** * Camera Capture Widget Model @@ -42,26 +40,26 @@ */ public class CameraCaptureWidgetModel extends WidgetModel { - //region fields - private final DataProcessor cameraModeDataProcessor; + //region Fields + private FlatCameraModule flatCameraModule; //endregion - //region lifecycle + //region Lifecycle public CameraCaptureWidgetModel(@NonNull DJISDKModel djiSdkModel, @NonNull ObservableInMemoryKeyedStore keyedStore) { super(djiSdkModel, keyedStore); - cameraModeDataProcessor = DataProcessor.create(CameraMode.UNKNOWN); + flatCameraModule = new FlatCameraModule(); + addModule(flatCameraModule); } @Override protected void inSetup() { - DJIKey cameraModeKey = CameraKey.create(CameraKey.MODE); - bindDataProcessor(cameraModeKey, cameraModeDataProcessor); + // do nothing } @Override protected void inCleanup() { - // Empty function + // do nothing } @Override @@ -78,7 +76,7 @@ protected void updateStates() { * @return Flowable with {@link CameraMode} instance */ public Flowable getCameraMode() { - return cameraModeDataProcessor.toFlowable(); + return flatCameraModule.getCameraModeDataProcessor().toFlowable(); } //endregion } diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/recordvideo/RecordVideoWidget.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/recordvideo/RecordVideoWidget.java index 8f1c7b5b..ccd96f05 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/recordvideo/RecordVideoWidget.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/recordvideo/RecordVideoWidget.java @@ -50,10 +50,10 @@ import dji.ux.beta.cameracore.R; import dji.ux.beta.cameracore.util.CameraActionSound; import dji.ux.beta.cameracore.widget.cameracapture.recordvideo.RecordVideoWidgetModel.RecordingState; -import dji.ux.beta.core.base.ConstraintLayoutWidget; import dji.ux.beta.core.base.DJISDKModel; import dji.ux.beta.core.base.SchedulerProvider; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; import dji.ux.beta.core.util.CameraUtil; import dji.ux.beta.core.util.ProductUtil; import dji.ux.beta.core.util.SettingDefinitions.CameraIndex; @@ -66,7 +66,7 @@ */ public class RecordVideoWidget extends ConstraintLayoutWidget implements OnClickListener { - //region fields + //region Fields private static final String TAG = "RecordVideoWidget"; private RecordVideoWidgetModel widgetModel; private ImageView centerImageView; @@ -79,11 +79,10 @@ public class RecordVideoWidget extends ConstraintLayoutWidget implements OnClick private Drawable recordVideoStopDrawable; private Drawable recordVideoStartHasselbladDrawable; private Drawable recordVideoStopHasselbladDrawable; - private SchedulerProvider schedulerProvider; private CameraActionSound cameraActionSound; //endregion - //region lifecycle + //region Lifecycle public RecordVideoWidget(Context context) { super(context); } @@ -107,12 +106,10 @@ protected void initView(@NonNull Context context, @Nullable AttributeSet attrs, storageSDCardIconMap = new HashMap<>(); centerImageView.setOnClickListener(this); cameraActionSound = new CameraActionSound(context); - schedulerProvider = SchedulerProvider.getInstance(); if (!isInEditMode()) { widgetModel = new RecordVideoWidgetModel(DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - schedulerProvider); + ObservableInMemoryKeyedStore.getInstance()); } initDefaults(); if (attrs != null) { @@ -140,19 +137,19 @@ protected void onDetachedFromWindow() { protected void reactToModelChanges() { addReaction( widgetModel.getRecordingTimeInSeconds() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe( this::updateRecordingTime, logErrorConsumer(TAG, "record time: "))); addReaction( widgetModel.getRecordingState() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe( recordingState -> onIsRecordingVideoChange(recordingState, true), logErrorConsumer(TAG, "is recording: "))); addReaction( widgetModel.getCameraVideoStorageState() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe( this::updateCameraForegroundResource, logErrorConsumer(TAG, "camera storage update: "))); @@ -175,7 +172,7 @@ public void onClick(View v) { } else { return Completable.complete(); } - }).observeOn(schedulerProvider.ui()).subscribe(() -> { + }).observeOn(SchedulerProvider.ui()).subscribe(() -> { }, logErrorConsumer(TAG, "START STOP VIDEO"))); } } @@ -202,6 +199,7 @@ private void initDefaults() { private void initAttributes(@NonNull Context context, @NonNull AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RecordVideoWidget); setCameraIndex(CameraIndex.find(typedArray.getInt(R.styleable.RecordVideoWidget_uxsdk_cameraIndex, 0))); + setLensType(SettingsDefinitions.LensType.find(typedArray.getInt(R.styleable.RecordVideoWidget_uxsdk_lensType, 0))); int textAppearance = typedArray.getResourceId(R.styleable.RecordVideoWidget_uxsdk_timerTextAppearance, INVALID_RESOURCE); if (textAppearance != INVALID_RESOURCE) { @@ -312,9 +310,9 @@ private void onIsRecordingVideoChange(RecordingState recordingState, boolean pla storageStatusOverlayImageView.setVisibility(isRecordingVideo ? View.GONE : View.VISIBLE); if (playSound) { if (recordingState == RecordingState.RECORDING_IN_PROGRESS) { - addDisposable(cameraActionSound.playStartRecordVideo(schedulerProvider)); + addDisposable(cameraActionSound.playStartRecordVideo()); } else if (recordingState == RecordingState.RECORDING_STOPPED) { - addDisposable(cameraActionSound.playStopRecordVideo(schedulerProvider)); + addDisposable(cameraActionSound.playStopRecordVideo()); } } } @@ -322,7 +320,7 @@ private void onIsRecordingVideoChange(RecordingState recordingState, boolean pla private void checkAndUpdateCameraForegroundResource() { if (!isInEditMode()) { addDisposable(widgetModel.getCameraVideoStorageState().firstOrError() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::updateCameraForegroundResource, logErrorConsumer(TAG, "check and update camera foreground resource: "))); } @@ -331,7 +329,7 @@ private void checkAndUpdateCameraForegroundResource() { private void checkAndUpdateCenterImageView() { if (!isInEditMode()) { addDisposable(widgetModel.getRecordingState().firstOrError() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(recordingState -> onIsRecordingVideoChange(recordingState, false), logErrorConsumer(TAG, "check and update camera foreground resource: "))); } @@ -361,6 +359,27 @@ public void setCameraIndex(@NonNull CameraIndex cameraIndex) { } } + /** + * Get the current type of the lens the widget is reacting to + * + * @return current lens type + */ + @NonNull + public SettingsDefinitions.LensType getLensType() { + return widgetModel.getLensType(); + } + + /** + * Set the type of the lens for which the widget should react + * + * @param lensType lens type + */ + public void setLensType(@NonNull SettingsDefinitions.LensType lensType) { + if (!isInEditMode()) { + widgetModel.setLensType(lensType); + } + } + /** * Get the current start recording video icon * diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/recordvideo/RecordVideoWidgetModel.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/recordvideo/RecordVideoWidgetModel.java index 834ff8de..6ad43f69 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/recordvideo/RecordVideoWidgetModel.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/recordvideo/RecordVideoWidgetModel.java @@ -34,9 +34,8 @@ import dji.thirdparty.io.reactivex.Completable; import dji.thirdparty.io.reactivex.Flowable; import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.SchedulerProviderInterface; import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; import dji.ux.beta.core.util.DataProcessor; import dji.ux.beta.core.util.SettingDefinitions; @@ -75,18 +74,16 @@ public class RecordVideoWidgetModel extends WidgetModel { private final DataProcessor ssdRecordingTime; private final DataProcessor recordingStateProcessor; private int cameraIndex; + private SettingsDefinitions.LensType lensType = SettingsDefinitions.LensType.ZOOM; private DJIKey stopVideoRecordingKey; private DJIKey startVideoRecordingKey; - private SchedulerProviderInterface schedulerProvider; //endregion //region Constructor public RecordVideoWidgetModel(@NonNull DJISDKModel djiSdkModel, - @NonNull ObservableInMemoryKeyedStore uxKeyManager, - @NonNull SchedulerProviderInterface schedulerProvider) { + @NonNull ObservableInMemoryKeyedStore uxKeyManager) { super(djiSdkModel, uxKeyManager); this.cameraIndex = SettingDefinitions.CameraIndex.CAMERA_INDEX_0.getIndex(); - this.schedulerProvider = schedulerProvider; CameraSDVideoStorageState cameraSDVideoStorageState = new CameraSDVideoStorageState( SettingsDefinitions.StorageLocation.SDCARD, 0, @@ -150,7 +147,7 @@ protected void inSetup() { bindDataProcessor(ssdRecordingTimeKey, ssdRecordingTime); bindDataProcessor(ssdVideoLicenseKey, cameraSSDVideoLicenseDataProcessor); // Resolution and Frame Rates - DJIKey nonSSDRecordedVideoParametersKey = CameraKey.create(CameraKey.RESOLUTION_FRAME_RATE, cameraIndex); + DJIKey nonSSDRecordedVideoParametersKey = djiSdkModel.createLensKey(CameraKey.RESOLUTION_FRAME_RATE, cameraIndex, lensType.value()); DJIKey ssdRecordedVideoParametersKey = CameraKey.create(CameraKey.SSD_VIDEO_RESOLUTION_AND_FRAME_RATE, cameraIndex); bindDataProcessor(nonSSDRecordedVideoParametersKey, nonSSDRecordedVideoParameters); bindDataProcessor(ssdRecordedVideoParametersKey, ssdRecordedVideoParameters); @@ -237,6 +234,26 @@ public void setCameraIndex(@NonNull SettingDefinitions.CameraIndex cameraIndex) this.cameraIndex = cameraIndex.getIndex(); restart(); } + + /** + * Get the current type of the lens the widget model is reacting to + * + * @return current lens type + */ + @NonNull + public SettingsDefinitions.LensType getLensType() { + return lensType; + } + + /** + * Set the type of the lens for which the widget model should react + * + * @param lensType lens type + */ + public void setLensType(@NonNull SettingsDefinitions.LensType lensType) { + this.lensType = lensType; + restart(); + } //endregion //region Actions @@ -251,8 +268,7 @@ public Completable startRecordVideo() { return Completable.complete(); } - return djiSdkModel.performAction(startVideoRecordingKey) - .subscribeOn(schedulerProvider.io()); + return djiSdkModel.performAction(startVideoRecordingKey); } /** @@ -265,8 +281,7 @@ public Completable stopRecordVideo() { return Completable.complete(); } - return djiSdkModel.performAction(stopVideoRecordingKey) - .subscribeOn(schedulerProvider.io()); + return djiSdkModel.performAction(stopVideoRecordingKey); } //endregion diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/shootphoto/ShootPhotoWidget.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/shootphoto/ShootPhotoWidget.java index 5ac94915..034d71da 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/shootphoto/ShootPhotoWidget.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/shootphoto/ShootPhotoWidget.java @@ -53,10 +53,10 @@ import dji.ux.beta.cameracore.ui.ProgressRingView; import dji.ux.beta.cameracore.util.CameraActionSound; import dji.ux.beta.cameracore.util.CameraResource; -import dji.ux.beta.core.base.ConstraintLayoutWidget; import dji.ux.beta.core.base.DJISDKModel; import dji.ux.beta.core.base.SchedulerProvider; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; import dji.ux.beta.core.util.ProductUtil; import static dji.ux.beta.core.util.SettingDefinitions.CameraIndex; @@ -68,7 +68,7 @@ * displays the storage state and errors associated with it. */ public class ShootPhotoWidget extends ConstraintLayoutWidget implements View.OnClickListener { - //region fields + //region Fields private static final String TAG = "ShootPhotoWidget"; private ShootPhotoWidgetModel widgetModel; private ProgressRingView borderProgressRingView; @@ -85,11 +85,10 @@ public class ShootPhotoWidget extends ConstraintLayoutWidget implements View.OnC private Map storageInternalIconMap; private Map storageSSDIconMap; private Map storageSDCardIconMap; - private SchedulerProvider schedulerProvider; private CameraActionSound cameraActionSound; //endregion - //region lifecycle + //region Lifecycle public ShootPhotoWidget(Context context) { super(context); } @@ -111,14 +110,12 @@ protected void initView(@NonNull Context context, @Nullable AttributeSet attrs, storageInternalIconMap = new HashMap<>(); storageSSDIconMap = new HashMap<>(); storageSDCardIconMap = new HashMap<>(); - schedulerProvider = SchedulerProvider.getInstance(); cameraActionSound = new CameraActionSound(context); if (!isInEditMode()) { centerImageView.setOnClickListener(this); widgetModel = new ShootPhotoWidgetModel(DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - schedulerProvider); + ObservableInMemoryKeyedStore.getInstance()); } initDefaults(); if (attrs != null) { @@ -146,7 +143,7 @@ protected void onDetachedFromWindow() { protected void reactToModelChanges() { addReaction( widgetModel.isShootingPhoto() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe( this::onIsShootingPhotoChange, logErrorConsumer(TAG, "isShootingPhoto: "))); @@ -175,7 +172,7 @@ public void onClick(View v) { return widgetModel.startShootPhoto(); } return Completable.complete(); - }).observeOn(schedulerProvider.ui()) + }).observeOn(SchedulerProvider.ui()) .subscribe( () -> { }, logErrorConsumer(TAG, "Start Stop Shoot Photo"))); @@ -252,7 +249,7 @@ private void onIsShootingPhotoChange(boolean isShootingPhoto) { DJILog.d(TAG, "onIsShootingPhotoChange " + isShootingPhoto); borderProgressRingView.setIndeterminate(isShootingPhoto); if (isShootingPhoto) { - addDisposable(cameraActionSound.playCapturePhoto(schedulerProvider)); + addDisposable(cameraActionSound.playCapturePhoto()); } } @@ -260,7 +257,7 @@ private Disposable reactToPhotoStateAndPhotoStorageState() { return Flowable.combineLatest(widgetModel.getCameraPhotoState(), widgetModel.getCameraStorageState(), Pair::new) - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(values -> updateCameraForegroundResource(values.first, values.second), logErrorConsumer(TAG, "reactToPhotoStateAndPhotoStorageState ")); } @@ -270,7 +267,7 @@ private Disposable reactToCanStartOrStopShootingPhoto() { widgetModel.canStartShootingPhoto(), widgetModel.canStopShootingPhoto(), Pair::new) - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(values -> updateImages(values.first, values.second), logErrorConsumer(TAG, "reactToCanStartOrStopShootingPhoto: ")); } @@ -281,7 +278,7 @@ private void checkAndUpdatePhotoStateAndPhotoStorageState() { widgetModel.getCameraStorageState(), Pair::new) .firstOrError() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(values -> updateCameraForegroundResource(values.first, values.second), logErrorConsumer(TAG, "checkAndUpdatePhotoStateAndPhotoStorageState "))); } @@ -294,7 +291,7 @@ private void checkAndUpdateCanStartOrStopShootingPhoto() { widgetModel.canStopShootingPhoto(), Pair::new) .firstOrError() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(values -> updateImages(values.first, values.second), logErrorConsumer(TAG, "checkAndUpdateCanStartOrStopShootingPhoto: "))); } diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/shootphoto/ShootPhotoWidgetModel.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/shootphoto/ShootPhotoWidgetModel.java index 155978b5..0149b639 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/shootphoto/ShootPhotoWidgetModel.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracapture/shootphoto/ShootPhotoWidgetModel.java @@ -32,9 +32,9 @@ import dji.thirdparty.io.reactivex.Completable; import dji.thirdparty.io.reactivex.Flowable; import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.SchedulerProviderInterface; import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.module.FlatCameraModule; import dji.ux.beta.core.util.DataProcessor; import dji.ux.beta.core.util.SettingDefinitions; @@ -59,13 +59,11 @@ public class ShootPhotoWidgetModel extends WidgetModel { private final DataProcessor canStartShootingPhoto; private final DataProcessor canStopShootingPhoto; private final DataProcessor cameraDisplayName; - private int cameraIndex; + private final DataProcessor isShootingInterval; //endregion //region Internal data - private final DataProcessor isShootingInterval; private final DataProcessor isShootingPanorama; - private final DataProcessor shootPhotoMode; private final DataProcessor aebCount; private final DataProcessor burstCount; private final DataProcessor rawBurstCount; @@ -80,22 +78,21 @@ public class ShootPhotoWidgetModel extends WidgetModel { private final DataProcessor innerStorageAvailableCaptureCount; private final DataProcessor ssdAvailableCaptureCount; private final DataProcessor isProductConnected; - private SchedulerProviderInterface schedulerProvider; //endregion //region Other fields private final SettingsDefinitions.PhotoTimeIntervalSettings defaultIntervalSettings; + private int cameraIndex; private DJIKey stopShootPhotoKey; private DJIKey startShootPhotoKey; + private FlatCameraModule flatCameraModule; //endregion //region Constructor public ShootPhotoWidgetModel(@NonNull DJISDKModel djiSdkModel, - @NonNull ObservableInMemoryKeyedStore keyedStore, - @NonNull SchedulerProviderInterface schedulerProvider) { + @NonNull ObservableInMemoryKeyedStore keyedStore) { super(djiSdkModel, keyedStore); this.cameraIndex = SettingDefinitions.CameraIndex.CAMERA_INDEX_0.getIndex(); - this.schedulerProvider = schedulerProvider; defaultIntervalSettings = new SettingsDefinitions.PhotoTimeIntervalSettings(0, 0); CameraPhotoState cameraPhotoState = new CameraPhotoState(SettingsDefinitions.ShootPhotoMode.UNKNOWN); @@ -105,7 +102,6 @@ public ShootPhotoWidgetModel(@NonNull DJISDKModel djiSdkModel, 0, SettingsDefinitions.SDCardOperationState.NOT_INSERTED); cameraStorageState = DataProcessor.create(cameraSDStorageState); - shootPhotoMode = DataProcessor.create(cameraPhotoState.getShootPhotoMode()); canStartShootingPhoto = DataProcessor.create(false); canStopShootingPhoto = DataProcessor.create(false); cameraDisplayName = DataProcessor.create(""); @@ -127,6 +123,8 @@ public ShootPhotoWidgetModel(@NonNull DJISDKModel djiSdkModel, innerStorageAvailableCaptureCount = DataProcessor.create(INVALID_AVAILABLE_CAPTURE_COUNT_LONG); ssdAvailableCaptureCount = DataProcessor.create(INVALID_AVAILABLE_CAPTURE_COUNT); isProductConnected = DataProcessor.create(false); + flatCameraModule = new FlatCameraModule(); + addModule(flatCameraModule); } //endregion @@ -191,22 +189,23 @@ public Flowable canStopShootingPhoto() { } /** - * Set the index of the camera for which the widget model should react + * Get the current index of the camera the widget model is reacting to * - * @param cameraIndex camera index + * @return current camera index */ - public void setCameraIndex(@NonNull SettingDefinitions.CameraIndex cameraIndex) { - this.cameraIndex = cameraIndex.getIndex(); - restart(); + public SettingDefinitions.CameraIndex getCameraIndex() { + return SettingDefinitions.CameraIndex.find(cameraIndex); } /** - * Get the current index of the camera the widget model is reacting to + * Set the index of the camera for which the widget model should react * - * @return current camera index + * @param cameraIndex camera index */ - public SettingDefinitions.CameraIndex getCameraIndex() { - return SettingDefinitions.CameraIndex.find(cameraIndex); + public void setCameraIndex(@NonNull SettingDefinitions.CameraIndex cameraIndex) { + this.cameraIndex = cameraIndex.getIndex(); + flatCameraModule.setCameraIndex(cameraIndex); + restart(); } /** @@ -230,9 +229,7 @@ public Completable startShootPhoto() { if (!canStartShootingPhoto.getValue() || !djiSdkModel.isAvailable()) { return Completable.complete(); } - - return djiSdkModel.performAction(startShootPhotoKey) - .subscribeOn(schedulerProvider.io()); + return djiSdkModel.performAction(startShootPhotoKey); } /** @@ -244,8 +241,7 @@ public Completable stopShootPhoto() { if (!canStopShootingPhoto.getValue() || !djiSdkModel.isAvailable()) { return Completable.complete(); } - return djiSdkModel.performAction(stopShootPhotoKey) - .subscribeOn(schedulerProvider.io()); + return djiSdkModel.performAction(stopShootPhotoKey); } //endregion @@ -259,13 +255,11 @@ protected void inSetup() { DJIKey cameraConnectionKey = CameraKey.create(CameraKey.CONNECTION, cameraIndex); bindDataProcessor(cameraConnectionKey, isProductConnected, newValue -> onCameraConnected((boolean) newValue)); // Photo mode - DJIKey shootPhotoModeKey = CameraKey.create(CameraKey.SHOOT_PHOTO_MODE, cameraIndex); DJIKey photoAEBParamKey = CameraKey.create(CameraKey.PHOTO_AEB_COUNT, cameraIndex); DJIKey photoBurstCountKey = CameraKey.create(CameraKey.PHOTO_BURST_COUNT, cameraIndex); DJIKey photoIntervalParamKey = CameraKey.create(CameraKey.PHOTO_TIME_INTERVAL_SETTINGS, cameraIndex); DJIKey rawBurstCountKey = CameraKey.create(CameraKey.PHOTO_RAW_BURST_COUNT, cameraIndex); DJIKey panoramaModeKey = CameraKey.create(CameraKey.PHOTO_PANORAMA_MODE, cameraIndex); - bindDataProcessor(shootPhotoModeKey, shootPhotoMode); bindDataProcessor(photoAEBParamKey, aebCount); bindDataProcessor(photoBurstCountKey, burstCount); bindDataProcessor(rawBurstCountKey, rawBurstCount); @@ -311,7 +305,7 @@ protected void inSetup() { @Override protected void inCleanup() { - //Nothing to clean + // do nothing } @Override @@ -324,7 +318,7 @@ protected void updateStates() { //region Helpers private void updateCameraPhotoState() { CameraPhotoState cameraPhotoState = null; - SettingsDefinitions.ShootPhotoMode shootPhotoMode = this.shootPhotoMode.getValue(); + SettingsDefinitions.ShootPhotoMode shootPhotoMode = flatCameraModule.getShootPhotoModeProcessor().getValue(); switch (shootPhotoMode) { case SINGLE: case HDR: @@ -389,7 +383,7 @@ private void updateCameraStorageState() { return; } - SettingsDefinitions.ShootPhotoMode currentShootPhotoMode = shootPhotoMode.getValue(); + SettingsDefinitions.ShootPhotoMode currentShootPhotoMode = flatCameraModule.getShootPhotoModeProcessor().getValue(); long availableCaptureCount = getAvailableCaptureCount(currentStorageLocation, currentShootPhotoMode); if (availableCaptureCount == INVALID_AVAILABLE_CAPTURE_COUNT) { return; diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/CameraControlsWidget.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/CameraControlsWidget.java index f35a4b60..364092ea 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/CameraControlsWidget.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/CameraControlsWidget.java @@ -34,7 +34,7 @@ import dji.ux.beta.cameracore.widget.cameracontrols.camerasettingsindicator.CameraSettingsMenuIndicatorWidget; import dji.ux.beta.cameracore.widget.cameracontrols.exposuresettingsindicator.ExposureSettingsIndicatorWidget; import dji.ux.beta.cameracore.widget.cameracontrols.photovideoswitch.PhotoVideoSwitchWidget; -import dji.ux.beta.core.base.ConstraintLayoutWidget; +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget; /** * Compound widget which combines the state and interaction related to camera. @@ -45,14 +45,14 @@ */ public class CameraControlsWidget extends ConstraintLayoutWidget { - //region fields + //region Fields private CameraSettingsMenuIndicatorWidget cameraSettingsMenuIndicatorWidget; private PhotoVideoSwitchWidget photoVideoSwitchWidget; private CameraCaptureWidget cameraCaptureWidget; private ExposureSettingsIndicatorWidget exposureSettingsIndicatorWidget; //endregion - //region lifecycle + //region Lifecycle public CameraControlsWidget(@NonNull Context context) { super(context); } diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/camerasettingsindicator/CameraSettingsMenuIndicatorWidget.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/camerasettingsindicator/CameraSettingsMenuIndicatorWidget.java index 3f583deb..f24674c9 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/camerasettingsindicator/CameraSettingsMenuIndicatorWidget.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/camerasettingsindicator/CameraSettingsMenuIndicatorWidget.java @@ -39,8 +39,8 @@ import dji.ux.beta.cameracore.R; import dji.ux.beta.cameracore.widget.cameracontrols.CameraControlsWidget; -import dji.ux.beta.core.base.FrameLayoutWidget; -import dji.ux.beta.core.base.OnStateChangeCallback; +import dji.ux.beta.core.base.widget.FrameLayoutWidget; +import dji.ux.beta.core.communication.OnStateChangeCallback; /** * Camera Settings Menu Indicator Widget @@ -50,13 +50,13 @@ */ public class CameraSettingsMenuIndicatorWidget extends FrameLayoutWidget implements View.OnClickListener { - //region fields + //region Fields private TextView foregroundTextView; private OnStateChangeCallback stateChangeCallback = null; private int stateChangeResourceId; //endregion - //region lifecycle + //region Lifecycle public CameraSettingsMenuIndicatorWidget(@NonNull Context context) { super(context); } diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/exposuresettingsindicator/ExposureSettingsIndicatorWidget.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/exposuresettingsindicator/ExposureSettingsIndicatorWidget.java index 26709052..6cb108a5 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/exposuresettingsindicator/ExposureSettingsIndicatorWidget.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/exposuresettingsindicator/ExposureSettingsIndicatorWidget.java @@ -37,13 +37,14 @@ import java.util.HashMap; import java.util.Map; +import dji.common.camera.SettingsDefinitions; import dji.common.camera.SettingsDefinitions.ExposureMode; -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers; import dji.ux.beta.cameracore.R; import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.FrameLayoutWidget; -import dji.ux.beta.core.base.OnStateChangeCallback; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.base.SchedulerProvider; +import dji.ux.beta.core.base.widget.FrameLayoutWidget; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.communication.OnStateChangeCallback; import dji.ux.beta.core.util.SettingDefinitions.CameraIndex; /** @@ -52,7 +53,7 @@ */ public class ExposureSettingsIndicatorWidget extends FrameLayoutWidget implements View.OnClickListener { - //region fields + //region Fields private static final String TAG = "ExposureSetIndicWidget"; private ImageView foregroundImageView; private ExposureSettingsIndicatorWidgetModel widgetModel; @@ -61,7 +62,7 @@ public class ExposureSettingsIndicatorWidget extends FrameLayoutWidget implement private int stateChangeResourceId; //endregion - //region lifecycle + //region Lifecycle public ExposureSettingsIndicatorWidget(@NonNull Context context) { super(context); } @@ -93,7 +94,7 @@ protected void initView(@NonNull Context context, @Nullable AttributeSet attrs, @Override protected void reactToModelChanges() { addReaction(widgetModel.getExposureMode() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::updateUI)); } @@ -134,7 +135,7 @@ private void checkAndUpdateUI() { if (!isInEditMode()) { addDisposable(widgetModel.getExposureMode() .firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::updateUI, logErrorConsumer(TAG, "get exposure mode"))); } } @@ -163,6 +164,7 @@ private void initDefaults() { private void initAttributes(Context context, AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ExposureSettingsIndicatorWidget); setCameraIndex(CameraIndex.find(typedArray.getInt(R.styleable.ExposureSettingsIndicatorWidget_uxsdk_cameraIndex, 0))); + setLensType(SettingsDefinitions.LensType.find(typedArray.getInt(R.styleable.ExposureSettingsIndicatorWidget_uxsdk_lensType, 0))); stateChangeResourceId = typedArray.getResourceId(R.styleable.ExposureSettingsIndicatorWidget_uxsdk_onStateChange, INVALID_RESOURCE); Drawable drawable = typedArray.getDrawable(R.styleable.ExposureSettingsIndicatorWidget_uxsdk_aperturePriorityModeDrawable); @@ -233,6 +235,27 @@ public void setCameraIndex(@NonNull CameraIndex cameraIndex) { } } + /** + * Get the current type of the lens the widget is reacting to + * + * @return current lens type + */ + @NonNull + public SettingsDefinitions.LensType getLensType() { + return widgetModel.getLensType(); + } + + /** + * Set the type of the lens for which the widget should react + * + * @param lensType lens type + */ + public void setLensType(@NonNull SettingsDefinitions.LensType lensType) { + if (!isInEditMode()) { + widgetModel.setLensType(lensType); + } + } + /** * Set the icon for exposure mode * diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/exposuresettingsindicator/ExposureSettingsIndicatorWidgetModel.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/exposuresettingsindicator/ExposureSettingsIndicatorWidgetModel.java index f36987ce..273d2496 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/exposuresettingsindicator/ExposureSettingsIndicatorWidgetModel.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/exposuresettingsindicator/ExposureSettingsIndicatorWidgetModel.java @@ -25,13 +25,14 @@ import androidx.annotation.NonNull; +import dji.common.camera.SettingsDefinitions; import dji.common.camera.SettingsDefinitions.ExposureMode; import dji.keysdk.CameraKey; import dji.keysdk.DJIKey; import dji.thirdparty.io.reactivex.Flowable; import dji.ux.beta.core.base.DJISDKModel; import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; import dji.ux.beta.core.util.DataProcessor; import dji.ux.beta.core.util.SettingDefinitions.CameraIndex; @@ -43,12 +44,13 @@ */ public class ExposureSettingsIndicatorWidgetModel extends WidgetModel { - //region fields + //region Fields private final DataProcessor exposureModeDataProcessor; private int cameraIndex = CameraIndex.CAMERA_INDEX_0.getIndex(); + private SettingsDefinitions.LensType lensType = SettingsDefinitions.LensType.ZOOM; //endregion - //region lifecycle + //region Lifecycle public ExposureSettingsIndicatorWidgetModel(@NonNull DJISDKModel djiSdkModel, @NonNull ObservableInMemoryKeyedStore uxKeyManager) { super(djiSdkModel, uxKeyManager); @@ -57,7 +59,7 @@ public ExposureSettingsIndicatorWidgetModel(@NonNull DJISDKModel djiSdkModel, @Override protected void inSetup() { - DJIKey exposureModeKey = CameraKey.create(CameraKey.EXPOSURE_MODE, cameraIndex); + DJIKey exposureModeKey = djiSdkModel.createLensKey(CameraKey.EXPOSURE_MODE, cameraIndex, lensType.value()); bindDataProcessor(exposureModeKey, exposureModeDataProcessor); } @@ -103,4 +105,24 @@ public void setCameraIndex(@NonNull CameraIndex cameraIndex) { this.cameraIndex = cameraIndex.getIndex(); restart(); } + + /** + * Get the current type of the lens the widget model is reacting to + * + * @return current lens type + */ + @NonNull + public SettingsDefinitions.LensType getLensType() { + return lensType; + } + + /** + * Set the type of the lens for which the widget model should react + * + * @param lensType lens type + */ + public void setLensType(@NonNull SettingsDefinitions.LensType lensType) { + this.lensType = lensType; + restart(); + } } diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/photovideoswitch/PhotoVideoSwitchWidget.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/photovideoswitch/PhotoVideoSwitchWidget.java index 79b8a7cc..2b8bb5a4 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/photovideoswitch/PhotoVideoSwitchWidget.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/photovideoswitch/PhotoVideoSwitchWidget.java @@ -34,13 +34,11 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import dji.common.camera.SettingsDefinitions.CameraMode; -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers; import dji.ux.beta.cameracore.R; import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.FrameLayoutWidget; import dji.ux.beta.core.base.SchedulerProvider; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.base.widget.FrameLayoutWidget; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; import dji.ux.beta.core.util.SettingDefinitions.CameraIndex; /** @@ -48,16 +46,15 @@ */ public class PhotoVideoSwitchWidget extends FrameLayoutWidget implements View.OnClickListener { - //region fields + //region Fields private static final String TAG = "PhotoVideoSwitchWidget"; private ImageView foregroundImageView; private Drawable photoModeDrawable; private Drawable videoModeDrawable; private PhotoVideoSwitchWidgetModel widgetModel; - private SchedulerProvider schedulerProvider; //endregion - //region lifecycle + //region Lifecycle public PhotoVideoSwitchWidget(@NonNull Context context) { super(context); } @@ -74,12 +71,10 @@ public PhotoVideoSwitchWidget(@NonNull Context context, @Nullable AttributeSet a protected void initView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { inflate(context, R.layout.uxsdk_widget_photo_video_switch, this); foregroundImageView = findViewById(R.id.image_view_foreground); - schedulerProvider = SchedulerProvider.getInstance(); if (!isInEditMode()) { widgetModel = new PhotoVideoSwitchWidgetModel(DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - schedulerProvider); + ObservableInMemoryKeyedStore.getInstance()); setOnClickListener(this); } photoModeDrawable = getResources().getDrawable(R.drawable.uxsdk_ic_camera_mode_photo); @@ -109,12 +104,12 @@ protected void onDetachedFromWindow() { protected void reactToModelChanges() { addReaction( widgetModel.isEnabled() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::enableWidget)); addReaction( - widgetModel.getCameraMode() - .observeOn(schedulerProvider.ui()) + widgetModel.isPictureMode() + .observeOn(SchedulerProvider.ui()) .subscribe(this::updateUI)); } @@ -127,7 +122,7 @@ public String getIdealDimensionRatioString() { @Override public void onClick(View v) { addDisposable(widgetModel.toggleCameraMode() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe( () -> { }, logErrorConsumer(TAG, "Switch camera Mode") @@ -152,11 +147,11 @@ private void initAttributes(Context context, AttributeSet attrs) { typedArray.recycle(); } - private void updateUI(CameraMode cameraMode) { - if (cameraMode == CameraMode.RECORD_VIDEO) { - foregroundImageView.setImageDrawable(videoModeDrawable); - } else if (cameraMode == CameraMode.SHOOT_PHOTO) { + private void updateUI(boolean isPictureMode) { + if (isPictureMode) { foregroundImageView.setImageDrawable(photoModeDrawable); + } else { + foregroundImageView.setImageDrawable(videoModeDrawable); } } @@ -166,8 +161,8 @@ private void enableWidget(Boolean isEnabled) { private void checkAndUpdateUI() { if (!isInEditMode()) { - addDisposable(widgetModel.getCameraMode().firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + addDisposable(widgetModel.isPictureMode().firstOrError() + .observeOn(SchedulerProvider.ui()) .subscribe(this::updateUI, logErrorConsumer(TAG, "Update UI "))); } } diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/photovideoswitch/PhotoVideoSwitchWidgetModel.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/photovideoswitch/PhotoVideoSwitchWidgetModel.java index 85d2fada..bb29f395 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/photovideoswitch/PhotoVideoSwitchWidgetModel.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/cameracontrols/photovideoswitch/PhotoVideoSwitchWidgetModel.java @@ -31,9 +31,9 @@ import dji.thirdparty.io.reactivex.Completable; import dji.thirdparty.io.reactivex.Flowable; import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.SchedulerProviderInterface; import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.module.FlatCameraModule; import dji.ux.beta.core.util.DataProcessor; import dji.ux.beta.core.util.SettingDefinitions.CameraIndex; @@ -45,7 +45,7 @@ */ public class PhotoVideoSwitchWidgetModel extends WidgetModel { - //region fields + //region Fields private final DataProcessor isCameraConnectedDataProcessor; private final DataProcessor isRecordingDataProcessor; private final DataProcessor isShootingDataProcessor; @@ -53,19 +53,15 @@ public class PhotoVideoSwitchWidgetModel extends WidgetModel { private final DataProcessor isShootingBurstDataProcessor; private final DataProcessor isShootingRawBurstDataProcessor; private final DataProcessor isShootingPanoramaDataProcessor; - private final DataProcessor cameraModeDataProcessor; private final DataProcessor isEnabledDataProcessor; private int cameraIndex = CameraIndex.CAMERA_INDEX_0.getIndex(); - private CameraKey cameraModeKey; - private SchedulerProviderInterface schedulerProvider; + private FlatCameraModule flatCameraModule; //endregion - //region lifecycle + //region Lifecycle public PhotoVideoSwitchWidgetModel(@NonNull DJISDKModel djiSdkModel, - @NonNull ObservableInMemoryKeyedStore keyedStore, - @NonNull SchedulerProviderInterface scheduletProviderInterface) { + @NonNull ObservableInMemoryKeyedStore keyedStore) { super(djiSdkModel, keyedStore); - this.schedulerProvider = scheduletProviderInterface; isCameraConnectedDataProcessor = DataProcessor.create(false); isRecordingDataProcessor = DataProcessor.create(false); isShootingDataProcessor = DataProcessor.create(false); @@ -73,8 +69,9 @@ public PhotoVideoSwitchWidgetModel(@NonNull DJISDKModel djiSdkModel, isShootingBurstDataProcessor = DataProcessor.create(false); isShootingRawBurstDataProcessor = DataProcessor.create(false); isShootingPanoramaDataProcessor = DataProcessor.create(false); - cameraModeDataProcessor = DataProcessor.create(CameraMode.UNKNOWN); isEnabledDataProcessor = DataProcessor.create(false); + flatCameraModule = new FlatCameraModule(); + addModule(flatCameraModule); } @Override @@ -93,14 +90,11 @@ protected void inSetup() { bindDataProcessor(isShootingRawBurstKey, isShootingRawBurstDataProcessor); CameraKey isShootingPanoramaKey = CameraKey.create(CameraKey.IS_SHOOTING_PANORAMA_PHOTO, cameraIndex); bindDataProcessor(isShootingPanoramaKey, isShootingPanoramaDataProcessor); - bindDataProcessor(isShootingBurstKey, isShootingBurstDataProcessor); - cameraModeKey = CameraKey.create(CameraKey.MODE, cameraIndex); - bindDataProcessor(cameraModeKey, cameraModeDataProcessor); } @Override protected void inCleanup() { - // No Code + // do nothing } @Override @@ -130,12 +124,14 @@ public Flowable isEnabled() { } /** - * Get the current camera mode + * Get whether the current camera mode is picture mode. * - * @return {@link CameraMode} + * @return Flowable with boolean value */ - public Flowable getCameraMode() { - return cameraModeDataProcessor.toFlowable(); + public Flowable isPictureMode() { + return flatCameraModule.getCameraModeDataProcessor().toFlowable().map(cameraMode -> + cameraMode == CameraMode.SHOOT_PHOTO + ); } //endregion @@ -147,11 +143,11 @@ public Flowable getCameraMode() { * @return Completable */ public Completable toggleCameraMode() { - CameraMode cameraMode = CameraMode.SHOOT_PHOTO; - if (cameraModeDataProcessor.getValue() == CameraMode.SHOOT_PHOTO) { - cameraMode = CameraMode.RECORD_VIDEO; + if (flatCameraModule.getCameraModeDataProcessor().getValue() == CameraMode.SHOOT_PHOTO) { + return flatCameraModule.setCameraMode(djiSdkModel, CameraMode.RECORD_VIDEO); + } else { + return flatCameraModule.setCameraMode(djiSdkModel, CameraMode.SHOOT_PHOTO); } - return djiSdkModel.setValue(cameraModeKey, cameraMode).subscribeOn(schedulerProvider.io()); } /** @@ -171,6 +167,7 @@ public CameraIndex getCameraIndex() { */ public void setCameraIndex(@NonNull CameraIndex cameraIndex) { this.cameraIndex = cameraIndex.getIndex(); + flatCameraModule.setCameraIndex(cameraIndex); restart(); } //endregion diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/focusexposureswitch/FocusExposureSwitchWidget.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/focusexposureswitch/FocusExposureSwitchWidget.java index 2a3d7160..c8225d30 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/focusexposureswitch/FocusExposureSwitchWidget.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/focusexposureswitch/FocusExposureSwitchWidget.java @@ -35,13 +35,14 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import dji.common.camera.SettingsDefinitions; import dji.ux.beta.cameracore.R; import dji.ux.beta.cameracore.widget.fpvinteraction.FPVInteractionWidget; import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.FrameLayoutWidget; -import dji.ux.beta.core.base.GlobalPreferencesManager; import dji.ux.beta.core.base.SchedulerProvider; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.base.widget.FrameLayoutWidget; +import dji.ux.beta.core.communication.GlobalPreferencesManager; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; import dji.ux.beta.core.util.SettingDefinitions.CameraIndex; import dji.ux.beta.core.util.SettingDefinitions.ControlMode; @@ -54,17 +55,16 @@ */ public class FocusExposureSwitchWidget extends FrameLayoutWidget implements OnClickListener { - //region fields + //region Fields private static final String TAG = "FocusExpoSwitchWidget"; private ImageView focusExposureSwitchImageView; private FocusExposureSwitchWidgetModel widgetModel; private Drawable manualFocusDrawable; private Drawable autoFocusDrawable; private Drawable spotMeterDrawable; - private SchedulerProvider schedulerProvider; //endregion - //region lifecycle + //region Lifecycle public FocusExposureSwitchWidget(@NonNull Context context) { super(context); } @@ -81,15 +81,13 @@ public FocusExposureSwitchWidget(@NonNull Context context, @Nullable AttributeSe protected void initView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { inflate(context, R.layout.uxsdk_widget_focus_exposure_switch, this); focusExposureSwitchImageView = findViewById(R.id.focus_exposure_switch_image_view); - schedulerProvider = SchedulerProvider.getInstance(); if (getBackground() == null) { setBackgroundResource(R.drawable.uxsdk_background_black_rectangle); } if (!isInEditMode()) { widgetModel = new FocusExposureSwitchWidgetModel(DJISDKModel.getInstance(), ObservableInMemoryKeyedStore.getInstance(), - GlobalPreferencesManager.getInstance(), - schedulerProvider); + GlobalPreferencesManager.getInstance()); } initDefaults(); @@ -107,7 +105,12 @@ private void initDefaults() { @Override protected void reactToModelChanges() { - addReaction(widgetModel.getControlMode().observeOn(schedulerProvider.ui()).subscribe(this::updateUI)); + addReaction(widgetModel.isFocusModeChangeSupported() + .observeOn(SchedulerProvider.ui()) + .subscribe(this::updateVisibility)); + addReaction(widgetModel.getControlMode() + .observeOn(SchedulerProvider.ui()) + .subscribe(this::updateUI)); } @Override @@ -115,7 +118,7 @@ public void onClick(View v) { int id = v.getId(); if (id == this.getId()) { addDisposable(widgetModel.switchControlMode() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(() -> { //do nothing }, logErrorConsumer(TAG, "switchControlMode: "))); @@ -140,6 +143,14 @@ protected void onDetachedFromWindow() { //endregion //region private methods + private void updateVisibility(boolean isFocusModeChangeSupported) { + if (isFocusModeChangeSupported) { + setVisibility(VISIBLE); + } else { + setVisibility(GONE); + } + } + private void updateUI(ControlMode controlMode) { if (controlMode == ControlMode.SPOT_METER || controlMode == ControlMode.CENTER_METER) { focusExposureSwitchImageView.setImageDrawable(spotMeterDrawable); @@ -153,7 +164,7 @@ private void updateUI(ControlMode controlMode) { private void checkAndUpdateUI() { if (!isInEditMode()) { addDisposable(widgetModel.getControlMode().firstOrError() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::updateUI, logErrorConsumer(TAG, "Update UI "))); } } @@ -161,6 +172,7 @@ private void checkAndUpdateUI() { private void initAttributes(Context context, AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FocusExposureSwitchWidget); setCameraIndex(CameraIndex.find(typedArray.getInt(R.styleable.FocusExposureSwitchWidget_uxsdk_cameraIndex, 0))); + setLensType(SettingsDefinitions.LensType.find(typedArray.getInt(R.styleable.FocusExposureSwitchWidget_uxsdk_lensType, 0))); if (typedArray.getDrawable(R.styleable.FocusExposureSwitchWidget_uxsdk_meteringDrawable) != null) { spotMeterDrawable = typedArray.getDrawable(R.styleable.FocusExposureSwitchWidget_uxsdk_meteringDrawable); } @@ -204,6 +216,27 @@ public void setCameraIndex(@NonNull CameraIndex cameraIndex) { } } + /** + * Get the current type of the lens the widget is reacting to + * + * @return current lens type + */ + @NonNull + public SettingsDefinitions.LensType getLensType() { + return widgetModel.getLensType(); + } + + /** + * Set the type of the lens for which the widget should react + * + * @param lensType lens type + */ + public void setLensType(@NonNull SettingsDefinitions.LensType lensType) { + if (!isInEditMode()) { + widgetModel.setLensType(lensType); + } + } + /** * Get current manual focus icon * diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/focusexposureswitch/FocusExposureSwitchWidgetModel.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/focusexposureswitch/FocusExposureSwitchWidgetModel.java index 055a1995..19cb0bfc 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/focusexposureswitch/FocusExposureSwitchWidgetModel.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/focusexposureswitch/FocusExposureSwitchWidgetModel.java @@ -26,6 +26,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import dji.common.camera.SettingsDefinitions; import dji.common.camera.SettingsDefinitions.FocusMode; import dji.common.camera.SettingsDefinitions.MeteringMode; import dji.keysdk.CameraKey; @@ -34,13 +35,12 @@ import dji.thirdparty.io.reactivex.Completable; import dji.thirdparty.io.reactivex.Flowable; import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.GlobalPreferencesInterface; -import dji.ux.beta.core.base.SchedulerProviderInterface; import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.GlobalPreferenceKeys; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; -import dji.ux.beta.core.base.uxsdkkeys.UXKey; -import dji.ux.beta.core.base.uxsdkkeys.UXKeys; +import dji.ux.beta.core.communication.GlobalPreferenceKeys; +import dji.ux.beta.core.communication.GlobalPreferencesInterface; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.communication.UXKey; +import dji.ux.beta.core.communication.UXKeys; import dji.ux.beta.core.util.DataProcessor; import dji.ux.beta.core.util.SettingDefinitions.CameraIndex; import dji.ux.beta.core.util.SettingDefinitions.ControlMode; @@ -53,30 +53,31 @@ */ public class FocusExposureSwitchWidgetModel extends WidgetModel { - //region fields + //region Fields private static final String TAG = "FocusExpoSwitchWidMod"; + private final DataProcessor isFocusModeSupportedDataProcessor; private final DataProcessor focusModeDataProcessor; private final DataProcessor meteringModeDataProcessor; private final DataProcessor controlModeDataProcessor; private final ObservableInMemoryKeyedStore keyedStore; private final GlobalPreferencesInterface preferencesManager; + private DJIKey focusModeKey; private DJIKey meteringModeKey; private UXKey controlModeKey; private int cameraIndex; - private SchedulerProviderInterface schedulerProvider; + private SettingsDefinitions.LensType lensType = SettingsDefinitions.LensType.ZOOM; //endregion - //region lifecycle + //region Lifecycle public FocusExposureSwitchWidgetModel(@NonNull DJISDKModel djiSdkModel, @NonNull ObservableInMemoryKeyedStore keyedStore, - @Nullable GlobalPreferencesInterface preferencesManager, - @Nullable SchedulerProviderInterface schedulerProvider) { + @Nullable GlobalPreferencesInterface preferencesManager) { super(djiSdkModel, keyedStore); DJILog.d(TAG, "In CONSTRUCTOR"); - this.schedulerProvider = schedulerProvider; focusModeDataProcessor = DataProcessor.create(FocusMode.UNKNOWN); meteringModeDataProcessor = DataProcessor.create(MeteringMode.UNKNOWN); controlModeDataProcessor = DataProcessor.create(ControlMode.SPOT_METER); + isFocusModeSupportedDataProcessor = DataProcessor.create(false); if (preferencesManager != null) { controlModeDataProcessor.onNext(preferencesManager.getControlMode()); } @@ -88,8 +89,8 @@ public FocusExposureSwitchWidgetModel(@NonNull DJISDKModel djiSdkModel, @Override protected void inSetup() { DJILog.d(TAG, "In inSetup"); - meteringModeKey = CameraKey.create(CameraKey.METERING_MODE, cameraIndex); - DJIKey focusModeKey = CameraKey.create(CameraKey.FOCUS_MODE, cameraIndex); + meteringModeKey = djiSdkModel.createLensKey(CameraKey.METERING_MODE, cameraIndex, lensType.value()); + focusModeKey = djiSdkModel.createLensKey(CameraKey.FOCUS_MODE, cameraIndex, lensType.value()); bindDataProcessor(focusModeKey, focusModeDataProcessor); bindDataProcessor(meteringModeKey, meteringModeDataProcessor); @@ -114,10 +115,30 @@ protected void updateStates() { DJILog.d(TAG, "In updateStates"); updateFocusMode(); } + + @Override + protected void onProductConnectionChanged(boolean isConnected) { + super.onProductConnectionChanged(isConnected); + if (isConnected) { + isFocusModeSupportedDataProcessor.onNext(djiSdkModel.isKeySupported(focusModeKey)); + } else { + isFocusModeSupportedDataProcessor.onNext(false); + } + } //endregion //region Data + /** + * Check if focus mode change is supported + * + * @return Flowable with boolean true - supported false - not supported + */ + public Flowable isFocusModeChangeSupported() { + return isFocusModeSupportedDataProcessor.toFlowable(); + } + + /** * Get control mode * @@ -152,6 +173,26 @@ public void setCameraIndex(@NonNull CameraIndex cameraIndex) { restart(); } + /** + * Get the current type of the lens the widget model is reacting to + * + * @return current lens type + */ + @NonNull + public SettingsDefinitions.LensType getLensType() { + return lensType; + } + + /** + * Set the type of the lens for which the widget model should react + * + * @param lensType lens type + */ + public void setLensType(@NonNull SettingsDefinitions.LensType lensType) { + this.lensType = lensType; + restart(); + } + /** * Switch between exposure/metering mode and focus mode * @@ -179,7 +220,6 @@ private void updateFocusMode() { private Completable setMeteringMode() { return djiSdkModel.setValue(meteringModeKey, MeteringMode.SPOT) - .subscribeOn(schedulerProvider.io()) .doOnComplete( () -> { DJILog.d(TAG, "setMeteringMode success"); @@ -205,13 +245,13 @@ private Completable setFocusMode() { } if (focusModeDataProcessor.getValue() == FocusMode.MANUAL) { preferencesManager.setControlMode(ControlMode.MANUAL_FOCUS); - return keyedStore.setValue(controlModeKey, ControlMode.MANUAL_FOCUS).subscribeOn(schedulerProvider.io()); + return keyedStore.setValue(controlModeKey, ControlMode.MANUAL_FOCUS); } else if (focusModeDataProcessor.getValue() == FocusMode.AFC) { preferencesManager.setControlMode(ControlMode.AUTO_FOCUS_CONTINUE); - return keyedStore.setValue(controlModeKey, ControlMode.AUTO_FOCUS_CONTINUE).subscribeOn(schedulerProvider.io()); + return keyedStore.setValue(controlModeKey, ControlMode.AUTO_FOCUS_CONTINUE); } else { preferencesManager.setControlMode(ControlMode.AUTO_FOCUS); - return keyedStore.setValue(controlModeKey, ControlMode.AUTO_FOCUS).subscribeOn(schedulerProvider.io()); + return keyedStore.setValue(controlModeKey, ControlMode.AUTO_FOCUS); } } //endregion diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/focusmode/FocusModeWidget.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/focusmode/FocusModeWidget.java index 3846254a..1f54536e 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/focusmode/FocusModeWidget.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/focusmode/FocusModeWidget.java @@ -41,15 +41,16 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import dji.common.camera.SettingsDefinitions; import dji.common.camera.SettingsDefinitions.FocusMode; import dji.thirdparty.io.reactivex.Flowable; import dji.thirdparty.io.reactivex.disposables.Disposable; import dji.ux.beta.cameracore.R; import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.FrameLayoutWidget; -import dji.ux.beta.core.base.GlobalPreferencesManager; import dji.ux.beta.core.base.SchedulerProvider; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.base.widget.FrameLayoutWidget; +import dji.ux.beta.core.communication.GlobalPreferencesManager; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; import dji.ux.beta.core.util.DisplayUtil; import dji.ux.beta.core.util.SettingDefinitions; @@ -68,15 +69,14 @@ public class FocusModeWidget extends FrameLayoutWidget implements OnClickListene private static final String TAG = "FocusModeWidget"; //endregion - //region fields + //region Fields private FocusModeWidgetModel widgetModel; private TextView titleTextView; private int activeColor; private int inactiveColor; - private SchedulerProvider schedulerProvider; //endregion - //region lifecycle + //region Lifecycle public FocusModeWidget(Context context) { super(context); } @@ -96,12 +96,10 @@ protected void initView(@NonNull Context context, @Nullable AttributeSet attrs, setBackgroundResource(R.drawable.uxsdk_background_black_rectangle); } titleTextView = findViewById(R.id.text_view_camera_control_af); - schedulerProvider = SchedulerProvider.getInstance(); if (!isInEditMode()) { widgetModel = new FocusModeWidgetModel(DJISDKModel.getInstance(), ObservableInMemoryKeyedStore.getInstance(), - GlobalPreferencesManager.getInstance(), - schedulerProvider); + GlobalPreferencesManager.getInstance()); } setOnClickListener(this); activeColor = getResources().getColor(R.color.uxsdk_green); @@ -129,9 +127,13 @@ protected void onDetachedFromWindow() { @Override protected void reactToModelChanges() { + addReaction(widgetModel.isFocusModeChangeSupported() + .observeOn(SchedulerProvider.ui()) + .subscribe(this::updateVisibility)); addReaction(reactToFocusModeChange()); } + @NonNull @Override public String getIdealDimensionRatioString() { @@ -141,7 +143,7 @@ public String getIdealDimensionRatioString() { @Override public void onClick(View v) { addDisposable(widgetModel.toggleFocusMode() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(() -> { // Do nothing }, logErrorConsumer(TAG, "switch focus mode: "))); @@ -155,7 +157,7 @@ private void checkAndUpdateUI() { if (!isInEditMode()) { addDisposable(Flowable.combineLatest(widgetModel.isAFCEnabled(), widgetModel.getFocusMode(), Pair::new) .firstOrError() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(values -> updateUI(values.first, values.second), logErrorConsumer(TAG, "check and update focus mode: "))); } @@ -163,11 +165,19 @@ private void checkAndUpdateUI() { private Disposable reactToFocusModeChange() { return Flowable.combineLatest(widgetModel.isAFCEnabled(), widgetModel.getFocusMode(), Pair::new) - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(values -> updateUI(values.first, values.second), logErrorConsumer(TAG, "react to Focus Mode Change: ")); } + private void updateVisibility(boolean isFocusModeChangeSupported) { + if (isFocusModeChangeSupported) { + setVisibility(VISIBLE); + } else { + setVisibility(GONE); + } + } + private void updateUI(boolean isAFCEnabled, FocusMode focusMode) { int autoFocusTextColor; int manualFocusTextColor; @@ -210,6 +220,7 @@ private void makeSpannableString(String autoFocusText, int autoFocusColor, int m private void initAttributes(Context context, AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FocusModeWidget); setCameraIndex(SettingDefinitions.CameraIndex.find(typedArray.getInt(R.styleable.FocusModeWidget_uxsdk_cameraIndex, 0))); + setLensType(SettingsDefinitions.LensType.find(typedArray.getInt(R.styleable.FocusModeWidget_uxsdk_lensType, 0))); activeColor = typedArray.getColor(R.styleable.FocusModeWidget_uxsdk_activeModeTextColor, getResources().getColor(R.color.uxsdk_green)); inactiveColor = typedArray.getColor(R.styleable.FocusModeWidget_uxsdk_inactiveModeTextColor, getResources().getColor(R.color.uxsdk_white)); Drawable background = typedArray.getDrawable(R.styleable.FocusModeWidget_uxsdk_widgetTitleBackground); @@ -246,6 +257,27 @@ public void setCameraIndex(@NonNull SettingDefinitions.CameraIndex cameraIndex) } } + /** + * Get the current type of the lens the widget is reacting to + * + * @return current lens type + */ + @NonNull + public SettingsDefinitions.LensType getLensType() { + return widgetModel.getLensType(); + } + + /** + * Set the type of the lens for which the widget should react + * + * @param lensType lens type + */ + public void setLensType(@NonNull SettingsDefinitions.LensType lensType) { + if (!isInEditMode()) { + widgetModel.setLensType(lensType); + } + } + /** * Get active mode text color * diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/focusmode/FocusModeWidgetModel.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/focusmode/FocusModeWidgetModel.java index 858a97b1..c5323e0d 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/focusmode/FocusModeWidgetModel.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/focusmode/FocusModeWidgetModel.java @@ -26,19 +26,19 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import dji.common.camera.SettingsDefinitions; import dji.common.camera.SettingsDefinitions.FocusMode; import dji.keysdk.CameraKey; import dji.keysdk.DJIKey; import dji.thirdparty.io.reactivex.Completable; import dji.thirdparty.io.reactivex.Flowable; import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.GlobalPreferencesInterface; -import dji.ux.beta.core.base.SchedulerProviderInterface; import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.GlobalPreferenceKeys; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; -import dji.ux.beta.core.base.uxsdkkeys.UXKey; -import dji.ux.beta.core.base.uxsdkkeys.UXKeys; +import dji.ux.beta.core.communication.GlobalPreferenceKeys; +import dji.ux.beta.core.communication.GlobalPreferencesInterface; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.communication.UXKey; +import dji.ux.beta.core.communication.UXKeys; import dji.ux.beta.core.util.DataProcessor; import dji.ux.beta.core.util.SettingDefinitions; import dji.ux.beta.core.util.SettingDefinitions.CameraIndex; @@ -51,8 +51,9 @@ */ public class FocusModeWidgetModel extends WidgetModel { - //region fields + //region Fields private static final String TAG = "FocusModeWidgetModel"; + private final DataProcessor isFocusModeSupportedDataProcessor; private final DataProcessor focusModeDataProcessor; private final DataProcessor isAFCSupportedProcessor; private final DataProcessor isAFCEnabledKeyProcessor; @@ -63,19 +64,19 @@ public class FocusModeWidgetModel extends WidgetModel { private DJIKey focusModeKey; private UXKey controlModeKey; private int cameraIndex = CameraIndex.CAMERA_INDEX_0.getIndex(); - private SchedulerProviderInterface schedulerProvider; + private SettingsDefinitions.LensType lensType = SettingsDefinitions.LensType.ZOOM; //endregion - //region lifecycle + //region Lifecycle public FocusModeWidgetModel(@NonNull DJISDKModel djiSdkModel, @NonNull ObservableInMemoryKeyedStore keyedStore, - @Nullable GlobalPreferencesInterface preferencesManager, - @NonNull SchedulerProviderInterface schedulerProvider) { + @Nullable GlobalPreferencesInterface preferencesManager) { super(djiSdkModel, keyedStore); focusModeDataProcessor = DataProcessor.create(FocusMode.UNKNOWN); isAFCSupportedProcessor = DataProcessor.create(false); isAFCEnabledKeyProcessor = DataProcessor.create(false); isAFCEnabledProcessor = DataProcessor.create(false); + isFocusModeSupportedDataProcessor = DataProcessor.create(false); controlModeProcessor = DataProcessor.create(SettingDefinitions.ControlMode.SPOT_METER); if (preferencesManager != null) { isAFCEnabledKeyProcessor.onNext(preferencesManager.getAFCEnabled()); @@ -84,14 +85,13 @@ public FocusModeWidgetModel(@NonNull DJISDKModel djiSdkModel, } this.preferencesManager = preferencesManager; this.keyedStore = keyedStore; - this.schedulerProvider = schedulerProvider; } @Override protected void inSetup() { - focusModeKey = CameraKey.create(CameraKey.FOCUS_MODE, cameraIndex); + focusModeKey = djiSdkModel.createLensKey(CameraKey.FOCUS_MODE, cameraIndex, lensType.value()); bindDataProcessor(focusModeKey, focusModeDataProcessor); - DJIKey isAFCSupportedKey = CameraKey.create(CameraKey.IS_AFC_SUPPORTED, cameraIndex); + DJIKey isAFCSupportedKey = djiSdkModel.createLensKey(CameraKey.IS_AFC_SUPPORTED, cameraIndex, lensType.value()); bindDataProcessor(isAFCSupportedKey, isAFCSupportedProcessor); UXKey afcEnabledKey = UXKeys.create(GlobalPreferenceKeys.AFC_ENABLED); bindDataProcessor(afcEnabledKey, isAFCEnabledKeyProcessor); @@ -120,6 +120,16 @@ private void updateAFCEnabledProcessor() { isAFCEnabledProcessor.onNext(isAFCEnabledKeyProcessor.getValue() && isAFCSupportedProcessor.getValue()); } + @Override + protected void onProductConnectionChanged(boolean isConnected) { + super.onProductConnectionChanged(isConnected); + if (isConnected) { + isFocusModeSupportedDataProcessor.onNext(djiSdkModel.isKeySupported(focusModeKey)); + } else { + isFocusModeSupportedDataProcessor.onNext(false); + } + } + //endregion //region Actions @@ -144,6 +154,26 @@ public void setCameraIndex(@NonNull CameraIndex cameraIndex) { restart(); } + /** + * Get the current type of the lens the widget model is reacting to + * + * @return current lens type + */ + @NonNull + public SettingsDefinitions.LensType getLensType() { + return lensType; + } + + /** + * Set the type of the lens for which the widget model should react + * + * @param lensType lens type + */ + public void setLensType(@NonNull SettingsDefinitions.LensType lensType) { + this.lensType = lensType; + restart(); + } + /** * Switch between focus modes * @@ -154,7 +184,6 @@ public Completable toggleFocusMode() { final FocusMode nextFocusMode = getNextFocusMode(currentFocusMode); return djiSdkModel.setValue(focusModeKey, nextFocusMode) - .subscribeOn(schedulerProvider.io()) .doOnComplete(() -> onFocusModeUpdate(nextFocusMode)) .doOnError(error -> focusModeDataProcessor.onNext(currentFocusMode)); } @@ -193,6 +222,15 @@ public Flowable getFocusMode() { public Flowable isAFCEnabled() { return isAFCEnabledProcessor.toFlowable(); } + + /** + * Check if focus mode change is supported + * + * @return Flowable with boolean true - supported false - not supported + */ + public Flowable isFocusModeChangeSupported() { + return isFocusModeSupportedDataProcessor.toFlowable(); + } //endregion //region Helpers diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/fpvinteraction/ExposureMeterView.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/fpvinteraction/ExposureMeterView.java index c281c1f4..7d746b24 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/fpvinteraction/ExposureMeterView.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/fpvinteraction/ExposureMeterView.java @@ -18,6 +18,7 @@ * 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.ux.beta.cameracore.widget.fpvinteraction; @@ -61,7 +62,7 @@ public class ExposureMeterView extends FrameLayout { private ControlMode controlMode; //endregion - //region Constructors + //region Constructor public ExposureMeterView(@NonNull Context context) { super(context); initView(context); diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/fpvinteraction/FPVInteractionWidget.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/fpvinteraction/FPVInteractionWidget.java index a33d1a8a..80eb859f 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/fpvinteraction/FPVInteractionWidget.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/fpvinteraction/FPVInteractionWidget.java @@ -18,6 +18,7 @@ * 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.ux.beta.cameracore.widget.fpvinteraction; @@ -42,15 +43,16 @@ import java.util.concurrent.atomic.AtomicBoolean; import dji.common.airlink.PhysicalSource; +import dji.common.camera.CameraVideoStreamSource; import dji.thirdparty.io.reactivex.Flowable; import dji.thirdparty.io.reactivex.disposables.Disposable; import dji.ux.beta.cameracore.R; -import dji.ux.beta.core.base.ConstraintLayoutWidget; import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.GlobalPreferencesManager; import dji.ux.beta.core.base.SchedulerProvider; -import dji.ux.beta.core.base.SchedulerProviderInterface; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget; +import dji.ux.beta.core.communication.GlobalPreferencesManager; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.util.CameraUtil; import dji.ux.beta.core.util.SettingDefinitions; import dji.ux.beta.core.util.SettingDefinitions.CameraIndex; import dji.ux.beta.core.util.SettingDefinitions.ControlMode; @@ -92,7 +94,6 @@ public class FPVInteractionWidget extends ConstraintLayoutWidget implements View private float moveDeltaY; private float velocityFactor; private Disposable gimbalMoveDisposable; - private SchedulerProviderInterface schedulerProvider; private AtomicBoolean isInteractionEnabledAtomic; private String cameraName; @@ -105,7 +106,7 @@ public void run() { }; //endregion - //region Constructors + //region Constructor public FPVInteractionWidget(@NonNull Context context) { super(context); } @@ -126,15 +127,13 @@ protected void initView(@NonNull Context context, @Nullable AttributeSet attrs, gimbalControlView = findViewById(R.id.view_gimbal_control); setOnTouchListener(this); velocityFactor = DEFAULT_VELOCITY_FACTOR; - schedulerProvider = SchedulerProvider.getInstance(); isInteractionEnabledAtomic = new AtomicBoolean(true); cameraName = ""; if (!isInEditMode()) { widgetModel = new FPVInteractionWidgetModel(DJISDKModel.getInstance(), ObservableInMemoryKeyedStore.getInstance(), - GlobalPreferencesManager.getInstance(), - schedulerProvider); + GlobalPreferencesManager.getInstance()); } if (attrs != null) { @@ -178,13 +177,23 @@ public void onCameraSideChange(@Nullable SettingDefinitions.CameraSide cameraSid if (cameraSide == SettingDefinitions.CameraSide.PORT) { setGimbalIndex(SettingDefinitions.GimbalIndex.PORT); setCameraIndex(SettingDefinitions.CameraIndex.CAMERA_INDEX_0); - } else { + } else if (cameraSide == SettingDefinitions.CameraSide.STARBOARD) { setGimbalIndex(SettingDefinitions.GimbalIndex.STARBOARD); setCameraIndex(SettingDefinitions.CameraIndex.CAMERA_INDEX_2); + } else { + setGimbalIndex(SettingDefinitions.GimbalIndex.TOP); + setCameraIndex(SettingDefinitions.CameraIndex.CAMERA_INDEX_4); } } } + @Override + public void onStreamSourceChange(@Nullable CameraVideoStreamSource streamSource) { + if (streamSource != null) { + setLensIndex(CameraUtil.getLensIndex(streamSource, cameraName)); + } + } + @Override public void onFPVSizeChange(@Nullable FPVWidget.FPVSize fpvSize) { if (fpvSize != null) { @@ -194,10 +203,10 @@ public void onFPVSizeChange(@Nullable FPVWidget.FPVSize fpvSize) { //endregion - //region reaction helpers + //region Reaction helpers private Disposable reactToUpdateVisibility() { return Flowable.combineLatest(widgetModel.getControlMode(), widgetModel.isAeLocked(), Pair::new) - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(values -> updateViewVisibility(values.first, values.second), logErrorConsumer(TAG, "reactToUpdateVisibility: ")); } @@ -264,7 +273,7 @@ public boolean onTouch(View v, MotionEvent event) { float targetY = absTargetY / (float) viewHeight; addDisposable(Flowable.combineLatest(widgetModel.getControlMode(), widgetModel.isAeLocked(), Pair::new) .firstOrError() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe((Pair values) -> updateTarget((ControlMode) (values.first), (Boolean) (values.second), targetX, targetY), logErrorConsumer(TAG, "Update Target: "))); } @@ -336,6 +345,26 @@ public void setGimbalIndex(@Nullable GimbalIndex gimbalIndex) { } } + /** + * Get the index of the lens to which the widget is reacting + * + * @return current lens index + */ + public int getLensIndex() { + return widgetModel.getLensIndex(); + } + + /** + * Set the index of lens to which the widget should react + * + * @param lensIndex index of the lens. + */ + public void setLensIndex(int lensIndex) { + if (!isInEditMode()) { + widgetModel.setLensIndex(lensIndex); + } + } + /** * Adjust the width and height of the interaction area. This should be called whenever the * size of the video feed changes. The interaction area will be centered within the view. @@ -358,12 +387,12 @@ private void updateTarget(ControlMode controlMode, boolean isAeLocked, float tar if (spotMeteringEnabled && isInBounds() && !isAeLocked) { final ControlMode newControlMode = exposureMeterView.clickEvent(controlMode, absTargetX, absTargetY, viewWidth, viewHeight); addDisposable(widgetModel.setControlMode(newControlMode) - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(() -> { //do nothing }, logErrorConsumer(TAG, "updateTarget: "))); addDisposable(widgetModel.updateMetering(targetX, targetY) - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(() -> { // do nothing }, throwable -> onExposureMeterSetFail(newControlMode))); @@ -371,7 +400,7 @@ private void updateTarget(ControlMode controlMode, boolean isAeLocked, float tar } else if (touchFocusEnabled && isInBounds()) { focusTargetView.clickEvent(absTargetX, absTargetY); addDisposable(widgetModel.updateFocusTarget(targetX, targetY) - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(() -> { //do nothing }, throwable -> onFocusTargetSetFail())); @@ -393,7 +422,7 @@ private void onExposureMeterSetFail(ControlMode controlMode) { oldAbsTargetY, viewWidth, viewHeight)) - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(() -> { //do nothing }, logErrorConsumer(TAG, "onExposureMeterSetFail: "))); @@ -450,14 +479,14 @@ private void stopGimbalRotation() { private void toggleGimbalRotateBySpeed() { gimbalMoveDisposable = Flowable.interval(50, TimeUnit.MILLISECONDS) - .subscribeOn(schedulerProvider.io()) + .subscribeOn(SchedulerProvider.io()) .subscribe(aLong -> { float yawVelocity = moveDeltaX / velocityFactor; float pitchVelocity = moveDeltaY / velocityFactor; if (Math.abs(yawVelocity) >= 1 || Math.abs(pitchVelocity) >= 1) { addDisposable(widgetModel.rotateGimbalBySpeed(yawVelocity, -pitchVelocity) - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(() -> { //do nothing }, logErrorConsumer(TAG, "rotate gimbal: "))); @@ -480,6 +509,7 @@ private void initAttributes(@NonNull Context context, @NonNull AttributeSet attr setCameraIndex(CameraIndex.find(typedArray.getInt(R.styleable.FPVInteractionWidget_uxsdk_cameraIndex, 0))); setGimbalIndex(GimbalIndex.find(typedArray.getInt(R.styleable.FPVInteractionWidget_uxsdk_gimbalIndex, 0))); + setLensIndex(typedArray.getInt(R.styleable.FPVInteractionWidget_uxsdk_lensType, 0)); Drawable manualFocusIcon = typedArray.getDrawable(R.styleable.FPVInteractionWidget_uxsdk_manualFocusIcon); if (manualFocusIcon != null) { diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/fpvinteraction/FPVInteractionWidgetModel.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/fpvinteraction/FPVInteractionWidgetModel.java index 766b42cd..efeb43b5 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/fpvinteraction/FPVInteractionWidgetModel.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/fpvinteraction/FPVInteractionWidgetModel.java @@ -18,6 +18,7 @@ * 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.ux.beta.cameracore.widget.fpvinteraction; @@ -44,13 +45,12 @@ import dji.thirdparty.io.reactivex.Completable; import dji.thirdparty.io.reactivex.Flowable; import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.GlobalPreferencesInterface; -import dji.ux.beta.core.base.SchedulerProviderInterface; import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.GlobalPreferenceKeys; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; -import dji.ux.beta.core.base.uxsdkkeys.UXKey; -import dji.ux.beta.core.base.uxsdkkeys.UXKeys; +import dji.ux.beta.core.communication.GlobalPreferenceKeys; +import dji.ux.beta.core.communication.GlobalPreferencesInterface; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.communication.UXKey; +import dji.ux.beta.core.communication.UXKeys; import dji.ux.beta.core.util.DataProcessor; import dji.ux.beta.core.util.ProductUtil; import dji.ux.beta.core.util.SettingDefinitions.CameraIndex; @@ -76,19 +76,18 @@ public class FPVInteractionWidgetModel extends WidgetModel { //region Fields private int cameraIndex; private int gimbalIndex; + private int lensIndex; private DJIKey focusTargetKey; private DJIKey meteringTargetKey; private DJIKey meteringModeKey; private Rotation.Builder builder; private UXKey controlModeKey; - private SchedulerProviderInterface schedulerProvider; //endregion //region Constructor public FPVInteractionWidgetModel(@NonNull DJISDKModel djiSdkModel, @NonNull ObservableInMemoryKeyedStore keyedStore, - @Nullable GlobalPreferencesInterface preferencesManager, - @NonNull SchedulerProviderInterface schedulerProvider) { + @Nullable GlobalPreferencesInterface preferencesManager) { super(djiSdkModel, keyedStore); cameraIndex = CameraIndex.CAMERA_INDEX_0.getIndex(); gimbalIndex = GimbalIndex.PORT.getIndex(); @@ -102,18 +101,17 @@ public FPVInteractionWidgetModel(@NonNull DJISDKModel djiSdkModel, builder = new Rotation.Builder().mode(RotationMode.SPEED); this.preferencesManager = preferencesManager; this.keyedStore = keyedStore; - this.schedulerProvider = schedulerProvider; } //endregion //region Lifecycle @Override protected void inSetup() { - focusTargetKey = CameraKey.create(CameraKey.FOCUS_TARGET, cameraIndex); - meteringTargetKey = CameraKey.create(CameraKey.SPOT_METERING_TARGET, cameraIndex); - meteringModeKey = CameraKey.create(CameraKey.METERING_MODE, cameraIndex); + focusTargetKey = djiSdkModel.createLensKey(CameraKey.FOCUS_TARGET, cameraIndex, lensIndex); + meteringTargetKey = djiSdkModel.createLensKey(CameraKey.SPOT_METERING_TARGET, cameraIndex, lensIndex); + meteringModeKey = djiSdkModel.createLensKey(CameraKey.METERING_MODE, cameraIndex, lensIndex); bindDataProcessor(meteringModeKey, meteringModeProcessor, meteringMode -> setMeteringMode((MeteringMode) meteringMode)); - DJIKey aeLockedKey = CameraKey.create(CameraKey.AE_LOCK, cameraIndex); + DJIKey aeLockedKey = djiSdkModel.createLensKey(CameraKey.AE_LOCK, cameraIndex, lensIndex); bindDataProcessor(aeLockedKey, aeLockedProcessor); DJIKey capabilitiesKey = GimbalKey.create(GimbalKey.CAPABILITIES, gimbalIndex); bindDataProcessor(capabilitiesKey, capabilitiesMapProcessor); @@ -192,6 +190,25 @@ public void setGimbalIndex(@Nullable GimbalIndex gimbalIndex) { restart(); } + /** + * Get the current index of the lens the widget model is reacting to + * + * @return current lens index + */ + public int getLensIndex() { + return lensIndex; + } + + /** + * Set the index of the lens for which the widget model should react + * + * @param lensIndex lens index + */ + public void setLensIndex(int lensIndex) { + this.lensIndex = lensIndex; + restart(); + } + /** * Set the control mode. * @@ -227,7 +244,7 @@ public Flowable isAeLocked() { } //endregion - //region reactions to user input + //region Reactions to user input /** * Set the focus target to the location (targetX, targetY). This is a relative coordinate @@ -242,8 +259,7 @@ public Flowable isAeLocked() { @NonNull public Completable updateFocusTarget(@FloatRange(from = 0, to = 1) float targetX, @FloatRange(from = 0, to = 1) float targetY) { - return djiSdkModel.setValue(focusTargetKey, createPointF(targetX, targetY)) - .subscribeOn(schedulerProvider.io()); + return djiSdkModel.setValue(focusTargetKey, createPointF(targetX, targetY)); } /** @@ -266,17 +282,14 @@ public Completable updateMetering(@FloatRange(from = 0, to = 1) float targetX, if (column >= 0 && column < NUM_COLUMNS && row >= 0 && row < NUM_ROWS) { if (meteringModeProcessor.getValue() != MeteringMode.SPOT) { return djiSdkModel.setValue(meteringModeKey, MeteringMode.SPOT) - .andThen(djiSdkModel.setValue(meteringTargetKey, createPoint(column, row)) - .subscribeOn(schedulerProvider.io())) - .subscribeOn(schedulerProvider.io()); + .andThen(djiSdkModel.setValue(meteringTargetKey, createPoint(column, row))); } else { - return djiSdkModel.setValue(meteringTargetKey, createPoint(column, row)) - .subscribeOn(schedulerProvider.io()); + return djiSdkModel.setValue(meteringTargetKey, createPoint(column, row)); + } } } else if (controlModeProcessor.getValue() == ControlMode.CENTER_METER) { - return djiSdkModel.setValue(meteringModeKey, MeteringMode.CENTER) - .subscribeOn(schedulerProvider.io()); + return djiSdkModel.setValue(meteringModeKey, MeteringMode.CENTER); } return Completable.complete(); } @@ -308,8 +321,7 @@ public Completable rotateGimbalBySpeed(float yaw, float pitch) { pitch = Rotation.NO_ROTATION; } Rotation r = builder.yaw(yaw).pitch(pitch).build(); - return djiSdkModel.performAction(GimbalKey.create(GimbalKey.ROTATE, gimbalIndex), r) - .subscribeOn(schedulerProvider.io()); + return djiSdkModel.performAction(GimbalKey.create(GimbalKey.ROTATE, gimbalIndex), r); } //endregion diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/fpvinteraction/FocusTargetView.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/fpvinteraction/FocusTargetView.java index b9970544..e41ca127 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/fpvinteraction/FocusTargetView.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/fpvinteraction/FocusTargetView.java @@ -18,6 +18,7 @@ * 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.ux.beta.cameracore.widget.fpvinteraction; @@ -57,7 +58,7 @@ public class FocusTargetView extends FrameLayout { private Map iconMap; //endregion - //region Constructors + //region Constructor public FocusTargetView(@NonNull Context context) { super(context); initView(); diff --git a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/fpvinteraction/GimbalControlView.java b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/fpvinteraction/GimbalControlView.java index 100c54dd..d80a2873 100644 --- a/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/fpvinteraction/GimbalControlView.java +++ b/android-uxsdk-beta-cameracore/src/main/java/dji/ux/beta/cameracore/widget/fpvinteraction/GimbalControlView.java @@ -18,6 +18,7 @@ * 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.ux.beta.cameracore.widget.fpvinteraction; @@ -59,7 +60,7 @@ public class GimbalControlView extends FrameLayout { private int vibrationDuration; //endregion - //region Constructors + //region Constructor public GimbalControlView(@NonNull Context context) { super(context); initView(context); diff --git a/android-uxsdk-beta-cameracore/src/main/res/layout/uxsdk_widget_record_video.xml b/android-uxsdk-beta-cameracore/src/main/res/layout/uxsdk_widget_record_video.xml index 4f925a11..4246002e 100644 --- a/android-uxsdk-beta-cameracore/src/main/res/layout/uxsdk_widget_record_video.xml +++ b/android-uxsdk-beta-cameracore/src/main/res/layout/uxsdk_widget_record_video.xml @@ -78,6 +78,7 @@ app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/progress_ring_view_border" tools:text="00:00" - tools:visibility="visible" /> + tools:visibility="visible" + tools:ignore="SmallSp" /> \ No newline at end of file diff --git a/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_camera_ev_center.mp3 b/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_camera_ev_center.mp3 new file mode 100755 index 00000000..c0ddb325 Binary files /dev/null and b/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_camera_ev_center.mp3 differ diff --git a/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_camera_simple_click.mp3 b/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_camera_simple_click.mp3 new file mode 100755 index 00000000..7c2ad4ba Binary files /dev/null and b/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_camera_simple_click.mp3 differ diff --git a/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_end_video_record.mp3 b/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_end_video_record.mp3 new file mode 100644 index 00000000..c48e83d7 Binary files /dev/null and b/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_end_video_record.mp3 differ diff --git a/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_shutter_1.mp3 b/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_shutter_1.mp3 new file mode 100644 index 00000000..0ffb32bb Binary files /dev/null and b/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_shutter_1.mp3 differ diff --git a/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_shutter_10.mp3 b/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_shutter_10.mp3 new file mode 100644 index 00000000..ae20cb34 Binary files /dev/null and b/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_shutter_10.mp3 differ diff --git a/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_shutter_14.mp3 b/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_shutter_14.mp3 new file mode 100644 index 00000000..5d32013f Binary files /dev/null and b/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_shutter_14.mp3 differ diff --git a/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_shutter_3.mp3 b/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_shutter_3.mp3 new file mode 100644 index 00000000..be173e9f Binary files /dev/null and b/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_shutter_3.mp3 differ diff --git a/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_shutter_5.mp3 b/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_shutter_5.mp3 new file mode 100644 index 00000000..baa37d11 Binary files /dev/null and b/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_shutter_5.mp3 differ diff --git a/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_shutter_7.mp3 b/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_shutter_7.mp3 new file mode 100644 index 00000000..b96022c6 Binary files /dev/null and b/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_shutter_7.mp3 differ diff --git a/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_video_voice.mp3 b/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_video_voice.mp3 new file mode 100644 index 00000000..284b184b Binary files /dev/null and b/android-uxsdk-beta-cameracore/src/main/res/raw/uxsdk_video_voice.mp3 differ diff --git a/android-uxsdk-beta-cameracore/src/main/res/values/attrs.xml b/android-uxsdk-beta-cameracore/src/main/res/values/attrs.xml index 1478211d..be1b74de 100644 --- a/android-uxsdk-beta-cameracore/src/main/res/values/attrs.xml +++ b/android-uxsdk-beta-cameracore/src/main/res/values/attrs.xml @@ -24,8 +24,11 @@ + + + @@ -37,6 +40,7 @@ + @@ -51,6 +55,7 @@ + @@ -59,6 +64,7 @@ + @@ -84,6 +90,7 @@ + @@ -127,6 +134,7 @@ + diff --git a/android-uxsdk-beta-cameracore/src/main/res/values/dimens.xml b/android-uxsdk-beta-cameracore/src/main/res/values/dimens.xml new file mode 100644 index 00000000..e6dd1a98 --- /dev/null +++ b/android-uxsdk-beta-cameracore/src/main/res/values/dimens.xml @@ -0,0 +1,33 @@ + + + + + 2dp + 12dp + 9dp + 18dp + 22dp + 1dp + 6dp + \ No newline at end of file diff --git a/android-uxsdk-beta-cameracore/src/main/res/values/strings.xml b/android-uxsdk-beta-cameracore/src/main/res/values/strings.xml index cc8109d0..dd9431c6 100644 --- a/android-uxsdk-beta-cameracore/src/main/res/values/strings.xml +++ b/android-uxsdk-beta-cameracore/src/main/res/values/strings.xml @@ -24,7 +24,7 @@ AE - + MENU diff --git a/android-uxsdk-beta-core/build.gradle b/android-uxsdk-beta-core/build.gradle index 1c1335db..f31f2efa 100644 --- a/android-uxsdk-beta-core/build.gradle +++ b/android-uxsdk-beta-core/build.gradle @@ -67,7 +67,7 @@ android { } dependencies { - implementation ('com.dji:dji-sdk:4.13.1', { + implementation ('com.dji:dji-sdk:4.14-trial1', { /** * Comment the "library-anti-distortion" if your app needs Anti Distortion for Mavic 2 Pro * and Mavic 2 Zoom. @@ -79,7 +79,7 @@ dependencies { exclude module: 'library-anti-distortion' exclude module: 'fly-safe-database' }) - compileOnly ('com.dji:dji-sdk-provided:4.13.1') + compileOnly ('com.dji:dji-sdk-provided:4.14-trial1') implementation 'androidx.multidex:multidex:2.0.0' implementation 'androidx.appcompat:appcompat:1.0.0' diff --git a/android-uxsdk-beta-core/src/main/AndroidManifest.xml b/android-uxsdk-beta-core/src/main/AndroidManifest.xml index 89d7449f..8dfe3936 100644 --- a/android-uxsdk-beta-core/src/main/AndroidManifest.xml +++ b/android-uxsdk-beta-core/src/main/AndroidManifest.xml @@ -21,5 +21,4 @@ ~ --> - - + diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/BaseModule.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/BaseModule.kt new file mode 100644 index 00000000..ff3baabe --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/BaseModule.kt @@ -0,0 +1,77 @@ +/* + * 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.ux.beta.core.base + +import androidx.annotation.CheckResult +import dji.keysdk.DJIKey +import dji.thirdparty.io.reactivex.functions.Consumer +import dji.ux.beta.core.util.DataProcessor +import dji.ux.beta.core.util.RxUtil + +/** + * Base module class for grouping sets of data that are often used together. + */ +abstract class BaseModule { + + //region Lifecycle + /** + * Setup method for initialization that must be implemented + */ + protected abstract fun setup(widgetModel: WidgetModel) + + /** + * Cleanup method for post-usage destruction that must be implemented + */ + protected abstract fun cleanup() + //endregion + + /** + * Bind the given DJIKey to the given data processor and attach the given consumer to it. + * The data processor and side effect consumer will be invoked with every update to the key. + * The side effect consumer will be called before the data processor is updated. + * + * @param key DJIKey to be bound + * @param dataProcessor DataProcessor to be bound + * @param sideEffectConsumer Consumer to be called along with data processor + */ + protected open fun bindDataProcessor(widgetModel: WidgetModel, + key: DJIKey, + dataProcessor: DataProcessor<*>, + sideEffectConsumer: Consumer = Consumer {}) { + widgetModel.bindDataProcessor(key, dataProcessor, sideEffectConsumer) + } + + /** + * Get a throwable error consumer for the given error. + * + * @param tag Tag for the log + * @param message Message to be logged + * @return Throwable consumer + */ + @CheckResult + protected open fun logErrorConsumer(tag: String, message: String): Consumer? { + return RxUtil.logErrorConsumer(tag, message) + } + +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/DJISDKModel.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/DJISDKModel.java index 21418e67..517b962f 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/DJISDKModel.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/DJISDKModel.java @@ -23,6 +23,7 @@ package dji.ux.beta.core.base; +import androidx.annotation.IntRange; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -32,6 +33,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import dji.common.error.DJIError; +import dji.keysdk.CameraKey; import dji.keysdk.DJIKey; import dji.keysdk.KeyManager; import dji.keysdk.callback.ActionCallback; @@ -39,12 +41,12 @@ import dji.keysdk.callback.KeyListener; import dji.keysdk.callback.SetCallback; import dji.log.DJILog; +import dji.sdk.camera.Camera; import dji.thirdparty.io.reactivex.BackpressureStrategy; import dji.thirdparty.io.reactivex.Completable; import dji.thirdparty.io.reactivex.Flowable; import dji.thirdparty.io.reactivex.FlowableEmitter; import dji.thirdparty.io.reactivex.Single; -import dji.thirdparty.io.reactivex.schedulers.Schedulers; /** * Encapsulates communication with SDK KeyManager for SDKKeys. @@ -106,7 +108,7 @@ public Flowable addListener(@NonNull final DJIKey key, @NonNull final Ob DJILog.d(TAG, "Registering key " + key.toString() + " for " + listener.getClass().getName()); registerKey(emitter, key, listener); }, BackpressureStrategy.LATEST) - .subscribeOn(Schedulers.computation()); + .subscribeOn(SchedulerProvider.computation()); } /** @@ -136,7 +138,7 @@ public void onFailure(@NonNull DJIError djiError) { DJILog.e(TAG, "Failure getting key " + key.toString() + ". " + djiError.getDescription()); } }); - }); + }).subscribeOn(SchedulerProvider.computation()); } /** @@ -182,7 +184,7 @@ public void onFailure(@NonNull DJIError djiError) { emitter.onError(new UXSDKError(djiError)); } }); - }); + }).subscribeOn(SchedulerProvider.computation()); } /** @@ -203,16 +205,17 @@ public Completable performAction(@NonNull final DJIKey key, final Object... argu getKeyManager().performAction(key, new ActionCallback() { @Override public void onSuccess() { - DJILog.d(TAG, "performed action"); + DJILog.d(TAG, "Performed action for key " + key.toString()); emitter.onComplete(); } @Override public void onFailure(@NonNull DJIError djiError) { + DJILog.e(TAG, "Failure performing action key " + key.toString() + ". " + djiError.getDescription()); emitter.onError(new UXSDKError(djiError)); } }, arguments); - }); + }).subscribeOn(SchedulerProvider.computation()); } /** @@ -229,6 +232,43 @@ public boolean isKeySupported(DJIKey key) { return false; } + /** + * Create a lens key or camera key, depending on whether the product has multi lens support. + * + * @param keyName A valid CameraKey name + * @param componentIndex The index of the camera component. + * @param subComponentIndex The index of the camera sub-component. + * @return A camera key. + */ + @NonNull + public CameraKey createLensKey(@NonNull String keyName, + @IntRange(from = 0, to = MAX_COMPONENT_INDEX) int componentIndex, + @IntRange(from = 0, to = MAX_COMPONENT_INDEX) int subComponentIndex) { + boolean isMultiLensCameraSupported = false; + if (getCacheValue(CameraKey.create(CameraKey.IS_MULTI_LENS_CAMERA_SUPPORTED, componentIndex)) != null) { + isMultiLensCameraSupported = (boolean) getCacheValue(CameraKey.create(CameraKey.IS_MULTI_LENS_CAMERA_SUPPORTED, componentIndex)); + } + if (!isMultiLensCameraSupported) { + String displayName = ""; + if (getCacheValue(CameraKey.create(CameraKey.DISPLAY_NAME)) != null) { + displayName = (String) getCacheValue(CameraKey.create(CameraKey.DISPLAY_NAME)); + } + if (Camera.DisplayNameXT2_VL.equals(displayName) || + Camera.DisplayNameMavic2EnterpriseDual_VL.equals(displayName)) { + if (subComponentIndex == Camera.XT2_IR_CAMERA_INDEX) { + return CameraKey.create(keyName, subComponentIndex); + } else { + return CameraKey.create(keyName, componentIndex); + } + } else { + return CameraKey.create(keyName, componentIndex); + } + } else { + return CameraKey.createLensKey(keyName, componentIndex, subComponentIndex); + } + } + //endregion + //region Class Helpers @Nullable private KeyManager getKeyManager() { @@ -293,7 +333,7 @@ private IllegalStateException getKeyManagerException() { return new IllegalStateException("KeyManager is not available yet"); } - //region Constructors + //region Constructor private static class SingletonHolder { private static DJISDKModel instance = new DJISDKModel(); } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/SchedulerProvider.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/SchedulerProvider.kt similarity index 55% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/SchedulerProvider.java rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/SchedulerProvider.kt index 1310a34d..8bf51f4a 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/SchedulerProvider.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/SchedulerProvider.kt @@ -21,50 +21,38 @@ * */ -package dji.ux.beta.core.base; +package dji.ux.beta.core.base - -import androidx.annotation.NonNull; - -import dji.thirdparty.io.reactivex.Scheduler; -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers; -import dji.thirdparty.io.reactivex.schedulers.Schedulers; +import dji.thirdparty.io.reactivex.Scheduler +import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers +import dji.thirdparty.io.reactivex.schedulers.Schedulers /** - * Class to be used for getting schedulers + * A singleton class is used throughout the UXSDK for getting schedulers. + * The class provides an option to inject custom schedulers using [SchedulerProviderInterface] */ -public class SchedulerProvider implements SchedulerProviderInterface { - - private static SchedulerProvider schedulerProvider; - - private SchedulerProvider() { - //private constructor +object SchedulerProvider { + + /** + * Custom scheduler to be used instead of default schedulers + */ + @JvmStatic + @Volatile + var scheduler: SchedulerProviderInterface? = null + + @JvmStatic + fun io(): Scheduler { + return scheduler?.io() ?: Schedulers.io() } - public static SchedulerProvider getInstance() { - synchronized (SchedulerProvider.class) { - if (schedulerProvider == null) { - schedulerProvider = new SchedulerProvider(); - } - } - return schedulerProvider; + @JvmStatic + fun computation(): Scheduler { + return scheduler?.computation() ?: Schedulers.computation() } - @Override - @NonNull - public Scheduler io() { - return Schedulers.io(); + @JvmStatic + fun ui(): Scheduler { + return scheduler?.ui() ?: AndroidSchedulers.mainThread() } - @Override - @NonNull - public Scheduler computation() { - return Schedulers.computation(); - } - - @Override - @NonNull - public Scheduler ui() { - return AndroidSchedulers.mainThread(); - } -} +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/WidgetModel.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/WidgetModel.java index 69d85308..5f96b344 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/WidgetModel.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/WidgetModel.java @@ -23,6 +23,7 @@ package dji.ux.beta.core.base; +import androidx.annotation.CheckResult; import androidx.annotation.NonNull; import java.util.ArrayList; @@ -36,8 +37,8 @@ import dji.thirdparty.io.reactivex.disposables.CompositeDisposable; import dji.thirdparty.io.reactivex.disposables.Disposable; import dji.thirdparty.io.reactivex.functions.Consumer; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; -import dji.ux.beta.core.base.uxsdkkeys.UXKey; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.communication.UXKey; import dji.ux.beta.core.util.DataProcessor; import dji.ux.beta.core.util.RxUtil; @@ -64,10 +65,12 @@ public abstract class WidgetModel { private CompositeDisposable keyDisposables; private CompositeDisposable compositeDisposable; private Disposable timerDisposable; + private List moduleList = new ArrayList<>(); //endregion //region Default Constructor - public WidgetModel(@NonNull DJISDKModel djiSdkModel, @NonNull ObservableInMemoryKeyedStore uxKeyManager) { + public WidgetModel(@NonNull DJISDKModel djiSdkModel, + @NonNull ObservableInMemoryKeyedStore uxKeyManager) { this.djiSdkModel = djiSdkModel; pendingKeys = new ArrayList<>(); this.uxKeyManager = uxKeyManager; @@ -77,6 +80,16 @@ public WidgetModel(@NonNull DJISDKModel djiSdkModel, @NonNull ObservableInMemory //region Lifecycle + protected void addModule(@NonNull BaseModule baseModule) { + if (isStarted()) { + throw new IllegalStateException("WidgetModel is already setup. Modules should" + + " be added during initialization."); + } + if (!moduleList.contains(baseModule)) { + moduleList.add(baseModule); + } + } + /** * Set up the widget model by initializing all the required resources */ @@ -88,6 +101,9 @@ public void setup() { compositeDisposable = new CompositeDisposable(); initializeConnection(); inSetup(); + for (BaseModule module : moduleList) { + module.setup(this); + } if (!djiSdkModel.isAvailable()) { startPendingKeysTimer(); } @@ -111,6 +127,9 @@ public void cleanup() { djiSdkModel.removeListener(this); + for (BaseModule module : moduleList) { + module.cleanup(); + } inCleanup(); } @@ -225,6 +244,7 @@ protected void bindDataProcessor(@NonNull UXKey key, * @param message Message to be logged * @return Throwable consumer */ + @CheckResult protected Consumer logErrorConsumer(@NonNull String tag, @NonNull String message) { return RxUtil.logErrorConsumer(tag, message); } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/BarPanelWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/BarPanelWidget.kt similarity index 99% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/BarPanelWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/BarPanelWidget.kt index 815e2d78..d8ed77e0 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/BarPanelWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/BarPanelWidget.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base.panelwidget +package dji.ux.beta.core.base.panel import android.annotation.SuppressLint import android.content.Context @@ -32,9 +32,9 @@ import androidx.annotation.IntRange import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.Guideline import androidx.core.content.res.use -import dji.ux.beta.R +import dji.ux.beta.core.R import dji.ux.beta.core.base.WidgetSizeDescription -import dji.ux.beta.core.base.panelwidget.BarPanelWidget.BarPanelWidgetOrientation +import dji.ux.beta.core.base.panel.BarPanelWidget.BarPanelWidgetOrientation import dji.ux.beta.core.extension.getDimensionAndUse import dji.ux.beta.core.extension.getFloatAndUse import dji.ux.beta.core.extension.getIntAndUse @@ -184,7 +184,7 @@ abstract class BarPanelWidget @JvmOverloads constructor( private val midGuideline = Guideline(context) //endregion - //region Constructors + //region Constructor init { check(this.panelWidgetConfiguration.isBarPanelWidget()) { "PanelWidgetConfiguration.panelWidgetType should " + diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/FreeFormPanelWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/FreeFormPanelWidget.kt new file mode 100644 index 00000000..3c0e162a --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/FreeFormPanelWidget.kt @@ -0,0 +1,703 @@ +/* + * 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.ux.beta.core.base.panel + +import android.content.Context +import android.graphics.Color +import android.util.AttributeSet +import android.view.View +import android.widget.TextView +import androidx.annotation.ColorInt +import androidx.constraintlayout.widget.ConstraintSet +import androidx.core.view.contains +import dji.ux.beta.core.R +import dji.ux.beta.core.extension.getColor +import dji.ux.beta.core.extension.hide +import dji.ux.beta.core.extension.show +import dji.ux.beta.core.util.ViewIDGenerator +import kotlin.random.Random + +/** + * Base class for [FreeFormPanelWidget]. + * A [FreeFormPanelWidget] is a panel containing splittable panes. + * Out of the box the panel contains only one pane. + * Based on the requirement, a pane can be split into rows or columns with proportion of choice. + * Each pane can hold one view or widget and can be split only once. If a pane containing a view or + * a widget is split, the view/widget will be removed permanently before splitting the pane. + * Use [FreeFormPanelWidget.splitPane] to split a pane. + * A pane which has been split can be unified by merging its children. If any child of the pane to + * be merged contains a view or a widget, the view/widget will be removed permanently. + * Use [FreeFormPanelWidget.mergeChildren] to merge the children of a pane. + * Use [FreeFormPanelWidget.mergeSiblings] to merge the siblings of a pane. + * + * The class provides helper methods to add, remove and get view/widget in a pane. + * + */ +abstract class FreeFormPanelWidget @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + configuration: PanelWidgetConfiguration +) : PanelWidget( + context, + attrs, + defStyleAttr, + configuration) { + + //region Fields + private val paneMap: HashMap = hashMapOf() + + private var isLabelAssistEnabled: Boolean = false + + private var isBackgroundAssistEnabled: Boolean = false + + /** + * Color of the text for in debug labels. + */ + var debugTextColor: Int = getColor(R.color.uxsdk_white) + + /** + * Color of the background of debug labels. + */ + var debugTextBackgroundColor = getColor(R.color.uxsdk_black) + + /** + * ID of root pane. + */ + val rootID: Int = ViewIDGenerator.generateViewId() + + /** + * List of the currently visible pane IDs. + */ + val listOfViewIds: List + get() = paneMap.filter { !it.value.isSplit }.keys.toList() + //endregion + + //region Lifecycle + init { + check(panelWidgetConfiguration.panelWidgetType == PanelWidgetType.FREE_FORM) { + "PanelWidgetConfiguration.panelWidgetType should be PanelWidgetType.FREE_FORM" + } + val defaultView = View(context) + addView(defaultView, childCount) + defaultView.id = rootID + defaultView.setBackgroundColor(getColor(R.color.uxsdk_yellow)) + val constraintSet = ConstraintSet() + constraintSet.clone(this) + constraintSet.constrainHeight(rootID, 0) + constraintSet.constrainWidth(rootID, 0) + constraintSet.constraintToParentStart(defaultView) + constraintSet.constraintToParentEnd(defaultView) + constraintSet.constraintToParentBottom(defaultView) + constraintSet.constraintToParentTop(defaultView) + constraintSet.applyTo(this) + paneMap[rootID] = Pane(id = rootID, parentId = -1, background = defaultView) + } + + override fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { + // Empty method + } + + override fun initPanelWidget(context: Context, attrs: AttributeSet?, defStyleAttr: Int, widgetConfiguration: PanelWidgetConfiguration?) { + // No implementation + } + + + @Throws(UnsupportedOperationException::class) + override fun getWidget(index: Int): PanelItem? = + throw UnsupportedOperationException(("This call is not supported. " + + "Try findViewPane instead")) + + @Throws(UnsupportedOperationException::class) + override fun addWidgets(items: Array) = + throw UnsupportedOperationException(("This call is not supported.")) + + @Throws(UnsupportedOperationException::class) + override fun size(): Int = + throw UnsupportedOperationException(("This call is not supported.")) + + @Throws(UnsupportedOperationException::class) + override fun addWidget(index: Int, item: PanelItem) = + throw UnsupportedOperationException(("This call is not supported. " + + "Try addView instead")) + + @Throws(UnsupportedOperationException::class) + override fun removeWidget(index: Int): PanelItem? = + throw UnsupportedOperationException(("This call is not supported. " + + "Try remove view instead")) + + @Throws(UnsupportedOperationException::class) + override fun updateUI() = + throw UnsupportedOperationException(("This call is not supported.")) + + override fun removeAllWidgets() { + for ((_, pane) in paneMap) { + removeView(pane.view) + pane.view = null + } + } + //endregion + + //region Panel Customizations + /** + * Split pane into multiple panes. + * + * @param paneId - the ID of the pane that needs to be split. + * @param splitType - instance of [SplitType] representing the axis to use for the splits. + * @param proportions - float array representing the proportions in which the pane should be. + * split. The sum of the proportions should not exceed 1. If the sum is less + * than 1 the balance will be added to the last pane. + * @return List of integer IDs of the newly created panes. + */ + fun splitPane(paneId: Int, splitType: SplitType, proportions: Array): List { + // Pane has already been split or pane does not exist + val parentPane = paneMap[paneId] + if (parentPane == null || parentPane.isSplit) { + return emptyList() + } + + // Pane cannot be split into less than 2 parts + if (proportions.size < 2) { + return emptyList() + } + + val sum = proportions.sum() + + // Sum of proportions cannot exceed 1 which is the total size + if (sum > 1f) { + return emptyList() + } + + // Remove all the debug labels + removeDebugViews() + + // If a view / widget exists in the current pane, remove it + if (parentPane.view != null) { + removeView(paneId) + } + + val set = ConstraintSet() + set.clone(this) + + val childrenViewList = arrayListOf() + val childrenIdList = IntArray(proportions.size) + + val weights = proportions.toFloatArray() + + // Loop over number of proportions and create child elements + proportions.forEachIndexed { index, _ -> + val childView = View(context) + childView.id = ViewIDGenerator.generateViewId() + childrenIdList[index] = childView.id + childrenViewList.add(childView) + addView(childView, childCount) + val childPane = Pane(id = childView.id, parentId = parentPane.id, background = childView) + paneMap[childView.id] = childPane + if (splitType == SplitType.HORIZONTAL) { + // If Split type is horizontal make height 100% + set.constrainPercentHeight(childrenViewList[index].id, 1f) + set.connect(childView.id, ConstraintSet.TOP, paneId, ConstraintSet.TOP) + set.connect(childView.id, ConstraintSet.BOTTOM, paneId, ConstraintSet.BOTTOM) + } else { + // If Split type is vertical make width 100% + set.constrainPercentWidth(childrenViewList[index].id, 1f) + set.connect(childView.id, ConstraintSet.LEFT, paneId, ConstraintSet.LEFT) + set.connect(childView.id, ConstraintSet.RIGHT, paneId, ConstraintSet.RIGHT) + } + } + + // Adjust the last pane proportion to compensate for the left over space. + proportions[proportions.lastIndex] = 1.0f - sum + proportions[proportions.lastIndex] + + if (splitType == SplitType.HORIZONTAL) { + set.createHorizontalChain(paneId, + ConstraintSet.LEFT, + paneId, + ConstraintSet.RIGHT, + childrenIdList, + weights, + ConstraintSet.CHAIN_SPREAD) + } else { + set.createVerticalChain(paneId, + ConstraintSet.TOP, + paneId, + ConstraintSet.BOTTOM, + childrenIdList, + weights, + ConstraintSet.CHAIN_SPREAD) + } + set.applyTo(this) + + // Assign the list of children to the pane + parentPane.childrenIdList = childrenIdList.toList() + parentPane.isSplit = true + + // Assign the list of children as siblings to each other + childrenIdList.forEach { childId -> paneMap[childId]?.siblingIdList = childrenIdList.toList() } + + //Add debug labels if debugging is enabled + addDebugViews(isLabelAssistEnabled, isBackgroundAssistEnabled) + return childrenIdList.toList() + + } + + /** + * The method addView installs a descendant of [View] into the designated pane, + * removing the existing view already there. + * + * @param paneId - the ID of the pane to which view should be added. + * @param widgetView - the instance of widget or view to be added to the pane. + * @param position - instance of [ViewAlignment] representing the position of the view in the pane. + * @param leftMargin - Optional left margin for the widget or view. Defaults to 0. + * @param topMargin - Optional top margin for the widget or view. Defaults to 0. + * @param rightMargin - Optional right margin for the widget or view. Defaults to 0. + * @param bottomMargin - Optional bottom margin for the widget or view. Defaults to 0. + */ + fun addView(paneId: Int, widgetView: View, position: ViewAlignment = ViewAlignment.CENTER, + leftMargin: Int = 0, topMargin: Int = 0, rightMargin: Int = 0, bottomMargin: Int = 0) { + val pane = paneMap[paneId] ?: return + removeView(paneId) + if (widgetView.id == -1) { + widgetView.id = ViewIDGenerator.generateViewId() + } + addView(widgetView, childCount) + setViewAlignment(paneId, widgetView, position, leftMargin, topMargin, rightMargin, bottomMargin) + pane.view = widgetView + pane.position = position + } + + /** + * Remove the view in a pane. + * + * @param - ID of the pane from which the view should be removed. + */ + fun removeView(paneId: Int) { + val pane = paneMap[paneId] ?: return + val viewToRemove = pane.view ?: return + val set = ConstraintSet() + set.clone(this) + set.clear(viewToRemove.id) + set.applyTo(this) + if (contains(viewToRemove)) { + removeView(viewToRemove) + } + pane.view = null + } + + /** + * The method returns the integer ID of the pane in which the given view in + * installed or -1. + */ + fun findViewPane(view: View): Int { + val key = paneMap.filter { it.value.view == view }.keys.firstOrNull() + return key ?: -1 + } + + /** + * The method returns the view installed in the given pane ID. + */ + fun viewInPane(paneId: Int): View? { + return paneMap[paneId]?.view + } + + /** + * Get the list of siblings of a pane. + */ + fun getSiblingList(paneId: Int): List { + val list = paneMap[paneId]?.siblingIdList + return list ?: emptyList() + } + + /** + * Get the list of children of a pane. + */ + fun getChildrenList(paneId: Int): List { + val list = paneMap[paneId]?.childrenIdList + return list ?: emptyList() + } + + /** + * Get the parent id of a pane. + */ + fun getParentId(paneId: Int): Int { + val parentId = paneMap[paneId]?.parentId + return parentId ?: -1 + } + + /** + * The method enablePaneDebug activates or deactivates some visual debugging tools + * for the Freeform panel. These tools can help you quickly debug how your panes are + * appearing and where, and how large they are. + * + * + * @param enableLabelAssist - Turns debugging PaneIDs on or off in each pane. + * @param enableBackgroundAssist - This optional boolean sets the background color of panes + * using a rotating list of colors to help visualize where + * each pane displays. Defaults to off. + * @param textColor - This optional parameter sets the UIColor of the text label for each pane + * created after the assist is turned on. Defaults to white. + * @param textBackgroundColor - This optional parameter sets the UIColor to use as the + * background color of the text label. Defaults to black. + */ + fun enablePaneDebug(enableLabelAssist: Boolean, enableBackgroundAssist: Boolean = false, + @ColorInt textColor: Int = INVALID_COLOR, @ColorInt textBackgroundColor: Int = INVALID_COLOR) { + if (textColor != INVALID_COLOR) { + debugTextColor = textColor + } + if (textBackgroundColor != INVALID_COLOR) { + debugTextBackgroundColor = textBackgroundColor + } + + isLabelAssistEnabled = enableLabelAssist + isBackgroundAssistEnabled = enableBackgroundAssist + removeDebugViews() + addDebugViews(isLabelAssistEnabled, isBackgroundAssistEnabled) + } + + /** + * Call method mergeChildren to merge all the children of a pane back to form the original pane. + * This deletes all the children and re-exposes the parent pane. + * + * @param paneId - The paneID to be revealed after removing the child panes. + */ + fun mergeChildren(paneId: Int) { + removeDebugViews() + mergePaneChildren(paneId) + addDebugViews(isLabelAssistEnabled, isBackgroundAssistEnabled) + } + + /** + * Call method mergeSiblings to merge all the siblings of a pane back to form the parent pane. + * This deletes all the siblings and re-exposes the parent pane. + * + * @param paneId - The paneID to be deleted along with its siblings. + */ + fun mergeSiblings(paneId: Int) { + val pane = paneMap[paneId] ?: return + mergeChildren(pane.parentId) + } + + /** + * Get the [ViewAlignment] used by the pane to align its content. + */ + fun getPanePositioning(paneId: Int): ViewAlignment? { + return paneMap[paneId]?.position + } + + /** + * Set the [ViewAlignment] to a pane to align its content. + * + * @param paneId - the ID of the pane to which the position should be set. + * @param position - instance of [ViewAlignment] representing the position of the view in the pane. + * @param leftMargin - Optional left margin for the widget or view. Defaults to 0. + * @param topMargin - Optional top margin for the widget or view. Defaults to 0. + * @param rightMargin - Optional right margin for the widget or view. Defaults to 0. + * @param bottomMargin + */ + fun setPanePosition(paneId: Int, position: ViewAlignment, + leftMargin: Int = 0, topMargin: Int = 0, + rightMargin: Int = 0, bottomMargin: Int = 0) { + val pane = paneMap[paneId] ?: return + val widgetView = pane.view ?: return + pane.position = position + setViewAlignment(paneId, widgetView, position, leftMargin, topMargin, rightMargin, bottomMargin) + } + + /** + * Set the background color of the pane. + */ + fun setPaneBackgroundColor(paneId: Int, @ColorInt color: Int) { + val pane = paneMap[paneId] ?: return + pane.backgroundColor = color + pane.background.setBackgroundColor(color) + } + + /** + * Set the background color of the pane. + */ + fun setPaneVisibility(paneId: Int, isVisible: Boolean) { + val pane = paneMap[paneId] ?: return + if (isVisible) { + pane.background.show() + } else { + pane.background.hide() + } + val view = pane.view ?: return + if (isVisible) { + view.show() + } else { + view.hide() + } + } + + //endregion + + //region Helpers + private fun setViewAlignment(paneId: Int, widgetView: View, position: ViewAlignment, + leftMargin: Int, topMargin: Int, rightMargin: Int, bottomMargin: Int) { + val set = ConstraintSet() + set.clone(this) + set.clear(widgetView.id) + set.setMargin(widgetView.id, ConstraintSet.LEFT, leftMargin) + set.setMargin(widgetView.id, ConstraintSet.TOP, topMargin) + set.setMargin(widgetView.id, ConstraintSet.RIGHT, rightMargin) + set.setMargin(widgetView.id, ConstraintSet.BOTTOM, bottomMargin) + when (position) { + ViewAlignment.CENTER -> { + set.connect(widgetView.id, ConstraintSet.LEFT, paneId, ConstraintSet.LEFT) + set.connect(widgetView.id, ConstraintSet.RIGHT, paneId, ConstraintSet.RIGHT) + set.connect(widgetView.id, ConstraintSet.TOP, paneId, ConstraintSet.TOP) + set.connect(widgetView.id, ConstraintSet.BOTTOM, paneId, ConstraintSet.BOTTOM) + } + ViewAlignment.TOP -> { + set.constrainHeight(widgetView.id, ConstraintSet.WRAP_CONTENT) + set.constrainWidth(widgetView.id, 0) + set.connect(widgetView.id, ConstraintSet.LEFT, paneId, ConstraintSet.LEFT) + set.connect(widgetView.id, ConstraintSet.RIGHT, paneId, ConstraintSet.RIGHT) + set.connect(widgetView.id, ConstraintSet.TOP, paneId, ConstraintSet.TOP) + } + ViewAlignment.BOTTOM -> { + set.constrainHeight(widgetView.id, ConstraintSet.WRAP_CONTENT) + set.constrainWidth(widgetView.id, 0) + set.connect(widgetView.id, ConstraintSet.LEFT, paneId, ConstraintSet.LEFT) + set.connect(widgetView.id, ConstraintSet.RIGHT, paneId, ConstraintSet.RIGHT) + set.connect(widgetView.id, ConstraintSet.BOTTOM, paneId, ConstraintSet.BOTTOM) + } + ViewAlignment.LEFT -> { + set.constrainHeight(widgetView.id, 0) + set.constrainWidth(widgetView.id, ConstraintSet.WRAP_CONTENT) + set.connect(widgetView.id, ConstraintSet.LEFT, paneId, ConstraintSet.LEFT) + set.connect(widgetView.id, ConstraintSet.TOP, paneId, ConstraintSet.TOP) + set.connect(widgetView.id, ConstraintSet.BOTTOM, paneId, ConstraintSet.BOTTOM) + } + ViewAlignment.RIGHT -> { + set.constrainHeight(widgetView.id, 0) + set.constrainWidth(widgetView.id, ConstraintSet.WRAP_CONTENT) + set.connect(widgetView.id, ConstraintSet.RIGHT, paneId, ConstraintSet.RIGHT) + set.connect(widgetView.id, ConstraintSet.TOP, paneId, ConstraintSet.TOP) + set.connect(widgetView.id, ConstraintSet.BOTTOM, paneId, ConstraintSet.BOTTOM) + } + ViewAlignment.LEFT_TOP -> { + set.constrainHeight(widgetView.id, ConstraintSet.WRAP_CONTENT) + set.constrainWidth(widgetView.id, ConstraintSet.WRAP_CONTENT) + set.connect(widgetView.id, ConstraintSet.LEFT, paneId, ConstraintSet.LEFT) + set.connect(widgetView.id, ConstraintSet.TOP, paneId, ConstraintSet.TOP) + } + ViewAlignment.LEFT_BOTTOM -> { + set.constrainHeight(widgetView.id, ConstraintSet.WRAP_CONTENT) + set.constrainWidth(widgetView.id, ConstraintSet.WRAP_CONTENT) + set.connect(widgetView.id, ConstraintSet.LEFT, paneId, ConstraintSet.LEFT) + set.connect(widgetView.id, ConstraintSet.BOTTOM, paneId, ConstraintSet.BOTTOM) + } + ViewAlignment.RIGHT_TOP -> { + set.constrainHeight(widgetView.id, ConstraintSet.WRAP_CONTENT) + set.constrainWidth(widgetView.id, ConstraintSet.WRAP_CONTENT) + set.connect(widgetView.id, ConstraintSet.RIGHT, paneId, ConstraintSet.RIGHT) + set.connect(widgetView.id, ConstraintSet.TOP, paneId, ConstraintSet.TOP) + } + ViewAlignment.RIGHT_BOTTOM -> { + set.constrainHeight(widgetView.id, ConstraintSet.WRAP_CONTENT) + set.constrainWidth(widgetView.id, ConstraintSet.WRAP_CONTENT) + set.connect(widgetView.id, ConstraintSet.RIGHT, paneId, ConstraintSet.RIGHT) + set.connect(widgetView.id, ConstraintSet.BOTTOM, paneId, ConstraintSet.BOTTOM) + } + } + set.applyTo(this) + } + + private fun mergePaneChildren(paneId: Int) { + val pane = paneMap[paneId] ?: return + if (!pane.isSplit) return + + for (childId in pane.childrenIdList) { + // Call function recursively to delete all children + mergePaneChildren(childId) + val childPane = paneMap[childId] ?: continue + val set = ConstraintSet() + set.clone(this) + // Remove widget / view from the pane + val widgetView = childPane.view + if (widgetView != null) { + set.clear(widgetView.id) + removeView(widgetView) + } + // Remove debug text view from the pane + val debugTextView = childPane.debugTextView + if (debugTextView != null) { + set.clear(debugTextView.id) + removeView(debugTextView) + childPane.debugTextView = null + } + set.clear(childPane.id) + // Remove Pane + removeView(childPane.background) + childPane.siblingIdList = emptyList() + paneMap.remove(childPane.id) + set.applyTo(this) + } + pane.childrenIdList = emptyList() + pane.isSplit = false + } + + private fun addDebugViews(isLabelAssist: Boolean = false, + isBackgroundAssist: Boolean = false) { + val visiblePaneMap = paneMap.filter { !it.value.isSplit } + for ((_, pane) in visiblePaneMap) { + // If background assist is enabled apply random color to view background + if (isBackgroundAssist) { + val color: Int = Color.argb(255, + Random.nextInt(256), + Random.nextInt(256), + Random.nextInt(256)) + pane.background.setBackgroundColor(color) + } + // If label assist is enabled add debug label to text view + if (isLabelAssist) { + if (pane.debugTextView == null) { + val debugTextView = TextView(context) + debugTextView.id = ViewIDGenerator.generateViewId() + debugTextView.text = pane.id.toString() + debugTextView.setTextColor(debugTextColor) + debugTextView.setBackgroundColor(debugTextBackgroundColor) + addView(debugTextView, childCount) + val set = ConstraintSet() + set.clone(this) + set.connect(debugTextView.id, ConstraintSet.LEFT, pane.background.id, ConstraintSet.LEFT) + set.connect(debugTextView.id, ConstraintSet.TOP, pane.background.id, ConstraintSet.TOP) + set.applyTo(this) + pane.debugTextView = debugTextView + } + } + } + } + + + private fun removeDebugViews() { + val visiblePaneMap = paneMap.filter { !it.value.isSplit } + for ((_, pane) in visiblePaneMap) { + // If no color was applied prior to debug make background null + if (pane.backgroundColor == INVALID_COLOR) { + pane.background.background = null + } else { + pane.background.setBackgroundColor(pane.backgroundColor) + } + val debugTextView = pane.debugTextView + // Remove debug text view if present + if (debugTextView != null) { + val set = ConstraintSet() + set.clone(this) + set.clear(debugTextView.id) + set.applyTo(this) + if (contains(debugTextView)) { + removeView(debugTextView) + } + pane.debugTextView = null + } + } + } + + //endregion + + /** + * View placement positions in a pane. + */ + enum class ViewAlignment { + /** + * Center of the pane. + */ + CENTER, + + /** + * Top edge of the pane horizontally centered. + */ + TOP, + + /** + * Bottom edge of the pane horizontally centered. + */ + BOTTOM, + + /** + * Left edge of the pane vertically centered. + */ + LEFT, + + /** + * Right edge of the pane vertically centered. + */ + RIGHT, + + /** + * Top left corner of the pane. + */ + LEFT_TOP, + + /** + * Bottom left corner of the pane. + */ + LEFT_BOTTOM, + + /** + * Top right corner of the pane. + */ + RIGHT_TOP, + + /** + * Bottom right corner of the pane. + */ + RIGHT_BOTTOM + + } + + /** + * Pane class for caching pane data. + * + */ + private data class Pane(val id: Int, + val parentId: Int, + val background: View, + var backgroundColor: Int = INVALID_COLOR, + var isSplit: Boolean = false, + var view: View? = null, + var position: ViewAlignment? = null, + var childrenIdList: List = emptyList(), + var siblingIdList: List = emptyList(), + var debugTextView: TextView? = null) + + /** + * Enum to define types of split. + */ + enum class SplitType { + /** + * Pane will be split into columns + */ + HORIZONTAL, + + /** + * Pane will be split into rows + */ + VERTICAL + } + +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/ListPanelWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/ListPanelWidget.kt similarity index 98% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/ListPanelWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/ListPanelWidget.kt index c07a775a..2122517b 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/ListPanelWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/ListPanelWidget.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base.panelwidget +package dji.ux.beta.core.base.panel import android.content.Context import android.util.AttributeSet @@ -128,7 +128,7 @@ abstract class ListPanelWidget @JvmOverloads constructor( smartListModel?.setUp() listPanelWidgetBaseModel.widgetList - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { updateUI() } } } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/ListPanelWidgetBaseModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/ListPanelWidgetBaseModel.kt similarity index 98% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/ListPanelWidgetBaseModel.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/ListPanelWidgetBaseModel.kt index 58e34482..89578215 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/ListPanelWidgetBaseModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/ListPanelWidgetBaseModel.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base.panelwidget +package dji.ux.beta.core.base.panel import android.view.View import androidx.annotation.IntRange diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/Navigation.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/Navigation.kt similarity index 99% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/Navigation.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/Navigation.kt index d6d343b6..1348cefc 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/Navigation.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/Navigation.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base.panelwidget +package dji.ux.beta.core.base.panel import android.annotation.SuppressLint import android.content.Context @@ -34,7 +34,7 @@ import androidx.annotation.AnimRes import androidx.core.content.res.use import dji.thirdparty.io.reactivex.Flowable import dji.thirdparty.io.reactivex.processors.BehaviorProcessor -import dji.ux.beta.R +import dji.ux.beta.core.R import dji.ux.beta.core.extension.addListener import dji.ux.beta.core.extension.getResourceIdAndUse import java.util.* diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/PanelItem.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/PanelItem.kt similarity index 95% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/PanelItem.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/PanelItem.kt index d29b0630..131d05fd 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/PanelItem.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/PanelItem.kt @@ -21,13 +21,13 @@ * */ -package dji.ux.beta.core.base.panelwidget +package dji.ux.beta.core.base.panel import android.view.View import androidx.annotation.IntRange -import dji.ux.beta.core.base.ConstraintLayoutWidget -import dji.ux.beta.core.base.FrameLayoutWidget import dji.ux.beta.core.base.WidgetSizeDescription +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget +import dji.ux.beta.core.base.widget.FrameLayoutWidget /** * Encapsulates a View with properties used to define how it is laid out. diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/PanelWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/PanelWidget.kt similarity index 99% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/PanelWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/PanelWidget.kt index 25381d82..d33b5b18 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/PanelWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/PanelWidget.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base.panelwidget +package dji.ux.beta.core.base.panel import android.annotation.SuppressLint import android.content.Context @@ -39,8 +39,8 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.core.content.res.use import dji.thirdparty.io.reactivex.Flowable import dji.thirdparty.io.reactivex.processors.PublishProcessor -import dji.ux.beta.R -import dji.ux.beta.core.base.ConstraintLayoutWidget +import dji.ux.beta.core.R +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget import dji.ux.beta.core.extension.* import dji.ux.beta.core.util.DisplayUtil import dji.ux.beta.core.util.ViewIDGenerator @@ -234,7 +234,7 @@ abstract class PanelWidget @JvmOverloads constructor( } //endregion - //region Constructors + //region Constructor override fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { // Nothing to do } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/PanelWidgetConfiguration.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/PanelWidgetConfiguration.kt similarity index 97% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/PanelWidgetConfiguration.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/PanelWidgetConfiguration.kt index ea5733c7..58cf14b7 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/PanelWidgetConfiguration.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/PanelWidgetConfiguration.kt @@ -21,12 +21,12 @@ * */ -package dji.ux.beta.core.base.panelwidget +package dji.ux.beta.core.base.panel import android.content.Context import androidx.annotation.DimenRes import androidx.annotation.Px -import dji.ux.beta.R +import dji.ux.beta.core.R /** * Configuration properties to initialize a panel. diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/PanelWidgetType.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/PanelWidgetType.kt similarity index 73% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/PanelWidgetType.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/PanelWidgetType.kt index 896f6040..801dca2f 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/PanelWidgetType.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/PanelWidgetType.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base.panelwidget +package dji.ux.beta.core.base.panel import androidx.annotation.IntRange @@ -43,13 +43,36 @@ enum class PanelWidgetType(val value: Int) { /** * Vertical list type for [ListPanelWidget] */ - LIST(2); + LIST(2), + + /** + * A custom structure formed using [FreeFormPanelWidget] + */ + FREE_FORM(3), + + /** + * A custom structure formed using [ToolbarPanelWidget] + */ + TOOLBAR_LEFT(4), + + /** + * A custom structure formed using [ToolbarPanelWidget] + */ + TOOLBAR_RIGHT(5), + + /** + * A custom structure formed using [ToolbarPanelWidget] + */ + TOOLBAR_TOP(6); companion object { + @JvmStatic + val values = values() + /** * Find a [PanelWidgetType] from an int value. */ @JvmStatic - fun find(@IntRange(from = 0, to = 2) index: Int): PanelWidgetType = values()[index] + fun find(@IntRange(from = 0, to = 3) index: Int): PanelWidgetType = values[index] } } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/SmartListModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/SmartListModel.kt similarity index 91% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/SmartListModel.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/SmartListModel.kt index f3d89d22..1ae9ac6e 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panelwidget/SmartListModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/SmartListModel.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base.panelwidget +package dji.ux.beta.core.base.panel import android.content.Context import android.util.AttributeSet @@ -29,14 +29,14 @@ import android.view.View import androidx.annotation.IntRange import dji.common.product.Model import dji.thirdparty.io.reactivex.Flowable -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers +import dji.ux.beta.core.base.SchedulerProvider import dji.thirdparty.io.reactivex.disposables.CompositeDisposable import dji.thirdparty.io.reactivex.disposables.Disposable import dji.thirdparty.io.reactivex.functions.Consumer import dji.thirdparty.io.reactivex.processors.PublishProcessor import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.panelwidget.systemstatus.SmartListInternalModel +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.panel.systemstatus.SmartListInternalModel import dji.ux.beta.core.util.RxUtil /** @@ -61,7 +61,7 @@ typealias WidgetID = String abstract class SmartListModel @JvmOverloads constructor( protected val context: Context, private val attrs: AttributeSet? = null, - private val blacklist: Set? = null) { + private val excludedItems: Set? = null) { //region Properties /** @@ -119,10 +119,15 @@ abstract class SmartListModel @JvmOverloads constructor( buildAndInstallWidgets(defaultActiveWidgetSet) compositeDisposable = CompositeDisposable() widgetModel.setup() + addDisposable(widgetModel.productConnection + .observeOn(SchedulerProvider.ui()) + .subscribe(Consumer { onProductConnectionChanged(it) }, + RxUtil.logErrorConsumer("SmartListModel", "Error on Product changed. "))) addDisposable(widgetModel.aircraftModel - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { onAircraftModelChanged(it) }, RxUtil.logErrorConsumer("SmartListModel", "Error on Aircraft Model Changed. "))) + inSetUp() } @@ -146,6 +151,11 @@ abstract class SmartListModel @JvmOverloads constructor( */ protected abstract fun inCleanUp() + /** + * Setup the list based product connection + */ + protected abstract fun onProductConnectionChanged(isConnected: Boolean) + /** * Setup the list based on the connected aircraft */ @@ -279,7 +289,7 @@ abstract class SmartListModel @JvmOverloads constructor( listPanelWidgetBaseModel?.addWidgets(activeWidgetList) } - private fun WidgetID.isNotBlacklisted(): Boolean = blacklist == null || !blacklist.contains(this) + private fun WidgetID.isNotExcluded(): Boolean = excludedItems == null || !excludedItems.contains(this) private fun buildActiveWidgetList(newActiveWidgetIDs: Set) { // Prevent widgetIDs that were not originally registered @@ -287,7 +297,7 @@ abstract class SmartListModel @JvmOverloads constructor( // Create views if they don't exist activeWidgetSet .filter { widgetID -> - widgetID.isNotBlacklisted() && !createdWidgetsMap.containsKey(widgetID) + widgetID.isNotExcluded() && !createdWidgetsMap.containsKey(widgetID) } .map { widgetID -> val createdWidget = createWidget(widgetID) @@ -304,7 +314,7 @@ abstract class SmartListModel @JvmOverloads constructor( private fun reorderActiveWidgets() { activeWidgetList = currentOrderList .filter { widgetID -> - widgetID.isNotBlacklisted() + widgetID.isNotExcluded() && activeWidgetSet.contains(widgetID) && createdWidgetsMap.containsKey(widgetID) }.map { originalIndex -> createdWidgetsMap[originalIndex] as View } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/ListItemEditTextButtonWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/listitem/ListItemEditTextButtonWidget.kt similarity index 91% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/ListItemEditTextButtonWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/listitem/ListItemEditTextButtonWidget.kt index 0aec3458..4f8d12be 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/ListItemEditTextButtonWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/listitem/ListItemEditTextButtonWidget.kt @@ -21,20 +21,19 @@ * */ -package dji.ux.beta.core.base.widget +package dji.ux.beta.core.base.panel.listitem import android.annotation.SuppressLint import android.app.Activity import android.content.Context import android.content.res.ColorStateList import android.graphics.drawable.Drawable +import android.os.Build import android.text.Editable import android.text.InputType import android.text.TextWatcher import android.util.AttributeSet -import android.view.Gravity -import android.view.KeyEvent -import android.view.View +import android.view.* import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager import android.widget.EditText @@ -44,7 +43,8 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.core.content.res.use import dji.thirdparty.io.reactivex.Flowable import dji.thirdparty.io.reactivex.processors.PublishProcessor -import dji.ux.beta.R +import dji.ux.beta.core.R +import dji.ux.beta.core.base.panel.listitem.ListItemEditTextButtonWidget.WidgetType import dji.ux.beta.core.extension.* import dji.ux.beta.core.util.ViewIDGenerator @@ -52,29 +52,31 @@ import dji.ux.beta.core.util.ViewIDGenerator /** * This is the base class to be used for list item * The class represents the item with icon, item name and editable fields + * @property widgetType - The [WidgetType] of the current widget. */ abstract class ListItemEditTextButtonWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, - val widgetType: WidgetType -) : ListItemTitleWidget(context, attrs, defStyleAttr), View.OnClickListener { + val widgetType: WidgetType, + @StyleRes defaultStyle: Int +) : ListItemTitleWidget(context, attrs, defStyleAttr, defaultStyle), View.OnClickListener { - //region fields + //region Fields private val listItemButton: TextView = TextView(context) private val listItemHintTextView: TextView = TextView(context) private val listItemEditTextView: EditText = EditText(context) - protected val uiUpdateStateProcessor: PublishProcessor = PublishProcessor.create() + protected val uiUpdateStateProcessor: PublishProcessor = PublishProcessor.create() //endregion /** - * Get the [WidgetUIState] updates + * Get the [UIState] updates */ - open fun getUIStateUpdates(): Flowable { - return uiUpdateStateProcessor + open fun getUIStateUpdates(): Flowable { + return uiUpdateStateProcessor.onBackpressureBuffer() } /** @@ -297,12 +299,12 @@ abstract class ListItemEditTextButtonWidget @JvmOverloads constructor( } val paddingValue = resources.getDimension(R.dimen.uxsdk_pre_flight_checklist_item_padding).toInt() setContentPadding(0, paddingValue, 0, paddingValue) - attrs?.let { initAttributes(context, it) } + initAttributes(context, attrs) } @SuppressLint("Recycle") - private fun initAttributes(context: Context, attrs: AttributeSet) { - context.obtainStyledAttributes(attrs, R.styleable.ListItemEditTextButtonWidget).use { typedArray -> + private fun initAttributes(context: Context, attrs: AttributeSet?) { + context.obtainStyledAttributes(attrs, R.styleable.ListItemEditTextButtonWidget, 0, defaultStyle).use { typedArray -> typedArray.getColorAndUse(R.styleable.ListItemEditTextButtonWidget_uxsdk_list_item_edit_text_normal_color) { editTextNormalColor = it } @@ -389,7 +391,7 @@ abstract class ListItemEditTextButtonWidget @JvmOverloads constructor( listItemEditTextView.setOnClickListener(this) listItemEditTextView.setOnEditorActionListener { p0, p1, p2 -> if (isDoneActionClicked(p1, p2)) { - uiUpdateStateProcessor.onNext(WidgetUIState.EditEndsUpdate) + uiUpdateStateProcessor.onNext(UIState.EditFinished) listItemEditTextView.isCursorVisible = false hideKeyboardFrom() onKeyboardDoneAction() @@ -416,7 +418,7 @@ abstract class ListItemEditTextButtonWidget @JvmOverloads constructor( private fun initHint() { listItemHintTextView.id = ViewIDGenerator.generateViewId() listItemHintTextSize = getDimension(R.dimen.uxsdk_list_item_hint_text_size) - listItemHintTextView.gravity = Gravity.CENTER_VERTICAL or Gravity.RIGHT or Gravity.END + listItemHintTextView.gravity = Gravity.CENTER_VERTICAL or Gravity.END listItemHintTextColor = getColor(R.color.uxsdk_white_50_percent) listItemHint = getString(R.string.uxsdk_string_default_value) } @@ -555,17 +557,28 @@ abstract class ListItemEditTextButtonWidget @JvmOverloads constructor( override fun onClick(view: View?) { super.onClick(view) if (view == listItemButton) { - uiUpdateStateProcessor.onNext(WidgetUIState.ButtonClick) + uiUpdateStateProcessor.onNext(UIState.ButtonClicked) onButtonClick() } else if (view == listItemEditTextView) { - uiUpdateStateProcessor.onNext(WidgetUIState.EditBeginsUpdate) + uiUpdateStateProcessor.onNext(UIState.EditStarted) listItemEditTextView.isCursorVisible = true } } @CallSuper override fun onListItemClick() { - uiUpdateStateProcessor.onNext(WidgetUIState.ListItemClick) + uiUpdateStateProcessor.onNext(UIState.ListItemClicked) + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + // Android 11 is calling onAttached and onDetached when keyboard is shown. + // This disposes the disposables mid action. To avoid misbehavior this is a + // temporary fix. + if (context is Activity && Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) { + val window: Window = (context as Activity).window + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING) + } } private fun isDoneActionClicked(actionId: Int, keyEvent: KeyEvent?): Boolean { @@ -603,42 +616,47 @@ abstract class ListItemEditTextButtonWidget @JvmOverloads constructor( /** * Widget UI update State */ - sealed class WidgetUIState { + sealed class UIState { /** * List Item click update */ - object ListItemClick : WidgetUIState() + object ListItemClicked : UIState() /** * Button click update */ - object ButtonClick : WidgetUIState() + object ButtonClicked : UIState() /** * Update when user clicks edit text to modify * value */ - object EditBeginsUpdate : WidgetUIState() + object EditStarted : UIState() /** * Update when user finishes edit action */ - object EditEndsUpdate : WidgetUIState() + object EditFinished : UIState() /** * Dialog shown update */ - data class DialogDisplayed(val info: Any?) : WidgetUIState() + data class DialogDisplayed(val info: Any?) : UIState() /** * Dialog action confirm */ - data class DialogActionConfirm(val info: Any?) : WidgetUIState() + data class DialogActionConfirmed(val info: Any?) : UIState() + + /** + * Dialog action cancel + */ + data class DialogActionCanceled(val info: Any?) : UIState() /** * Dialog action dismiss */ - data class DialogActionDismiss(val info: Any?) : WidgetUIState() + data class DialogDismissed(val info: Any?) : UIState() } /** diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/ListItemLabelButtonWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/listitem/ListItemLabelButtonWidget.kt similarity index 92% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/ListItemLabelButtonWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/listitem/ListItemLabelButtonWidget.kt index ae559a82..688ca993 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/ListItemLabelButtonWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/listitem/ListItemLabelButtonWidget.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base.widget +package dji.ux.beta.core.base.panel.listitem import android.annotation.SuppressLint import android.content.Context @@ -36,35 +36,37 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.core.content.res.use import dji.thirdparty.io.reactivex.Flowable import dji.thirdparty.io.reactivex.processors.PublishProcessor -import dji.ux.beta.R +import dji.ux.beta.core.R import dji.ux.beta.core.extension.* import dji.ux.beta.core.util.ViewIDGenerator /** * This is the base class to be used for display * type of list item + * @property widgetType - The [WidgetType] of the current widget. */ abstract class ListItemLabelButtonWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, - val widgetType: WidgetType -) : ListItemTitleWidget(context, attrs, defStyleAttr), View.OnClickListener { + val widgetType: WidgetType, + defaultStyle: Int +) : ListItemTitleWidget(context, attrs, defStyleAttr, defaultStyle), View.OnClickListener { - //region fields + //region Fields private val listItemButton: TextView = TextView(context) private val listItemLabelTextView: TextView = TextView(context) - protected val uiUpdateStateProcessor: PublishProcessor = PublishProcessor.create() + protected val uiUpdateStateProcessor: PublishProcessor = PublishProcessor.create() //endregion //region label customizations /** - * Get the [WidgetUIState] updates + * Get the [UIState] updates */ - open fun getUIStateUpdates(): Flowable { - return uiUpdateStateProcessor + open fun getUIStateUpdates(): Flowable { + return uiUpdateStateProcessor.onBackpressureBuffer() } /** @@ -215,7 +217,7 @@ abstract class ListItemLabelButtonWidget @JvmOverloads constructor( @SuppressLint("Recycle") private fun initAttributes(context: Context, attrs: AttributeSet) { - context.obtainStyledAttributes(attrs, R.styleable.ListItemLabelButtonWidget).use { typedArray -> + context.obtainStyledAttributes(attrs, R.styleable.ListItemLabelButtonWidget, 0, defaultStyle).use { typedArray -> typedArray.getResourceIdAndUse(R.styleable.ListItemLabelButtonWidget_uxsdk_list_item_label_appearance) { setListItemLabelTextAppearance(it) } @@ -267,7 +269,7 @@ abstract class ListItemLabelButtonWidget @JvmOverloads constructor( private fun initLabel() { listItemLabelTextView.id = ViewIDGenerator.generateViewId() - listItemLabelTextView.gravity = Gravity.CENTER_VERTICAL or Gravity.RIGHT or Gravity.END + listItemLabelTextView.gravity = Gravity.CENTER_VERTICAL or Gravity.END listItemLabelTextColors = resources.getColorStateList(R.color.uxsdk_selector_text_color) listItemLabelTextSize = getDimension(R.dimen.uxsdk_list_item_label_text_size) listItemLabel = getString(R.string.uxsdk_string_default_value) @@ -376,14 +378,14 @@ abstract class ListItemLabelButtonWidget @JvmOverloads constructor( override fun onClick(view: View?) { super.onClick(view) if (view == listItemButton) { - uiUpdateStateProcessor.onNext(WidgetUIState.ButtonClick) + uiUpdateStateProcessor.onNext(UIState.ButtonClicked) onButtonClick() } } @CallSuper override fun onListItemClick() { - uiUpdateStateProcessor.onNext(WidgetUIState.ListItemClick) + uiUpdateStateProcessor.onNext(UIState.ListItemClicked) } abstract fun onButtonClick() @@ -391,31 +393,36 @@ abstract class ListItemLabelButtonWidget @JvmOverloads constructor( /** * Widget UI update State */ - sealed class WidgetUIState { + sealed class UIState { /** * List Item click update */ - object ListItemClick : WidgetUIState() + object ListItemClicked : UIState() /** * Button click update */ - object ButtonClick : WidgetUIState() + object ButtonClicked : UIState() /** * Dialog shown update */ - data class DialogDisplayed(val info: Any?) : WidgetUIState() + data class DialogDisplayed(val info: Any?) : UIState() /** * Dialog action confirm */ - data class DialogActionConfirm(val info: Any?) : WidgetUIState() + data class DialogActionConfirmed(val info: Any?) : UIState() + + /** + * Dialog action cancel + */ + data class DialogActionCanceled(val info: Any?) : UIState() /** * Dialog action dismiss */ - data class DialogActionDismiss(val info: Any?) : WidgetUIState() + data class DialogDismissed(val info: Any?) : UIState() } /** diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/ListItemRadioButtonWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/listitem/ListItemRadioButtonWidget.kt similarity index 88% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/ListItemRadioButtonWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/listitem/ListItemRadioButtonWidget.kt index abc15a8c..9a1477aa 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/ListItemRadioButtonWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/listitem/ListItemRadioButtonWidget.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base.widget +package dji.ux.beta.core.base.panel.listitem import android.annotation.SuppressLint import android.content.Context @@ -38,7 +38,7 @@ import androidx.core.content.res.use import androidx.core.view.get import dji.thirdparty.io.reactivex.Flowable import dji.thirdparty.io.reactivex.processors.PublishProcessor -import dji.ux.beta.R +import dji.ux.beta.core.R import dji.ux.beta.core.extension.* import dji.ux.beta.core.util.ViewIDGenerator @@ -49,10 +49,12 @@ import dji.ux.beta.core.util.ViewIDGenerator abstract class ListItemRadioButtonWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : ListItemTitleWidget(context, attrs, defStyleAttr), RadioGroup.OnCheckedChangeListener { + defStyleAttr: Int = 0, + defaultStyle: Int +) : ListItemTitleWidget(context, attrs, defStyleAttr, defaultStyle), + RadioGroup.OnCheckedChangeListener { - protected val uiUpdateStateProcessor: PublishProcessor = PublishProcessor.create() + protected val uiUpdateStateProcessor: PublishProcessor = PublishProcessor.create() private val radioGroup: RadioGroup = RadioGroup(context) @@ -124,17 +126,17 @@ abstract class ListItemRadioButtonWidget @JvmOverloads constructor( val optionCount: Int = radioGroup.childCount /** - * Get the [WidgetUIState] updates + * Get the [UIState] updates */ - fun getUIStateUpdates(): Flowable { - return uiUpdateStateProcessor + fun getUIStateUpdates(): Flowable { + return uiUpdateStateProcessor.onBackpressureBuffer() } init { radioGroup.id = ViewIDGenerator.generateViewId() radioGroup.orientation = LinearLayout.HORIZONTAL radioGroup.setOnCheckedChangeListener(this) - radioGroup.gravity = Gravity.CENTER_VERTICAL or Gravity.RIGHT or Gravity.END + radioGroup.gravity = Gravity.CENTER_VERTICAL or Gravity.END val layoutParams = LayoutParams(0, ConstraintSet.WRAP_CONTENT) layoutParams.rightToLeft = clickIndicatorId layoutParams.topToTop = guidelineTop.id @@ -144,12 +146,12 @@ abstract class ListItemRadioButtonWidget @JvmOverloads constructor( addView(radioGroup) val paddingValue = resources.getDimension(R.dimen.uxsdk_pre_flight_checklist_item_padding).toInt() setContentPadding(0, paddingValue, 0, paddingValue) - attrs?.let { initAttributes(context, it) } + initAttributes(context, attrs) } @SuppressLint("Recycle") - private fun initAttributes(context: Context, attrs: AttributeSet) { - context.obtainStyledAttributes(attrs, R.styleable.ListItemRadioButtonWidget).use { typedArray -> + private fun initAttributes(context: Context, attrs: AttributeSet?) { + context.obtainStyledAttributes(attrs, R.styleable.ListItemRadioButtonWidget, 0, defaultStyle).use { typedArray -> typedArray.getResourceIdAndUse(R.styleable.ListItemRadioButtonWidget_uxsdk_option_text_appearance) { setOptionTextAppearance(it) } @@ -222,7 +224,7 @@ abstract class ListItemRadioButtonWidget @JvmOverloads constructor( if (radioGroup[i].id == radioButtonId) { val radioButton: RadioButton? = findViewById(radioButtonId) if (radioButton != null) { - uiUpdateStateProcessor.onNext(WidgetUIState.OptionTapped(i, + uiUpdateStateProcessor.onNext(UIState.OptionSelected(i, radioButton.text.toString())) onOptionTapped(i, radioButton.text.toString()) } @@ -233,7 +235,7 @@ abstract class ListItemRadioButtonWidget @JvmOverloads constructor( } override fun onListItemClick() { - uiUpdateStateProcessor.onNext(WidgetUIState.ListItemClick) + uiUpdateStateProcessor.onNext(UIState.ListItemClicked) } override fun setEnabled(enabled: Boolean) { @@ -267,16 +269,31 @@ abstract class ListItemRadioButtonWidget @JvmOverloads constructor( /** * Widget UI update State */ - sealed class WidgetUIState { + sealed class UIState { /** * List Item click update */ - object ListItemClick : WidgetUIState() + object ListItemClicked : UIState() /** * Option click update */ - data class OptionTapped(val optionIndex: Int, val optionLabel: String) : WidgetUIState() + data class OptionSelected(val optionIndex: Int, val optionLabel: String) : UIState() + + /** + * Dialog shown update + */ + data class DialogDisplayed(val info: Any?) : UIState() + + /** + * Dialog action dismiss + */ + data class DialogDismissed(val info: Any?) : UIState() + + /** + * Never show again checkbox checked + */ + data class NeverShowAgainCheckChanged(val isChecked: Boolean) : UIState() } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/ListItemSwitchWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/listitem/ListItemSwitchWidget.kt similarity index 82% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/ListItemSwitchWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/listitem/ListItemSwitchWidget.kt index a4fe6ce9..f3e92280 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/ListItemSwitchWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/listitem/ListItemSwitchWidget.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base.widget +package dji.ux.beta.core.base.panel.listitem import android.annotation.SuppressLint import android.content.Context @@ -32,8 +32,9 @@ import android.widget.Switch import androidx.annotation.DrawableRes import androidx.constraintlayout.widget.ConstraintSet import androidx.core.content.res.use +import dji.thirdparty.io.reactivex.Flowable import dji.thirdparty.io.reactivex.processors.PublishProcessor -import dji.ux.beta.R +import dji.ux.beta.core.R import dji.ux.beta.core.extension.getDrawable import dji.ux.beta.core.extension.getDrawableAndUse import dji.ux.beta.core.util.ViewIDGenerator @@ -45,12 +46,14 @@ import dji.ux.beta.core.util.ViewIDGenerator abstract class ListItemSwitchWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : ListItemTitleWidget(context, attrs, defStyleAttr), CompoundButton.OnCheckedChangeListener { + defStyleAttr: Int = 0, + defaultStyle: Int +) : ListItemTitleWidget(context, attrs, defStyleAttr, defaultStyle), + CompoundButton.OnCheckedChangeListener { - //region fields + //region Fields private val listItemSwitch: Switch = Switch(context) - protected val uiUpdateStateProcessor: PublishProcessor = PublishProcessor.create() + protected val uiUpdateStateProcessor: PublishProcessor = PublishProcessor.create() /** * The icon used for thumb for list item switch @@ -72,12 +75,12 @@ abstract class ListItemSwitchWidget @JvmOverloads constructor( configureSwitchWidget() val paddingValue = resources.getDimension(R.dimen.uxsdk_pre_flight_checklist_item_padding).toInt() setContentPadding(0, paddingValue, 0, paddingValue) - attrs?.let { initAttributes(context, it) } + initAttributes(context, attrs) } @SuppressLint("Recycle") - private fun initAttributes(context: Context, attrs: AttributeSet) { - context.obtainStyledAttributes(attrs, R.styleable.ListItemSwitchWidget).use { typedArray -> + private fun initAttributes(context: Context, attrs: AttributeSet?) { + context.obtainStyledAttributes(attrs, R.styleable.ListItemSwitchWidget, 0, defaultStyle).use { typedArray -> typedArray.getDrawableAndUse(R.styleable.ListItemSwitchWidget_uxsdk_list_item_switch_background) { switchBackground = it } @@ -111,13 +114,13 @@ abstract class ListItemSwitchWidget @JvmOverloads constructor( override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) { if (buttonView == listItemSwitch) { - uiUpdateStateProcessor.onNext(WidgetUIState.SwitchToggle) + uiUpdateStateProcessor.onNext(UIState.SwitchChanged) onSwitchToggle(isChecked) } } override fun onListItemClick() { - uiUpdateStateProcessor.onNext(WidgetUIState.ListItemClick) + uiUpdateStateProcessor.onNext(UIState.ListItemClicked) } override fun setEnabled(enabled: Boolean) { @@ -160,35 +163,46 @@ abstract class ListItemSwitchWidget @JvmOverloads constructor( switchBackground = getDrawable(resourceId) } + /** + * Get the [UIState] updates + */ + open fun getUIStateUpdates(): Flowable { + return uiUpdateStateProcessor.onBackpressureBuffer() + } /** * Widget UI update State */ - sealed class WidgetUIState { + sealed class UIState { /** * List Item click update */ - object ListItemClick : WidgetUIState() + object ListItemClicked : UIState() /** * Button click update */ - object SwitchToggle : WidgetUIState() + object SwitchChanged : UIState() /** * Dialog shown update */ - data class DialogDisplayed(val info: Any?) : WidgetUIState() + data class DialogDisplayed(val info: Any?) : UIState() /** * Dialog action confirm */ - data class DialogActionConfirm(val info: Any?) : WidgetUIState() + data class DialogActionConfirmed(val info: Any?) : UIState() + + /** + * Dialog action cancel + */ + data class DialogActionCanceled(val info: Any?) : UIState() /** * Dialog action dismiss */ - data class DialogActionDismiss(val info: Any?) : WidgetUIState() + data class DialogDismissed(val info: Any?) : UIState() } } \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/ListItemTitleWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/listitem/ListItemTitleWidget.kt similarity index 96% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/ListItemTitleWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/listitem/ListItemTitleWidget.kt index 77b795e4..86a3708f 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/ListItemTitleWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/panel/listitem/ListItemTitleWidget.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base.widget +package dji.ux.beta.core.base.panel.listitem import android.annotation.SuppressLint import android.content.Context @@ -35,8 +35,8 @@ import android.widget.TextView import androidx.annotation.* import androidx.constraintlayout.widget.Guideline import androidx.core.content.res.use -import dji.ux.beta.R -import dji.ux.beta.core.base.ConstraintLayoutWidget +import dji.ux.beta.core.R +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget import dji.ux.beta.core.extension.* import dji.ux.beta.core.util.ViewIDGenerator import kotlin.math.roundToInt @@ -45,11 +45,14 @@ import kotlin.math.roundToInt /** * This is the base class used for list item. The class represents * the item title and item icon. + * @property defaultStyle - Resource id for tyle used for defining the default setup + * of the widget. */ abstract class ListItemTitleWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, - defStyleAttr: Int = 0 + defStyleAttr: Int = 0, + @StyleRes protected val defaultStyle: Int ) : ConstraintLayoutWidget(context, attrs, defStyleAttr), View.OnClickListener { private val listItemTitleTextView = findViewById(R.id.text_view_list_item_title) @@ -225,12 +228,12 @@ abstract class ListItemTitleWidget @JvmOverloads constructor( setOnClickListener(this) val padding = (getDimension(R.dimen.uxsdk_pre_flight_checklist_item_padding)).roundToInt() setPadding(padding, 0, padding, 0) - attrs?.let { initAttributes(context, it) } + initAttributes(context, attrs) } @SuppressLint("Recycle") - private fun initAttributes(context: Context, attrs: AttributeSet) { - context.obtainStyledAttributes(attrs, R.styleable.ListItemTitleWidget).use { typedArray -> + private fun initAttributes(context: Context, attrs: AttributeSet?) { + context.obtainStyledAttributes(attrs, R.styleable.ListItemTitleWidget,0, defaultStyle).use { typedArray -> typedArray.getResourceIdAndUse(R.styleable.ListItemTitleWidget_uxsdk_list_item_title_appearance) { setListItemTitleTextAppearance(it) } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/BaseTelemetryWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/BaseTelemetryWidget.kt new file mode 100644 index 00000000..b8e9fc4a --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/BaseTelemetryWidget.kt @@ -0,0 +1,655 @@ +/* + * 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.ux.beta.core.base.widget + +import android.annotation.SuppressLint +import android.content.Context +import android.content.res.ColorStateList +import android.content.res.TypedArray +import android.graphics.PorterDuff +import android.graphics.drawable.Drawable +import android.text.TextPaint +import android.util.AttributeSet +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.* +import androidx.constraintlayout.widget.ConstraintSet +import androidx.constraintlayout.widget.Guideline +import androidx.core.content.res.use +import dji.ux.beta.core.R +import dji.ux.beta.core.base.widget.BaseTelemetryWidget.WidgetType +import dji.ux.beta.core.extension.* +import dji.ux.beta.core.util.UnitConversionUtil +import dji.ux.beta.core.util.ViewIDGenerator +import java.text.DecimalFormat + +/** + * Base class for telemetry widgets + * @property widgetType - The [WidgetType] for the widget. + * @property widgetTheme - Resource id for styling the widget. + * @property defaultStyle - Resource id for style used for defining the default setup + * of the widget. + */ +abstract class BaseTelemetryWidget @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + val widgetType: WidgetType, + protected val widgetTheme: Int = 0, + @StyleRes protected val defaultStyle: Int +) : ConstraintLayoutWidget(context, attrs, defStyleAttr) { + + private val guidelineLeft: Guideline = findViewById(R.id.guideline_left) + private val guidelineTop: Guideline = findViewById(R.id.guideline_top) + private val guidelineRight: Guideline = findViewById(R.id.guideline_right) + private val guidelineBottom: Guideline = findViewById(R.id.guideline_bottom) + private val labelTextView: TextView = findViewById(R.id.text_view_label) + private val valueTextView: TextView = findViewById(R.id.text_view_value) + private val unitTextView: TextView = findViewById(R.id.text_view_unit) + private val imageView: ImageView = ImageView(context) + protected abstract val metricDecimalFormat: DecimalFormat + protected abstract val imperialDecimalFormat: DecimalFormat + + //region color customizations + /** + * Color of the value is in error state + */ + @ColorInt + var errorValueColor: Int = getColor(R.color.uxsdk_red) + + /** + * Color of the value is in normal state + */ + @ColorInt + var normalValueColor: Int = getColor(R.color.uxsdk_white) + + //endregion + + + //region widget customizations + /** + * Left padding of the list item content + */ + var contentPaddingLeft: Int = getDimension(R.dimen.uxsdk_telemetry_item_padding).toInt() + set(value) { + field = value + guidelineLeft.setGuidelineBegin(value) + } + + /** + * Top padding of the list item content + */ + var contentPaddingTop: Int = getDimension(R.dimen.uxsdk_telemetry_item_padding).toInt() + set(value) { + field = value + guidelineTop.setGuidelineBegin(value) + } + + /** + * Right padding of the list item content + */ + var contentPaddingRight: Int = getDimension(R.dimen.uxsdk_telemetry_item_padding).toInt() + set(value) { + field = value + guidelineRight.setGuidelineEnd(value) + } + + /** + * Bottom padding of the list item content + */ + var contentPaddingBottom: Int = getDimension(R.dimen.uxsdk_telemetry_item_padding).toInt() + set(value) { + field = value + guidelineBottom.setGuidelineEnd(value) + } + //endregion + + //region image customizations + /** + * The icon for the widget + */ + var widgetIcon: Drawable? + get() = imageView.imageDrawable + set(@Nullable value) { + imageView.imageDrawable = value + } + + /** + * The color of the icon + */ + @get:ColorInt + var widgetIconColor: Int = getColor(R.color.uxsdk_white) + set(@ColorInt value) { + field = value + imageView.setColorFilter(value, PorterDuff.Mode.SRC_IN) + } + + /** + * The background of the widget icon + */ + var widgetIconBackground: Drawable? + get() = imageView.background + set(@Nullable value) { + imageView.background = value + } + + /** + * Visibility of the icon + */ + var widgetIconVisibility: Boolean + get() = imageView.visibility == View.VISIBLE + set(value) { + if (value) { + imageView.show() + } else { + imageView.hide() + } + } + //endregion + + //region label customizations + /** + * String value of label + */ + var labelString: String? + @Nullable get() = labelTextView.text.toString() + set(value) { + labelTextView.text = value + } + + /** + * Float text size of the label + */ + var labelTextSize: Float + @Dimension get() = labelTextView.textSize + set(@Dimension value) { + labelTextView.textSize = value + } + + /** + * Integer color for label + */ + var labelTextColor: Int + @ColorInt get() = labelTextView.textColor + set(@ColorInt value) { + labelTextView.textColor = value + } + + /** + * Color state list of the label + */ + var labelTextColors: ColorStateList? + get() = labelTextView.textColorStateList + set(value) { + labelTextView.textColorStateList = value + } + + /** + * Background of the label + */ + var labelBackground: Drawable? + get() = labelTextView.background + set(value) { + labelTextView.background = value + } + + /** + * Visibility of the label + */ + var labelVisibility: Boolean + get() = labelTextView.visibility == View.VISIBLE + set(value) { + if (value) { + labelTextView.show() + } else { + labelTextView.hide() + } + } + + //endregion + + //region value customizations + /** + * String for value text view + */ + var valueString: String? + @Nullable get() = valueTextView.text.toString() + set(value) { + valueTextView.text = value + } + + /** + * Float text size of the value + */ + var valueTextSize: Float + @Dimension get() = valueTextView.textSize + set(@Dimension value) { + valueTextView.textSize = value + } + + /** + * Integer color for value + */ + var valueTextColor: Int + @ColorInt get() = valueTextView.textColor + set(@ColorInt value) { + valueTextView.textColor = value + } + + /** + * Color state list of the value + */ + var valueTextColors: ColorStateList? + get() = valueTextView.textColorStateList + set(value) { + valueTextView.textColorStateList = value + } + + /** + * Background of the value + */ + var valueBackground: Drawable? + get() = valueTextView.background + set(value) { + valueTextView.background = value + } + + /** + * Visibility of the value + */ + var valueVisibility: Boolean + get() = valueTextView.visibility == View.VISIBLE + set(value) { + if (value) { + valueTextView.show() + } else { + valueTextView.hide() + } + } + + /** + * Text position of the value + */ + var valueTextGravity: Int + get() = valueTextView.gravity + set(value) { + valueTextView.gravity = value + } + + //endregion + + //region value customizations + /** + * String for unit text view + */ + var unitString: String? + @Nullable get() = unitTextView.text.toString() + set(value) { + unitTextView.text = value + } + + /** + * Float text size of the unit + */ + var unitTextSize: Float + @Dimension get() = unitTextView.textSize + set(@Dimension value) { + unitTextView.textSize = value + } + + /** + * Integer color for unit + */ + var unitTextColor: Int + @ColorInt get() = unitTextView.textColor + set(@ColorInt value) { + unitTextView.textColor = value + } + + /** + * Color state list of the unit + */ + var unitTextColors: ColorStateList? + get() = unitTextView.textColorStateList + set(value) { + unitTextView.textColorStateList = value + } + + /** + * Background of the unit + */ + var unitBackground: Drawable? + get() = unitTextView.background + set(value) { + unitTextView.background = value + } + + /** + * Visibility of the unit + */ + var unitVisibility: Boolean + get() = unitTextView.visibility == View.VISIBLE + set(value) { + if (value) { + unitTextView.show() + } else { + unitTextView.hide() + } + } + + //endregion + override fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { + View.inflate(context, R.layout.uxsdk_widget_base_telemetry, this) + } + + init { + if (widgetType == WidgetType.TEXT_IMAGE_RIGHT) { + configureRightImageTypeWidget() + } else if (widgetType == WidgetType.TEXT_IMAGE_LEFT) { + configureLeftImageTypeWidget() + } + initBaseTelemetryAttributes(context) + setValueTextViewMinWidthByText("888.8") + initAttributes(context, attrs) + } + + @SuppressLint("Recycle") + private fun initBaseTelemetryAttributes(context: Context) { + val baseTelemetryAttributeArray: IntArray = R.styleable.BaseTelemetryWidget + context.obtainStyledAttributes(widgetTheme, baseTelemetryAttributeArray).use { + initAttributesByTypedArray(it) + } + } + + @SuppressLint("Recycle") + private fun initAttributes(context: Context, attrs: AttributeSet?) { + context.obtainStyledAttributes(attrs, R.styleable.BaseTelemetryWidget, 0, defaultStyle).use { typedArray -> + initAttributesByTypedArray(typedArray) + } + } + + private fun initAttributesByTypedArray(typedArray: TypedArray) { + typedArray.getResourceIdAndUse(R.styleable.BaseTelemetryWidget_uxsdk_label_text_appearance) { + setLabelTextAppearance(it) + } + typedArray.getDimensionAndUse(R.styleable.BaseTelemetryWidget_uxsdk_label_text_size) { + labelTextSize = it + } + typedArray.getColorAndUse(R.styleable.BaseTelemetryWidget_uxsdk_label_text_color) { + labelTextColor = it + } + typedArray.getColorStateListAndUse(R.styleable.BaseTelemetryWidget_uxsdk_label_text_color) { + labelTextColors = it + } + typedArray.getDrawableAndUse(R.styleable.BaseTelemetryWidget_uxsdk_label_background) { + labelBackground = it + } + labelVisibility = typedArray.getBoolean(R.styleable.BaseTelemetryWidget_uxsdk_label_visibility, + labelVisibility) + labelString = + typedArray.getString(R.styleable.BaseTelemetryWidget_uxsdk_label_string, + getString(R.string.uxsdk_string_default_value)) + + typedArray.getResourceIdAndUse(R.styleable.BaseTelemetryWidget_uxsdk_value_text_appearance) { + setValueTextAppearance(it) + } + typedArray.getDimensionAndUse(R.styleable.BaseTelemetryWidget_uxsdk_value_text_size) { + valueTextSize = it + } + typedArray.getColorAndUse(R.styleable.BaseTelemetryWidget_uxsdk_value_text_color) { + valueTextColor = it + } + typedArray.getColorStateListAndUse(R.styleable.BaseTelemetryWidget_uxsdk_value_text_color) { + valueTextColors = it + } + typedArray.getDrawableAndUse(R.styleable.BaseTelemetryWidget_uxsdk_value_background) { + valueBackground = it + } + valueVisibility = typedArray.getBoolean(R.styleable.BaseTelemetryWidget_uxsdk_value_visibility, + valueVisibility) + typedArray.getIntegerAndUse(R.styleable.BaseTelemetryWidget_uxsdk_value_gravity) { + valueTextGravity = it + } + valueString = + typedArray.getString(R.styleable.BaseTelemetryWidget_uxsdk_value_string, + getString(R.string.uxsdk_string_default_value)) + + typedArray.getResourceIdAndUse(R.styleable.BaseTelemetryWidget_uxsdk_unit_text_appearance) { + setUnitTextAppearance(it) + } + typedArray.getDimensionAndUse(R.styleable.BaseTelemetryWidget_uxsdk_unit_text_size) { + unitTextSize = it + } + typedArray.getColorAndUse(R.styleable.BaseTelemetryWidget_uxsdk_unit_text_color) { + unitTextColor = it + } + typedArray.getColorStateListAndUse(R.styleable.BaseTelemetryWidget_uxsdk_unit_text_color) { + unitTextColors = it + } + typedArray.getDrawableAndUse(R.styleable.BaseTelemetryWidget_uxsdk_unit_background) { + unitBackground = it + } + unitVisibility = typedArray.getBoolean(R.styleable.BaseTelemetryWidget_uxsdk_unit_visibility, + unitVisibility) + unitString = + typedArray.getString(R.styleable.BaseTelemetryWidget_uxsdk_unit_string, + getString(R.string.uxsdk_string_default_value)) + + typedArray.getDrawableAndUse(R.styleable.BaseTelemetryWidget_uxsdk_widget_icon) { + widgetIcon = it + } + typedArray.getColorAndUse(R.styleable.BaseTelemetryWidget_uxsdk_widget_icon_color) { + widgetIconColor = it + } + typedArray.getDrawableAndUse(R.styleable.BaseTelemetryWidget_uxsdk_widget_icon_background) { + widgetIconBackground = it + } + widgetIconVisibility = typedArray.getBoolean(R.styleable.BaseTelemetryWidget_uxsdk_widget_icon_visibility, + widgetIconVisibility) + typedArray.getDimensionAndUse(R.styleable.BaseTelemetryWidget_uxsdk_widget_padding_left) { + contentPaddingLeft = it.toInt() + } + typedArray.getDimensionAndUse(R.styleable.BaseTelemetryWidget_uxsdk_widget_padding_top) { + contentPaddingTop = it.toInt() + } + typedArray.getDimensionAndUse(R.styleable.BaseTelemetryWidget_uxsdk_widget_padding_right) { + contentPaddingRight = it.toInt() + } + typedArray.getDimensionAndUse(R.styleable.BaseTelemetryWidget_uxsdk_widget_padding_bottom) { + contentPaddingBottom = it.toInt() + } + typedArray.getColorAndUse(R.styleable.BaseTelemetryWidget_uxsdk_normal_text_color) { + normalValueColor = it + } + typedArray.getColorAndUse(R.styleable.BaseTelemetryWidget_uxsdk_error_text_color) { + errorValueColor = it + } + } + + private fun configureLeftImageTypeWidget() { + imageView.id = ViewIDGenerator.generateViewId() + val set = ConstraintSet() + set.clone(this) + set.clear(imageView.id) + imageView.adjustViewBounds = true + set.setMargin(imageView.id, ConstraintSet.RIGHT, getDimension(R.dimen.uxsdk_telemetry_view_margin).toInt()) + set.clear(labelTextView.id, ConstraintSet.LEFT) + set.constrainHeight(imageView.id, 0) + set.setDimensionRatio(imageView.id, "1:1") + set.setHorizontalChainStyle(imageView.id, ConstraintSet.CHAIN_SPREAD) + set.connect(imageView.id, ConstraintSet.TOP, guidelineTop.id, ConstraintSet.TOP) + set.connect(imageView.id, ConstraintSet.BOTTOM, guidelineBottom.id, ConstraintSet.BOTTOM) + set.connect(imageView.id, ConstraintSet.LEFT, guidelineLeft.id, ConstraintSet.LEFT) + set.connect(imageView.id, ConstraintSet.RIGHT, labelTextView.id, ConstraintSet.LEFT) + set.connect(labelTextView.id, ConstraintSet.LEFT, imageView.id, ConstraintSet.RIGHT) + addView(imageView) + set.applyTo(this) + + } + + + private fun configureRightImageTypeWidget() { + imageView.id = ViewIDGenerator.generateViewId() + val set = ConstraintSet() + set.clone(this) + set.clear(imageView.id) + imageView.adjustViewBounds = true + set.setMargin(imageView.id, ConstraintSet.LEFT, getDimension(R.dimen.uxsdk_telemetry_view_margin).toInt()) + set.clear(unitTextView.id, ConstraintSet.RIGHT) + set.constrainHeight(imageView.id, 0) + set.setDimensionRatio(imageView.id, "1:1") + set.setHorizontalChainStyle(imageView.id, ConstraintSet.CHAIN_SPREAD) + set.connect(imageView.id, ConstraintSet.TOP, guidelineTop.id, ConstraintSet.TOP) + set.connect(imageView.id, ConstraintSet.BOTTOM, guidelineBottom.id, ConstraintSet.BOTTOM) + set.connect(imageView.id, ConstraintSet.RIGHT, guidelineRight.id, ConstraintSet.RIGHT) + set.connect(imageView.id, ConstraintSet.LEFT, unitTextView.id, ConstraintSet.RIGHT) + set.connect(unitTextView.id, ConstraintSet.RIGHT, imageView.id, ConstraintSet.LEFT) + addView(imageView) + set.applyTo(this) + + } + + protected fun setValueTextViewMinWidthByText(maxText: String) { + val textPaint = TextPaint() + textPaint.textSize = valueTextView.textSize + valueTextView.minWidth = textPaint.measureText(maxText).toInt() + } + + protected fun getDecimalFormat(unitType: UnitConversionUtil.UnitType): DecimalFormat { + return if (unitType == UnitConversionUtil.UnitType.IMPERIAL) { + imperialDecimalFormat + } else { + metricDecimalFormat + } + } + + /** + * Set the icon + * + * @param resourceId Integer ID of the background resource + */ + fun setIcon(@DrawableRes resourceId: Int) { + widgetIcon = getDrawable(resourceId) + } + + /** + * Set the background of the icon + * + * @param resourceId Integer ID of the background resource + */ + fun setIconBackground(@DrawableRes resourceId: Int) { + widgetIconBackground = getDrawable(resourceId) + } + + /** + * Set the background of the label + * + * @param resourceId Integer ID of the background resource + */ + fun setLabelBackground(@DrawableRes resourceId: Int) { + labelBackground = getDrawable(resourceId) + } + + /** + * Set the text appearance of the label + * + * @param textAppearanceResId Style resource for text appearance + */ + fun setLabelTextAppearance(@StyleRes textAppearanceResId: Int) { + labelTextView.setTextAppearance(context, textAppearanceResId) + } + + /** + * Set the background of the value + * + * @param resourceId Integer ID of the background resource + */ + fun setValueBackground(@DrawableRes resourceId: Int) { + valueBackground = getDrawable(resourceId) + } + + /** + * Set the text appearance of the value + * + * @param textAppearanceResId Style resource for text appearance + */ + fun setValueTextAppearance(@StyleRes textAppearanceResId: Int) { + valueTextView.setTextAppearance(context, textAppearanceResId) + } + + /** + * Set the background of the unit + * + * @param resourceId Integer ID of the background resource + */ + fun setUnitBackground(@DrawableRes resourceId: Int) { + unitBackground = getDrawable(resourceId) + } + + /** + * Set the text appearance of the unit + * + * @param textAppearanceResId Style resource for text appearance + */ + fun setUnitTextAppearance(@StyleRes textAppearanceResId: Int) { + unitTextView.setTextAppearance(context, textAppearanceResId) + } + + /** + * Set padding to the content of the item + */ + fun setContentPadding(left: Int, top: Int, right: Int, bottom: Int) { + contentPaddingLeft = left + contentPaddingTop = top + contentPaddingRight = right + contentPaddingBottom = bottom + } + + + /** + * Defines the type of widget + */ + enum class WidgetType { + + /** + * The type represents + * | LABEL | VALUE | UNIT | + */ + TEXT, + + /** + * The class represents the with icon, + * | IMAGE | LABEL | VALUE | UNIT | + */ + TEXT_IMAGE_LEFT, + + /** + * The class represents the with icon, + * | LABEL | VALUE | UNIT | IMAGE | + */ + TEXT_IMAGE_RIGHT + } + +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/ConstraintLayoutWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/ConstraintLayoutWidget.kt similarity index 97% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/ConstraintLayoutWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/ConstraintLayoutWidget.kt index 012a3fbd..0ac383c3 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/ConstraintLayoutWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/ConstraintLayoutWidget.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base +package dji.ux.beta.core.base.widget import android.content.Context import android.util.AttributeSet @@ -32,6 +32,7 @@ import dji.thirdparty.io.reactivex.disposables.CompositeDisposable import dji.thirdparty.io.reactivex.disposables.Disposable import dji.thirdparty.io.reactivex.functions.Consumer import dji.thirdparty.io.reactivex.processors.PublishProcessor +import dji.ux.beta.core.base.WidgetSizeDescription import dji.ux.beta.core.util.RxUtil /** @@ -54,7 +55,7 @@ abstract class ConstraintLayoutWidget @JvmOverloads constructor( //endregion - //region Constructors + //region Constructor init { initView(context, attrs, defStyleAttr) } @@ -158,12 +159,10 @@ abstract class ConstraintLayoutWidget @JvmOverloads constructor( * * @return update with widget state */ - open fun getWidgetStateUpdate(): Flowable = widgetStateDataProcessor + open fun getWidgetStateUpdate(): Flowable = widgetStateDataProcessor.onBackpressureBuffer() companion object { private const val TAG = "ConstraintLayoutWidget" - const val DISABLE_ALPHA = 0.38f - const val ENABLE_ALPHA = 1.0f const val INVALID_RESOURCE = -1 const val INVALID_COLOR = 0 const val INVALID_DIMENSION = 0f diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/FrameLayoutWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/FrameLayoutWidget.kt similarity index 96% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/FrameLayoutWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/FrameLayoutWidget.kt index edee6ec6..e4537db0 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/FrameLayoutWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/FrameLayoutWidget.kt @@ -21,16 +21,18 @@ * */ -package dji.ux.beta.core.base +package dji.ux.beta.core.base.widget import android.content.Context import android.util.AttributeSet import android.widget.FrameLayout +import androidx.annotation.CheckResult import dji.thirdparty.io.reactivex.Flowable import dji.thirdparty.io.reactivex.disposables.CompositeDisposable import dji.thirdparty.io.reactivex.disposables.Disposable import dji.thirdparty.io.reactivex.functions.Consumer import dji.thirdparty.io.reactivex.processors.PublishProcessor +import dji.ux.beta.core.base.WidgetSizeDescription import dji.ux.beta.core.util.RxUtil /** @@ -54,7 +56,7 @@ abstract class FrameLayoutWidget @JvmOverloads constructor( //endregion - //region Constructors + //region Constructor init { initView(context, attrs, defStyleAttr) } @@ -150,6 +152,7 @@ abstract class FrameLayoutWidget @JvmOverloads constructor( * @param message Message to be logged * @return Throwable consumer */ + @CheckResult protected fun logErrorConsumer(tag: String, message: String): Consumer { return RxUtil.logErrorConsumer(tag, message) } @@ -159,7 +162,7 @@ abstract class FrameLayoutWidget @JvmOverloads constructor( * * @return update with widget state */ - open fun getWidgetStateUpdate(): Flowable = widgetStateDataProcessor + open fun getWidgetStateUpdate(): Flowable = widgetStateDataProcessor.onBackpressureBuffer() //endregion companion object { diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/IconButtonWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/IconButtonWidget.kt index b0229143..447a05dd 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/IconButtonWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/widget/IconButtonWidget.kt @@ -35,20 +35,23 @@ import androidx.annotation.DrawableRes import androidx.core.content.res.use import dji.thirdparty.io.reactivex.Flowable import dji.thirdparty.io.reactivex.processors.PublishProcessor -import dji.ux.beta.R -import dji.ux.beta.core.base.ConstraintLayoutWidget +import dji.ux.beta.core.R import dji.ux.beta.core.extension.* +/** + * Abstract class that represents a widget with a single Image View. + * The class provides functionality and customizations for widgets to reuse + */ abstract class IconButtonWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : ConstraintLayoutWidget(context, attrs, defStyleAttr), View.OnClickListener { - //region fields + //region Fields protected val foregroundImageView: ImageView = findViewById(R.id.image_view_button) - protected val uiUpdateStateProcessor: PublishProcessor = PublishProcessor.create() + protected val uiUpdateStateProcessor: PublishProcessor = PublishProcessor.create() /** * The color of the icon when the product is connected @@ -109,7 +112,7 @@ abstract class IconButtonWidget @JvmOverloads constructor( @CallSuper override fun onClick(view: View?) { - uiUpdateStateProcessor.onNext(WidgetUIState.WidgetClick) + uiUpdateStateProcessor.onNext(UIState.WidgetClicked) } override fun getIdealDimensionRatioString(): String { @@ -126,39 +129,44 @@ abstract class IconButtonWidget @JvmOverloads constructor( } /** - * Get the [WidgetUIState] updates + * Get the [UIState] updates */ - fun getUIStateUpdates(): Flowable { - return uiUpdateStateProcessor + fun getUIStateUpdates(): Flowable { + return uiUpdateStateProcessor.onBackpressureBuffer() } /** * Widget UI update State */ - sealed class WidgetUIState { + sealed class UIState { /** * Widget click update */ - object WidgetClick : WidgetUIState() + object WidgetClicked : UIState() /** * Dialog shown update */ - data class DialogDisplayed(val info: Any?) : WidgetUIState() + data class DialogDisplayed(val info: Any?) : UIState() /** * Dialog action confirm */ - data class DialogActionConfirm(val info: Any?) : WidgetUIState() + data class DialogActionConfirmed(val info: Any?) : UIState() + + /** + * Dialog action dismiss + */ + data class DialogDismissed(val info: Any?) : UIState() /** * Dialog action dismiss */ - data class DialogActionDismiss(val info: Any?) : WidgetUIState() + data class DialogActionCancelled(val info: Any?) : UIState() /** * Dialog checkbox interaction */ - data class DialogCheckboxCheckChanged(val info: Any?) : WidgetUIState() + data class DialogCheckboxCheckChanged(val info: Any?) : UIState() } } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/BroadcastValues.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/BroadcastValues.java similarity index 97% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/BroadcastValues.java rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/BroadcastValues.java index 9e2ccea3..c6b15467 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/BroadcastValues.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/BroadcastValues.java @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base.uxsdkkeys; +package dji.ux.beta.core.communication; import androidx.annotation.NonNull; import androidx.annotation.Nullable; diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/CameraKeys.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/CameraKeys.java similarity index 97% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/CameraKeys.java rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/CameraKeys.java index 627b5457..a5c68812 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/CameraKeys.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/CameraKeys.java @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base.uxsdkkeys; +package dji.ux.beta.core.communication; /** * Class containing all UX keys related to camera functions diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/DefaultGlobalPreferences.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/DefaultGlobalPreferences.kt similarity index 86% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/DefaultGlobalPreferences.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/DefaultGlobalPreferences.kt index 6afe7ca6..7e1cd57b 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/DefaultGlobalPreferences.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/DefaultGlobalPreferences.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base +package dji.ux.beta.core.communication import android.content.Context import android.content.SharedPreferences @@ -53,6 +53,12 @@ class DefaultGlobalPreferences(context: Context) : GlobalPreferencesInterface { UnitConversionUtil.UnitType.METRIC.value())) set(unitType) = sharedPreferences.edit { putInt(PREF_GLOBAL_UNIT_TYPE, unitType.value()) } + override var temperatureUnitType: UnitConversionUtil.TemperatureUnitType + get() = UnitConversionUtil.TemperatureUnitType.find(sharedPreferences.getInt(PREF_TEMPERATURE_UNIT_TYPE, + UnitConversionUtil.UnitType.METRIC.value())) + set(temperatureUnit) = sharedPreferences.edit { putInt(PREF_TEMPERATURE_UNIT_TYPE, temperatureUnit.value()) } + + @Suppress("INAPPLICABLE_JVM_NAME") @get:JvmName("getAFCEnabled") @set:JvmName("setAFCEnabled") @@ -90,18 +96,22 @@ class DefaultGlobalPreferences(context: Context) : GlobalPreferencesInterface { SettingDefinitions.ControlMode.SPOT_METER.value())) set(controlMode) = sharedPreferences.edit { putInt(PREF_CONTROL_MODE, controlMode.value()) } + override var isUnitModeDialogNeverShown: Boolean + get() = sharedPreferences.getBoolean(PREF_UNIT_MODE_DIALOG_NEVER_SHOWN, false) + set(neverShown) = + sharedPreferences.edit { putBoolean(PREF_UNIT_MODE_DIALOG_NEVER_SHOWN, neverShown) } + companion object { //region Constants private const val PREF_IS_AFC_ENABLED: String = "afcEnabled" private const val PREF_GLOBAL_UNIT_TYPE: String = "globalUnitType" private const val PREF_TEMPERATURE_UNIT_TYPE: String = "temperatureUnitType" private const val PREF_AIR_SENSE_TERMS_NEVER_SHOWN: String = "airSenseTerms" - private const val COLOR_WAVEFORM_ENABLED: String = "colorWaveformEnabled" - private const val COLOR_WAVEFORM_DISPLAY_STATE: String = "colorWaveformDisplayState" private const val PREF_GRID_LINE_TYPE: String = "gridLineType" private const val PREF_CENTER_POINT_TYPE: String = "centerPointType" private const val PREF_CENTER_POINT_COLOR: String = "centerPointColor" private const val PREF_CONTROL_MODE: String = "controlMode" + private const val PREF_UNIT_MODE_DIALOG_NEVER_SHOWN: String = "unitMode" private fun getSharedPreferences(context: Context): SharedPreferences = context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE) diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/FlatStore.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/FlatStore.java similarity index 98% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/FlatStore.java rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/FlatStore.java index 0de16aa9..e45ea5ba 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/FlatStore.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/FlatStore.java @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base.uxsdkkeys; +package dji.ux.beta.core.communication; import androidx.annotation.NonNull; diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/GlobalPreferenceKeys.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/GlobalPreferenceKeys.java similarity index 74% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/GlobalPreferenceKeys.java rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/GlobalPreferenceKeys.java index 919727a6..6133f525 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/GlobalPreferenceKeys.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/GlobalPreferenceKeys.java @@ -21,8 +21,9 @@ * */ -package dji.ux.beta.core.base.uxsdkkeys; +package dji.ux.beta.core.communication; +import dji.common.camera.ColorWaveformSettings; import dji.ux.beta.core.util.SettingDefinitions; import dji.ux.beta.core.util.UnitConversionUtil; @@ -36,9 +37,18 @@ public final class GlobalPreferenceKeys extends UXKeys { @UXParamKey(type = Boolean.class, updateType = UpdateType.ON_CHANGE) public static final String AFC_ENABLED = "AFCEnabled"; + @UXParamKey(type = ColorWaveformSettings.ColorWaveformDisplayState.class, updateType = UpdateType.ON_CHANGE) + public static final String COLOR_WAVEFORM_DISPLAY_STATE = "ColorWaveformDisplayState"; + + @UXParamKey(type = Boolean.class, updateType = UpdateType.ON_CHANGE) + public static final String COLOR_WAVEFORM_ENABLED = "ColorWaveformEnabled"; + @UXParamKey(type = SettingDefinitions.ControlMode.class, updateType = UpdateType.ON_CHANGE) public static final String CONTROL_MODE = "ControlMode"; + @UXParamKey(type = UnitConversionUtil.TemperatureUnitType.class, updateType = UpdateType.ON_CHANGE) + public static final String TEMPERATURE_UNIT_TYPE = "TemperatureUnitType"; + private GlobalPreferenceKeys() { super(); } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/GlobalPreferencesInterface.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/GlobalPreferencesInterface.kt similarity index 88% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/GlobalPreferencesInterface.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/GlobalPreferencesInterface.kt index 3587597e..834cfe67 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/GlobalPreferencesInterface.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/GlobalPreferencesInterface.kt @@ -21,12 +21,13 @@ * */ -package dji.ux.beta.core.base +package dji.ux.beta.core.communication import androidx.annotation.ColorInt import dji.ux.beta.core.ui.CenterPointView.CenterPointType import dji.ux.beta.core.ui.GridLineView.GridLineType import dji.ux.beta.core.util.SettingDefinitions.ControlMode +import dji.ux.beta.core.util.UnitConversionUtil.TemperatureUnitType import dji.ux.beta.core.util.UnitConversionUtil.UnitType @@ -51,6 +52,11 @@ interface GlobalPreferencesInterface { */ var unitType: UnitType + /** + * [TemperatureUnitType] value saved. + */ + var temperatureUnitType: TemperatureUnitType + /** * Boolean value indicating if AFC is enabled if saved. */ @@ -85,6 +91,11 @@ interface GlobalPreferencesInterface { * Control mode from [ControlMode] */ var controlMode: ControlMode + + /** + * Boolean value indicating if the Unit Mode dialog should never be shown. + */ + var isUnitModeDialogNeverShown: Boolean //endregion } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/GlobalPreferencesManager.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/GlobalPreferencesManager.java similarity index 98% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/GlobalPreferencesManager.java rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/GlobalPreferencesManager.java index f430a6f2..236acd28 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/GlobalPreferencesManager.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/GlobalPreferencesManager.java @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base; +package dji.ux.beta.core.communication; import androidx.annotation.NonNull; diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/MessagingKeys.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/MessagingKeys.java similarity index 97% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/MessagingKeys.java rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/MessagingKeys.java index 86caa550..ff26e84f 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/MessagingKeys.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/MessagingKeys.java @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base.uxsdkkeys; +package dji.ux.beta.core.communication; import dji.ux.beta.core.model.VoiceNotificationType; import dji.ux.beta.core.model.WarningMessage; diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/ModelValue.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/ModelValue.java similarity index 98% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/ModelValue.java rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/ModelValue.java index b37aefda..e6c02cda 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/ModelValue.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/ModelValue.java @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base.uxsdkkeys; +package dji.ux.beta.core.communication; import androidx.annotation.Nullable; diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/ObservableInMemoryKeyedStore.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/ObservableInMemoryKeyedStore.java similarity index 96% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/ObservableInMemoryKeyedStore.java rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/ObservableInMemoryKeyedStore.java index 487dbc4a..81641db3 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/ObservableInMemoryKeyedStore.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/ObservableInMemoryKeyedStore.java @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base.uxsdkkeys; +package dji.ux.beta.core.communication; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -35,7 +35,7 @@ import dji.thirdparty.io.reactivex.Flowable; import dji.thirdparty.io.reactivex.disposables.Disposable; import dji.thirdparty.io.reactivex.processors.PublishProcessor; -import dji.thirdparty.io.reactivex.schedulers.Schedulers; +import dji.ux.beta.core.base.SchedulerProvider; import dji.ux.beta.core.base.UXSDKError; import dji.ux.beta.core.base.UXSDKErrorDescription; @@ -81,7 +81,7 @@ public Flowable addObserver(@NonNull UXKey key) { processor = PublishProcessor.create(); } keyStringProcessorMap.put(key.getKeyPath(), processor); - return processor.observeOn(Schedulers.computation()).onBackpressureLatest(); + return processor.observeOn(SchedulerProvider.computation()).onBackpressureLatest(); } finally { lock.unlock(); } @@ -193,7 +193,7 @@ public Completable setValue(@NonNull UXKey key, @NonNull Object value) { } else { emitter.onError(new UXSDKError(UXSDKErrorDescription.VALUE_TYPE_MISMATCH)); } - }); + }).subscribeOn(SchedulerProvider.computation()); } finally { lock.unlock(); } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/ObservableKeyedStore.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/ObservableKeyedStore.java similarity index 98% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/ObservableKeyedStore.java rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/ObservableKeyedStore.java index c7f9e044..6c03b626 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/ObservableKeyedStore.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/ObservableKeyedStore.java @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base.uxsdkkeys; +package dji.ux.beta.core.communication; import androidx.annotation.NonNull; import androidx.annotation.Nullable; diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/OnStateChangeCallback.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/OnStateChangeCallback.kt similarity index 97% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/OnStateChangeCallback.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/OnStateChangeCallback.kt index 2b529353..4c2a9de5 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/OnStateChangeCallback.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/OnStateChangeCallback.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base +package dji.ux.beta.core.communication /** * Interface to be implemented by widgets for coupled 1:1 communication diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/UXKey.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/UXKey.java similarity index 98% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/UXKey.java rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/UXKey.java index 1bde9fed..406fab1d 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/UXKey.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/UXKey.java @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base.uxsdkkeys; +package dji.ux.beta.core.communication; import androidx.annotation.NonNull; diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/UXKeys.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/UXKeys.java similarity index 99% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/UXKeys.java rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/UXKeys.java index be8be7e7..d9b3330b 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/base/uxsdkkeys/UXKeys.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/communication/UXKeys.java @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.base.uxsdkkeys; +package dji.ux.beta.core.communication; import androidx.annotation.CheckResult; import androidx.annotation.NonNull; diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/extension/AnimationExtensions.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/extension/AnimationExtensions.kt index ddecc554..36a9ed76 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/extension/AnimationExtensions.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/extension/AnimationExtensions.kt @@ -18,7 +18,7 @@ * 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.ux.beta.core.extension diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/extension/CameraExtensions.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/extension/CameraExtensions.kt new file mode 100644 index 00000000..ed497455 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/extension/CameraExtensions.kt @@ -0,0 +1,80 @@ +/* + * 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. + * + */ + +@file:JvmName("CameraExtensions") + +package dji.ux.beta.core.extension + +import dji.common.camera.SettingsDefinitions.FlatCameraMode +import dji.common.camera.SettingsDefinitions.ShootPhotoMode + +/** + * Convert [FlatCameraMode] to [ShootPhotoMode] + */ +fun FlatCameraMode.toShootPhotoMode(): ShootPhotoMode { + return when (this) { + FlatCameraMode.PHOTO_SINGLE -> ShootPhotoMode.SINGLE + FlatCameraMode.PHOTO_HDR -> ShootPhotoMode.HDR + FlatCameraMode.PHOTO_BURST -> ShootPhotoMode.BURST + FlatCameraMode.PHOTO_AEB -> ShootPhotoMode.AEB + FlatCameraMode.PHOTO_INTERVAL -> ShootPhotoMode.INTERVAL + FlatCameraMode.PHOTO_PANORAMA -> ShootPhotoMode.PANORAMA + FlatCameraMode.PHOTO_EHDR -> ShootPhotoMode.EHDR + FlatCameraMode.PHOTO_HYPER_LIGHT -> ShootPhotoMode.HYPER_LIGHT + FlatCameraMode.PHOTO_TIME_LAPSE -> ShootPhotoMode.TIME_LAPSE + else -> ShootPhotoMode.UNKNOWN + } +} + +/** + * Convert [ShootPhotoMode] to [FlatCameraMode] + */ +fun ShootPhotoMode.toFlatCameraMode(): FlatCameraMode { + return when (this) { + ShootPhotoMode.SINGLE -> FlatCameraMode.PHOTO_SINGLE + ShootPhotoMode.HDR -> FlatCameraMode.PHOTO_HDR + ShootPhotoMode.BURST -> FlatCameraMode.PHOTO_BURST + ShootPhotoMode.AEB -> FlatCameraMode.PHOTO_AEB + ShootPhotoMode.INTERVAL -> FlatCameraMode.PHOTO_INTERVAL + ShootPhotoMode.PANORAMA -> FlatCameraMode.PHOTO_PANORAMA + ShootPhotoMode.EHDR -> FlatCameraMode.PHOTO_EHDR + ShootPhotoMode.HYPER_LIGHT -> FlatCameraMode.PHOTO_HYPER_LIGHT + ShootPhotoMode.TIME_LAPSE -> FlatCameraMode.PHOTO_TIME_LAPSE + else -> FlatCameraMode.UNKNOWN + } +} + +/** + * Check if flat camera mode is picture mode + */ +fun FlatCameraMode.isPictureMode(): Boolean { + return this == FlatCameraMode.PHOTO_TIME_LAPSE + || this == FlatCameraMode.PHOTO_AEB + || this == FlatCameraMode.PHOTO_SINGLE + || this == FlatCameraMode.PHOTO_BURST + || this == FlatCameraMode.PHOTO_HDR + || this == FlatCameraMode.PHOTO_INTERVAL + || this == FlatCameraMode.PHOTO_HYPER_LIGHT + || this == FlatCameraMode.PHOTO_PANORAMA + || this == FlatCameraMode.PHOTO_EHDR +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/extension/MathExtensions.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/extension/MathExtensions.kt index c71b0627..76396273 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/extension/MathExtensions.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/extension/MathExtensions.kt @@ -25,8 +25,37 @@ package dji.ux.beta.core.extension +import dji.ux.beta.core.util.UnitConversionUtil +import dji.ux.beta.core.util.UnitConversionUtil.UnitType /** * Convert milliVolts to Volts */ fun Float.milliVoltsToVolts(): Float = this / 1000f + +/** + * Convert velocity to appropriate value by unit + */ +fun Float.toVelocity(unitType: UnitType): Float { + return if (unitType == UnitType.IMPERIAL) { + UnitConversionUtil.convertMetersPerSecToMilesPerHr(this) + } else this +} + +/** + * Convert distance to appropriate value by unit + */ +fun Float.toDistance(unitType: UnitType): Float { + return if (unitType == UnitType.IMPERIAL) { + UnitConversionUtil.convertMetersToFeet(this) + } else this +} + +/** + * Convert distance to appropriate value by unit + */ +fun Double.toDistance(unitType: UnitType): Double { + return if (unitType == UnitType.IMPERIAL) { + UnitConversionUtil.convertMetersToFeet(this) + } else this +} diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/extension/TypedArrayExtensions.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/extension/TypedArrayExtensions.kt index 644fa50b..6e01104a 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/extension/TypedArrayExtensions.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/extension/TypedArrayExtensions.kt @@ -54,7 +54,6 @@ inline fun TypedArray.getStringAndUse(@StyleableRes index: Int, block: (Stri } } - /** * Retrieve the color value for the attribute at [index] and executes the given [block] * function with the retrieved resource. @@ -103,7 +102,6 @@ inline fun TypedArray.getDrawableAndUse(@StyleableRes index: Int, block: (Dr drawable?.let { block(drawable) } } - /** * Retrieve the int value for the attribute at [index] and executes the given [block] * function with the retrieved resource. @@ -130,7 +128,6 @@ inline fun TypedArray.getResourceIdAndUse(@StyleableRes index: Int, block: ( } } - /** * Retrieve the float value for the attribute at [index] and executes the given [block] * function with the retrieved resource. @@ -168,4 +165,20 @@ inline fun TypedArray.getBooleanAndUse(@StyleableRes index: Int, defaultValu block(booleanValue) } +/** + * Retrieve the drawable array value for the attribute at [index] and executes the given [block] + * function with the retrieved resource. + */ +inline fun TypedArray.getDrawableArrayAndUse(@StyleableRes index: Int, block: (Array) -> R) { + val arrayResourceId = getResourceId(index, INVALID_RESOURCE) + if (arrayResourceId != INVALID_RESOURCE) { + val resourceArray = resources.obtainTypedArray(arrayResourceId) + val drawableArray = Array(resourceArray.length()) { i -> + resourceArray.getDrawable(i) + } + block(drawableArray) + resourceArray.recycle() + } +} + diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/extension/ViewExtensions.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/extension/ViewExtensions.kt index 7112b6cd..5c9cf319 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/extension/ViewExtensions.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/extension/ViewExtensions.kt @@ -27,14 +27,19 @@ package dji.ux.beta.core.extension import android.content.DialogInterface import android.content.res.ColorStateList +import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable import android.view.View import android.widget.ImageView import android.widget.TextView import android.widget.Toast import androidx.annotation.* import androidx.appcompat.app.AlertDialog -import dji.ux.beta.R +import androidx.recyclerview.widget.RecyclerView +import dji.ux.beta.core.R +import dji.ux.beta.core.util.UnitConversionUtil.UnitType + /** * Get the [String] for the given [stringRes]. @@ -168,13 +173,15 @@ fun View.showAlertDialog(@StyleRes dialogTheme: Int = R.style.Theme_AppCompat_Di icon: Drawable? = null, message: String? = null, dismissButton: String? = getString(R.string.uxsdk_app_ok), - dialogClickListener: DialogInterface.OnClickListener? = null) { + dialogClickListener: DialogInterface.OnClickListener? = null, + dialogDismissListener: DialogInterface.OnDismissListener? = null) { val builder = AlertDialog.Builder(context, dialogTheme) builder.setTitle(title) builder.setIcon(icon) builder.setCancelable(isCancellable) builder.setMessage(message) builder.setPositiveButton(dismissButton, dialogClickListener) + builder.setOnDismissListener(dialogDismissListener) val dialog = builder.create() dialog.show() } @@ -197,7 +204,8 @@ fun View.showConfirmationDialog(@StyleRes dialogTheme: Int = R.style.Theme_AppCo message: String? = null, positiveButton: String? = getString(R.string.uxsdk_app_ok), negativeButton: String? = getString(R.string.uxsdk_app_cancel), - dialogClickListener: DialogInterface.OnClickListener? = null) { + dialogClickListener: DialogInterface.OnClickListener? = null, + dialogDismissListener: DialogInterface.OnDismissListener? = null) { val builder = AlertDialog.Builder(context, dialogTheme) builder.setIcon(icon) builder.setTitle(title) @@ -205,7 +213,78 @@ fun View.showConfirmationDialog(@StyleRes dialogTheme: Int = R.style.Theme_AppCo builder.setMessage(message) builder.setPositiveButton(positiveButton, dialogClickListener) builder.setNegativeButton(negativeButton, dialogClickListener) + builder.setOnDismissListener(dialogDismissListener) val dialog = builder.create() dialog.show() } +/** + * Get the unit string for velocity based on [UnitType] + */ +fun View.getVelocityString(unitType: UnitType): String { + return if (unitType == UnitType.IMPERIAL) { + getString(R.string.uxsdk_unit_mile_per_hr) + } else { + getString(R.string.uxsdk_unit_meter_per_second) + } +} + +/** + * Get the unit string for distance based on [UnitType] + */ +fun View.getDistanceString(unitType: UnitType): String { + return if (unitType == UnitType.IMPERIAL) { + getString(R.string.uxsdk_unit_feet) + } else { + getString(R.string.uxsdk_unit_meters) + } +} + +/** + * Set the border to a view. + * The extension creates a layered background which can be used to set up a border to the view. + * + * @param backgroundColor - The color for the solid background. + * @param borderColor - The color for the border for the view. + * @param leftBorder - The size of the left border. + * @param topBorder - The size of the top border. + * @param rightBorder - The size of the right border. + * @param bottomBorder - The size of the bottom border. + * + */ +fun View.setBorder(@ColorInt backgroundColor: Int = getColor(R.color.uxsdk_transparent), + @ColorInt borderColor: Int = getColor(R.color.uxsdk_transparent), + leftBorder: Int = 0, + topBorder: Int = 0, + rightBorder: Int = 0, + bottomBorder: Int = 0) { + val borderColorDrawable = ColorDrawable(borderColor) + val backgroundColorDrawable = ColorDrawable(backgroundColor) + + // Initialize a new array of drawable objects + val drawables = arrayOf(borderColorDrawable, backgroundColorDrawable) + + // Initialize a new layer drawable instance from drawables array + val layerDrawable = LayerDrawable(drawables) + + // Set padding for background color layer + layerDrawable.setLayerInset( + 1, // Index of the drawable to adjust [background color layer] + leftBorder, // Number of pixels to add to the left bound [left border] + topBorder, // Number of pixels to add to the top bound [top border] + rightBorder, // Number of pixels to add to the right bound [right border] + bottomBorder // Number of pixels to add to the bottom bound [bottom border] + ) + this.background = layerDrawable + +} + +/** + * On click listener for recycler view. + */ +fun T.listen(event: (position: Int) -> Unit): T { + itemView.setOnClickListener { + event.invoke(adapterPosition) + } + return this +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/unittype/UnitTypeListItemWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/unittype/UnitTypeListItemWidget.kt deleted file mode 100644 index 28a16310..00000000 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/unittype/UnitTypeListItemWidget.kt +++ /dev/null @@ -1,169 +0,0 @@ -/* - * 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.ux.beta.core.listitemwidget.unittype - -import android.content.Context -import android.util.AttributeSet -import dji.ux.beta.R -import dji.ux.beta.core.base.* -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.base.widget.ListItemRadioButtonWidget -import dji.ux.beta.core.extension.getDrawable -import dji.ux.beta.core.extension.getString -import dji.ux.beta.core.listitemwidget.unittype.UnitTypeListItemWidget.UnitTypeListItemState -import dji.ux.beta.core.listitemwidget.unittype.UnitTypeListItemWidget.UnitTypeListItemState.* -import dji.ux.beta.core.listitemwidget.unittype.UnitTypeListItemWidgetModel.UnitTypeState -import dji.ux.beta.core.util.UnitConversionUtil.UnitType - -/** - * Widget shows the current [UnitType] being used. - * It also provides an option to switch between them. - */ -open class UnitTypeListItemWidget @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : ListItemRadioButtonWidget(context, attrs, defStyleAttr) { - - private val schedulerProvider = SchedulerProvider.getInstance() - - - private val widgetModel: UnitTypeListItemWidgetModel by lazy { - UnitTypeListItemWidgetModel( - DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - schedulerProvider, - GlobalPreferencesManager.getInstance()) - } - - private var imperialItemIndex: Int = INVALID_OPTION_INDEX - private var metricItemIndex: Int = INVALID_OPTION_INDEX - - init { - listItemTitleIcon = getDrawable(R.drawable.uxsdk_ic_unit_type) - listItemTitle = getString(R.string.uxsdk_list_item_unit_type) - imperialItemIndex = addOptionToGroup(getString(R.string.uxsdk_list_item_unit_type_imperial)) - metricItemIndex = addOptionToGroup(getString(R.string.uxsdk_list_item_unit_type_metric)) - } - - - override fun reactToModelChanges() { - addReaction(widgetModel.productConnection - .observeOn(schedulerProvider.ui()) - .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) - addReaction(widgetModel.unitTypeState - .observeOn(schedulerProvider.ui()) - .subscribe { - widgetStateDataProcessor.onNext(UnitTypeUpdated(it)) - updateUI(it) - }) - } - - private fun updateUI(unitTypeState: UnitTypeState) { - isEnabled = if (unitTypeState is UnitTypeState.CurrentUnitType) { - if (unitTypeState.unitType == UnitType.IMPERIAL) { - setSelected(imperialItemIndex) - } else { - setSelected(metricItemIndex) - } - true - } else { - false - - } - } - - override fun onOptionTapped(optionIndex: Int, optionLabel: String) { - val newUnitType: UnitType = if (optionIndex == imperialItemIndex) { - UnitType.IMPERIAL - } else { - UnitType.METRIC - } - addDisposable(widgetModel.setUnitType(newUnitType) - .observeOn(schedulerProvider.ui()) - .subscribe({ - widgetStateDataProcessor.onNext(SetUnitTypeSuccess) - }, { - widgetStateDataProcessor.onNext(SetUnitTypeFailed(it as UXSDKError)) - resetUI() - })) - - } - - private fun resetUI() { - addDisposable(widgetModel.unitTypeState - .observeOn(schedulerProvider.ui()) - .subscribe { updateUI(it) }) - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - if (!isInEditMode) { - widgetModel.setup() - } - } - - override fun onDetachedFromWindow() { - if (!isInEditMode) { - widgetModel.cleanup() - } - super.onDetachedFromWindow() - } - - override fun getIdealDimensionRatioString(): String? { - return null - } - - override val widgetSizeDescription: WidgetSizeDescription = - WidgetSizeDescription(WidgetSizeDescription.SizeType.OTHER, - widthDimension = WidgetSizeDescription.Dimension.EXPAND, - heightDimension = WidgetSizeDescription.Dimension.WRAP) - - - /** - * Class defines widget state updates - */ - sealed class UnitTypeListItemState { - /** - * Product connection update - */ - data class ProductConnected(val isConnected: Boolean) : UnitTypeListItemState() - - /** - * Set unit type success - */ - object SetUnitTypeSuccess : UnitTypeListItemState() - - /** - * Set unit type failed - */ - data class SetUnitTypeFailed(val error: UXSDKError) : UnitTypeListItemState() - - /** - * Current unit type - */ - data class UnitTypeUpdated(val unitTypeState: UnitTypeState) : UnitTypeListItemState() - } - -} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/model/VoiceNotificationType.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/model/VoiceNotificationType.kt similarity index 76% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/model/VoiceNotificationType.java rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/model/VoiceNotificationType.kt index a4891b59..c7b7424d 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/model/VoiceNotificationType.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/model/VoiceNotificationType.kt @@ -20,25 +20,23 @@ * SOFTWARE. * */ +package dji.ux.beta.core.model -package dji.ux.beta.core.model; +import androidx.annotation.RawRes +import dji.ux.beta.core.R -import androidx.annotation.RawRes; - -import dji.ux.beta.R; - -public enum VoiceNotificationType { - - ATTI(R.raw.tips_voice_atti_mode); - - private int resId; +/** + * Enum represents voice notification type + */ +enum class VoiceNotificationType(@param:RawRes private val resId: Int) { - VoiceNotificationType(@RawRes int resId) { - this.resId = resId; - } + /** + * Notification of attitude mode + */ + ATTI(R.raw.uxsdk_tips_voice_atti_mode); @RawRes - public int value() { - return resId; + fun value(): Int { + return resId } -} +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/model/WarningMessage.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/model/WarningMessage.java index 329ecab1..dd4bcf78 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/model/WarningMessage.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/model/WarningMessage.java @@ -26,13 +26,33 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.util.Objects; + +/** + * The class represents warning messages. + * The messages will guide the user through the flight. + */ public class WarningMessage { private static final int DEFAULT_DISPLAY_DURATION = 5; + /** + * Warning levels for warning message + */ public enum Level { + /** + * Message is a notification + */ NOTIFY(0), + + /** + * Message is a warning + */ WARNING(1), + + /** + * Message implies dangerous condition + */ DANGEROUS(2); private int value; @@ -45,8 +65,17 @@ public int getValue() { return value; } + private static Level[] values; + + public static Level[] getValues() { + if (values == null) { + values = values(); + } + return values; + } + public static Level find(int value) { - for (Level item : values()) { + for (Level item : getValues()) { if (item.getValue() == value) { return item; } @@ -55,10 +84,32 @@ public static Level find(int value) { } } + /** + * Type of message behavior + */ public enum Type { + + /** + * Message will auto disappear from the screen + * after the number of seconds specified by showDuration. + */ AUTO_DISAPPEAR(0), + + /** + * Message stays until a removal message is sent. + */ PUSH(1), + + /** + * Message will be pinned above other messages and will + * have a close button to dismiss it. + */ PINNED(2), + + /** + * Message will be pinned above other messages and cannot + * be dismissed by user. + */ PINNED_NOT_CLOSE(3); private int value; @@ -71,8 +122,17 @@ public int getValue() { return value; } + private static Type[] values; + + public static Type[] getValues() { + if (values == null) { + values = values(); + } + return values; + } + public static Type find(int value) { - for (Type item : values()) { + for (Type item : getValues()) { if (item.getValue() == value) { return item; } @@ -81,26 +141,98 @@ public static Type find(int value) { } } + /** + * Action for warning message + */ public enum Action { + /** + * Insert warning message + */ INSERT, + + /** + * Remove warning message + */ REMOVE } + /** + * The type of the warning message component + */ public enum WarningType { + /** + * Air 1860 + */ AIR1860(0), + + /** + * Battery + */ BATTERY(1), + + /** + * Camera + */ CAMERA(2), + + /** + * Center board + */ CENTER_BOARD(3), + + /** + * OSD + */ OSD(4), + + /** + * Flight Controller + */ FLIGHT_CONTROLLER(5), + + /** + * Gimbal + */ GIMBAL(6), + + /** + * Lightbridge + */ LIGHT_BRIDGE(7), + + /** + * Remote controller + */ REMOTE_CONTROLLER(8), + + /** + * Vision + */ VISION(9), + + /** + * Flight record + */ FLIGHT_RECORD(10), + + /** + * Fly safe + */ FLY_SAFE(11), + + /** + * RTK + */ RTK(12), + + /** + * LTE + */ LTE(13), + + /** + * Other + */ OTHER(100); private int value; @@ -113,8 +245,17 @@ public int getValue() { return value; } + private static WarningType[] values; + + public static WarningType[] getValues() { + if (values == null) { + values = values(); + } + return values; + } + public static WarningType find(int value) { - for (WarningType item : values()) { + for (WarningType item : getValues()) { if (item.getValue() == value) { return item; } @@ -288,8 +429,8 @@ public boolean equals(Object o) { if (code != that.code) return false; if (subCode != that.subCode) return false; if (componentIndex != that.componentIndex) return false; - if (reason != null ? !reason.equals(that.reason) : that.reason != null) return false; - return solution != null ? solution.equals(that.solution) : that.solution == null; + if (!Objects.equals(reason, that.reason)) return false; + return Objects.equals(solution, that.solution); } @Override diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/model/WarningMessageError.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/model/WarningMessageError.java index 8f62afa8..066a3aff 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/model/WarningMessageError.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/model/WarningMessageError.java @@ -23,11 +23,29 @@ package dji.ux.beta.core.model; +/** + * {@link WarningMessage} errors + */ public enum WarningMessageError { + /** + * Vision/Obstacle avoidance sensor message + */ VISION_AVOID(1004), + + /** + * There are aircraft in vicinity + */ OTHER_AIRCRAFT_NEARBY(1022), + + /** + * Error due to customer use + */ CUSTOMER_USE_ERROR(10000), + + /** + * Unknown error + */ UNKNOWN(0); private int value; @@ -44,11 +62,20 @@ private boolean _equals(int b) { return value == b; } + private static WarningMessageError[] values; + + public static WarningMessageError[] getValues() { + if (values == null) { + values = values(); + } + return values; + } + public static WarningMessageError find(int b) { WarningMessageError result = UNKNOWN; - for (int i = 0; i < values().length; i++) { - if (values()[i]._equals(b)) { - result = values()[i]; + for (int i = 0; i < getValues().length; i++) { + if (getValues()[i]._equals(b)) { + result = getValues()[i]; break; } } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/module/FlatCameraModule.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/module/FlatCameraModule.kt new file mode 100644 index 00000000..e1393d8f --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/module/FlatCameraModule.kt @@ -0,0 +1,151 @@ +/* + * 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.ux.beta.core.module + +import dji.common.camera.SettingsDefinitions.* +import dji.keysdk.CameraKey +import dji.thirdparty.io.reactivex.Completable +import dji.thirdparty.io.reactivex.functions.Consumer +import dji.ux.beta.core.base.BaseModule +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.WidgetModel +import dji.ux.beta.core.extension.isPictureMode +import dji.ux.beta.core.extension.toFlatCameraMode +import dji.ux.beta.core.extension.toShootPhotoMode +import dji.ux.beta.core.util.DataProcessor +import dji.ux.beta.core.util.SettingDefinitions.CameraIndex + +/** + * Abstraction for getting and setting camera mode and photo mode. + */ +class FlatCameraModule : BaseModule() { + + //region Fields + private val isFlatCameraModeSupportedDataProcessor: DataProcessor = DataProcessor.create(false) + private val flatCameraModeDataProcessor: DataProcessor = DataProcessor.create(FlatCameraMode.UNKNOWN) + private var cameraIndex = CameraIndex.CAMERA_INDEX_0.index + private var cameraModeKey: CameraKey = CameraKey.create(CameraKey.MODE, cameraIndex) + private var shootPhotoModeKey: CameraKey = CameraKey.create(CameraKey.SHOOT_PHOTO_MODE, cameraIndex) + private var flatCameraModeKey: CameraKey = CameraKey.create(CameraKey.FLAT_CAMERA_MODE, cameraIndex) + + /** + * The camera mode. + */ + val cameraModeDataProcessor: DataProcessor = DataProcessor.create(CameraMode.UNKNOWN) + + /** + * The shoot photo mode. + */ + val shootPhotoModeProcessor: DataProcessor = DataProcessor.create(ShootPhotoMode.UNKNOWN) + //endregion + + //region Lifecycle + override fun setup(widgetModel: WidgetModel) { + cameraModeKey = CameraKey.create(CameraKey.MODE, cameraIndex) + bindDataProcessor(widgetModel, cameraModeKey, cameraModeDataProcessor) + shootPhotoModeKey = CameraKey.create(CameraKey.SHOOT_PHOTO_MODE, cameraIndex) + bindDataProcessor(widgetModel, shootPhotoModeKey, shootPhotoModeProcessor) + + val isFlatCameraModeSupportedKey = CameraKey.create(CameraKey.IS_FLAT_CAMERA_MODE_SUPPORTED, cameraIndex) + bindDataProcessor(widgetModel, isFlatCameraModeSupportedKey, isFlatCameraModeSupportedDataProcessor, Consumer { isFlatCameraModeSupported -> + if (isFlatCameraModeSupported as Boolean) { + updateModes(flatCameraModeDataProcessor.value) + } + }) + flatCameraModeKey = CameraKey.create(CameraKey.FLAT_CAMERA_MODE, cameraIndex) + bindDataProcessor(widgetModel, flatCameraModeKey, flatCameraModeDataProcessor, Consumer { flatCameraMode: Any -> + if (isFlatCameraModeSupportedDataProcessor.value) { + updateModes(flatCameraMode as FlatCameraMode) + } + }) + } + + override fun cleanup() { + // no code + } + //endregion + + //region Actions + /** + * Set camera mode + * + * @return Completable + */ + fun setCameraMode(djiSdkModel: DJISDKModel, cameraMode: CameraMode): Completable { + return if (isFlatCameraModeSupportedDataProcessor.value) { + if (cameraMode == CameraMode.SHOOT_PHOTO) { + djiSdkModel.setValue(flatCameraModeKey, FlatCameraMode.PHOTO_SINGLE) + } else { + djiSdkModel.setValue(flatCameraModeKey, FlatCameraMode.VIDEO_NORMAL) + } + } else { + djiSdkModel.setValue(cameraModeKey, cameraMode) + } + } + + /** + * Set photo mode + * + * @return Completable + */ + fun setPhotoMode(djiSdkModel: DJISDKModel, photoMode: ShootPhotoMode): Completable { + return if (isFlatCameraModeSupportedDataProcessor.value) { + djiSdkModel.setValue(flatCameraModeKey, photoMode.toFlatCameraMode()) + } else { + djiSdkModel.setValue(shootPhotoModeKey, photoMode) + } + } + //endregion + + //region Customizations + /** + * Get the camera index for which the module is reacting. + * + * @return current camera index. + */ + fun getCameraIndex(): CameraIndex { + return CameraIndex.find(cameraIndex) + } + + /** + * Set camera index to which the module should react. + * + * @param cameraIndex index of the camera. + */ + fun setCameraIndex(cameraIndex: CameraIndex) { + this.cameraIndex = cameraIndex.index + } + //endregion + + //region Helpers + private fun updateModes(flatCameraMode: FlatCameraMode) { + cameraModeDataProcessor.onNext(if (flatCameraMode.isPictureMode()) { + CameraMode.SHOOT_PHOTO + } else { + CameraMode.RECORD_VIDEO + }) + shootPhotoModeProcessor.onNext(flatCameraMode.toShootPhotoMode()) + } + //endregion +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/aircraftbatterytemperature/AircraftBatteryTemperatureListItemWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/aircraftbatterytemperature/AircraftBatteryTemperatureListItemWidget.kt new file mode 100644 index 00000000..7eeeb454 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/aircraftbatterytemperature/AircraftBatteryTemperatureListItemWidget.kt @@ -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.ux.beta.core.panel.listitem.aircraftbatterytemperature + +import android.content.Context +import android.util.AttributeSet +import dji.ux.beta.core.R +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.SchedulerProvider +import dji.ux.beta.core.base.WidgetSizeDescription +import dji.ux.beta.core.base.panel.listitem.ListItemLabelButtonWidget +import dji.ux.beta.core.communication.GlobalPreferencesManager +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.extension.getColor +import dji.ux.beta.core.extension.getString +import dji.ux.beta.core.panel.listitem.aircraftbatterytemperature.AircraftBatteryTemperatureListItemWidgetModel.AircraftBatteryTemperatureItemState +import dji.ux.beta.core.panel.listitem.aircraftbatterytemperature.AircraftBatteryTemperatureListItemWidgetModel.AircraftBatteryTemperatureItemState.AircraftBatteryState +import dji.ux.beta.core.panel.listitem.aircraftbatterytemperature.AircraftBatteryTemperatureListItemWidgetModel.AircraftBatteryTemperatureItemState.ProductDisconnected +import dji.ux.beta.core.util.UnitConversionUtil.TemperatureUnitType.* + +/** + * Aircraft battery temperature list item + * + */ +open class AircraftBatteryTemperatureListItemWidget @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ListItemLabelButtonWidget( + context, + attrs, + defStyleAttr, + WidgetType.LABEL, + R.style.UXSDKAircraftBatteryTemperatureListItem +) { + + private val widgetModel by lazy { + AircraftBatteryTemperatureListItemWidgetModel( + DJISDKModel.getInstance(), + ObservableInMemoryKeyedStore.getInstance(), + GlobalPreferencesManager.getInstance()) + } + + override fun reactToModelChanges() { + addReaction(widgetModel.aircraftBatteryTemperatureState + .observeOn(SchedulerProvider.ui()) + .subscribe { this.updateUI(it) }) + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + if (!isInEditMode) { + widgetModel.setup() + } + } + + override fun onDetachedFromWindow() { + if (!isInEditMode) { + widgetModel.cleanup() + } + super.onDetachedFromWindow() + } + + override fun getIdealDimensionRatioString(): String? = null + + + override val widgetSizeDescription: WidgetSizeDescription = + WidgetSizeDescription(WidgetSizeDescription.SizeType.OTHER, + widthDimension = WidgetSizeDescription.Dimension.EXPAND, + heightDimension = WidgetSizeDescription.Dimension.WRAP) + + + private fun updateUI(aircraftBatteryTemperatureItemState: AircraftBatteryTemperatureItemState) { + when (aircraftBatteryTemperatureItemState) { + is ProductDisconnected -> { + isEnabled = false + listItemLabelTextColor = disconnectedValueColor + listItemLabel = resources.getString(R.string.uxsdk_string_default_value) + } + is AircraftBatteryState -> { + listItemLabelTextColor = getColor(R.color.uxsdk_white) + listItemLabel = when (aircraftBatteryTemperatureItemState.unitType) { + CELSIUS -> String.format(getString(R.string.uxsdk_celsius_unit), aircraftBatteryTemperatureItemState.temperature) + FAHRENHEIT -> String.format(getString(R.string.uxsdk_celsius_unit), aircraftBatteryTemperatureItemState.temperature) + KELVIN -> String.format(getString(R.string.uxsdk_celsius_unit), aircraftBatteryTemperatureItemState.temperature) + } + + } + } + + } + + override fun onButtonClick() { + // No code needed + } + + +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/aircraftbatterytemperature/AircraftBatteryTemperatureListItemWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/aircraftbatterytemperature/AircraftBatteryTemperatureListItemWidgetModel.kt new file mode 100644 index 00000000..3cdbe763 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/aircraftbatterytemperature/AircraftBatteryTemperatureListItemWidgetModel.kt @@ -0,0 +1,110 @@ +/* + * 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.ux.beta.core.panel.listitem.aircraftbatterytemperature + +import dji.keysdk.BatteryKey +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.WidgetModel +import dji.ux.beta.core.communication.GlobalPreferenceKeys +import dji.ux.beta.core.communication.GlobalPreferencesInterface +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.panel.listitem.aircraftbatterytemperature.AircraftBatteryTemperatureListItemWidgetModel.AircraftBatteryTemperatureItemState.AircraftBatteryState +import dji.ux.beta.core.panel.listitem.aircraftbatterytemperature.AircraftBatteryTemperatureListItemWidgetModel.AircraftBatteryTemperatureItemState.ProductDisconnected +import dji.ux.beta.core.util.DataProcessor +import dji.ux.beta.core.util.UnitConversionUtil +import dji.ux.beta.core.util.UnitConversionUtil.TemperatureUnitType +import dji.ux.beta.core.util.UnitConversionUtil.TemperatureUnitType.* + + +/** + * Widget Model for the [AircraftBatteryTemperatureListItemWidget] used to define + * the underlying logic and communication + */ +class AircraftBatteryTemperatureListItemWidgetModel( + djiSdkModel: DJISDKModel, + keyedStore: ObservableInMemoryKeyedStore, + private val preferencesManager: GlobalPreferencesInterface? +) : WidgetModel(djiSdkModel, keyedStore) { + + //region Fields + + private val batteryTemperatureStateProcessor: DataProcessor = DataProcessor.create(ProductDisconnected) + private val batteryTemperatureProcessor: DataProcessor = DataProcessor.create(0.0f) + private val temperatureUnitTypeProcessor: DataProcessor = DataProcessor.create(CELSIUS) + + //endregion + + //region Data + /** + * Get the aircraft battery temperature state + */ + val aircraftBatteryTemperatureState: Flowable + get() = batteryTemperatureStateProcessor.toFlowable() + + //endregion + + //region Lifecycle + override fun inSetup() { + val batteryTemperatureKey = BatteryKey.create(BatteryKey.TEMPERATURE) + bindDataProcessor(batteryTemperatureKey, batteryTemperatureProcessor) + val temperatureUnitTypeKey = GlobalPreferenceKeys.create(GlobalPreferenceKeys.TEMPERATURE_UNIT_TYPE) + bindDataProcessor(temperatureUnitTypeKey, temperatureUnitTypeProcessor) + preferencesManager?.setUpListener() + } + + override fun inCleanup() { + preferencesManager?.cleanup() + } + + override fun updateStates() { + if (productConnectionProcessor.value) { + val temperatureValue = when (temperatureUnitTypeProcessor.value) { + CELSIUS -> batteryTemperatureProcessor.value + FAHRENHEIT -> UnitConversionUtil.celsiusToFahrenheit(batteryTemperatureProcessor.value) + KELVIN -> UnitConversionUtil.celsiusToKelvin(batteryTemperatureProcessor.value) + } + batteryTemperatureStateProcessor.onNext(AircraftBatteryState(temperatureValue, temperatureUnitTypeProcessor.value)) + } else { + batteryTemperatureStateProcessor.onNext(ProductDisconnected) + } + + } + + /** + * Class to represent states of AircraftBatteryItem + */ + sealed class AircraftBatteryTemperatureItemState { + /** + * When product is disconnected + */ + object ProductDisconnected : AircraftBatteryTemperatureItemState() + + /** + * When product is connected and battery temperature is received. + */ + data class AircraftBatteryState(val temperature: Float, val unitType: TemperatureUnitType) : AircraftBatteryTemperatureItemState() + } +} +//endregion \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/emmcstatus/EMMCStatusListItemWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/emmcstatus/EMMCStatusListItemWidget.kt similarity index 77% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/emmcstatus/EMMCStatusListItemWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/emmcstatus/EMMCStatusListItemWidget.kt index 5058ea8a..5f572afb 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/emmcstatus/EMMCStatusListItemWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/emmcstatus/EMMCStatusListItemWidget.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.listitemwidget.emmcstatus +package dji.ux.beta.core.panel.listitem.emmcstatus import android.annotation.SuppressLint import android.content.Context @@ -33,18 +33,18 @@ import androidx.core.content.res.use import dji.common.camera.SettingsDefinitions.SDCardOperationState import dji.common.camera.SettingsDefinitions.SDCardOperationState.* import dji.thirdparty.io.reactivex.Flowable -import dji.ux.beta.R +import dji.ux.beta.core.R import dji.ux.beta.core.base.DJISDKModel import dji.ux.beta.core.base.SchedulerProvider import dji.ux.beta.core.base.UXSDKError import dji.ux.beta.core.base.WidgetSizeDescription -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.base.widget.ListItemLabelButtonWidget -import dji.ux.beta.core.base.widget.ListItemLabelButtonWidget.WidgetUIState.* +import dji.ux.beta.core.base.panel.listitem.ListItemLabelButtonWidget +import dji.ux.beta.core.base.panel.listitem.ListItemLabelButtonWidget.UIState.* +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.* -import dji.ux.beta.core.listitemwidget.emmcstatus.EMMCStatusListItemWidget.EMMCListItemDialogState.* -import dji.ux.beta.core.listitemwidget.emmcstatus.EMMCStatusListItemWidget.EMMCListItemState -import dji.ux.beta.core.listitemwidget.emmcstatus.EMMCStatusListItemWidget.EMMCListItemState.ProductConnected +import dji.ux.beta.core.panel.listitem.emmcstatus.EMMCStatusListItemWidget.DialogType.* +import dji.ux.beta.core.panel.listitem.emmcstatus.EMMCStatusListItemWidget.ModelState +import dji.ux.beta.core.panel.listitem.emmcstatus.EMMCStatusListItemWidget.ModelState.ProductConnected import dji.ux.beta.core.util.UnitConversionUtil.getSpaceWithUnit @@ -59,10 +59,15 @@ open class EMMCStatusListItemWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ListItemLabelButtonWidget(context, attrs, defStyleAttr, WidgetType.LABEL_BUTTON) { - - private val schedulerProvider = SchedulerProvider.getInstance() - +) : ListItemLabelButtonWidget( + context, + attrs, + defStyleAttr, + WidgetType.LABEL_BUTTON, + R.style.UXSDKEMMCStatusListItem +) { + + //region Fields /** * Theme for the dialogs shown for format */ @@ -87,86 +92,69 @@ open class EMMCStatusListItemWidget @JvmOverloads constructor( private val widgetModel: EMMCStatusListItemWidgetModel by lazy { EMMCStatusListItemWidgetModel( DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - schedulerProvider) + ObservableInMemoryKeyedStore.getInstance()) } + //endregion + //region Constructor init { - listItemTitleIcon = getDrawable(R.drawable.uxsdk_ic_emmc) - listItemTitle = getString(R.string.uxsdk_list_item_emmc) - listItemButtonText = getString(R.string.uxsdk_list_item_format_button) - attrs?.let { initAttributes(context, it) } + initAttributes(context, attrs) } + //endregion - @SuppressLint("Recycle") - private fun initAttributes(context: Context, attrs: AttributeSet) { - context.obtainStyledAttributes(attrs, R.styleable.EMMCStatusListItemWidget).use { typedArray -> - typedArray.getDrawableAndUse(R.styleable.EMMCStatusListItemWidget_uxsdk_list_item_confirmation_dialog_icon) { - formatConfirmationDialogIcon = it - } - typedArray.getDrawableAndUse(R.styleable.EMMCStatusListItemWidget_uxsdk_list_item_success_dialog_icon) { - formatSuccessDialogIcon = it - } - typedArray.getDrawableAndUse(R.styleable.EMMCStatusListItemWidget_uxsdk_list_item_error_dialog_icon) { - formatErrorDialogIcon = it - } - typedArray.getResourceIdAndUse(R.styleable.EMMCStatusListItemWidget_uxsdk_list_item_dialog_theme) { - dialogTheme = it - } + //region Lifecycle + override fun onAttachedToWindow() { + super.onAttachedToWindow() + if (!isInEditMode) { + widgetModel.setup() + } + } + override fun onDetachedFromWindow() { + if (!isInEditMode) { + widgetModel.cleanup() } + super.onDetachedFromWindow() + } + + + override fun reactToModelChanges() { + addReaction(widgetModel.eMMCState + .observeOn(SchedulerProvider.ui()) + .subscribe { this.updateUI(it) }) + addReaction(widgetModel.productConnection + .observeOn(SchedulerProvider.ui()) + .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) } override fun onButtonClick() { val dialogListener = DialogInterface.OnClickListener { dialogInterface, buttonId: Int -> if (buttonId == DialogInterface.BUTTON_POSITIVE) { - uiUpdateStateProcessor.onNext(DialogActionConfirm(FormatConfirmationDialog)) + uiUpdateStateProcessor.onNext(DialogActionConfirmed(FormatConfirmation)) formatEMMC() + } else if (buttonId == DialogInterface.BUTTON_NEGATIVE) { + uiUpdateStateProcessor.onNext(DialogActionCanceled(FormatConfirmation)) } dialogInterface.dismiss() - uiUpdateStateProcessor.onNext(DialogActionDismiss(FormatConfirmationDialog)) + + } + val dialogDismissListener = DialogInterface.OnDismissListener { + uiUpdateStateProcessor.onNext(DialogDismissed(FormatConfirmation)) } + showConfirmationDialog(title = getString(R.string.uxsdk_emmc_dialog_title), icon = formatConfirmationDialogIcon, dialogTheme = dialogTheme, message = getString(R.string.uxsdk_emmc_format_confirmation), - dialogClickListener = dialogListener) - uiUpdateStateProcessor.onNext(DialogDisplayed(FormatConfirmationDialog)) - } - - private fun formatEMMC() { - addDisposable(widgetModel.formatEMMC() - .observeOn(schedulerProvider.ui()) - .subscribe({ - showAlertDialog(title = getString(R.string.uxsdk_emmc_dialog_title), - icon = formatSuccessDialogIcon, - dialogTheme = dialogTheme, - message = getString(R.string.uxsdk_emmc_format_complete)) - uiUpdateStateProcessor.onNext(DialogDisplayed(FormatSuccessDialog)) - }, { error -> - if (error is UXSDKError) { - showAlertDialog(title = getString(R.string.uxsdk_emmc_dialog_title), - icon = formatErrorDialogIcon, - dialogTheme = dialogTheme, - message = String.format(getString(R.string.uxsdk_emmc_format_error), - error.djiError.description)) - uiUpdateStateProcessor.onNext(DialogDisplayed(FormatErrorDialog)) - } - })) - } - - override fun reactToModelChanges() { - addReaction(widgetModel.eMMCState - .observeOn(schedulerProvider.ui()) - .subscribe { this.updateUI(it) }) - addReaction(widgetModel.productConnection - .observeOn(schedulerProvider.ui()) - .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) + dialogClickListener = dialogListener, + dialogDismissListener = dialogDismissListener) + uiUpdateStateProcessor.onNext(DialogDisplayed(FormatConfirmation)) } + //endregion - + //region Reaction to model private fun updateUI(eMMCState: EMMCStatusListItemWidgetModel.EMMCState) { - widgetStateDataProcessor.onNext(EMMCListItemState.CurrentEMMCListItemState(eMMCState)) + widgetStateDataProcessor.onNext(ModelState.EMMCStateUpdated(eMMCState)) when (eMMCState) { EMMCStatusListItemWidgetModel.EMMCState.ProductDisconnected, EMMCStatusListItemWidgetModel.EMMCState.NotSupported -> updateDisabledState(eMMCState) @@ -193,7 +181,38 @@ open class EMMCStatusListItemWidget @JvmOverloads constructor( } isEnabled = false } + //endregion + + //region private helpers + private fun formatEMMC() { + var dialogType: DialogType? = null + val dialogDismissListener = DialogInterface.OnDismissListener { + uiUpdateStateProcessor.onNext(DialogDismissed(dialogType)) + } + addDisposable(widgetModel.formatEMMC() + .observeOn(SchedulerProvider.ui()) + .subscribe({ + dialogType = FormatSuccess + showAlertDialog(title = getString(R.string.uxsdk_emmc_dialog_title), + icon = formatSuccessDialogIcon, + dialogTheme = dialogTheme, + message = getString(R.string.uxsdk_emmc_format_complete), + dialogDismissListener = dialogDismissListener) + uiUpdateStateProcessor.onNext(DialogDisplayed(dialogType)) + }, { error -> + if (error is UXSDKError) { + dialogType = FormatError + showAlertDialog(title = getString(R.string.uxsdk_emmc_dialog_title), + icon = formatErrorDialogIcon, + dialogTheme = dialogTheme, + message = String.format(getString(R.string.uxsdk_emmc_format_error), + error.djiError.description), + dialogDismissListener = dialogDismissListener) + uiUpdateStateProcessor.onNext(DialogDisplayed(dialogType)) + } + })) + } private fun getFormatButtonVisibility(eMMCOperationState: SDCardOperationState): Boolean { return when (eMMCOperationState) { @@ -239,21 +258,9 @@ open class EMMCStatusListItemWidget @JvmOverloads constructor( } } + //endregion - override fun onAttachedToWindow() { - super.onAttachedToWindow() - if (!isInEditMode) { - widgetModel.setup() - } - } - - override fun onDetachedFromWindow() { - if (!isInEditMode) { - widgetModel.cleanup() - } - super.onDetachedFromWindow() - } - + //region Customization override val widgetSizeDescription: WidgetSizeDescription = WidgetSizeDescription(WidgetSizeDescription.SizeType.OTHER, widthDimension = WidgetSizeDescription.Dimension.EXPAND, @@ -263,54 +270,77 @@ open class EMMCStatusListItemWidget @JvmOverloads constructor( return null } + + @SuppressLint("Recycle") + private fun initAttributes(context: Context, attrs: AttributeSet?) { + context.obtainStyledAttributes(attrs, R.styleable.EMMCStatusListItemWidget, 0, defaultStyle).use { typedArray -> + typedArray.getDrawableAndUse(R.styleable.EMMCStatusListItemWidget_uxsdk_list_item_confirmation_dialog_icon) { + formatConfirmationDialogIcon = it + } + typedArray.getDrawableAndUse(R.styleable.EMMCStatusListItemWidget_uxsdk_list_item_success_dialog_icon) { + formatSuccessDialogIcon = it + } + typedArray.getDrawableAndUse(R.styleable.EMMCStatusListItemWidget_uxsdk_list_item_error_dialog_icon) { + formatErrorDialogIcon = it + } + typedArray.getResourceIdAndUse(R.styleable.EMMCStatusListItemWidget_uxsdk_list_item_dialog_theme) { + dialogTheme = it + } + + } + } + //endregion + + //region Hooks /** - * Get the [EMMCListItemState] updates + * Get the [ListItemLabelButtonWidget.UIState] updates + * The info parameter is instance of [DialogType] */ - override fun getWidgetStateUpdate(): Flowable { - return super.getWidgetStateUpdate() + override fun getUIStateUpdates(): Flowable { + return uiUpdateStateProcessor.onBackpressureBuffer() } /** - * Get the [ListItemLabelButtonWidget.WidgetUIState] updates - * The info parameter is instance of [EMMCListItemDialogState] + * Get the [ModelState] updates */ - override fun getUIStateUpdates(): Flowable { - return uiUpdateStateProcessor + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { + return super.getWidgetStateUpdate() } /** * eMMC List Item Dialog Identifiers */ - sealed class EMMCListItemDialogState { + sealed class DialogType { /** * Dialog shown for format confirmation */ - object FormatConfirmationDialog : EMMCListItemDialogState() + object FormatConfirmation : DialogType() /** * Dialog shown for format success */ - object FormatSuccessDialog : EMMCListItemDialogState() + object FormatSuccess : DialogType() /** * Dialog shown for format fail */ - object FormatErrorDialog : EMMCListItemDialogState() + object FormatError : DialogType() } /** * Class defines widget state updates */ - sealed class EMMCListItemState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : EMMCListItemState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * Current eMMC List Item State */ - data class CurrentEMMCListItemState(val eMMCState: EMMCStatusListItemWidgetModel.EMMCState) : EMMCListItemState() + data class EMMCStateUpdated(val eMMCState: EMMCStatusListItemWidgetModel.EMMCState) : ModelState() } - + //endregion } \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/emmcstatus/EMMCStatusListItemWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/emmcstatus/EMMCStatusListItemWidgetModel.kt similarity index 92% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/emmcstatus/EMMCStatusListItemWidgetModel.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/emmcstatus/EMMCStatusListItemWidgetModel.kt index e4519dc3..07515a7f 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/emmcstatus/EMMCStatusListItemWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/emmcstatus/EMMCStatusListItemWidgetModel.kt @@ -21,28 +21,25 @@ * */ -package dji.ux.beta.core.listitemwidget.emmcstatus +package dji.ux.beta.core.panel.listitem.emmcstatus import dji.common.camera.SettingsDefinitions import dji.keysdk.CameraKey import dji.thirdparty.io.reactivex.Completable import dji.thirdparty.io.reactivex.Flowable import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.SchedulerProviderInterface import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.util.DataProcessor import dji.ux.beta.core.util.SettingDefinitions - /** * Widget Model for the [EMMCStatusListItemWidget] used to define * the underlying logic and communication */ class EMMCStatusListItemWidgetModel( djiSdkModel: DJISDKModel, - keyedStore: ObservableInMemoryKeyedStore, - private val schedulerProvider: SchedulerProviderInterface + keyedStore: ObservableInMemoryKeyedStore ) : WidgetModel(djiSdkModel, keyedStore) { @@ -100,7 +97,6 @@ class EMMCStatusListItemWidgetModel( val eMMCFormatKey = CameraKey.create(CameraKey.FORMAT_INTERNAL_STORAGE, cameraIndex) return djiSdkModel.performAction(eMMCFormatKey, SettingsDefinitions.StorageLocation.INTERNAL_STORAGE) - .subscribeOn(schedulerProvider.io()) } /** diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/flightmode/FlightModeListItemWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/flightmode/FlightModeListItemWidget.kt similarity index 68% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/flightmode/FlightModeListItemWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/flightmode/FlightModeListItemWidget.kt index 1cafdbde..684b57be 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/flightmode/FlightModeListItemWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/flightmode/FlightModeListItemWidget.kt @@ -21,24 +21,21 @@ * */ -package dji.ux.beta.core.listitemwidget.flightmode +package dji.ux.beta.core.panel.listitem.flightmode import android.content.Context import android.util.AttributeSet import dji.thirdparty.io.reactivex.Flowable -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers -import dji.ux.beta.R +import dji.ux.beta.core.R import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.SchedulerProvider import dji.ux.beta.core.base.WidgetSizeDescription -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.base.widget.ListItemLabelButtonWidget -import dji.ux.beta.core.extension.getDrawable +import dji.ux.beta.core.base.panel.listitem.ListItemLabelButtonWidget +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.getString -import dji.ux.beta.core.listitemwidget.flightmode.FlightModeListItemWidget.FlightModeListItemState -import dji.ux.beta.core.listitemwidget.flightmode.FlightModeListItemWidget.FlightModeListItemState.FlightModeTextUpdated -import dji.ux.beta.core.listitemwidget.flightmode.FlightModeListItemWidget.FlightModeListItemState.ProductConnected +import dji.ux.beta.core.panel.listitem.flightmode.FlightModeListItemWidget.ModelState +import dji.ux.beta.core.panel.listitem.flightmode.FlightModeListItemWidget.ModelState.ProductConnected import dji.ux.beta.core.widget.flightmode.FlightModeWidgetModel -import dji.ux.beta.core.widget.flightmode.FlightModeWidgetModel.FlightModeState.FlightModeUpdated /** * Widget displays the current flight mode @@ -48,25 +45,29 @@ open class FlightModeListItemWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ListItemLabelButtonWidget(context, attrs, defStyleAttr, WidgetType.LABEL) { - +) : ListItemLabelButtonWidget( + context, + attrs, + defStyleAttr, + WidgetType.LABEL, + R.style.UXSDKFlightModeListItem +) { + + //region Fields private val widgetModel by lazy { FlightModeWidgetModel( DJISDKModel.getInstance(), ObservableInMemoryKeyedStore.getInstance()) } + //endregion - init { - listItemTitleIcon = getDrawable(R.drawable.uxsdk_ic_flight_mode_list_item) - listItemTitle = getString(R.string.uxsdk_list_item_flight_mode_title) - } - + //region Lifecycle override fun reactToModelChanges() { addReaction(widgetModel.flightModeState - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { this.updateUI(it) }) addReaction(widgetModel.productConnection - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) } @@ -84,6 +85,12 @@ open class FlightModeListItemWidget @JvmOverloads constructor( super.onDetachedFromWindow() } + override fun onButtonClick() { + // No code needed + } + //endregion + + //region Customization override fun getIdealDimensionRatioString(): String? = null @@ -92,45 +99,45 @@ open class FlightModeListItemWidget @JvmOverloads constructor( widthDimension = WidgetSizeDescription.Dimension.EXPAND, heightDimension = WidgetSizeDescription.Dimension.WRAP) + //endregion + //region Reactions to Model private fun updateUI(flightModeState: FlightModeWidgetModel.FlightModeState) { - if (flightModeState is FlightModeUpdated) { + if (flightModeState is FlightModeWidgetModel.FlightModeState.FlightModeUpdated) { listItemLabel = flightModeState.flightModeString listItemLabelTextColor = normalValueColor - widgetStateDataProcessor.onNext(FlightModeTextUpdated(flightModeState.flightModeString)) + widgetStateDataProcessor.onNext(ModelState.FlightModeUpdated(flightModeState.flightModeString)) } else { listItemLabel = getString(R.string.uxsdk_string_default_value) listItemLabelTextColor = disconnectedValueColor } } + //endregion - - override fun onButtonClick() { - // No code needed - } - + //region Hooks /** - * Get the [FlightModeListItemState] updates + * Get the [ModelState] updates */ - override fun getWidgetStateUpdate(): Flowable { + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { return super.getWidgetStateUpdate() } /** * Class defines the widget state updates */ - sealed class FlightModeListItemState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : FlightModeListItemState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * Flight mode text update */ - data class FlightModeTextUpdated(val flightModeText: String) : FlightModeListItemState() + data class FlightModeUpdated(val flightModeText: String) : ModelState() } - + //endregion } \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/maxaltitude/MaxAltitudeListItemWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/maxaltitude/MaxAltitudeListItemWidget.kt similarity index 76% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/maxaltitude/MaxAltitudeListItemWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/maxaltitude/MaxAltitudeListItemWidget.kt index 3067bf2a..613ee44e 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/maxaltitude/MaxAltitudeListItemWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/maxaltitude/MaxAltitudeListItemWidget.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.listitemwidget.maxaltitude +package dji.ux.beta.core.panel.listitem.maxaltitude import android.annotation.SuppressLint import android.content.Context @@ -32,18 +32,21 @@ import androidx.annotation.StyleRes import androidx.core.content.res.use import dji.log.DJILog import dji.thirdparty.io.reactivex.Flowable -import dji.ux.beta.R -import dji.ux.beta.core.base.* -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.base.widget.ListItemEditTextButtonWidget -import dji.ux.beta.core.base.widget.ListItemEditTextButtonWidget.WidgetUIState.* +import dji.ux.beta.core.R +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.SchedulerProvider +import dji.ux.beta.core.base.UXSDKError +import dji.ux.beta.core.base.WidgetSizeDescription +import dji.ux.beta.core.base.panel.listitem.ListItemEditTextButtonWidget +import dji.ux.beta.core.base.panel.listitem.ListItemEditTextButtonWidget.UIState.* +import dji.ux.beta.core.communication.GlobalPreferencesManager +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.* -import dji.ux.beta.core.listitemwidget.maxaltitude.MaxAltitudeListItemWidget.MaxAltitudeItemDialogState.* -import dji.ux.beta.core.listitemwidget.maxaltitude.MaxAltitudeListItemWidget.MaxAltitudeItemState -import dji.ux.beta.core.listitemwidget.maxaltitude.MaxAltitudeListItemWidget.MaxAltitudeItemState.CurrentMaxAltitudeState -import dji.ux.beta.core.listitemwidget.maxaltitude.MaxAltitudeListItemWidget.MaxAltitudeItemState.ProductConnected -import dji.ux.beta.core.listitemwidget.maxaltitude.MaxAltitudeListItemWidgetModel.MaxAltitudeState -import dji.ux.beta.core.util.DisplayUtil +import dji.ux.beta.core.panel.listitem.maxaltitude.MaxAltitudeListItemWidget.DialogType.* +import dji.ux.beta.core.panel.listitem.maxaltitude.MaxAltitudeListItemWidget.ModelState +import dji.ux.beta.core.panel.listitem.maxaltitude.MaxAltitudeListItemWidget.ModelState.MaxAltitudeStateUpdated +import dji.ux.beta.core.panel.listitem.maxaltitude.MaxAltitudeListItemWidget.ModelState.ProductConnected +import dji.ux.beta.core.panel.listitem.maxaltitude.MaxAltitudeListItemWidgetModel.MaxAltitudeState import dji.ux.beta.core.util.UnitConversionUtil.* import kotlin.math.roundToInt @@ -61,10 +64,15 @@ open class MaxAltitudeListItemWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleArr: Int = 0 -) : ListItemEditTextButtonWidget(context, attrs, defStyleArr, WidgetType.EDIT) { - - private val schedulerProvider = SchedulerProvider.getInstance() - +) : ListItemEditTextButtonWidget( + context, + attrs, + defStyleArr, + WidgetType.EDIT, + R.style.UXSDKMaxAltitudeListItem +) { + + //region Fields /** * Icon for confirmation dialog */ @@ -86,36 +94,22 @@ open class MaxAltitudeListItemWidget @JvmOverloads constructor( @StyleRes var dialogTheme: Int = R.style.UXSDKDialogTheme - init { - listItemTitle = getString(R.string.uxsdk_list_item_max_flight_altitude) - listItemTitleIcon = getDrawable(R.drawable.uxsdk_ic_max_flight_altitude) - attrs?.let { initAttributes(context, it) } - } - - @SuppressLint("Recycle") - private fun initAttributes(context: Context, attrs: AttributeSet) { - context.obtainStyledAttributes(attrs, R.styleable.MaxAltitudeListItemWidget).use { typedArray -> - toastMessagesEnabled = typedArray.getBoolean(R.styleable.MaxAltitudeListItemWidget_uxsdk_toast_messages_enabled, toastMessagesEnabled) - typedArray.getResourceIdAndUse(R.styleable.MaxAltitudeListItemWidget_uxsdk_list_item_dialog_theme) { - dialogTheme = it - } - typedArray.getDrawableAndUse(R.styleable.SDCardStatusListItemWidget_uxsdk_list_item_confirmation_dialog_icon) { - confirmationDialogIcon = it - } - typedArray.getDrawableAndUse(R.styleable.SDCardStatusListItemWidget_uxsdk_list_item_error_dialog_icon) { - errorDialogIcon = it - } - } - } private val widgetModel: MaxAltitudeListItemWidgetModel by lazy { MaxAltitudeListItemWidgetModel( DJISDKModel.getInstance(), ObservableInMemoryKeyedStore.getInstance(), - schedulerProvider, GlobalPreferencesManager.getInstance()) } + //endregion + //region Constructor + init { + initAttributes(context, attrs) + } + //endregion + + //region Lifecycle override fun onAttachedToWindow() { super.onAttachedToWindow() if (!isInEditMode) { @@ -130,22 +124,76 @@ open class MaxAltitudeListItemWidget @JvmOverloads constructor( super.onDetachedFromWindow() } - override fun onButtonClick() { - // No code required - } override fun reactToModelChanges() { addReaction(widgetModel.maxAltitudeState - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { - widgetStateDataProcessor.onNext(CurrentMaxAltitudeState(it)) + widgetStateDataProcessor.onNext(MaxAltitudeStateUpdated(it)) updateUI(it) }) addReaction(widgetModel.productConnection - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) } + override fun onButtonClick() { + // No code required + } + + override fun onEditorTextChanged(currentText: String?) { + listItemEditTextColor = if (!currentText.isNullOrBlank() + && currentText.toIntOrNull() != null + && widgetModel.isInputInRange(currentText.toInt())) { + editTextNormalColor + } else { + errorValueColor + } + + } + + override fun onKeyboardDoneAction() { + val currentValue = listItemEditTextValue?.toIntOrNull() + if (currentValue != null + && widgetModel.isInputInRange(currentValue)) { + addDisposable(widgetModel.maxAltitudeState.firstOrError() + .observeOn(SchedulerProvider.ui()) + .subscribe({ + if (it is MaxAltitudeState.MaxAltitudeValue) { + when { + it.needFlightLimit && isOverAlarmLimit(currentValue, it.unitType) -> { + val dialogDismissListener = DialogInterface.OnDismissListener { + uiUpdateStateProcessor.onNext(DialogDismissed(FlightLimitNeededError)) + } + showAlertDialog(dialogTheme = dialogTheme, + title = getString(R.string.uxsdk_list_item_max_flight_altitude), + icon = errorDialogIcon, + message = getString(R.string.uxsdk_limit_required_error), + dialogDismissListener = dialogDismissListener) + uiUpdateStateProcessor.onNext(DialogDisplayed(FlightLimitNeededError)) + resetToDefaultValue() + } + isOverAlarmLimit(currentValue, it.unitType) -> { + showOverAlarmLimitDialog(currentValue, it.returnToHomeHeight, it.unitType) + } + else -> { + verifyReturnHomeAltitudeValue(currentValue, it.returnToHomeHeight, it.unitType) + } + } + } + }, { + DJILog.d(TAG, it.message) + })) + + } else { + showToast(getString(R.string.uxsdk_list_item_value_out_of_range)) + resetToDefaultValue() + } + + } + //endregion + + //region Reactions to model private fun updateUI(maxAltitudeState: MaxAltitudeState) { when (maxAltitudeState) { MaxAltitudeState.ProductDisconnected -> updateProductDisconnectedState() @@ -192,77 +240,37 @@ open class MaxAltitudeListItemWidget @JvmOverloads constructor( isEnabled = false listItemEditTextColor = disconnectedValueColor } + //endregion + //region Helpers private fun showToast(message: String?) { if (toastMessagesEnabled) { showShortToast(message) } } - - override fun getIdealDimensionRatioString(): String? { - return null - } - - override val widgetSizeDescription: WidgetSizeDescription = - WidgetSizeDescription(WidgetSizeDescription.SizeType.OTHER, - widthDimension = WidgetSizeDescription.Dimension.EXPAND, - heightDimension = WidgetSizeDescription.Dimension.WRAP) - - override fun onKeyboardDoneAction() { - val currentValue = listItemEditTextValue?.toIntOrNull() - if (currentValue != null - && widgetModel.isInputInRange(currentValue)) { - addDisposable(widgetModel.maxAltitudeState.firstOrError() - .observeOn(schedulerProvider.ui()) - .subscribe({ - if (it is MaxAltitudeState.MaxAltitudeValue) { - when { - it.needFlightLimit && isOverAlarmLimit(currentValue, it.unitType) -> { - showAlertDialog(dialogTheme = dialogTheme, - title = getString(R.string.uxsdk_list_item_max_flight_altitude), - icon = errorDialogIcon, - message = getString(R.string.uxsdk_limit_required_error)) - uiUpdateStateProcessor.onNext(DialogDisplayed(FlightLimitNeededErrorDialog)) - resetToDefaultValue() - } - isOverAlarmLimit(currentValue, it.unitType) -> { - showOverAlarmLimitDialog(currentValue, it.returnToHomeHeight, it.unitType) - } - else -> { - verifyReturnHomeAltitudeValue(currentValue, it.returnToHomeHeight, it.unitType) - } - } - } - }, { - DJILog.d(TAG, it.message) - })) - - } else { - showToast(getString(R.string.uxsdk_list_item_value_out_of_range)) - resetToDefaultValue() - } - - } - private fun showOverAlarmLimitDialog(currentValue: Int, currentReturnToHomeValue: Int, unitType: UnitType) { val dialogListener = DialogInterface.OnClickListener { dialogInterface, buttonId: Int -> if (buttonId == DialogInterface.BUTTON_POSITIVE) { verifyReturnHomeAltitudeValue(currentValue, currentReturnToHomeValue, unitType) - uiUpdateStateProcessor.onNext(DialogActionConfirm(MaxAltitudeOverAlarmConfirmationDialog)) + uiUpdateStateProcessor.onNext(DialogActionConfirmed(MaxAltitudeOverAlarmConfirmation)) } else { + uiUpdateStateProcessor.onNext(DialogActionCanceled(MaxAltitudeOverAlarmConfirmation)) resetToDefaultValue() } dialogInterface.dismiss() - uiUpdateStateProcessor.onNext(DialogActionDismiss(MaxAltitudeOverAlarmConfirmationDialog)) } + val dialogDismissListener = DialogInterface.OnDismissListener { + uiUpdateStateProcessor.onNext(DialogDismissed(MaxAltitudeOverAlarmConfirmation)) + } showConfirmationDialog(dialogTheme = dialogTheme, title = getString(R.string.uxsdk_list_item_max_flight_altitude), icon = confirmationDialogIcon, message = getString(R.string.uxsdk_limit_high_notice), - dialogClickListener = dialogListener) - uiUpdateStateProcessor.onNext(DialogDisplayed(MaxAltitudeOverAlarmConfirmationDialog)) + dialogClickListener = dialogListener, + dialogDismissListener = dialogDismissListener) + uiUpdateStateProcessor.onNext(DialogDisplayed(MaxAltitudeOverAlarmConfirmation)) } @@ -290,21 +298,25 @@ open class MaxAltitudeListItemWidget @JvmOverloads constructor( val dialogListener = DialogInterface.OnClickListener { dialogInterface, buttonId: Int -> if (buttonId == DialogInterface.BUTTON_POSITIVE) { setMaxAltitudeValue(currentValue) - uiUpdateStateProcessor.onNext(DialogActionConfirm(ReturnHomeAltitudeUpdateDialog)) + uiUpdateStateProcessor.onNext(DialogActionConfirmed(ReturnHomeAltitudeUpdate)) } else { + uiUpdateStateProcessor.onNext(DialogActionCanceled(ReturnHomeAltitudeUpdate)) resetToDefaultValue() } dialogInterface.dismiss() - uiUpdateStateProcessor.onNext(DialogActionDismiss(ReturnHomeAltitudeUpdateDialog)) + } + val dialogDismissListener = DialogInterface.OnDismissListener { + uiUpdateStateProcessor.onNext(DialogDismissed(ReturnHomeAltitudeUpdate)) } showConfirmationDialog(dialogTheme = dialogTheme, icon = confirmationDialogIcon, title = getString(R.string.uxsdk_list_item_max_flight_altitude), message = String.format(getString(R.string.uxsdk_limit_return_home_warning), imperialHeight, metricHeight), - dialogClickListener = dialogListener) - uiUpdateStateProcessor.onNext(DialogDisplayed(ReturnHomeAltitudeUpdateDialog)) + dialogClickListener = dialogListener, + dialogDismissListener = dialogDismissListener) + uiUpdateStateProcessor.onNext(DialogDisplayed(ReturnHomeAltitudeUpdate)) } else { setMaxAltitudeValue(currentValue) } @@ -313,102 +325,123 @@ open class MaxAltitudeListItemWidget @JvmOverloads constructor( private fun setMaxAltitudeValue(currentValue: Int) { addDisposable(widgetModel.setFlightMaxAltitude(currentValue) - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe({ showToast(getString(R.string.uxsdk_success)) - widgetStateDataProcessor.onNext(MaxAltitudeItemState.SetMaxAltitudeSuccess) + widgetStateDataProcessor.onNext(ModelState.SetMaxAltitudeSucceeded) }, { error -> resetToDefaultValue() if (error is UXSDKError) { showToast(error.djiError.description) - widgetStateDataProcessor.onNext(MaxAltitudeItemState.SetMaxAltitudeFailed(error)) + widgetStateDataProcessor.onNext(ModelState.SetMaxAltitudeFailed(error)) } })) } private fun resetToDefaultValue() { addDisposable(widgetModel.maxAltitudeState.firstOrError() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe({ updateUI(it) }, { DJILog.e(TAG, it.message) })) } + //endregion - override fun onEditorTextChanged(currentText: String?) { - listItemEditTextColor = if (!currentText.isNullOrBlank() - && currentText.toIntOrNull() != null - && widgetModel.isInputInRange(currentText.toInt())) { - editTextNormalColor - } else { - errorValueColor - } + //region Customization + override fun getIdealDimensionRatioString(): String? { + return null + } + override val widgetSizeDescription: WidgetSizeDescription = + WidgetSizeDescription(WidgetSizeDescription.SizeType.OTHER, + widthDimension = WidgetSizeDescription.Dimension.EXPAND, + heightDimension = WidgetSizeDescription.Dimension.WRAP) + + @SuppressLint("Recycle") + private fun initAttributes(context: Context, attrs: AttributeSet?) { + context.obtainStyledAttributes(attrs, R.styleable.MaxAltitudeListItemWidget, 0, defaultStyle).use { typedArray -> + toastMessagesEnabled = typedArray.getBoolean(R.styleable.MaxAltitudeListItemWidget_uxsdk_toast_messages_enabled, toastMessagesEnabled) + typedArray.getResourceIdAndUse(R.styleable.MaxAltitudeListItemWidget_uxsdk_list_item_dialog_theme) { + dialogTheme = it + } + typedArray.getDrawableAndUse(R.styleable.SDCardStatusListItemWidget_uxsdk_list_item_confirmation_dialog_icon) { + confirmationDialogIcon = it + } + typedArray.getDrawableAndUse(R.styleable.SDCardStatusListItemWidget_uxsdk_list_item_error_dialog_icon) { + errorDialogIcon = it + } + } } + //endregion + //region Hooks /** - * Get the [MaxAltitudeItemState] updates + * Get the [ListItemEditTextButtonWidget.UIState] updates + * The info parameter is instance of [DialogType] */ - override fun getWidgetStateUpdate(): Flowable { - return super.getWidgetStateUpdate() + override fun getUIStateUpdates(): Flowable { + return uiUpdateStateProcessor.onBackpressureBuffer() } /** - * Get the [ListItemEditTextButtonWidget.WidgetUIState] updates - * The info parameter is instance of [MaxAltitudeItemDialogState] + * Get the [ModelState] updates */ - override fun getUIStateUpdates(): Flowable { - return uiUpdateStateProcessor + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { + return super.getWidgetStateUpdate() } /** * Max altitude List Item Dialog Identifiers */ - sealed class MaxAltitudeItemDialogState { + sealed class DialogType { /** * Dialog shown when max altitude is over alarm * levels. */ - object MaxAltitudeOverAlarmConfirmationDialog : MaxAltitudeItemDialogState() + object MaxAltitudeOverAlarmConfirmation : DialogType() /** * Dialog shown when flight limit is restricted and the user * tries to set a higher value */ - object FlightLimitNeededErrorDialog : MaxAltitudeItemDialogState() + object FlightLimitNeededError : DialogType() /** * Dialog shown to confirm that the user will have to update return home * altitude along with max flight limit */ - object ReturnHomeAltitudeUpdateDialog : MaxAltitudeItemDialogState() + object ReturnHomeAltitudeUpdate : DialogType() } /** * Class defines widget state updates */ - sealed class MaxAltitudeItemState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : MaxAltitudeItemState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * Max altitude set action successful */ - object SetMaxAltitudeSuccess : MaxAltitudeItemState() + object SetMaxAltitudeSucceeded : ModelState() /** * Max altitude set action failed */ - data class SetMaxAltitudeFailed(val error: UXSDKError) : MaxAltitudeItemState() + data class SetMaxAltitudeFailed(val error: UXSDKError) : ModelState() /** - * Current max altitude state + * Max altitude state update */ - data class CurrentMaxAltitudeState(val maxAltitudeState: MaxAltitudeState) : MaxAltitudeItemState() + data class MaxAltitudeStateUpdated(val maxAltitudeState: MaxAltitudeState) : ModelState() } + //endregion + } \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/maxaltitude/MaxAltitudeListItemWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/maxaltitude/MaxAltitudeListItemWidgetModel.kt similarity index 94% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/maxaltitude/MaxAltitudeListItemWidgetModel.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/maxaltitude/MaxAltitudeListItemWidgetModel.kt index f059ed83..e904b562 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/maxaltitude/MaxAltitudeListItemWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/maxaltitude/MaxAltitudeListItemWidgetModel.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.listitemwidget.maxaltitude +package dji.ux.beta.core.panel.listitem.maxaltitude import dji.common.util.DJIParamMinMaxCapability import dji.keysdk.DJIKey @@ -30,13 +30,12 @@ import dji.log.DJILog import dji.thirdparty.io.reactivex.Completable import dji.thirdparty.io.reactivex.Flowable import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.GlobalPreferencesInterface -import dji.ux.beta.core.base.SchedulerProviderInterface import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.GlobalPreferenceKeys -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.listitemwidget.maxaltitude.MaxAltitudeListItemWidgetModel.MaxAltitudeState.MaxAltitudeValue -import dji.ux.beta.core.listitemwidget.maxaltitude.MaxAltitudeListItemWidgetModel.MaxAltitudeState.ProductDisconnected +import dji.ux.beta.core.communication.GlobalPreferenceKeys +import dji.ux.beta.core.communication.GlobalPreferencesInterface +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.panel.listitem.maxaltitude.MaxAltitudeListItemWidgetModel.MaxAltitudeState.MaxAltitudeValue +import dji.ux.beta.core.panel.listitem.maxaltitude.MaxAltitudeListItemWidgetModel.MaxAltitudeState.ProductDisconnected import dji.ux.beta.core.util.DataProcessor import dji.ux.beta.core.util.UnitConversionUtil.* import kotlin.math.roundToInt @@ -50,7 +49,6 @@ private const val TAG = "MaxAltitudeListItemWidgetModel" class MaxAltitudeListItemWidgetModel( djiSdkModel: DJISDKModel, keyedStore: ObservableInMemoryKeyedStore, - private val schedulerProvider: SchedulerProviderInterface, private val preferencesManager: GlobalPreferencesInterface? ) : WidgetModel(djiSdkModel, keyedStore) { @@ -168,7 +166,7 @@ class MaxAltitudeListItemWidgetModel( } else { maxAltitudeLimit } - return djiSdkModel.setValue(maxFlightHeightKey, tempLimit).subscribeOn(schedulerProvider.io()) + return djiSdkModel.setValue(maxFlightHeightKey, tempLimit) .doOnComplete { if (tempLimit < returnHomeFlightHeightProcessor.value) { addDisposable(setReturnHomeMaxAltitude(tempLimit).subscribe({ @@ -181,7 +179,7 @@ class MaxAltitudeListItemWidgetModel( private fun setReturnHomeMaxAltitude(maxAltitudeLimit: Int): Completable { - return djiSdkModel.setValue(returnHomeFlightHeightKey, maxAltitudeLimit).subscribeOn(schedulerProvider.io()) + return djiSdkModel.setValue(returnHomeFlightHeightKey, maxAltitudeLimit) } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/maxflightdistance/MaxFlightDistanceListItemWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/maxflightdistance/MaxFlightDistanceListItemWidget.kt similarity index 78% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/maxflightdistance/MaxFlightDistanceListItemWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/maxflightdistance/MaxFlightDistanceListItemWidget.kt index 55b90a5e..d6e29026 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/maxflightdistance/MaxFlightDistanceListItemWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/maxflightdistance/MaxFlightDistanceListItemWidget.kt @@ -21,24 +21,30 @@ * */ -package dji.ux.beta.core.listitemwidget.maxflightdistance +package dji.ux.beta.core.panel.listitem.maxflightdistance import android.annotation.SuppressLint import android.content.Context import android.util.AttributeSet import androidx.core.content.res.use import dji.log.DJILog -import dji.ux.beta.R -import dji.ux.beta.core.base.* -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.base.widget.ListItemEditTextButtonWidget -import dji.ux.beta.core.extension.* -import dji.ux.beta.core.listitemwidget.maxflightdistance.MaxFlightDistanceListItemWidget.MaxFlightDistanceItemState -import dji.ux.beta.core.listitemwidget.maxflightdistance.MaxFlightDistanceListItemWidget.MaxFlightDistanceItemState.CurrentMaxFlightDistanceState -import dji.ux.beta.core.listitemwidget.maxflightdistance.MaxFlightDistanceListItemWidget.MaxFlightDistanceItemState.ProductConnected -import dji.ux.beta.core.listitemwidget.maxflightdistance.MaxFlightDistanceListItemWidgetModel.MaxFlightDistanceState -import dji.ux.beta.core.listitemwidget.maxflightdistance.MaxFlightDistanceListItemWidgetModel.MaxFlightDistanceState.MaxFlightDistanceValue -import dji.ux.beta.core.util.DisplayUtil +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.R +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.SchedulerProvider +import dji.ux.beta.core.base.UXSDKError +import dji.ux.beta.core.base.WidgetSizeDescription +import dji.ux.beta.core.base.panel.listitem.ListItemEditTextButtonWidget +import dji.ux.beta.core.communication.GlobalPreferencesManager +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.extension.getString +import dji.ux.beta.core.extension.getStringAndUse +import dji.ux.beta.core.extension.showShortToast +import dji.ux.beta.core.panel.listitem.maxflightdistance.MaxFlightDistanceListItemWidget.ModelState +import dji.ux.beta.core.panel.listitem.maxflightdistance.MaxFlightDistanceListItemWidget.ModelState.MaxFlightDistanceStateUpdated +import dji.ux.beta.core.panel.listitem.maxflightdistance.MaxFlightDistanceListItemWidget.ModelState.ProductConnected +import dji.ux.beta.core.panel.listitem.maxflightdistance.MaxFlightDistanceListItemWidgetModel.MaxFlightDistanceState +import dji.ux.beta.core.panel.listitem.maxflightdistance.MaxFlightDistanceListItemWidgetModel.MaxFlightDistanceState.MaxFlightDistanceValue import dji.ux.beta.core.util.UnitConversionUtil.UnitType private const val TAG = "MaxFlightDistanceItem" @@ -53,15 +59,19 @@ open class MaxFlightDistanceListItemWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleArr: Int = 0 -) : ListItemEditTextButtonWidget(context, attrs, defStyleArr, WidgetType.EDIT_BUTTON) { - - private val schedulerProvider = SchedulerProvider.getInstance() - +) : ListItemEditTextButtonWidget( + context, + attrs, + defStyleArr, + WidgetType.EDIT_BUTTON, + R.style.UXSDKMaxFlightDistanceListItem +) { + + //region Fields private val widgetModel: MaxFlightDistanceListItemWidgetModel by lazy { MaxFlightDistanceListItemWidgetModel( DJISDKModel.getInstance(), ObservableInMemoryKeyedStore.getInstance(), - schedulerProvider, GlobalPreferencesManager.getInstance()) } @@ -80,26 +90,15 @@ open class MaxFlightDistanceListItemWidget @JvmOverloads constructor( */ var toastMessagesEnabled: Boolean = true - init { - listItemTitle = getString(R.string.uxsdk_list_item_max_flight_distance) - listItemTitleIcon = getDrawable(R.drawable.uxsdk_ic_max_flight_distance) - attrs?.let { initAttributes(context, it) } - - } + //endregion - @SuppressLint("Recycle") - private fun initAttributes(context: Context, attrs: AttributeSet) { - context.obtainStyledAttributes(attrs, R.styleable.MaxFlightDistanceListItemWidget).use { typedArray -> - toastMessagesEnabled = typedArray.getBoolean(R.styleable.MaxFlightDistanceListItemWidget_uxsdk_toast_messages_enabled, toastMessagesEnabled) - typedArray.getStringAndUse(R.styleable.MaxFlightDistanceListItemWidget_uxsdk_enable_action_button_string) { - enableActionButtonString = it - } - typedArray.getStringAndUse(R.styleable.MaxFlightDistanceListItemWidget_uxsdk_disable_action_button_string) { - disableActionButtonString = it - } - } + //region Constructor + init { + initAttributes(context, attrs) } + //endregion + //region Lifecycle override fun onAttachedToWindow() { super.onAttachedToWindow() if (!isInEditMode) { @@ -116,7 +115,7 @@ open class MaxFlightDistanceListItemWidget @JvmOverloads constructor( override fun onButtonClick() { addDisposable(widgetModel.toggleFlightDistanceAvailability() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe({ }, { error -> if (error is UXSDKError) { @@ -128,16 +127,38 @@ open class MaxFlightDistanceListItemWidget @JvmOverloads constructor( override fun reactToModelChanges() { addReaction(widgetModel.maxFlightDistanceState - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { - widgetStateDataProcessor.onNext(CurrentMaxFlightDistanceState(it)) + widgetStateDataProcessor.onNext(MaxFlightDistanceStateUpdated(it)) updateUI(it) }) addReaction(widgetModel.productConnection - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) } + override fun onKeyboardDoneAction() { + val currentValue = listItemEditTextValue?.toIntOrNull() + if (currentValue == null || !widgetModel.isInputInRange(currentValue)) { + showToast(getString(R.string.uxsdk_list_item_value_out_of_range)) + resetToDefaultValue() + } else { + setMaxFlightDistance(currentValue) + } + } + + override fun onEditorTextChanged(currentText: String?) { + listItemEditTextColor = if (!currentText.isNullOrBlank() + && currentText.toIntOrNull() != null + && widgetModel.isInputInRange(currentText.toInt())) { + editTextNormalColor + } else { + errorValueColor + } + } + //endregion + + //region Reactions to model private fun updateUI(maxFlightDistanceState: MaxFlightDistanceState) { when (maxFlightDistanceState) { MaxFlightDistanceState.ProductDisconnected -> updateProductDisconnectedState() @@ -197,7 +218,9 @@ open class MaxFlightDistanceListItemWidget @JvmOverloads constructor( isEnabled = false } + //endregion + //region Helpers private fun showToast(message: String?) { if (toastMessagesEnabled) { showShortToast(message) @@ -206,7 +229,7 @@ open class MaxFlightDistanceListItemWidget @JvmOverloads constructor( private fun resetToDefaultValue() { addDisposable(widgetModel.maxFlightDistanceState.firstOrError() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe({ updateUI(it) }, { @@ -216,20 +239,34 @@ open class MaxFlightDistanceListItemWidget @JvmOverloads constructor( private fun setMaxFlightDistance(maxFlightDistance: Int) { addDisposable(widgetModel.setMaxFlightDistance(maxFlightDistance) - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe({ showToast(getString(R.string.uxsdk_success)) - widgetStateDataProcessor.onNext(MaxFlightDistanceItemState.SetMaxFlightDistanceSuccess) + widgetStateDataProcessor.onNext(ModelState.SetMaxFlightDistanceSucceeded) }, { if (it is UXSDKError) { showToast(it.djiError.description) DJILog.e(TAG, it.djiError.description) - widgetStateDataProcessor.onNext(MaxFlightDistanceItemState.SetMaxFlightDistanceFailed(it)) + widgetStateDataProcessor.onNext(ModelState.SetMaxFlightDistanceFailed(it)) } resetToDefaultValue() })) } + //endregion + //region Customization + @SuppressLint("Recycle") + private fun initAttributes(context: Context, attrs: AttributeSet?) { + context.obtainStyledAttributes(attrs, R.styleable.MaxFlightDistanceListItemWidget, 0, defaultStyle).use { typedArray -> + toastMessagesEnabled = typedArray.getBoolean(R.styleable.MaxFlightDistanceListItemWidget_uxsdk_toast_messages_enabled, toastMessagesEnabled) + typedArray.getStringAndUse(R.styleable.MaxFlightDistanceListItemWidget_uxsdk_enable_action_button_string) { + enableActionButtonString = it + } + typedArray.getStringAndUse(R.styleable.MaxFlightDistanceListItemWidget_uxsdk_disable_action_button_string) { + disableActionButtonString = it + } + } + } override fun getIdealDimensionRatioString(): String? { return null @@ -240,49 +277,41 @@ open class MaxFlightDistanceListItemWidget @JvmOverloads constructor( widthDimension = WidgetSizeDescription.Dimension.EXPAND, heightDimension = WidgetSizeDescription.Dimension.WRAP) - override fun onKeyboardDoneAction() { - val currentValue = listItemEditTextValue?.toIntOrNull() - if (currentValue == null || !widgetModel.isInputInRange(currentValue)) { - showToast(getString(R.string.uxsdk_list_item_value_out_of_range)) - resetToDefaultValue() - } else { - setMaxFlightDistance(currentValue) - } - } + //endregion - override fun onEditorTextChanged(currentText: String?) { - listItemEditTextColor = if (!currentText.isNullOrBlank() - && currentText.toIntOrNull() != null - && widgetModel.isInputInRange(currentText.toInt())) { - editTextNormalColor - } else { - errorValueColor - } + //region Hooks + + /** + * Get the [ModelState] updates + */ + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { + return super.getWidgetStateUpdate() } /** * Class defines widget state updates */ - sealed class MaxFlightDistanceItemState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : MaxFlightDistanceItemState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * Max flight distance set action successful */ - object SetMaxFlightDistanceSuccess : MaxFlightDistanceItemState() + object SetMaxFlightDistanceSucceeded : ModelState() /** * Max flight distance set action failed */ - data class SetMaxFlightDistanceFailed(val error: UXSDKError) : MaxFlightDistanceItemState() + data class SetMaxFlightDistanceFailed(val error: UXSDKError) : ModelState() /** - * Current max flight distance state + * Max flight distance state updated */ - data class CurrentMaxFlightDistanceState(val maxFlightDistanceState: MaxFlightDistanceState) : MaxFlightDistanceItemState() + data class MaxFlightDistanceStateUpdated(val maxFlightDistanceState: MaxFlightDistanceState) : ModelState() } - + //endregion } \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/maxflightdistance/MaxFlightDistanceListItemWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/maxflightdistance/MaxFlightDistanceListItemWidgetModel.kt similarity index 93% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/maxflightdistance/MaxFlightDistanceListItemWidgetModel.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/maxflightdistance/MaxFlightDistanceListItemWidgetModel.kt index 77cd6bfc..77b35f76 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/maxflightdistance/MaxFlightDistanceListItemWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/maxflightdistance/MaxFlightDistanceListItemWidgetModel.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.listitemwidget.maxflightdistance +package dji.ux.beta.core.panel.listitem.maxflightdistance import dji.common.util.DJIParamMinMaxCapability import dji.keysdk.DJIKey @@ -29,12 +29,11 @@ import dji.keysdk.FlightControllerKey import dji.thirdparty.io.reactivex.Completable import dji.thirdparty.io.reactivex.Flowable import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.GlobalPreferencesInterface -import dji.ux.beta.core.base.SchedulerProviderInterface import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.GlobalPreferenceKeys -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.listitemwidget.maxflightdistance.MaxFlightDistanceListItemWidgetModel.MaxFlightDistanceState.* +import dji.ux.beta.core.communication.GlobalPreferenceKeys +import dji.ux.beta.core.communication.GlobalPreferencesInterface +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.panel.listitem.maxflightdistance.MaxFlightDistanceListItemWidgetModel.MaxFlightDistanceState.* import dji.ux.beta.core.util.DataProcessor import dji.ux.beta.core.util.UnitConversionUtil.* import kotlin.math.roundToInt @@ -46,7 +45,6 @@ import kotlin.math.roundToInt class MaxFlightDistanceListItemWidgetModel( djiSdkModel: DJISDKModel, keyedStore: ObservableInMemoryKeyedStore, - private val schedulerProvider: SchedulerProviderInterface, private val preferencesManager: GlobalPreferencesInterface? ) : WidgetModel(djiSdkModel, keyedStore) { @@ -136,7 +134,6 @@ class MaxFlightDistanceListItemWidgetModel( */ fun toggleFlightDistanceAvailability(): Completable { return djiSdkModel.setValue(maxFlightDistanceEnabledKey, !maxFlightDistanceEnabledProcessor.value) - .subscribeOn(schedulerProvider.io()) } /** @@ -151,7 +148,7 @@ class MaxFlightDistanceListItemWidgetModel( flightDistance } return djiSdkModel.setValue(maxFlightDistanceKey, tempFlightDistance) - .subscribeOn(schedulerProvider.io()) + } /** diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/novicemode/NoviceModeListItemWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/novicemode/NoviceModeListItemWidget.kt similarity index 68% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/novicemode/NoviceModeListItemWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/novicemode/NoviceModeListItemWidget.kt index 509eba09..d869d2a3 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/novicemode/NoviceModeListItemWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/novicemode/NoviceModeListItemWidget.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.listitemwidget.novicemode +package dji.ux.beta.core.panel.listitem.novicemode import android.annotation.SuppressLint import android.content.Context @@ -31,21 +31,22 @@ import android.util.AttributeSet import androidx.annotation.StyleRes import androidx.core.content.res.use import dji.log.DJILog -import dji.ux.beta.R +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.R import dji.ux.beta.core.base.DJISDKModel import dji.ux.beta.core.base.SchedulerProvider import dji.ux.beta.core.base.UXSDKError import dji.ux.beta.core.base.WidgetSizeDescription -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.base.widget.ListItemSwitchWidget -import dji.ux.beta.core.base.widget.ListItemSwitchWidget.WidgetUIState.* +import dji.ux.beta.core.base.panel.listitem.ListItemSwitchWidget +import dji.ux.beta.core.base.panel.listitem.ListItemSwitchWidget.UIState.* +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.* -import dji.ux.beta.core.listitemwidget.novicemode.NoviceModeListItemWidget.NoviceModeItemDialogState.NoviceModeDisableConfirmation -import dji.ux.beta.core.listitemwidget.novicemode.NoviceModeListItemWidget.NoviceModeItemDialogState.NoviceModeEnableSuccess -import dji.ux.beta.core.listitemwidget.novicemode.NoviceModeListItemWidget.NoviceModeListItemState -import dji.ux.beta.core.listitemwidget.novicemode.NoviceModeListItemWidget.NoviceModeListItemState.CurrentNoviceModeListItemState -import dji.ux.beta.core.listitemwidget.novicemode.NoviceModeListItemWidget.NoviceModeListItemState.ProductConnected -import dji.ux.beta.core.listitemwidget.novicemode.NoviceModeListItemWidgetModel.NoviceModeState +import dji.ux.beta.core.panel.listitem.novicemode.NoviceModeListItemWidget.DialogType.NoviceModeDisableConfirmation +import dji.ux.beta.core.panel.listitem.novicemode.NoviceModeListItemWidget.DialogType.NoviceModeEnabled +import dji.ux.beta.core.panel.listitem.novicemode.NoviceModeListItemWidget.ModelState +import dji.ux.beta.core.panel.listitem.novicemode.NoviceModeListItemWidget.ModelState.NoviceModeStateUpdated +import dji.ux.beta.core.panel.listitem.novicemode.NoviceModeListItemWidget.ModelState.ProductConnected +import dji.ux.beta.core.panel.listitem.novicemode.NoviceModeListItemWidgetModel.NoviceModeState private const val TAG = "NoviceModeListItemW" @@ -58,16 +59,18 @@ open class NoviceModeListItemWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleArr: Int = 0 -) : ListItemSwitchWidget(context, attrs, defStyleArr) { - - - private val schedulerProvider = SchedulerProvider.getInstance() +) : ListItemSwitchWidget( + context, + attrs, + defStyleArr, + R.style.UXSDKNoviceModeListItem +) { + //region Fields private val widgetModel: NoviceModeListItemWidgetModel by lazy { NoviceModeListItemWidgetModel( DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - schedulerProvider) + ObservableInMemoryKeyedStore.getInstance()) } /** @@ -86,58 +89,27 @@ open class NoviceModeListItemWidget @JvmOverloads constructor( */ @StyleRes var dialogTheme: Int = R.style.UXSDKDialogTheme + //endregion + //region Constructor init { - listItemTitleIcon = getDrawable(R.drawable.uxsdk_ic_system_status_list_novice_mode) - listItemTitle = getString(R.string.uxsdk_list_item_novice_mode) - attrs?.let { initAttributes(context, it) } - } - - @SuppressLint("Recycle") - private fun initAttributes(context: Context, attrs: AttributeSet) { - context.obtainStyledAttributes(attrs, R.styleable.NoviceModeListItemWidget).use { typedArray -> - typedArray.getDrawableAndUse(R.styleable.NoviceModeListItemWidget_uxsdk_list_item_confirmation_dialog_icon) { - confirmationDialogIcon = it - } - typedArray.getDrawableAndUse(R.styleable.NoviceModeListItemWidget_uxsdk_list_item_success_dialog_icon) { - successDialogIcon = it - } - typedArray.getResourceIdAndUse(R.styleable.NoviceModeListItemWidget_uxsdk_list_item_dialog_theme) { - dialogTheme = it - } - - } + initAttributes(context, attrs) } + //endregion + //region Lifecycle override fun reactToModelChanges() { addReaction(widgetModel.productConnection - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) addReaction(widgetModel.noviceModeState - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { - widgetStateDataProcessor.onNext(CurrentNoviceModeListItemState(it)) + widgetStateDataProcessor.onNext(NoviceModeStateUpdated(it)) updateUI(it) }) } - private fun updateUI(noviceModeState: NoviceModeState) { - when (noviceModeState) { - NoviceModeState.ProductDisconnected -> isEnabled = false - NoviceModeState.Enabled -> { - updateState(true) - } - NoviceModeState.Disabled -> { - updateState(false) - } - } - } - - private fun updateState(noviceModeEnabled: Boolean) { - isEnabled = true - setChecked(noviceModeEnabled) - } - override fun onAttachedToWindow() { super.onAttachedToWindow() if (!isInEditMode) { @@ -156,13 +128,34 @@ open class NoviceModeListItemWidget @JvmOverloads constructor( if (isChecked) { toggleNoviceMode(isChecked) } else { - showConfirmationDialog() + showNoviceModeConfirmationDialog() } } + //endregion + //region Reactions to Model + private fun updateUI(noviceModeState: NoviceModeState) { + when (noviceModeState) { + NoviceModeState.ProductDisconnected -> isEnabled = false + NoviceModeState.Enabled -> { + updateState(true) + } + NoviceModeState.Disabled -> { + updateState(false) + } + } + } + + private fun updateState(noviceModeEnabled: Boolean) { + isEnabled = true + setChecked(noviceModeEnabled) + } + //endregion + + //region Helpers private fun toggleNoviceMode(checked: Boolean) { addDisposable(widgetModel.toggleNoviceMode() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe({ if (checked) { showEnabledDialog() @@ -176,43 +169,53 @@ open class NoviceModeListItemWidget @JvmOverloads constructor( })) } - private fun showConfirmationDialog() { + private fun showNoviceModeConfirmationDialog() { val dialogListener = DialogInterface.OnClickListener { dialogInterface, buttonId: Int -> if (buttonId == DialogInterface.BUTTON_POSITIVE) { toggleNoviceMode(false) - uiUpdateStateProcessor.onNext(DialogActionConfirm(NoviceModeDisableConfirmation)) + uiUpdateStateProcessor.onNext(DialogActionConfirmed(NoviceModeDisableConfirmation)) } else { + uiUpdateStateProcessor.onNext(DialogActionCanceled(NoviceModeDisableConfirmation)) resetSwitchState() } dialogInterface.dismiss() - uiUpdateStateProcessor.onNext(DialogActionDismiss(NoviceModeDisableConfirmation)) + } + val dialogDismissListener = DialogInterface.OnDismissListener { + uiUpdateStateProcessor.onNext(DialogDismissed(NoviceModeDisableConfirmation)) } showConfirmationDialog(dialogTheme = dialogTheme, icon = confirmationDialogIcon, title = getString(R.string.uxsdk_list_item_novice_mode), message = getString(R.string.uxsdk_novice_mode_disabled_message), - dialogClickListener = dialogListener) + dialogClickListener = dialogListener, + dialogDismissListener = dialogDismissListener) uiUpdateStateProcessor.onNext(DialogDisplayed(NoviceModeDisableConfirmation)) } private fun showEnabledDialog() { + val dialogDismissListener = DialogInterface.OnDismissListener { + uiUpdateStateProcessor.onNext(DialogDismissed(NoviceModeEnabled)) + } showAlertDialog(dialogTheme = dialogTheme, icon = successDialogIcon, title = getString(R.string.uxsdk_list_item_novice_mode), - message = getString(R.string.uxsdk_novice_mode_enabled_message)) - uiUpdateStateProcessor.onNext(DialogDisplayed(NoviceModeEnableSuccess)) + message = getString(R.string.uxsdk_novice_mode_enabled_message), + dialogDismissListener = dialogDismissListener) + uiUpdateStateProcessor.onNext(DialogDisplayed(NoviceModeEnabled)) } private fun resetSwitchState() { addDisposable(widgetModel.noviceModeState.firstOrError() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe({ updateUI(it) }, { DJILog.e(TAG, it.message) })) } + //endregion + //region Customization override fun getIdealDimensionRatioString(): String? { return null } @@ -222,34 +225,70 @@ open class NoviceModeListItemWidget @JvmOverloads constructor( widthDimension = WidgetSizeDescription.Dimension.EXPAND, heightDimension = WidgetSizeDescription.Dimension.WRAP) + @SuppressLint("Recycle") + private fun initAttributes(context: Context, attrs: AttributeSet?) { + context.obtainStyledAttributes(attrs, R.styleable.NoviceModeListItemWidget, 0, defaultStyle).use { typedArray -> + typedArray.getDrawableAndUse(R.styleable.NoviceModeListItemWidget_uxsdk_list_item_confirmation_dialog_icon) { + confirmationDialogIcon = it + } + typedArray.getDrawableAndUse(R.styleable.NoviceModeListItemWidget_uxsdk_list_item_success_dialog_icon) { + successDialogIcon = it + } + typedArray.getResourceIdAndUse(R.styleable.NoviceModeListItemWidget_uxsdk_list_item_dialog_theme) { + dialogTheme = it + } + + } + } + //endregion + + //region Hooks + + /** + * Get the [ListItemSwitchWidget.UIState] updates + * The info parameter is instance of [DialogType] + */ + override fun getUIStateUpdates(): Flowable { + return uiUpdateStateProcessor.onBackpressureBuffer() + } + + /** + * Get the [ModelState] updates + */ + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { + return super.getWidgetStateUpdate() + } + /** * Novice mode dialog identifiers */ - sealed class NoviceModeItemDialogState { + sealed class DialogType { /** * Dialog shown when novice mode is enabled successfully */ - object NoviceModeEnableSuccess : NoviceModeItemDialogState() + object NoviceModeEnabled : DialogType() /** * Dialog shown when switching from enabled to disabled */ - object NoviceModeDisableConfirmation : NoviceModeItemDialogState() + object NoviceModeDisableConfirmation : DialogType() } /** * Class defines widget state updates */ - sealed class NoviceModeListItemState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : NoviceModeListItemState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * Current novice mode state */ - data class CurrentNoviceModeListItemState(val noviceModeState: NoviceModeState) : NoviceModeListItemState() + data class NoviceModeStateUpdated(val noviceModeState: NoviceModeState) : ModelState() } + //endregion } \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/novicemode/NoviceModeListItemWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/novicemode/NoviceModeListItemWidgetModel.kt similarity index 89% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/novicemode/NoviceModeListItemWidgetModel.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/novicemode/NoviceModeListItemWidgetModel.kt index a1cda72d..dbad1c88 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/novicemode/NoviceModeListItemWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/novicemode/NoviceModeListItemWidgetModel.kt @@ -21,17 +21,16 @@ * */ -package dji.ux.beta.core.listitemwidget.novicemode +package dji.ux.beta.core.panel.listitem.novicemode import dji.keysdk.DJIKey import dji.keysdk.FlightControllerKey import dji.thirdparty.io.reactivex.Completable import dji.thirdparty.io.reactivex.Flowable import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.SchedulerProviderInterface import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.listitemwidget.novicemode.NoviceModeListItemWidgetModel.NoviceModeState.ProductDisconnected +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.panel.listitem.novicemode.NoviceModeListItemWidgetModel.NoviceModeState.ProductDisconnected import dji.ux.beta.core.util.DataProcessor /** @@ -40,8 +39,7 @@ import dji.ux.beta.core.util.DataProcessor */ class NoviceModeListItemWidgetModel( djiSdkModel: DJISDKModel, - keyedStore: ObservableInMemoryKeyedStore, - private val schedulerProvider: SchedulerProviderInterface + keyedStore: ObservableInMemoryKeyedStore ) : WidgetModel(djiSdkModel, keyedStore) { private val noviceModeKey: DJIKey = FlightControllerKey.create(FlightControllerKey.NOVICE_MODE_ENABLED) @@ -79,7 +77,7 @@ class NoviceModeListItemWidgetModel( * Toggle novice mode on/off */ fun toggleNoviceMode(): Completable { - return djiSdkModel.setValue(noviceModeKey, !noviceModeDataProcessor.value).subscribeOn(schedulerProvider.io()) + return djiSdkModel.setValue(noviceModeKey, !noviceModeDataProcessor.value) } /** diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/overviewstatus/OverviewStatusListItemWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/overview/OverviewListItemWidget.kt similarity index 68% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/overviewstatus/OverviewStatusListItemWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/overview/OverviewListItemWidget.kt index 62e88388..e61b0e25 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/overviewstatus/OverviewStatusListItemWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/overview/OverviewListItemWidget.kt @@ -21,53 +21,79 @@ * */ -package dji.ux.beta.core.listitemwidget.overviewstatus +package dji.ux.beta.core.panel.listitem.overview import android.content.Context import android.util.AttributeSet import dji.common.logics.warningstatuslogic.WarningStatusItem -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers -import dji.ux.beta.R +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.R import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.SchedulerProvider import dji.ux.beta.core.base.WidgetSizeDescription -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.base.widget.ListItemLabelButtonWidget -import dji.ux.beta.core.extension.getDrawable +import dji.ux.beta.core.base.panel.listitem.ListItemLabelButtonWidget +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.getString -import dji.ux.beta.core.listitemwidget.overviewstatus.OverviewListItemWidget.OverviewListItemState.CurrentOverviewStatus -import dji.ux.beta.core.listitemwidget.overviewstatus.OverviewListItemWidget.OverviewListItemState.ProductConnected -import dji.ux.beta.core.listitemwidget.overviewstatus.OverviewListItemWidgetModel.OverviewState +import dji.ux.beta.core.panel.listitem.overview.OverviewListItemWidget.ModelState +import dji.ux.beta.core.panel.listitem.overview.OverviewListItemWidget.ModelState.OverviewStateUpdated +import dji.ux.beta.core.panel.listitem.overview.OverviewListItemWidget.ModelState.ProductConnected +import dji.ux.beta.core.panel.listitem.overview.OverviewListItemWidgetModel.OverviewState /** * Widget displays the overall status of the drone */ -open class OverviewListItemWidget @JvmOverloads constructor(context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : ListItemLabelButtonWidget(context, attrs, defStyleAttr, WidgetType.LABEL) { - +open class OverviewListItemWidget @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ListItemLabelButtonWidget( + context, + attrs, + defStyleAttr, + WidgetType.LABEL, + R.style.UXSDKOverviewListItem +) { + //region Fields private val widgetModel by lazy { OverviewListItemWidgetModel(DJISDKModel.getInstance(), ObservableInMemoryKeyedStore.getInstance()) } + //endregion - init { - listItemTitleIcon = getDrawable(R.drawable.uxsdk_ic_overview_status) - listItemTitle = getString(R.string.uxsdk_list_item_overview_status) - } - + //region Lifecycle override fun reactToModelChanges() { addReaction(widgetModel.productConnection - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) addReaction(widgetModel.overviewStatus - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { updateUI(it) }) } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + if (!isInEditMode) { + widgetModel.setup() + } + } + + override fun onDetachedFromWindow() { + if (!isInEditMode) { + widgetModel.cleanup() + } + super.onDetachedFromWindow() + } + + override fun onButtonClick() { + // No code needed + } + //endregion + + //region Reactions to model private fun updateUI(overviewState: OverviewState) { - widgetStateDataProcessor.onNext(CurrentOverviewStatus(overviewState)) + widgetStateDataProcessor.onNext(OverviewStateUpdated(overviewState)) if (overviewState is OverviewState.CurrentStatus) { listItemLabelTextColor = when (overviewState.warningStatusItem.warningLevel) { WarningStatusItem.WarningLevel.NONE -> normalValueColor @@ -84,21 +110,9 @@ open class OverviewListItemWidget @JvmOverloads constructor(context: Context, isEnabled = false } } + //endregion - override fun onAttachedToWindow() { - super.onAttachedToWindow() - if (!isInEditMode) { - widgetModel.setup() - } - } - - override fun onDetachedFromWindow() { - if (!isInEditMode) { - widgetModel.cleanup() - } - super.onDetachedFromWindow() - } - + //region Customization override fun getIdealDimensionRatioString(): String? = null override val widgetSizeDescription: WidgetSizeDescription = @@ -106,22 +120,30 @@ open class OverviewListItemWidget @JvmOverloads constructor(context: Context, widthDimension = WidgetSizeDescription.Dimension.EXPAND, heightDimension = WidgetSizeDescription.Dimension.WRAP) - override fun onButtonClick() { - // No code needed + // endregion + + //region Hooks + /** + * Get the [ModelState] updates + */ + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { + return super.getWidgetStateUpdate() } /** * Class defines the widget state updates */ - sealed class OverviewListItemState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : OverviewListItemState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * Overview status update */ - data class CurrentOverviewStatus(val overviewState: OverviewState) : OverviewListItemState() + data class OverviewStateUpdated(val overviewState: OverviewState) : ModelState() } + //endregion } \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/overviewstatus/OverviewStatusListItemWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/overview/OverviewListItemWidgetModel.kt similarity index 92% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/overviewstatus/OverviewStatusListItemWidgetModel.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/overview/OverviewListItemWidgetModel.kt index 149856c7..9abf7631 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/overviewstatus/OverviewStatusListItemWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/overview/OverviewListItemWidgetModel.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.listitemwidget.overviewstatus +package dji.ux.beta.core.panel.listitem.overview import dji.common.logics.warningstatuslogic.WarningStatusItem import dji.keysdk.DJIKey @@ -29,8 +29,8 @@ import dji.keysdk.DiagnosticsKey import dji.thirdparty.io.reactivex.Flowable import dji.ux.beta.core.base.DJISDKModel import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.listitemwidget.overviewstatus.OverviewListItemWidgetModel.OverviewState.CurrentStatus +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.panel.listitem.overview.OverviewListItemWidgetModel.OverviewState.CurrentStatus import dji.ux.beta.core.util.DataProcessor /** diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/rcbattery/RCBatteryListItemWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/rcbattery/RCBatteryListItemWidget.kt similarity index 70% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/rcbattery/RCBatteryListItemWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/rcbattery/RCBatteryListItemWidget.kt index 313d4b18..7757ee2d 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/rcbattery/RCBatteryListItemWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/rcbattery/RCBatteryListItemWidget.kt @@ -21,22 +21,22 @@ * */ -package dji.ux.beta.core.listitemwidget.rcbattery +package dji.ux.beta.core.panel.listitem.rcbattery import android.content.Context import android.util.AttributeSet -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers -import dji.ux.beta.R +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.R import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.SchedulerProvider import dji.ux.beta.core.base.WidgetSizeDescription -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.base.widget.ListItemLabelButtonWidget -import dji.ux.beta.core.extension.getDrawable +import dji.ux.beta.core.base.panel.listitem.ListItemLabelButtonWidget +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.getString -import dji.ux.beta.core.listitemwidget.rcbattery.RCBatteryListItemWidget.RCBatteryListItemState -import dji.ux.beta.core.listitemwidget.rcbattery.RCBatteryListItemWidget.RCBatteryListItemState.ProductConnected -import dji.ux.beta.core.listitemwidget.rcbattery.RCBatteryListItemWidget.RCBatteryListItemState.RCBatteryStateUpdate -import dji.ux.beta.core.listitemwidget.rcbattery.RCBatteryListItemWidgetModel.RCBatteryState +import dji.ux.beta.core.panel.listitem.rcbattery.RCBatteryListItemWidget.ModelState +import dji.ux.beta.core.panel.listitem.rcbattery.RCBatteryListItemWidget.ModelState.ProductConnected +import dji.ux.beta.core.panel.listitem.rcbattery.RCBatteryListItemWidget.ModelState.RCBatteryStateUpdated +import dji.ux.beta.core.panel.listitem.rcbattery.RCBatteryListItemWidgetModel.RCBatteryState /** * Remote controller battery list item @@ -45,25 +45,29 @@ open class RCBatteryListItemWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ListItemLabelButtonWidget(context, attrs, defStyleAttr, WidgetType.LABEL) { - +) : ListItemLabelButtonWidget( + context, + attrs, + defStyleAttr, + WidgetType.LABEL, + R.style.UXSDKRCBatteryListItem +) { + + //region Fields private val widgetModel by lazy { RCBatteryListItemWidgetModel( DJISDKModel.getInstance(), ObservableInMemoryKeyedStore.getInstance()) } + //endregion - init { - listItemTitleIcon = getDrawable(R.drawable.uxsdk_ic_rc_battery) - listItemTitle = getString(R.string.uxsdk_list_item_rc_batery) - } - + //region Lifecycle override fun reactToModelChanges() { addReaction(widgetModel.productConnection - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) addReaction(widgetModel.rcBatteryState - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { this.updateUI(it) }) } @@ -81,19 +85,16 @@ open class RCBatteryListItemWidget @JvmOverloads constructor( super.onDetachedFromWindow() } - override fun getIdealDimensionRatioString(): String? = null - - - override val widgetSizeDescription: WidgetSizeDescription = - WidgetSizeDescription(WidgetSizeDescription.SizeType.OTHER, - widthDimension = WidgetSizeDescription.Dimension.EXPAND, - heightDimension = WidgetSizeDescription.Dimension.WRAP) - + override fun onButtonClick() { + // No implementation needed + } + //endregion + //region Reactions to model private fun updateUI(rcBatteryState: RCBatteryState) { - widgetStateDataProcessor.onNext(RCBatteryStateUpdate(rcBatteryState)) + widgetStateDataProcessor.onNext(RCBatteryStateUpdated(rcBatteryState)) when (rcBatteryState) { - RCBatteryState.ProductDisconnected -> { + RCBatteryState.RCDisconnected -> { listItemLabelTextColor = disconnectedValueColor listItemLabel = getString(R.string.uxsdk_string_default_value) } @@ -109,24 +110,42 @@ open class RCBatteryListItemWidget @JvmOverloads constructor( } } } + //endregion + + //region Customization + override fun getIdealDimensionRatioString(): String? = null + + + override val widgetSizeDescription: WidgetSizeDescription = + WidgetSizeDescription(WidgetSizeDescription.SizeType.OTHER, + widthDimension = WidgetSizeDescription.Dimension.EXPAND, + heightDimension = WidgetSizeDescription.Dimension.WRAP) + + //endregion + + //region Hooks + /** + * Get the [ModelState] updates + */ + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { + return super.getWidgetStateUpdate() + } /** * Class defines the widget state updates */ - sealed class RCBatteryListItemState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : RCBatteryListItemState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * RC battery State update */ - data class RCBatteryStateUpdate(val rcBatteryState: RCBatteryState) : RCBatteryListItemState() - } - - override fun onButtonClick() { - // No implementation needed + data class RCBatteryStateUpdated(val rcBatteryState: RCBatteryState) : ModelState() } + //endregion } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/rcbattery/RCBatteryListItemWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/rcbattery/RCBatteryListItemWidgetModel.kt similarity index 81% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/rcbattery/RCBatteryListItemWidgetModel.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/rcbattery/RCBatteryListItemWidgetModel.kt index 01e42e90..8f1c71ce 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/rcbattery/RCBatteryListItemWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/rcbattery/RCBatteryListItemWidgetModel.kt @@ -21,18 +21,17 @@ * */ -package dji.ux.beta.core.listitemwidget.rcbattery +package dji.ux.beta.core.panel.listitem.rcbattery import dji.common.remotecontroller.BatteryState import dji.keysdk.RemoteControllerKey import dji.thirdparty.io.reactivex.Flowable import dji.ux.beta.core.base.DJISDKModel import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.listitemwidget.rcbattery.RCBatteryListItemWidgetModel.RCBatteryState.ProductDisconnected +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.panel.listitem.rcbattery.RCBatteryListItemWidgetModel.RCBatteryState.RCDisconnected import dji.ux.beta.core.util.DataProcessor - /** * Widget Model for the [RCBatteryListItemWidget] used to define * the underlying logic and communication @@ -46,9 +45,9 @@ class RCBatteryListItemWidgetModel( //region Fields private val rcBatteryLevelProcessor: DataProcessor = DataProcessor.create(BatteryState(0, 0)) - private val rcBatteryStateProcessor: DataProcessor = DataProcessor.create(ProductDisconnected) + private val rcBatteryStateProcessor: DataProcessor = DataProcessor.create(RCDisconnected) private val rcBatteryLowProcessor: DataProcessor = DataProcessor.create(false) - + private val rcConnectionProcessor: DataProcessor = DataProcessor.create(false) //endregion //region Data @@ -62,6 +61,8 @@ class RCBatteryListItemWidgetModel( //region Lifecycle override fun inSetup() { + val rcConnectionKey = RemoteControllerKey.create(RemoteControllerKey.CONNECTION) + bindDataProcessor(rcConnectionKey, rcConnectionProcessor) val rcBatteryLevelKey = RemoteControllerKey.create(RemoteControllerKey.BATTERY_STATE) bindDataProcessor(rcBatteryLevelKey, rcBatteryLevelProcessor) val rcBatteryLowKey = RemoteControllerKey.create(RemoteControllerKey.IS_CHARGE_REMAINING_LOW) @@ -74,12 +75,12 @@ class RCBatteryListItemWidgetModel( override fun updateStates() { val rcBatteryLevelPercent = rcBatteryLevelProcessor.value.remainingChargeInPercent - if (productConnectionProcessor.value && rcBatteryLowProcessor.value) { + if (rcConnectionProcessor.value && rcBatteryLowProcessor.value) { rcBatteryStateProcessor.onNext(RCBatteryState.Low(rcBatteryLevelPercent)) - } else if (productConnectionProcessor.value) { + } else if (rcConnectionProcessor.value) { rcBatteryStateProcessor.onNext(RCBatteryState.Normal(rcBatteryLevelPercent)) } else { - rcBatteryStateProcessor.onNext(ProductDisconnected) + rcBatteryStateProcessor.onNext(RCDisconnected) } } @@ -88,9 +89,9 @@ class RCBatteryListItemWidgetModel( */ sealed class RCBatteryState { /** - * When product is disconnected + * When remote controller is disconnected */ - object ProductDisconnected : RCBatteryState() + object RCDisconnected : RCBatteryState() /** * When product is connected and rc battery is normal diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/rcstickmode/RCStickModeListItemWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/rcstickmode/RCStickModeListItemWidget.kt similarity index 79% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/rcstickmode/RCStickModeListItemWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/rcstickmode/RCStickModeListItemWidget.kt index 013e25b2..944c040d 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/rcstickmode/RCStickModeListItemWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/rcstickmode/RCStickModeListItemWidget.kt @@ -21,24 +21,23 @@ * */ -package dji.ux.beta.core.listitemwidget.rcstickmode +package dji.ux.beta.core.panel.listitem.rcstickmode import android.content.Context import android.util.AttributeSet import dji.log.DJILog import dji.thirdparty.io.reactivex.Flowable -import dji.ux.beta.R +import dji.ux.beta.core.R import dji.ux.beta.core.base.DJISDKModel import dji.ux.beta.core.base.SchedulerProvider import dji.ux.beta.core.base.UXSDKError import dji.ux.beta.core.base.WidgetSizeDescription -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.base.widget.ListItemRadioButtonWidget -import dji.ux.beta.core.extension.getDrawable +import dji.ux.beta.core.base.panel.listitem.ListItemRadioButtonWidget +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.getString -import dji.ux.beta.core.listitemwidget.rcstickmode.RCStickModeListItemWidget.RCStickModeListItemState -import dji.ux.beta.core.listitemwidget.rcstickmode.RCStickModeListItemWidget.RCStickModeListItemState.* -import dji.ux.beta.core.listitemwidget.rcstickmode.RCStickModeListItemWidgetModel.RCStickModeState +import dji.ux.beta.core.panel.listitem.rcstickmode.RCStickModeListItemWidget.ModelState +import dji.ux.beta.core.panel.listitem.rcstickmode.RCStickModeListItemWidget.ModelState.* +import dji.ux.beta.core.panel.listitem.rcstickmode.RCStickModeListItemWidgetModel.RCStickModeState private const val TAG = "RCStickModeListItemWidget" @@ -51,36 +50,41 @@ open class RCStickModeListItemWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ListItemRadioButtonWidget(context, attrs, defStyleAttr) { - - private val schedulerProvider = SchedulerProvider.getInstance() - +) : ListItemRadioButtonWidget( + context, + attrs, + defStyleAttr, + R.style.UXSDKRCStickModeListItem +) { + + //region Fields private val widgetModel by lazy { RCStickModeListItemWidgetModel( DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - schedulerProvider) + ObservableInMemoryKeyedStore.getInstance()) } private var mode1ItemIndex: Int = INVALID_OPTION_INDEX private var mode2ItemIndex: Int = INVALID_OPTION_INDEX private var mode3ItemIndex: Int = INVALID_OPTION_INDEX private var modeCustomItemIndex: Int = INVALID_OPTION_INDEX + //endregion + //region Constructor init { - listItemTitleIcon = getDrawable(R.drawable.uxsdk_ic_rc_list_item) - listItemTitle = getString(R.string.uxsdk_list_item_rc_stick_mode) mode1ItemIndex = addOptionToGroup(getString(R.string.uxsdk_rc_stick_mode_1)) mode2ItemIndex = addOptionToGroup(getString(R.string.uxsdk_rc_stick_mode_2)) mode3ItemIndex = addOptionToGroup(getString(R.string.uxsdk_rc_stick_mode_3)) } + //endregion + //region Lifecycle override fun reactToModelChanges() { addReaction(widgetModel.rcStickModeState - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { updateUI(it) }) addReaction(widgetModel.productConnection - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) } @@ -98,6 +102,34 @@ open class RCStickModeListItemWidget @JvmOverloads constructor( super.onDetachedFromWindow() } + override fun onOptionTapped(optionIndex: Int, optionLabel: String) { + val rcStickModeState = when (optionIndex) { + mode1ItemIndex -> { + RCStickModeState.Mode1 + } + mode2ItemIndex -> { + RCStickModeState.Mode2 + } + mode3ItemIndex -> { + RCStickModeState.Mode3 + } + modeCustomItemIndex -> { + RCStickModeState.Custom + } + else -> return + } + addDisposable(widgetModel.setControlStickMode(rcStickModeState) + .observeOn(SchedulerProvider.ui()) + .subscribe({ + widgetStateDataProcessor.onNext(SetRCStickModeSucceeded) + }) { + widgetStateDataProcessor.onNext(SetRCStickModeFailed(it as UXSDKError)) + DJILog.d(TAG, "failed " + it.djiError.description) + }) + } + //endregion + + //region Reaction to model private fun updateUI(rcStickModeState: RCStickModeState) { widgetStateDataProcessor.onNext(RCStickModeUpdated(rcStickModeState)) when (rcStickModeState) { @@ -122,7 +154,9 @@ open class RCStickModeListItemWidget @JvmOverloads constructor( } } } + //endregion + //region Customization override fun getIdealDimensionRatioString(): String? { return null } @@ -131,65 +165,41 @@ open class RCStickModeListItemWidget @JvmOverloads constructor( WidgetSizeDescription(WidgetSizeDescription.SizeType.OTHER, widthDimension = WidgetSizeDescription.Dimension.EXPAND, heightDimension = WidgetSizeDescription.Dimension.WRAP) + //endregion - - override fun onOptionTapped(optionIndex: Int, optionLabel: String) { - val rcStickModeState = when (optionIndex) { - mode1ItemIndex -> { - RCStickModeState.Mode1 - } - mode2ItemIndex -> { - RCStickModeState.Mode2 - } - mode3ItemIndex -> { - RCStickModeState.Mode3 - } - modeCustomItemIndex -> { - RCStickModeState.Custom - } - else -> return - } - addDisposable(widgetModel.setControlStickMode(rcStickModeState) - .observeOn(schedulerProvider.ui()) - .subscribe({ - widgetStateDataProcessor.onNext(SetRCStickModeSuccess) - }) { - widgetStateDataProcessor.onNext(SetRCStickModeFailed(it as UXSDKError)) - DJILog.d(TAG, "failed " + it.djiError.description) - }) - } - + //region Hooks /** - * Get the [RCStickModeListItemState] updates + * Get the [ModelState] updates */ - override fun getWidgetStateUpdate(): Flowable { + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { return super.getWidgetStateUpdate() } /** * Class defines widget state updates */ - sealed class RCStickModeListItemState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : RCStickModeListItemState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * Set RC stick mode success */ - object SetRCStickModeSuccess : RCStickModeListItemState() + object SetRCStickModeSucceeded : ModelState() /** * Set RC stick mode failed */ - data class SetRCStickModeFailed(val error: UXSDKError) : RCStickModeListItemState() + data class SetRCStickModeFailed(val error: UXSDKError) : ModelState() /** - * Current RC stick mode state + * RC stick mode state updated */ - data class RCStickModeUpdated(val rcStickModeState: RCStickModeState) : RCStickModeListItemState() + data class RCStickModeUpdated(val rcStickModeState: RCStickModeState) : ModelState() } - + //endregion } \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/rcstickmode/RCStickModeListItemWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/rcstickmode/RCStickModeListItemWidgetModel.kt similarity index 91% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/rcstickmode/RCStickModeListItemWidgetModel.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/rcstickmode/RCStickModeListItemWidgetModel.kt index 488c0a4c..868e75cc 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/rcstickmode/RCStickModeListItemWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/rcstickmode/RCStickModeListItemWidgetModel.kt @@ -18,10 +18,10 @@ * 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.ux.beta.core.listitemwidget.rcstickmode +package dji.ux.beta.core.panel.listitem.rcstickmode import dji.common.remotecontroller.AircraftMappingStyle import dji.common.remotecontroller.AircraftMappingStyle.* @@ -31,9 +31,8 @@ import dji.thirdparty.io.reactivex.Flowable import dji.thirdparty.io.reactivex.Single import dji.thirdparty.io.reactivex.functions.Consumer import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.SchedulerProviderInterface import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.util.DataProcessor private const val TAG = "RCStickModeListItemWidgetModel" @@ -44,8 +43,7 @@ private const val TAG = "RCStickModeListItemWidgetModel" */ class RCStickModeListItemWidgetModel( djiSdkModel: DJISDKModel, - keyedStore: ObservableInMemoryKeyedStore, - private val schedulerProvider: SchedulerProviderInterface + keyedStore: ObservableInMemoryKeyedStore ) : WidgetModel(djiSdkModel, keyedStore) { @@ -75,7 +73,7 @@ class RCStickModeListItemWidgetModel( RCStickModeState.Mode3 -> STYLE_3 else -> STYLE_2 } - return djiSdkModel.setValue(aircraftMappingStyleKey, aircraftMappingStyle).subscribeOn(schedulerProvider.io()) + return djiSdkModel.setValue(aircraftMappingStyleKey, aircraftMappingStyle) } override fun inSetup() { @@ -88,7 +86,7 @@ class RCStickModeListItemWidgetModel( override fun updateStates() { if (productConnectionProcessor.value) { - addDisposable(getControlStickMode().observeOn(schedulerProvider.computation()) + addDisposable(getControlStickMode() .subscribe(Consumer { if (it is AircraftMappingStyle) { updateCurrentStickMode(it) diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/returntohomealtitude/ReturnToHomeAltitudeListItemWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/returntohomealtitude/ReturnToHomeAltitudeListItemWidget.kt similarity index 72% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/returntohomealtitude/ReturnToHomeAltitudeListItemWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/returntohomealtitude/ReturnToHomeAltitudeListItemWidget.kt index e64d24b2..2108da83 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/returntohomealtitude/ReturnToHomeAltitudeListItemWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/returntohomealtitude/ReturnToHomeAltitudeListItemWidget.kt @@ -21,28 +21,34 @@ * */ -package dji.ux.beta.core.listitemwidget.returntohomealtitude +package dji.ux.beta.core.panel.listitem.returntohomealtitude import android.annotation.SuppressLint import android.content.Context +import android.content.DialogInterface import android.graphics.drawable.Drawable import android.util.AttributeSet import androidx.annotation.StyleRes import androidx.core.content.res.use import dji.log.DJILog -import dji.ux.beta.R -import dji.ux.beta.core.base.* -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.base.widget.ListItemEditTextButtonWidget -import dji.ux.beta.core.base.widget.ListItemEditTextButtonWidget.WidgetUIState.DialogDisplayed +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.R +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.SchedulerProvider +import dji.ux.beta.core.base.UXSDKError +import dji.ux.beta.core.base.WidgetSizeDescription +import dji.ux.beta.core.base.panel.listitem.ListItemEditTextButtonWidget +import dji.ux.beta.core.base.panel.listitem.ListItemEditTextButtonWidget.UIState.DialogDismissed +import dji.ux.beta.core.base.panel.listitem.ListItemEditTextButtonWidget.UIState.DialogDisplayed +import dji.ux.beta.core.communication.GlobalPreferencesManager +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.* -import dji.ux.beta.core.listitemwidget.returntohomealtitude.ReturnToHomeAltitudeListItemWidget.ReturnToHomeAltitudeItemState -import dji.ux.beta.core.listitemwidget.returntohomealtitude.ReturnToHomeAltitudeListItemWidget.ReturnToHomeAltitudeItemState.CurrentReturnToHomeAltitudeState -import dji.ux.beta.core.listitemwidget.returntohomealtitude.ReturnToHomeAltitudeListItemWidget.ReturnToHomeAltitudeItemState.ProductConnected -import dji.ux.beta.core.listitemwidget.returntohomealtitude.ReturnToHomeAltitudeListItemWidget.ReturnToHomeItemDialogState.MaxAltitudeExceededDialog -import dji.ux.beta.core.listitemwidget.returntohomealtitude.ReturnToHomeAltitudeListItemWidgetModel.ReturnToHomeAltitudeState -import dji.ux.beta.core.listitemwidget.returntohomealtitude.ReturnToHomeAltitudeListItemWidgetModel.ReturnToHomeAltitudeState.ReturnToHomeAltitudeValue -import dji.ux.beta.core.util.DisplayUtil +import dji.ux.beta.core.panel.listitem.returntohomealtitude.ReturnToHomeAltitudeListItemWidget.DialogType.MaxAltitudeExceeded +import dji.ux.beta.core.panel.listitem.returntohomealtitude.ReturnToHomeAltitudeListItemWidget.ModelState +import dji.ux.beta.core.panel.listitem.returntohomealtitude.ReturnToHomeAltitudeListItemWidget.ModelState.ProductConnected +import dji.ux.beta.core.panel.listitem.returntohomealtitude.ReturnToHomeAltitudeListItemWidget.ModelState.ReturnToHomeAltitudeStateUpdated +import dji.ux.beta.core.panel.listitem.returntohomealtitude.ReturnToHomeAltitudeListItemWidgetModel.ReturnToHomeAltitudeState +import dji.ux.beta.core.panel.listitem.returntohomealtitude.ReturnToHomeAltitudeListItemWidgetModel.ReturnToHomeAltitudeState.ReturnToHomeAltitudeValue import dji.ux.beta.core.util.UnitConversionUtil.UnitType private const val TAG = "RTHAltitudeListItem" @@ -57,10 +63,15 @@ open class ReturnToHomeAltitudeListItemWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleArr: Int = 0 -) : ListItemEditTextButtonWidget(context, attrs, defStyleArr, WidgetType.EDIT) { - - private val schedulerProvider = SchedulerProvider.getInstance() - +) : ListItemEditTextButtonWidget( + context, + attrs, + defStyleArr, + WidgetType.EDIT, + R.style.UXSDKReturnToHomeAltitudeListItem +) { + + //region Fields /** * Enable/Disable toast messages in the widget */ @@ -86,33 +97,17 @@ open class ReturnToHomeAltitudeListItemWidget @JvmOverloads constructor( ReturnToHomeAltitudeListItemWidgetModel( DJISDKModel.getInstance(), ObservableInMemoryKeyedStore.getInstance(), - schedulerProvider, GlobalPreferencesManager.getInstance()) } + //endregion + //region Constructor init { - listItemTitle = getString(R.string.uxsdk_list_item_max_return_to_home_altitude) - listItemTitleIcon = getDrawable(R.drawable.uxsdk_ic_return_home_altitude) - attrs?.let { initAttributes(context, it) } - } - - @SuppressLint("Recycle") - private fun initAttributes(context: Context, attrs: AttributeSet) { - context.obtainStyledAttributes(attrs, R.styleable.ReturnToHomeAltitudeListItemWidget).use { typedArray -> - toastMessagesEnabled = typedArray.getBoolean(R.styleable.ReturnToHomeAltitudeListItemWidget_uxsdk_toast_messages_enabled, toastMessagesEnabled) - typedArray.getResourceIdAndUse(R.styleable.ReturnToHomeAltitudeListItemWidget_uxsdk_list_item_dialog_theme) { - dialogTheme = it - } - typedArray.getDrawableAndUse(R.styleable.ReturnToHomeAltitudeListItemWidget_uxsdk_list_item_error_dialog_icon) { - errorDialogIcon = it - } - typedArray.getDrawableAndUse(R.styleable.ReturnToHomeAltitudeListItemWidget_uxsdk_list_item_success_dialog_icon) { - successDialogIcon = it - } - } + initAttributes(context, attrs) } + //endregion - + //region Lifecycle override fun onAttachedToWindow() { super.onAttachedToWindow() if (!isInEditMode) { @@ -129,16 +124,66 @@ open class ReturnToHomeAltitudeListItemWidget @JvmOverloads constructor( override fun reactToModelChanges() { addReaction(widgetModel.returnToHomeAltitudeState - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { - widgetStateDataProcessor.onNext(CurrentReturnToHomeAltitudeState(it)) + widgetStateDataProcessor.onNext(ReturnToHomeAltitudeStateUpdated(it)) this.updateUI(it) }) addReaction(widgetModel.productConnection - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) } + override fun onButtonClick() { + // Do nothing + } + + override fun onKeyboardDoneAction() { + val currentValue = listItemEditTextValue?.toIntOrNull() + if (currentValue != null + && widgetModel.isInputInRange(currentValue)) { + addDisposable(widgetModel.returnToHomeAltitudeState.firstOrError() + .observeOn(SchedulerProvider.ui()) + .subscribe({ + if (it is ReturnToHomeAltitudeValue) { + if (it.maxFlightAltitude < currentValue) { + val dialogDismissListener = DialogInterface.OnDismissListener { + uiUpdateStateProcessor.onNext(DialogDismissed(MaxAltitudeExceeded)) + } + showAlertDialog(dialogTheme = dialogTheme, + icon = errorDialogIcon, + title = getString(R.string.uxsdk_list_rth_dialog_title), + message = getString(R.string.uxsdk_rth_error_dialog_message), + dialogDismissListener = dialogDismissListener) + uiUpdateStateProcessor.onNext(DialogDisplayed(MaxAltitudeExceeded)) + resetToDefaultValue() + } else { + setReturnToHomeAltitude(currentValue) + } + } + }, { + resetToDefaultValue() + DJILog.d(TAG, it.message) + })) + + } else { + resetToDefaultValue() + showToast(getString(R.string.uxsdk_list_item_value_out_of_range)) + } + } + + override fun onEditorTextChanged(currentText: String?) { + listItemEditTextColor = if (!currentText.isNullOrBlank() + && currentText.toIntOrNull() != null + && widgetModel.isInputInRange(currentText.toInt())) { + editTextNormalColor + } else { + errorValueColor + } + } + //endregion + + //region Reactions to model private fun updateUI(returnToHomeAltitudeState: ReturnToHomeAltitudeState) { when (returnToHomeAltitudeState) { ReturnToHomeAltitudeState.ProductDisconnected -> updateProductDisconnectedState() @@ -184,7 +229,9 @@ open class ReturnToHomeAltitudeListItemWidget @JvmOverloads constructor( isEnabled = false listItemEditTextColor = disconnectedValueColor } + //endregion + //region Helpers private fun showToast(message: String?) { if (toastMessagesEnabled) { showShortToast(message) @@ -193,18 +240,22 @@ open class ReturnToHomeAltitudeListItemWidget @JvmOverloads constructor( private fun setReturnToHomeAltitude(currentValue: Int) { addDisposable(widgetModel.setReturnToHomeAltitude(currentValue) - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe({ + val dialogDismissListener = DialogInterface.OnDismissListener { + uiUpdateStateProcessor.onNext(DialogDismissed(DialogType.ReturnHomeAltitudeChangeConfirmation)) + } showAlertDialog(dialogTheme = dialogTheme, icon = successDialogIcon, title = getString(R.string.uxsdk_list_rth_dialog_title), - message = getString(R.string.uxsdk_rth_success_dialog_message)) - uiUpdateStateProcessor.onNext(DialogDisplayed(ReturnToHomeItemDialogState.ReturnHomeAltitudeChangeDialog)) - widgetStateDataProcessor.onNext(ReturnToHomeAltitudeItemState.SetReturnToHomeAltitudeSuccess) + message = getString(R.string.uxsdk_rth_success_dialog_message), + dialogDismissListener = dialogDismissListener) + uiUpdateStateProcessor.onNext(DialogDisplayed(DialogType.ReturnHomeAltitudeChangeConfirmation)) + widgetStateDataProcessor.onNext(ModelState.SetReturnToHomeAltitudeSucceeded) }, { error -> if (error is UXSDKError) { showToast(error.djiError.description) - widgetStateDataProcessor.onNext(ReturnToHomeAltitudeItemState.SetReturnToHomeAltitudeFailed(error)) + widgetStateDataProcessor.onNext(ModelState.SetReturnToHomeAltitudeFailed(error)) DJILog.e(TAG, error.djiError.description) } @@ -213,59 +264,32 @@ open class ReturnToHomeAltitudeListItemWidget @JvmOverloads constructor( private fun resetToDefaultValue() { addDisposable(widgetModel.returnToHomeAltitudeState.firstOrError() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe({ updateUI(it) }, { DJILog.e(TAG, it.message) })) } + //endregion - override fun onButtonClick() { - // Do nothing - } - - override fun onKeyboardDoneAction() { - val currentValue = listItemEditTextValue?.toIntOrNull() - if (currentValue != null - && widgetModel.isInputInRange(currentValue)) { - addDisposable(widgetModel.returnToHomeAltitudeState.firstOrError() - .observeOn(schedulerProvider.ui()) - .subscribe({ - if (it is ReturnToHomeAltitudeValue) { - if (it.maxFlightAltitude < currentValue) { - showAlertDialog(dialogTheme = dialogTheme, - icon = errorDialogIcon, - title = getString(R.string.uxsdk_list_rth_dialog_title), - message = getString(R.string.uxsdk_rth_error_dialog_message)) - uiUpdateStateProcessor.onNext(DialogDisplayed(MaxAltitudeExceededDialog)) - resetToDefaultValue() - } else { - setReturnToHomeAltitude(currentValue) - } - } - }, { - resetToDefaultValue() - DJILog.d(TAG, it.message) - })) - - } else { - resetToDefaultValue() - showToast(getString(R.string.uxsdk_list_item_value_out_of_range)) - } - } - - override fun onEditorTextChanged(currentText: String?) { - listItemEditTextColor = if (!currentText.isNullOrBlank() - && currentText.toIntOrNull() != null - && widgetModel.isInputInRange(currentText.toInt())) { - editTextNormalColor - } else { - errorValueColor + //region Customization + @SuppressLint("Recycle") + private fun initAttributes(context: Context, attrs: AttributeSet?) { + context.obtainStyledAttributes(attrs, R.styleable.ReturnToHomeAltitudeListItemWidget, 0, defaultStyle).use { typedArray -> + toastMessagesEnabled = typedArray.getBoolean(R.styleable.ReturnToHomeAltitudeListItemWidget_uxsdk_toast_messages_enabled, toastMessagesEnabled) + typedArray.getResourceIdAndUse(R.styleable.ReturnToHomeAltitudeListItemWidget_uxsdk_list_item_dialog_theme) { + dialogTheme = it + } + typedArray.getDrawableAndUse(R.styleable.ReturnToHomeAltitudeListItemWidget_uxsdk_list_item_error_dialog_icon) { + errorDialogIcon = it + } + typedArray.getDrawableAndUse(R.styleable.ReturnToHomeAltitudeListItemWidget_uxsdk_list_item_success_dialog_icon) { + successDialogIcon = it + } } } - override fun getIdealDimensionRatioString(): String? { return null } @@ -274,48 +298,69 @@ open class ReturnToHomeAltitudeListItemWidget @JvmOverloads constructor( WidgetSizeDescription(WidgetSizeDescription.SizeType.OTHER, widthDimension = WidgetSizeDescription.Dimension.EXPAND, heightDimension = WidgetSizeDescription.Dimension.WRAP) + //endregion + + + //region Hooks + + /** + * Get the [ListItemEditTextButtonWidget.UIState] updates + * The info parameter is instance of [DialogType] + */ + override fun getUIStateUpdates(): Flowable { + return uiUpdateStateProcessor.onBackpressureBuffer() + } + /** + * Get the [ModelState] updates + */ + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { + return super.getWidgetStateUpdate() + } /** * Return to home list item dialog identifiers */ - sealed class ReturnToHomeItemDialogState { + sealed class DialogType { /** * Dialog shown when return to home altitude * exceeds max altitude limit */ - object MaxAltitudeExceededDialog : ReturnToHomeItemDialogState() + object MaxAltitudeExceeded : DialogType() /** * Dialog shown to warn user when return to home altitude is * updated successfully */ - object ReturnHomeAltitudeChangeDialog : ReturnToHomeItemDialogState() + object ReturnHomeAltitudeChangeConfirmation : DialogType() } /** * Class defines widget state updates */ - sealed class ReturnToHomeAltitudeItemState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : ReturnToHomeAltitudeItemState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * Return to home altitude set action successful */ - object SetReturnToHomeAltitudeSuccess : ReturnToHomeAltitudeItemState() + object SetReturnToHomeAltitudeSucceeded : ModelState() /** * Return to home altitude set action failed */ - data class SetReturnToHomeAltitudeFailed(val error: UXSDKError) : ReturnToHomeAltitudeItemState() + data class SetReturnToHomeAltitudeFailed(val error: UXSDKError) : ModelState() /** - * Current return to home altitude state + * Return to home altitude state updated */ - data class CurrentReturnToHomeAltitudeState(val maxAltitudeState: ReturnToHomeAltitudeState) : ReturnToHomeAltitudeItemState() + data class ReturnToHomeAltitudeStateUpdated(val maxAltitudeState: ReturnToHomeAltitudeState) : ModelState() } + //endregion + } \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/returntohomealtitude/ReturnToHomeAltitudeListItemWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/returntohomealtitude/ReturnToHomeAltitudeListItemWidgetModel.kt similarity index 94% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/returntohomealtitude/ReturnToHomeAltitudeListItemWidgetModel.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/returntohomealtitude/ReturnToHomeAltitudeListItemWidgetModel.kt index 093082d7..f49ce088 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/returntohomealtitude/ReturnToHomeAltitudeListItemWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/returntohomealtitude/ReturnToHomeAltitudeListItemWidgetModel.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.listitemwidget.returntohomealtitude +package dji.ux.beta.core.panel.listitem.returntohomealtitude import dji.common.util.DJIParamMinMaxCapability import dji.keysdk.DJIKey @@ -29,12 +29,11 @@ import dji.keysdk.FlightControllerKey import dji.thirdparty.io.reactivex.Completable import dji.thirdparty.io.reactivex.Flowable import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.GlobalPreferencesInterface -import dji.ux.beta.core.base.SchedulerProviderInterface import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.GlobalPreferenceKeys -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.listitemwidget.returntohomealtitude.ReturnToHomeAltitudeListItemWidgetModel.ReturnToHomeAltitudeState.* +import dji.ux.beta.core.communication.GlobalPreferenceKeys +import dji.ux.beta.core.communication.GlobalPreferencesInterface +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.panel.listitem.returntohomealtitude.ReturnToHomeAltitudeListItemWidgetModel.ReturnToHomeAltitudeState.* import dji.ux.beta.core.util.DataProcessor import dji.ux.beta.core.util.UnitConversionUtil.* import kotlin.math.roundToInt @@ -49,7 +48,6 @@ private const val MAX_LIMIT = 500 class ReturnToHomeAltitudeListItemWidgetModel( djiSdkModel: DJISDKModel, keyedStore: ObservableInMemoryKeyedStore, - private val schedulerProvider: SchedulerProviderInterface, private val preferencesManager: GlobalPreferencesInterface? ) : WidgetModel(djiSdkModel, keyedStore) { @@ -157,7 +155,6 @@ class ReturnToHomeAltitudeListItemWidgetModel( returnToHomeAltitude } return djiSdkModel.setValue(returnToHomeAltitudeKey, tempAltitude) - .subscribeOn(schedulerProvider.io()) } /** diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/sdcardstatus/SDCardStatusListItemWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/sdcardstatus/SDCardStatusListItemWidget.kt similarity index 74% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/sdcardstatus/SDCardStatusListItemWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/sdcardstatus/SDCardStatusListItemWidget.kt index aaa22a7c..b883ceb5 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/sdcardstatus/SDCardStatusListItemWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/sdcardstatus/SDCardStatusListItemWidget.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.listitemwidget.sdcardstatus +package dji.ux.beta.core.panel.listitem.sdcardstatus import android.annotation.SuppressLint import android.content.Context @@ -33,17 +33,19 @@ import androidx.core.content.res.use import dji.common.camera.SettingsDefinitions.SDCardOperationState import dji.common.camera.SettingsDefinitions.SDCardOperationState.* import dji.thirdparty.io.reactivex.Flowable -import dji.ux.beta.R +import dji.ux.beta.core.R import dji.ux.beta.core.base.DJISDKModel import dji.ux.beta.core.base.SchedulerProvider import dji.ux.beta.core.base.UXSDKError import dji.ux.beta.core.base.WidgetSizeDescription -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.base.widget.ListItemLabelButtonWidget +import dji.ux.beta.core.base.panel.listitem.ListItemLabelButtonWidget +import dji.ux.beta.core.base.panel.listitem.ListItemLabelButtonWidget.UIState.DialogDismissed +import dji.ux.beta.core.base.panel.listitem.ListItemLabelButtonWidget.UIState.DialogDisplayed +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.* -import dji.ux.beta.core.listitemwidget.sdcardstatus.SDCardStatusListItemWidget.SDCardListItemDialogState.* -import dji.ux.beta.core.listitemwidget.sdcardstatus.SDCardStatusListItemWidget.SDCardListItemState -import dji.ux.beta.core.listitemwidget.sdcardstatus.SDCardStatusListItemWidget.SDCardListItemState.ProductConnected +import dji.ux.beta.core.panel.listitem.sdcardstatus.SDCardStatusListItemWidget.DialogType.* +import dji.ux.beta.core.panel.listitem.sdcardstatus.SDCardStatusListItemWidget.ModelState +import dji.ux.beta.core.panel.listitem.sdcardstatus.SDCardStatusListItemWidget.ModelState.ProductConnected import dji.ux.beta.core.util.UnitConversionUtil.getSpaceWithUnit /** @@ -53,10 +55,15 @@ open class SDCardStatusListItemWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ListItemLabelButtonWidget(context, attrs, defStyleAttr, WidgetType.LABEL_BUTTON) { - - private val schedulerProvider = SchedulerProvider.getInstance() - +) : ListItemLabelButtonWidget( + context, + attrs, + defStyleAttr, + WidgetType.LABEL_BUTTON, + R.style.UXSDKSDCardStatusListItem +) { + + //region Fields /** * Theme for the dialogs shown for format */ @@ -81,86 +88,66 @@ open class SDCardStatusListItemWidget @JvmOverloads constructor( private val widgetModel: SDCardStatusListItemWidgetModel by lazy { SDCardStatusListItemWidgetModel( DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - schedulerProvider) + ObservableInMemoryKeyedStore.getInstance()) } + //endregion + //region Constructor init { - listItemTitleIcon = getDrawable(R.drawable.uxsdk_ic_sdcard) - listItemTitle = getString(R.string.uxsdk_list_item_sd_card) - listItemButtonText = getString(R.string.uxsdk_list_item_format_button) - attrs?.let { initAttributes(context, it) } + initAttributes(context, attrs) } + //endregion - @SuppressLint("Recycle") - private fun initAttributes(context: Context, attrs: AttributeSet) { - context.obtainStyledAttributes(attrs, R.styleable.SDCardStatusListItemWidget).use { typedArray -> - typedArray.getDrawableAndUse(R.styleable.SDCardStatusListItemWidget_uxsdk_list_item_confirmation_dialog_icon) { - formatConfirmationDialogIcon = it - } - typedArray.getDrawableAndUse(R.styleable.SDCardStatusListItemWidget_uxsdk_list_item_success_dialog_icon) { - formatSuccessDialogIcon = it - } - typedArray.getDrawableAndUse(R.styleable.SDCardStatusListItemWidget_uxsdk_list_item_error_dialog_icon) { - formatErrorDialogIcon = it - } - typedArray.getResourceIdAndUse(R.styleable.SDCardStatusListItemWidget_uxsdk_list_item_dialog_theme) { - dialogTheme = it - } - - } + //region Lifecycle + override fun reactToModelChanges() { + addReaction(widgetModel.sdCardState + .observeOn(SchedulerProvider.ui()) + .subscribe { this.updateUI(it) }) + addReaction(widgetModel.productConnection + .observeOn(SchedulerProvider.ui()) + .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) } override fun onButtonClick() { val dialogListener = DialogInterface.OnClickListener { dialogInterface, buttonId: Int -> if (buttonId == DialogInterface.BUTTON_POSITIVE) { - uiUpdateStateProcessor.onNext(WidgetUIState.DialogActionConfirm(FormatConfirmationDialog)) + uiUpdateStateProcessor.onNext(UIState.DialogActionConfirmed(FormatConfirmation)) formatSDCard() + } else { + uiUpdateStateProcessor.onNext(UIState.DialogActionCanceled(FormatConfirmation)) } dialogInterface.dismiss() - uiUpdateStateProcessor.onNext(WidgetUIState.DialogActionDismiss(FormatConfirmationDialog)) + } + val dialogDismissListener = DialogInterface.OnDismissListener { + uiUpdateStateProcessor.onNext(DialogDismissed(FormatConfirmation)) } showConfirmationDialog(title = getString(R.string.uxsdk_sd_card_dialog_title), icon = formatConfirmationDialogIcon, dialogTheme = dialogTheme, message = getString(R.string.uxsdk_sd_card_format_confirmation), - dialogClickListener = dialogListener) - uiUpdateStateProcessor.onNext(WidgetUIState.DialogDisplayed(FormatConfirmationDialog)) + dialogClickListener = dialogListener, + dialogDismissListener = dialogDismissListener) + uiUpdateStateProcessor.onNext(DialogDisplayed(FormatConfirmation)) } - private fun formatSDCard() { - addDisposable(widgetModel.formatSDCard() - .observeOn(schedulerProvider.ui()) - .subscribe({ - showAlertDialog(title = getString(R.string.uxsdk_sd_card_dialog_title), - icon = formatSuccessDialogIcon, - dialogTheme = dialogTheme, - message = getString(R.string.uxsdk_sd_card_format_complete)) - uiUpdateStateProcessor.onNext(WidgetUIState.DialogDisplayed(FormatSuccessDialog)) - }, { error -> - if (error is UXSDKError) { - showAlertDialog(title = getString(R.string.uxsdk_sd_card_dialog_title), - icon = formatErrorDialogIcon, - dialogTheme = dialogTheme, - message = String.format(getString(R.string.uxsdk_sd_card_format_error), - error.djiError.description)) - uiUpdateStateProcessor.onNext(WidgetUIState.DialogDisplayed(FormatErrorDialog)) - } - })) + override fun onAttachedToWindow() { + super.onAttachedToWindow() + if (!isInEditMode) { + widgetModel.setup() + } } - override fun reactToModelChanges() { - addReaction(widgetModel.sdCardState - .observeOn(schedulerProvider.ui()) - .subscribe { this.updateUI(it) }) - addReaction(widgetModel.productConnection - .observeOn(schedulerProvider.ui()) - .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) + override fun onDetachedFromWindow() { + if (!isInEditMode) { + widgetModel.cleanup() + } + super.onDetachedFromWindow() } + //endregion - + //region Reactions to model private fun updateUI(sdCardState: SDCardStatusListItemWidgetModel.SDCardState) { - widgetStateDataProcessor.onNext(SDCardListItemState.CurrentSDCardListItemState(sdCardState)) + widgetStateDataProcessor.onNext(ModelState.SDCardStateUpdated(sdCardState)) when (sdCardState) { SDCardStatusListItemWidgetModel.SDCardState.ProductDisconnected -> { listItemLabel = getString(R.string.uxsdk_string_default_value) @@ -176,6 +163,37 @@ open class SDCardStatusListItemWidget @JvmOverloads constructor( } } } + //endregion + + //region Helpers + private fun formatSDCard() { + var dialogType: DialogType? = null + val dialogDismissListener = DialogInterface.OnDismissListener { + uiUpdateStateProcessor.onNext(DialogDismissed(dialogType)) + } + addDisposable(widgetModel.formatSDCard() + .observeOn(SchedulerProvider.ui()) + .subscribe({ + dialogType = FormatSuccess + showAlertDialog(title = getString(R.string.uxsdk_sd_card_dialog_title), + icon = formatSuccessDialogIcon, + dialogTheme = dialogTheme, + message = getString(R.string.uxsdk_sd_card_format_complete), + dialogDismissListener = dialogDismissListener) + uiUpdateStateProcessor.onNext(DialogDisplayed(dialogType)) + }, { error -> + if (error is UXSDKError) { + dialogType = FormatError + showAlertDialog(title = getString(R.string.uxsdk_sd_card_dialog_title), + icon = formatErrorDialogIcon, + dialogTheme = dialogTheme, + message = String.format(getString(R.string.uxsdk_sd_card_format_error), + error.djiError.description), + dialogDismissListener = dialogDismissListener) + uiUpdateStateProcessor.onNext(DialogDisplayed(dialogType)) + } + })) + } private fun getFormatButtonVisibility(sdCardOperationState: SDCardOperationState): Boolean { @@ -222,19 +240,26 @@ open class SDCardStatusListItemWidget @JvmOverloads constructor( } } + //endregion - override fun onAttachedToWindow() { - super.onAttachedToWindow() - if (!isInEditMode) { - widgetModel.setup() - } - } + //region Customization + @SuppressLint("Recycle") + private fun initAttributes(context: Context, attrs: AttributeSet?) { + context.obtainStyledAttributes(attrs, R.styleable.SDCardStatusListItemWidget,0, defaultStyle).use { typedArray -> + typedArray.getDrawableAndUse(R.styleable.SDCardStatusListItemWidget_uxsdk_list_item_confirmation_dialog_icon) { + formatConfirmationDialogIcon = it + } + typedArray.getDrawableAndUse(R.styleable.SDCardStatusListItemWidget_uxsdk_list_item_success_dialog_icon) { + formatSuccessDialogIcon = it + } + typedArray.getDrawableAndUse(R.styleable.SDCardStatusListItemWidget_uxsdk_list_item_error_dialog_icon) { + formatErrorDialogIcon = it + } + typedArray.getResourceIdAndUse(R.styleable.SDCardStatusListItemWidget_uxsdk_list_item_dialog_theme) { + dialogTheme = it + } - override fun onDetachedFromWindow() { - if (!isInEditMode) { - widgetModel.cleanup() } - super.onDetachedFromWindow() } override val widgetSizeDescription: WidgetSizeDescription = @@ -245,55 +270,60 @@ open class SDCardStatusListItemWidget @JvmOverloads constructor( override fun getIdealDimensionRatioString(): String? { return null } + //endregion + + //region Hooks /** - * Get the [SDCardListItemState] updates + * Get the [ListItemLabelButtonWidget.UIState] updates + * The info parameter is instance of [DialogType] */ - override fun getWidgetStateUpdate(): Flowable { - return super.getWidgetStateUpdate() + override fun getUIStateUpdates(): Flowable { + return uiUpdateStateProcessor.onBackpressureBuffer() } /** - * Get the [ListItemLabelButtonWidget.WidgetUIState] updates - * The info parameter is instance of [SDCardListItemDialogState] + * Get the [ModelState] updates */ - override fun getUIStateUpdates(): Flowable { - return uiUpdateStateProcessor + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { + return super.getWidgetStateUpdate() } /** * SD Card List Item Dialog Identifiers */ - sealed class SDCardListItemDialogState { + sealed class DialogType { /** * Dialog shown for format confirmation */ - object FormatConfirmationDialog : SDCardListItemDialogState() + object FormatConfirmation : DialogType() /** * Dialog shown for format success */ - object FormatSuccessDialog : SDCardListItemDialogState() + object FormatSuccess : DialogType() /** * Dialog shown for format fail */ - object FormatErrorDialog : SDCardListItemDialogState() + object FormatError : DialogType() } /** * Class defines widget state updates */ - sealed class SDCardListItemState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : SDCardListItemState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * Current SD Card List Item State */ - data class CurrentSDCardListItemState(val sdCardState: SDCardStatusListItemWidgetModel.SDCardState) : SDCardListItemState() + data class SDCardStateUpdated(val sdCardState: SDCardStatusListItemWidgetModel.SDCardState) : ModelState() } + //endregion } \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/sdcardstatus/SDCardStatusListItemWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/sdcardstatus/SDCardStatusListItemWidgetModel.kt similarity index 91% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/sdcardstatus/SDCardStatusListItemWidgetModel.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/sdcardstatus/SDCardStatusListItemWidgetModel.kt index f12ea02e..f478f1f0 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/sdcardstatus/SDCardStatusListItemWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/sdcardstatus/SDCardStatusListItemWidgetModel.kt @@ -21,16 +21,15 @@ * */ -package dji.ux.beta.core.listitemwidget.sdcardstatus +package dji.ux.beta.core.panel.listitem.sdcardstatus import dji.common.camera.SettingsDefinitions import dji.keysdk.CameraKey import dji.thirdparty.io.reactivex.Completable import dji.thirdparty.io.reactivex.Flowable import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.SchedulerProviderInterface import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.util.DataProcessor import dji.ux.beta.core.util.SettingDefinitions @@ -41,8 +40,7 @@ import dji.ux.beta.core.util.SettingDefinitions */ class SDCardStatusListItemWidgetModel( djiSdkModel: DJISDKModel, - keyedStore: ObservableInMemoryKeyedStore, - private val schedulerProvider: SchedulerProviderInterface + keyedStore: ObservableInMemoryKeyedStore ) : WidgetModel(djiSdkModel, keyedStore) { @@ -92,7 +90,7 @@ class SDCardStatusListItemWidgetModel( */ fun formatSDCard(): Completable { val sdCardFormatKey = CameraKey.create(CameraKey.FORMAT_SD_CARD, cameraIndex) - return djiSdkModel.performAction(sdCardFormatKey).subscribeOn(schedulerProvider.io()) + return djiSdkModel.performAction(sdCardFormatKey) } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/ssdstatus/SSDStatusListItemWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/ssdstatus/SSDStatusListItemWidget.kt similarity index 73% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/ssdstatus/SSDStatusListItemWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/ssdstatus/SSDStatusListItemWidget.kt index 13b66a02..834f3410 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/ssdstatus/SSDStatusListItemWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/ssdstatus/SSDStatusListItemWidget.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.listitemwidget.ssdstatus +package dji.ux.beta.core.panel.listitem.ssdstatus import android.annotation.SuppressLint import android.content.Context @@ -31,21 +31,21 @@ import android.util.AttributeSet import androidx.annotation.StyleRes import androidx.core.content.res.use import dji.common.camera.SSDOperationState -import dji.ux.beta.R +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.R import dji.ux.beta.core.base.DJISDKModel import dji.ux.beta.core.base.SchedulerProvider import dji.ux.beta.core.base.UXSDKError import dji.ux.beta.core.base.WidgetSizeDescription -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.base.widget.ListItemLabelButtonWidget -import dji.ux.beta.core.base.widget.ListItemLabelButtonWidget.WidgetUIState.DialogActionConfirm -import dji.ux.beta.core.base.widget.ListItemLabelButtonWidget.WidgetUIState.DialogActionDismiss +import dji.ux.beta.core.base.panel.listitem.ListItemLabelButtonWidget +import dji.ux.beta.core.base.panel.listitem.ListItemLabelButtonWidget.UIState.* +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.* -import dji.ux.beta.core.listitemwidget.ssdstatus.SSDStatusListItemWidget.SSDListItemDialogState.* -import dji.ux.beta.core.listitemwidget.ssdstatus.SSDStatusListItemWidget.SSDListItemState -import dji.ux.beta.core.listitemwidget.ssdstatus.SSDStatusListItemWidget.SSDListItemState.CurrentSSDListItemState -import dji.ux.beta.core.listitemwidget.ssdstatus.SSDStatusListItemWidget.SSDListItemState.ProductConnected -import dji.ux.beta.core.listitemwidget.ssdstatus.SSDStatusListItemWidgetModel.SSDState +import dji.ux.beta.core.panel.listitem.ssdstatus.SSDStatusListItemWidget.DialogType.* +import dji.ux.beta.core.panel.listitem.ssdstatus.SSDStatusListItemWidget.ModelState +import dji.ux.beta.core.panel.listitem.ssdstatus.SSDStatusListItemWidget.ModelState.ProductConnected +import dji.ux.beta.core.panel.listitem.ssdstatus.SSDStatusListItemWidget.ModelState.SSDStateUpdated +import dji.ux.beta.core.panel.listitem.ssdstatus.SSDStatusListItemWidgetModel.SSDState import dji.ux.beta.core.util.UnitConversionUtil /** @@ -59,10 +59,15 @@ open class SSDStatusListItemWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ListItemLabelButtonWidget(context, attrs, defStyleAttr, WidgetType.LABEL_BUTTON) { - - private val schedulerProvider = SchedulerProvider.getInstance() - +) : ListItemLabelButtonWidget( + context, + attrs, + defStyleAttr, + WidgetType.LABEL_BUTTON, + R.style.UXSDKSSDStatusListItem +) { + + //region Fields /** * Theme for the dialogs shown for format */ @@ -87,48 +92,70 @@ open class SSDStatusListItemWidget @JvmOverloads constructor( private val widgetModel: SSDStatusListItemWidgetModel by lazy { SSDStatusListItemWidgetModel( DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - schedulerProvider) + ObservableInMemoryKeyedStore.getInstance()) } + //endregion + //region Constructor init { - listItemTitleIcon = getDrawable(R.drawable.uxsdk_ic_ssd) - listItemTitle = getString(R.string.uxsdk_list_item_ssd) - listItemButtonText = getString(R.string.uxsdk_list_item_format_button) - attrs?.let { initAttributes(context, it) } + initAttributes(context, attrs) } + //endregion - @SuppressLint("Recycle") - private fun initAttributes(context: Context, attrs: AttributeSet) { - context.obtainStyledAttributes(attrs, R.styleable.SSDStatusListItemWidget).use { typedArray -> - typedArray.getDrawableAndUse(R.styleable.SSDStatusListItemWidget_uxsdk_list_item_confirmation_dialog_icon) { - formatConfirmationDialogIcon = it - } - typedArray.getDrawableAndUse(R.styleable.SSDStatusListItemWidget_uxsdk_list_item_success_dialog_icon) { - formatSuccessDialogIcon = it - } - typedArray.getDrawableAndUse(R.styleable.SSDStatusListItemWidget_uxsdk_list_item_error_dialog_icon) { - formatErrorDialogIcon = it - } - typedArray.getResourceIdAndUse(R.styleable.SSDStatusListItemWidget_uxsdk_list_item_dialog_theme) { - dialogTheme = it + //region Lifecycle + override fun onAttachedToWindow() { + super.onAttachedToWindow() + if (!isInEditMode) { + widgetModel.setup() + } + } + + override fun onDetachedFromWindow() { + if (!isInEditMode) { + widgetModel.cleanup() + } + super.onDetachedFromWindow() + } + + override fun onButtonClick() { + val dialogListener = DialogInterface.OnClickListener { dialogInterface, buttonId: Int -> + if (buttonId == DialogInterface.BUTTON_POSITIVE) { + uiUpdateStateProcessor.onNext(DialogActionConfirmed(FormatConfirmation)) + formatSSD() + } else { + uiUpdateStateProcessor.onNext(DialogActionCanceled(FormatConfirmation)) } + dialogInterface.dismiss() } + val dialogDismissListener = DialogInterface.OnDismissListener { + uiUpdateStateProcessor.onNext(DialogDismissed(FormatConfirmation)) + } + showConfirmationDialog(title = getString(R.string.uxsdk_ssd_dialog_title), + icon = formatConfirmationDialogIcon, + dialogTheme = dialogTheme, + message = getString(R.string.uxsdk_ssd_format_confirmation), + dialogClickListener = dialogListener, + dialogDismissListener = dialogDismissListener) + uiUpdateStateProcessor.onNext(DialogDisplayed(FormatConfirmation)) } override fun reactToModelChanges() { addReaction(widgetModel.productConnection - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) addReaction(widgetModel.ssdState - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { this.updateUI(it) }) } + //endregion + + //region Reactions to models + private fun updateUI(ssdState: SSDState) { - widgetStateDataProcessor.onNext(CurrentSSDListItemState(ssdState)) + widgetStateDataProcessor.onNext(SSDStateUpdated(ssdState)) when (ssdState) { SSDState.ProductDisconnected, SSDState.NotSupported -> updateDisabledState(ssdState) @@ -142,7 +169,9 @@ open class SSDStatusListItemWidget @JvmOverloads constructor( } } } + //endregion + //region Helpers private fun getSSDMessage(ssdOperationState: SSDOperationState, remainingSpace: Int): String { return when (ssdOperationState) { SSDOperationState.NOT_FOUND -> getString(R.string.uxsdk_ssd_not_found) @@ -194,56 +223,53 @@ open class SSDStatusListItemWidget @JvmOverloads constructor( } private fun formatSSD() { + var dialogType: DialogType? = null + val dialogDismissListener = DialogInterface.OnDismissListener { + uiUpdateStateProcessor.onNext(DialogDismissed(dialogType)) + } addDisposable(widgetModel.formatSSD() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe({ + dialogType = FormatSuccess showAlertDialog(title = getString(R.string.uxsdk_ssd_dialog_title), icon = formatSuccessDialogIcon, dialogTheme = dialogTheme, - message = getString(R.string.uxsdk_ssd_format_complete)) - uiUpdateStateProcessor.onNext(WidgetUIState.DialogDisplayed(FormatSuccessDialog)) + message = getString(R.string.uxsdk_ssd_format_complete), + dialogDismissListener = dialogDismissListener) + uiUpdateStateProcessor.onNext(DialogDisplayed(dialogType)) }, { error -> if (error is UXSDKError) { + dialogType = FormatError showAlertDialog(title = getString(R.string.uxsdk_ssd_dialog_title), icon = formatErrorDialogIcon, dialogTheme = dialogTheme, message = String.format(getString(R.string.uxsdk_ssd_format_error), - error.djiError.description)) - uiUpdateStateProcessor.onNext(WidgetUIState.DialogDisplayed(FormatErrorDialog)) + error.djiError.description), + dialogDismissListener = dialogDismissListener) + uiUpdateStateProcessor.onNext(DialogDisplayed(dialogType)) } })) } + //endregion - override fun onButtonClick() { - val dialogListener = DialogInterface.OnClickListener { dialogInterface, buttonId: Int -> - if (buttonId == DialogInterface.BUTTON_POSITIVE) { - uiUpdateStateProcessor.onNext(DialogActionConfirm(FormatConfirmationDialog)) - formatSSD() + //region Customizations + @SuppressLint("Recycle") + private fun initAttributes(context: Context, attrs: AttributeSet?) { + context.obtainStyledAttributes(attrs, R.styleable.SSDStatusListItemWidget, 0, defaultStyle).use { typedArray -> + typedArray.getDrawableAndUse(R.styleable.SSDStatusListItemWidget_uxsdk_list_item_confirmation_dialog_icon) { + formatConfirmationDialogIcon = it + } + typedArray.getDrawableAndUse(R.styleable.SSDStatusListItemWidget_uxsdk_list_item_success_dialog_icon) { + formatSuccessDialogIcon = it + } + typedArray.getDrawableAndUse(R.styleable.SSDStatusListItemWidget_uxsdk_list_item_error_dialog_icon) { + formatErrorDialogIcon = it + } + typedArray.getResourceIdAndUse(R.styleable.SSDStatusListItemWidget_uxsdk_list_item_dialog_theme) { + dialogTheme = it } - dialogInterface.dismiss() - uiUpdateStateProcessor.onNext(DialogActionDismiss(FormatConfirmationDialog)) - } - showConfirmationDialog(title = getString(R.string.uxsdk_ssd_dialog_title), - icon = formatConfirmationDialogIcon, - dialogTheme = dialogTheme, - message = getString(R.string.uxsdk_ssd_format_confirmation), - dialogClickListener = dialogListener) - uiUpdateStateProcessor.onNext(WidgetUIState.DialogDisplayed(FormatConfirmationDialog)) - } - - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - if (!isInEditMode) { - widgetModel.setup() - } - } - override fun onDetachedFromWindow() { - if (!isInEditMode) { - widgetModel.cleanup() } - super.onDetachedFromWindow() } override val widgetSizeDescription: WidgetSizeDescription = @@ -254,42 +280,61 @@ open class SSDStatusListItemWidget @JvmOverloads constructor( override fun getIdealDimensionRatioString(): String? { return null } + //endregion + + //region Hooks + /** + * Get the [ListItemLabelButtonWidget.UIState] updates + * The info parameter is instance of [DialogType] + */ + override fun getUIStateUpdates(): Flowable { + return uiUpdateStateProcessor.onBackpressureBuffer() + } + + /** + * Get the [ModelState] updates + */ + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { + return super.getWidgetStateUpdate() + } /** * SSD List Item Dialog Identifiers */ - sealed class SSDListItemDialogState { + sealed class DialogType { /** * Dialog shown for format confirmation */ - object FormatConfirmationDialog : SSDListItemDialogState() + object FormatConfirmation : DialogType() /** * Dialog shown for format success */ - object FormatSuccessDialog : SSDListItemDialogState() + object FormatSuccess : DialogType() /** * Dialog shown for format fail */ - object FormatErrorDialog : SSDListItemDialogState() + object FormatError : DialogType() } /** * Class defines widget state updates */ - sealed class SSDListItemState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : SSDListItemState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * Current SSD List Item State */ - data class CurrentSSDListItemState(val ssdState: SSDState) : SSDListItemState() + data class SSDStateUpdated(val ssdState: SSDState) : ModelState() } + //endregion } \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/ssdstatus/SSDStatusListItemWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/ssdstatus/SSDStatusListItemWidgetModel.kt similarity index 91% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/ssdstatus/SSDStatusListItemWidgetModel.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/ssdstatus/SSDStatusListItemWidgetModel.kt index 7789786e..0f7070e5 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/ssdstatus/SSDStatusListItemWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/ssdstatus/SSDStatusListItemWidgetModel.kt @@ -21,16 +21,15 @@ * */ -package dji.ux.beta.core.listitemwidget.ssdstatus +package dji.ux.beta.core.panel.listitem.ssdstatus import dji.common.camera.SSDOperationState import dji.keysdk.CameraKey import dji.thirdparty.io.reactivex.Completable import dji.thirdparty.io.reactivex.Flowable import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.SchedulerProviderInterface import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.util.DataProcessor import dji.ux.beta.core.util.SettingDefinitions @@ -40,8 +39,7 @@ import dji.ux.beta.core.util.SettingDefinitions */ class SSDStatusListItemWidgetModel( djiSdkModel: DJISDKModel, - keyedStore: ObservableInMemoryKeyedStore, - private val schedulerProvider: SchedulerProviderInterface + keyedStore: ObservableInMemoryKeyedStore ) : WidgetModel(djiSdkModel, keyedStore) { private val ssdStateProcessor: DataProcessor = DataProcessor.create(SSDState.ProductDisconnected) @@ -95,7 +93,7 @@ class SSDStatusListItemWidgetModel( */ fun formatSSD(): Completable { val ssdFormatKey = CameraKey.create(CameraKey.FORMAT_SSD, cameraIndex) - return djiSdkModel.performAction(ssdFormatKey).subscribeOn(schedulerProvider.io()) + return djiSdkModel.performAction(ssdFormatKey) } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/travelmode/TravelModeListItemWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/travelmode/TravelModeListItemWidget.kt similarity index 73% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/travelmode/TravelModeListItemWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/travelmode/TravelModeListItemWidget.kt index 0e99e6c1..7fccf2f7 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/travelmode/TravelModeListItemWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/travelmode/TravelModeListItemWidget.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.core.listitemwidget.travelmode +package dji.ux.beta.core.panel.listitem.travelmode import android.annotation.SuppressLint import android.content.Context @@ -31,19 +31,20 @@ import android.util.AttributeSet import androidx.annotation.StyleRes import androidx.core.content.res.use import dji.thirdparty.io.reactivex.Flowable -import dji.ux.beta.R +import dji.ux.beta.core.R import dji.ux.beta.core.base.DJISDKModel import dji.ux.beta.core.base.SchedulerProvider import dji.ux.beta.core.base.UXSDKError import dji.ux.beta.core.base.WidgetSizeDescription -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.base.widget.ListItemLabelButtonWidget -import dji.ux.beta.core.base.widget.ListItemLabelButtonWidget.WidgetUIState.* +import dji.ux.beta.core.base.panel.listitem.ListItemLabelButtonWidget +import dji.ux.beta.core.base.panel.listitem.ListItemLabelButtonWidget.UIState.* +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.* -import dji.ux.beta.core.listitemwidget.travelmode.TravelModeListItemWidget.TravelModeItemDialogState.* -import dji.ux.beta.core.listitemwidget.travelmode.TravelModeListItemWidget.TravelModeListItemState.CurrentTravelModeListItemState -import dji.ux.beta.core.listitemwidget.travelmode.TravelModeListItemWidget.TravelModeListItemState.ProductConnected -import dji.ux.beta.core.listitemwidget.travelmode.TravelModeListItemWidgetModel.TravelModeState +import dji.ux.beta.core.panel.listitem.travelmode.TravelModeListItemWidget.DialogType.* +import dji.ux.beta.core.panel.listitem.travelmode.TravelModeListItemWidget.ModelState +import dji.ux.beta.core.panel.listitem.travelmode.TravelModeListItemWidget.ModelState.ProductConnected +import dji.ux.beta.core.panel.listitem.travelmode.TravelModeListItemWidget.ModelState.TravelModeStateUpdated +import dji.ux.beta.core.panel.listitem.travelmode.TravelModeListItemWidgetModel.TravelModeState /** * Travel Mode List Item @@ -53,10 +54,15 @@ open class TravelModeListItemWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ListItemLabelButtonWidget(context, attrs, defStyleAttr, WidgetType.BUTTON) { - - private val schedulerProvider = SchedulerProvider.getInstance() - +) : ListItemLabelButtonWidget( + context, + attrs, + defStyleAttr, + WidgetType.BUTTON, + R.style.UXSDKTravelModeListItem +) { + + //region Fields /** * Icon when landing gear is not in travel mode */ @@ -101,50 +107,24 @@ open class TravelModeListItemWidget @JvmOverloads constructor( private val widgetModel: TravelModeListItemWidgetModel by lazy { TravelModeListItemWidgetModel( DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - schedulerProvider) + ObservableInMemoryKeyedStore.getInstance()) } + //endregion + //region Constructor init { + initAttributes(context, attrs) listItemTitleIcon = travelModeInactiveIcon - listItemTitle = getString(R.string.uxsdk_list_item_travel_mode) - attrs?.let { initAttributes(context, it) } - } - - @SuppressLint("Recycle") - private fun initAttributes(context: Context, attrs: AttributeSet) { - context.obtainStyledAttributes(attrs, R.styleable.TravelModeListItemWidget).use { typedArray -> - typedArray.getDrawableAndUse(R.styleable.TravelModeListItemWidget_uxsdk_travel_mode_active_icon) { - travelModeActiveIcon = it - } - typedArray.getDrawableAndUse(R.styleable.TravelModeListItemWidget_uxsdk_travel_mode_inactive_icon) { - travelModeInactiveIcon = it - } - typedArray.getDrawableAndUse(R.styleable.TravelModeListItemWidget_uxsdk_list_item_confirmation_dialog_icon) { - confirmationDialogIcon = it - } - typedArray.getDrawableAndUse(R.styleable.TravelModeListItemWidget_uxsdk_list_item_error_dialog_icon) { - errorDialogIcon = it - } - typedArray.getDrawableAndUse(R.styleable.TravelModeListItemWidget_uxsdk_list_item_success_dialog_icon) { - successDialogIcon = it - } - - enterTravelModeButtonString = typedArray.getString(R.styleable.TravelModeListItemWidget_uxsdk_enter_travel_mode_button_string, enterTravelModeButtonString) - exitTravelModeButtonString = typedArray.getString(R.styleable.TravelModeListItemWidget_uxsdk_exit_travel_mode_button_string, exitTravelModeButtonString) - typedArray.getResourceIdAndUse(R.styleable.TravelModeListItemWidget_uxsdk_list_item_dialog_theme) { - dialogTheme = it - } - - } } + //endregion + //region Lifecycle override fun reactToModelChanges() { addReaction(widgetModel.travelModeState - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { this.updateUI(it) }) addReaction(widgetModel.productConnection - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) } @@ -163,22 +143,41 @@ open class TravelModeListItemWidget @JvmOverloads constructor( super.onDetachedFromWindow() } - override fun getIdealDimensionRatioString(): String? = null - - - override val widgetSizeDescription: WidgetSizeDescription = - WidgetSizeDescription(WidgetSizeDescription.SizeType.OTHER, - widthDimension = WidgetSizeDescription.Dimension.EXPAND, - heightDimension = WidgetSizeDescription.Dimension.WRAP) - - override fun onButtonClick() { checkAndToggleTravelMode() } + //endregion + //region Reactions to model + private fun updateUI(travelModeState: TravelModeState) { + widgetStateDataProcessor.onNext(TravelModeStateUpdated(travelModeState)) + when (travelModeState) { + TravelModeState.ProductDisconnected, + TravelModeState.NotSupported -> { + isEnabled = false + listItemTitleIcon = travelModeInactiveIcon + listItemButtonText = getString(R.string.uxsdk_string_default_value) + } + TravelModeState.Active -> { + isEnabled = true + listItemTitleIcon = travelModeActiveIcon + listItemButtonText = exitTravelModeButtonString + } + TravelModeState.Inactive -> { + isEnabled = true + listItemTitleIcon = travelModeInactiveIcon + listItemButtonText = enterTravelModeButtonString + } + } + + } + + //endregion + + //region Helpers private fun checkAndToggleTravelMode() { addDisposable(widgetModel.travelModeState.firstOrError() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe({ when (it) { TravelModeState.Inactive -> { @@ -197,31 +196,40 @@ open class TravelModeListItemWidget @JvmOverloads constructor( val dialogListener = DialogInterface.OnClickListener { dialogInterface, buttonId: Int -> if (buttonId == DialogInterface.BUTTON_POSITIVE) { enterTravelMode() - uiUpdateStateProcessor.onNext(DialogActionConfirm(EnterTravelModeConfirmation)) + uiUpdateStateProcessor.onNext(DialogActionConfirmed(EnterTravelModeConfirmation)) + } else { + uiUpdateStateProcessor.onNext(DialogActionCanceled(EnterTravelModeConfirmation)) } dialogInterface.dismiss() - uiUpdateStateProcessor.onNext(DialogActionDismiss(EnterTravelModeConfirmation)) + } + val dialogDismissListener = DialogInterface.OnDismissListener { + uiUpdateStateProcessor.onNext(DialogDismissed(EnterTravelModeConfirmation)) } showConfirmationDialog(title = getString(R.string.uxsdk_list_item_travel_mode), icon = confirmationDialogIcon, dialogTheme = dialogTheme, message = getString(R.string.uxsdk_travel_mode_enter_confirmation), - dialogClickListener = dialogListener) + dialogClickListener = dialogListener, + dialogDismissListener = dialogDismissListener) uiUpdateStateProcessor.onNext(DialogDisplayed(EnterTravelModeConfirmation)) } private fun exitTravelMode() { addDisposable(widgetModel.exitTravelMode() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe( { }, { error -> if (error is UXSDKError) { + val dialogDismissListener = DialogInterface.OnDismissListener { + uiUpdateStateProcessor.onNext(DialogDismissed(ExitTravelModeError)) + } showAlertDialog(title = getString(R.string.uxsdk_list_item_travel_mode), icon = errorDialogIcon, dialogTheme = dialogTheme, message = String.format(getString(R.string.uxsdk_exit_travel_mode_failed), - error.djiError.description)) + error.djiError.description), + dialogDismissListener = dialogDismissListener) uiUpdateStateProcessor.onNext(DialogDisplayed(ExitTravelModeError)) } } @@ -230,24 +238,32 @@ open class TravelModeListItemWidget @JvmOverloads constructor( } private fun enterTravelMode() { + var dialogType: DialogType? = null + val dialogDismissListener = DialogInterface.OnDismissListener { + uiUpdateStateProcessor.onNext(DialogDismissed(dialogType)) + } addDisposable(widgetModel.enterTravelMode() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe( { + dialogType = EnterTravelModeSuccess showAlertDialog(title = getString(R.string.uxsdk_list_item_travel_mode), icon = successDialogIcon, dialogTheme = dialogTheme, - message = getString(R.string.uxsdk_enter_travel_mode_success)) - uiUpdateStateProcessor.onNext(DialogDisplayed(EnterTravelModeSuccess)) + message = getString(R.string.uxsdk_enter_travel_mode_success), + dialogDismissListener = dialogDismissListener) + uiUpdateStateProcessor.onNext(DialogDisplayed(dialogType)) }, { error -> if (error is UXSDKError) { + dialogType = EnterTravelModeError showAlertDialog(title = getString(R.string.uxsdk_list_item_travel_mode), icon = errorDialogIcon, dialogTheme = dialogTheme, message = String.format(getString(R.string.uxsdk_enter_travel_mode_failed), - error.djiError.description)) - uiUpdateStateProcessor.onNext(DialogDisplayed(EnterTravelModeError)) + error.djiError.description), + dialogDismissListener = dialogDismissListener) + uiUpdateStateProcessor.onNext(DialogDisplayed(dialogType)) } } )) @@ -255,84 +271,106 @@ open class TravelModeListItemWidget @JvmOverloads constructor( } - private fun updateUI(travelModeState: TravelModeState) { - widgetStateDataProcessor.onNext(CurrentTravelModeListItemState(travelModeState)) - when (travelModeState) { - TravelModeState.ProductDisconnected, - TravelModeState.NotSupported -> { - isEnabled = false - listItemTitleIcon = travelModeInactiveIcon - listItemButtonText = getString(R.string.uxsdk_string_default_value) + //endregion + + //region Customization + override fun getIdealDimensionRatioString(): String? = null + + + override val widgetSizeDescription: WidgetSizeDescription = + WidgetSizeDescription(WidgetSizeDescription.SizeType.OTHER, + widthDimension = WidgetSizeDescription.Dimension.EXPAND, + heightDimension = WidgetSizeDescription.Dimension.WRAP) + + + @SuppressLint("Recycle") + private fun initAttributes(context: Context, attrs: AttributeSet?) { + context.obtainStyledAttributes(attrs, R.styleable.TravelModeListItemWidget, 0, defaultStyle).use { typedArray -> + typedArray.getDrawableAndUse(R.styleable.TravelModeListItemWidget_uxsdk_travel_mode_active_icon) { + travelModeActiveIcon = it } - TravelModeState.Active -> { - isEnabled = true - listItemTitleIcon = travelModeActiveIcon - listItemButtonText = exitTravelModeButtonString + typedArray.getDrawableAndUse(R.styleable.TravelModeListItemWidget_uxsdk_travel_mode_inactive_icon) { + travelModeInactiveIcon = it } - TravelModeState.Inactive -> { - isEnabled = true - listItemTitleIcon = travelModeInactiveIcon - listItemButtonText = enterTravelModeButtonString + typedArray.getDrawableAndUse(R.styleable.TravelModeListItemWidget_uxsdk_list_item_confirmation_dialog_icon) { + confirmationDialogIcon = it + } + typedArray.getDrawableAndUse(R.styleable.TravelModeListItemWidget_uxsdk_list_item_error_dialog_icon) { + errorDialogIcon = it + } + typedArray.getDrawableAndUse(R.styleable.TravelModeListItemWidget_uxsdk_list_item_success_dialog_icon) { + successDialogIcon = it } - } + enterTravelModeButtonString = typedArray.getString(R.styleable.TravelModeListItemWidget_uxsdk_enter_travel_mode_button_string, enterTravelModeButtonString) + exitTravelModeButtonString = typedArray.getString(R.styleable.TravelModeListItemWidget_uxsdk_exit_travel_mode_button_string, exitTravelModeButtonString) + typedArray.getResourceIdAndUse(R.styleable.TravelModeListItemWidget_uxsdk_list_item_dialog_theme) { + dialogTheme = it + } + + } } + //endregion + + //region Hooks + /** - * Get the [TravelModeListItemState] updates + * Get the [ModelState] updates */ - override fun getWidgetStateUpdate(): Flowable { + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { return super.getWidgetStateUpdate() } /** - * Get the [ListItemLabelButtonWidget.WidgetUIState] updates - * The info parameter is instance of [TravelModeItemDialogState] + * Get the [ListItemLabelButtonWidget.UIState] updates + * The info parameter is instance of [DialogType] */ - override fun getUIStateUpdates(): Flowable { - return uiUpdateStateProcessor + override fun getUIStateUpdates(): Flowable { + return uiUpdateStateProcessor.onBackpressureBuffer() } /** * Travel mode List Item Dialog Identifiers */ - sealed class TravelModeItemDialogState { + sealed class DialogType { /** * Dialog shown for confirmation to enter travel mode */ - object EnterTravelModeConfirmation : TravelModeItemDialogState() + object EnterTravelModeConfirmation : DialogType() /** * Dialog shown when entering travel mode success */ - object EnterTravelModeSuccess : TravelModeItemDialogState() + object EnterTravelModeSuccess : DialogType() /** * Dialog shown when entering travel mode fails */ - object EnterTravelModeError : TravelModeItemDialogState() + object EnterTravelModeError : DialogType() /** * Dialog shown when exiting travel mode fails */ - object ExitTravelModeError : TravelModeItemDialogState() + object ExitTravelModeError : DialogType() } /** * Class defines widget state updates */ - sealed class TravelModeListItemState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : TravelModeListItemState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** - * Current travel mode state + * Travel mode state updated */ - data class CurrentTravelModeListItemState(val travelModeState: TravelModeState) : TravelModeListItemState() + data class TravelModeStateUpdated(val travelModeState: TravelModeState) : ModelState() } - + //endregion } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/travelmode/TravelModeListItemWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/travelmode/TravelModeListItemWidgetModel.kt similarity index 90% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/travelmode/TravelModeListItemWidgetModel.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/travelmode/TravelModeListItemWidgetModel.kt index d80fe19d..2e1b9b9d 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/travelmode/TravelModeListItemWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/travelmode/TravelModeListItemWidgetModel.kt @@ -21,16 +21,15 @@ * */ -package dji.ux.beta.core.listitemwidget.travelmode +package dji.ux.beta.core.panel.listitem.travelmode import dji.common.flightcontroller.LandingGearMode import dji.keysdk.FlightControllerKey import dji.thirdparty.io.reactivex.Completable import dji.thirdparty.io.reactivex.Flowable import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.SchedulerProviderInterface import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.util.DataProcessor /** @@ -39,8 +38,7 @@ import dji.ux.beta.core.util.DataProcessor */ class TravelModeListItemWidgetModel( djiSdkModel: DJISDKModel, - keyedStore: ObservableInMemoryKeyedStore, - private val schedulerProvider: SchedulerProviderInterface + keyedStore: ObservableInMemoryKeyedStore ) : WidgetModel(djiSdkModel, keyedStore) { //region Fields @@ -98,7 +96,7 @@ class TravelModeListItemWidgetModel( */ fun enterTravelMode(): Completable { val enterTransportModeKey = FlightControllerKey.create(FlightControllerKey.ENTER_TRANSPORT_MODE) - return djiSdkModel.performAction(enterTransportModeKey).subscribeOn(schedulerProvider.io()) + return djiSdkModel.performAction(enterTransportModeKey) } /** @@ -106,7 +104,7 @@ class TravelModeListItemWidgetModel( */ fun exitTravelMode(): Completable { val enterTransportModeKey = FlightControllerKey.create(FlightControllerKey.EXIT_TRANSPORT_MODE) - return djiSdkModel.performAction(enterTransportModeKey).subscribeOn(schedulerProvider.io()) + return djiSdkModel.performAction(enterTransportModeKey) } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/unittype/UnitModeListItemWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/unittype/UnitModeListItemWidget.kt new file mode 100644 index 00000000..a5d856db --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/unittype/UnitModeListItemWidget.kt @@ -0,0 +1,348 @@ +/* + * 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.ux.beta.core.panel.listitem.unittype + +import android.annotation.SuppressLint +import android.app.AlertDialog +import android.content.Context +import android.content.DialogInterface +import android.content.res.ColorStateList +import android.graphics.drawable.Drawable +import android.text.Layout +import android.text.SpannableString +import android.text.SpannableStringBuilder +import android.text.style.AlignmentSpan +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.CheckBox +import android.widget.CompoundButton +import android.widget.TextView +import androidx.annotation.ColorInt +import androidx.annotation.Dimension +import androidx.annotation.DrawableRes +import androidx.annotation.StyleRes +import androidx.appcompat.view.ContextThemeWrapper +import androidx.core.content.res.use +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.R +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.SchedulerProvider +import dji.ux.beta.core.base.UXSDKError +import dji.ux.beta.core.base.WidgetSizeDescription +import dji.ux.beta.core.base.panel.listitem.ListItemRadioButtonWidget +import dji.ux.beta.core.communication.GlobalPreferencesManager +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.extension.* +import dji.ux.beta.core.panel.listitem.unittype.UnitModeListItemWidget.ModelState +import dji.ux.beta.core.panel.listitem.unittype.UnitModeListItemWidget.ModelState.* +import dji.ux.beta.core.panel.listitem.unittype.UnitModeListItemWidgetModel.UnitTypeState +import dji.ux.beta.core.util.DisplayUtil +import dji.ux.beta.core.util.UnitConversionUtil.UnitType + +/** + * Widget shows the current [UnitType] being used. + * It also provides an option to switch between them. + */ +open class UnitModeListItemWidget @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ListItemRadioButtonWidget( + context, + attrs, + defStyleAttr, + R.style.UXSDKUnitModeListItem +) { + + //region Fields + private val widgetModel: UnitModeListItemWidgetModel by lazy { + UnitModeListItemWidgetModel( + DJISDKModel.getInstance(), + ObservableInMemoryKeyedStore.getInstance(), + GlobalPreferencesManager.getInstance()) + } + + private var imperialItemIndex: Int = INVALID_OPTION_INDEX + private var metricItemIndex: Int = INVALID_OPTION_INDEX + + /** + * Theme for the dialogs shown for format + */ + @StyleRes + var dialogTheme: Int = R.style.UXSDKDialogTheme + + /** + * The text appearance of the check box label + */ + @get:StyleRes + @setparam:StyleRes + var checkBoxTextAppearance = 0 + + /** + * The text color state list of the check box label + */ + var checkBoxTextColor: ColorStateList? = null + + /** + * The background of the check box + */ + var checkBoxTextBackground: Drawable? = null + + /** + * The text size of the check box label + */ + @get:Dimension + @setparam:Dimension + var checkBoxTextSize: Float = 0f + + //endregion + + //region Constructor + init { + imperialItemIndex = addOptionToGroup(getString(R.string.uxsdk_list_item_unit_mode_imperial)) + metricItemIndex = addOptionToGroup(getString(R.string.uxsdk_list_item_unit_mode_metric)) + initAttributes(context, attrs) + } + //endregion + + //region Lifecycle + override fun reactToModelChanges() { + addReaction(widgetModel.productConnection + .observeOn(SchedulerProvider.ui()) + .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) + addReaction(widgetModel.unitTypeState + .observeOn(SchedulerProvider.ui()) + .subscribe { + widgetStateDataProcessor.onNext(UnitTypeUpdated(it)) + updateUI(it) + }) + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + if (!isInEditMode) { + widgetModel.setup() + } + } + + override fun onDetachedFromWindow() { + if (!isInEditMode) { + widgetModel.cleanup() + } + super.onDetachedFromWindow() + } + + override fun onOptionTapped(optionIndex: Int, optionLabel: String) { + val newUnitType: UnitType = if (optionIndex == imperialItemIndex) { + UnitType.IMPERIAL + } else { + UnitType.METRIC + } + addDisposable(widgetModel.setUnitType(newUnitType) + .observeOn(SchedulerProvider.ui()) + .subscribe({ + widgetStateDataProcessor.onNext(SetUnitTypeSucceeded) + if (newUnitType == UnitType.IMPERIAL + && !GlobalPreferencesManager.getInstance().isUnitModeDialogNeverShown) { + showWarningDialog() + } + }, { + widgetStateDataProcessor.onNext(SetUnitTypeFailed(it as UXSDKError)) + resetUI() + })) + + } + + //endregion + + //region Reactions to model + + private fun updateUI(unitTypeState: UnitTypeState) { + isEnabled = if (unitTypeState is UnitTypeState.CurrentUnitType) { + if (unitTypeState.unitType == UnitType.IMPERIAL) { + setSelected(imperialItemIndex) + } else { + setSelected(metricItemIndex) + } + true + } else { + false + } + } + + //endregion + + //region Helpers + @SuppressLint("InflateParams") + private fun showWarningDialog() { + val dialogListener = DialogInterface.OnClickListener { dialogInterface, _: Int -> + dialogInterface.dismiss() + } + val dialogDismissListener = DialogInterface.OnDismissListener { + uiUpdateStateProcessor.onNext(UIState.DialogDismissed(null)) + } + val ctw = ContextThemeWrapper(context, dialogTheme) + val inflater: LayoutInflater = ctw.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater + val view = inflater.inflate(R.layout.uxsdk_layout_dialog_checkbox, null) + val builder = AlertDialog.Builder(context, dialogTheme) + builder.setPositiveButton(getString(R.string.uxsdk_app_ok), dialogListener) + builder.setOnDismissListener(dialogDismissListener) + builder.setCancelable(true) + builder.setTitle(getString(R.string.uxsdk_list_item_unit_mode)) + val neverShowAgainCheckBox = view.findViewById(R.id.checkbox_dont_show_again) + neverShowAgainCheckBox.setTextColor(getColor(R.color.uxsdk_white)) + if (checkBoxTextAppearance != INVALID_RESOURCE) { + neverShowAgainCheckBox.setTextAppearance(context, checkBoxTextAppearance) + } + if (checkBoxTextColor != null) { + neverShowAgainCheckBox.setTextColor(checkBoxTextColor) + } + if (checkBoxTextBackground != null) { + neverShowAgainCheckBox.background = checkBoxTextBackground + } + if (checkBoxTextSize != INVALID_DIMENSION) { + neverShowAgainCheckBox.textSize = checkBoxTextSize + } + val textView = view.findViewById(R.id.textview_dialog_content) + textView.setText(getSpannableString(), TextView.BufferType.SPANNABLE) + neverShowAgainCheckBox.setOnCheckedChangeListener { _: CompoundButton?, checked: Boolean -> + GlobalPreferencesManager.getInstance().isUnitModeDialogNeverShown = checked + uiUpdateStateProcessor.onNext(UIState.NeverShowAgainCheckChanged(checked)) + } + builder.setView(view) + builder.create().show() + uiUpdateStateProcessor.onNext(UIState.DialogDisplayed(null)) + } + + private fun resetUI() { + addDisposable(widgetModel.unitTypeState + .observeOn(SchedulerProvider.ui()) + .subscribe { updateUI(it) }) + } + + private fun getSpannableString(): SpannableStringBuilder { + val builder = SpannableStringBuilder() + + val str1 = SpannableString(getString(R.string.uxsdk_dialog_unit_change_notice)) + str1.setSpan(AlignmentSpan.Standard(Layout.Alignment.ALIGN_NORMAL), 0, str1.length, 0) + builder.appendln(str1).appendln() + + val str2 = SpannableString(resources.getString(R.string.uxsdk_dialog_unit_change_example)) + str2.setSpan(AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER), 0, str2.length, 0) + builder.append(str2) + return builder + } + //endregion + + //region Customizations + + override fun getIdealDimensionRatioString(): String? { + return null + } + + override val widgetSizeDescription: WidgetSizeDescription = + WidgetSizeDescription(WidgetSizeDescription.SizeType.OTHER, + widthDimension = WidgetSizeDescription.Dimension.EXPAND, + heightDimension = WidgetSizeDescription.Dimension.WRAP) + + /** + * Set the text color for the check box label + * + * @param color color integer resource + */ + fun setCheckBoxTextColor(@ColorInt color: Int) { + if (color != INVALID_COLOR) { + checkBoxTextColor = ColorStateList.valueOf(color) + } + } + + /** + * Set the resource ID for the background of the check box + * + * @param resourceId Integer ID of the text view's background resource + */ + fun setCheckBoxBackground(@DrawableRes resourceId: Int) { + checkBoxTextBackground = getDrawable(resourceId) + } + + @SuppressLint("Recycle") + private fun initAttributes(context: Context, attrs: AttributeSet?) { + context.obtainStyledAttributes(attrs, R.styleable.UnitModeListItemWidget, 0, defaultStyle).use { typedArray -> + typedArray.getResourceIdAndUse(R.styleable.UnitModeListItemWidget_uxsdk_list_item_dialog_theme) { + dialogTheme = it + } + typedArray.getResourceIdAndUse(R.styleable.UnitModeListItemWidget_uxsdk_checkBoxTextAppearance) { + checkBoxTextAppearance = it + } + typedArray.getColorStateListAndUse(R.styleable.UnitModeListItemWidget_uxsdk_checkBoxTextColor) { + checkBoxTextColor = it + } + typedArray.getDrawableAndUse(R.styleable.UnitModeListItemWidget_uxsdk_checkBoxTextBackground) { + checkBoxTextBackground = it + } + typedArray.getDimensionAndUse(R.styleable.UnitModeListItemWidget_uxsdk_checkBoxTextSize) { + checkBoxTextSize = DisplayUtil.pxToSp(context, it) + } + } + } + + //endregion + + //region Hooks + + /** + * Get the [ModelState] updates + */ + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { + return super.getWidgetStateUpdate() + } + + /** + * Class defines widget state updates + */ + sealed class ModelState { + /** + * Product connection update + */ + data class ProductConnected(val isConnected: Boolean) : ModelState() + + /** + * Set unit type success + */ + object SetUnitTypeSucceeded : ModelState() + + /** + * Set unit type failed + */ + data class SetUnitTypeFailed(val error: UXSDKError) : ModelState() + + /** + * Unit type updated + */ + data class UnitTypeUpdated(val unitTypeState: UnitTypeState) : ModelState() + } + //endregion + +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/unittype/UnitTypeListItemWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/unittype/UnitModeListItemWidgetModel.kt similarity index 82% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/unittype/UnitTypeListItemWidgetModel.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/unittype/UnitModeListItemWidgetModel.kt index 34594848..9855246a 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/listitemwidget/unittype/UnitTypeListItemWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/listitem/unittype/UnitModeListItemWidgetModel.kt @@ -21,31 +21,25 @@ * */ -package dji.ux.beta.core.listitemwidget.unittype +package dji.ux.beta.core.panel.listitem.unittype import dji.thirdparty.io.reactivex.Completable import dji.thirdparty.io.reactivex.Flowable import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.GlobalPreferencesInterface -import dji.ux.beta.core.base.SchedulerProviderInterface import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.GlobalPreferenceKeys -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.base.uxsdkkeys.UXKey -import dji.ux.beta.core.base.uxsdkkeys.UXKeys -import dji.ux.beta.core.listitemwidget.unittype.UnitTypeListItemWidgetModel.UnitTypeState.CurrentUnitType -import dji.ux.beta.core.listitemwidget.unittype.UnitTypeListItemWidgetModel.UnitTypeState.ProductDisconnected +import dji.ux.beta.core.communication.* +import dji.ux.beta.core.panel.listitem.unittype.UnitModeListItemWidgetModel.UnitTypeState.CurrentUnitType +import dji.ux.beta.core.panel.listitem.unittype.UnitModeListItemWidgetModel.UnitTypeState.ProductDisconnected import dji.ux.beta.core.util.DataProcessor import dji.ux.beta.core.util.UnitConversionUtil.UnitType /** - * Widget Model for the [UnitTypeListItemWidget] used to define + * Widget Model for the [UnitModeListItemWidget] used to define * the underlying logic and communication */ -class UnitTypeListItemWidgetModel( +class UnitModeListItemWidgetModel( djiSdkModel: DJISDKModel, keyedStore: ObservableInMemoryKeyedStore, - private val schedulerProvider: SchedulerProviderInterface, private val preferencesManager: GlobalPreferencesInterface? ) : WidgetModel(djiSdkModel, keyedStore) { @@ -84,7 +78,6 @@ class UnitTypeListItemWidgetModel( if (unitType == unitTypeProcessor.value) return Completable.complete() preferencesManager?.unitType = unitType return uxKeyManager.setValue(unitTypeKey, unitType) - .subscribeOn(schedulerProvider.io()) } /** diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panelwidget/systemstatus/SmartListInternalModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/systemstatus/SmartListInternalModel.kt similarity index 94% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panelwidget/systemstatus/SmartListInternalModel.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/systemstatus/SmartListInternalModel.kt index 15f8b910..766bc401 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panelwidget/systemstatus/SmartListInternalModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/systemstatus/SmartListInternalModel.kt @@ -21,14 +21,14 @@ * */ -package dji.ux.beta.core.panelwidget.systemstatus +package dji.ux.beta.core.panel.systemstatus import dji.common.product.Model import dji.keysdk.ProductKey import dji.thirdparty.io.reactivex.Flowable import dji.ux.beta.core.base.DJISDKModel import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.util.DataProcessor /** diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panelwidget/systemstatus/SystemStatusListPanelWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/systemstatus/SystemStatusListPanelWidget.kt similarity index 75% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panelwidget/systemstatus/SystemStatusListPanelWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/systemstatus/SystemStatusListPanelWidget.kt index 1f809a6c..1e1dfd39 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panelwidget/systemstatus/SystemStatusListPanelWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/systemstatus/SystemStatusListPanelWidget.kt @@ -21,30 +21,31 @@ * */ -package dji.ux.beta.core.panelwidget.systemstatus +package dji.ux.beta.core.panel.systemstatus import android.annotation.SuppressLint import android.content.Context import android.util.AttributeSet import androidx.core.content.res.use -import dji.ux.beta.R -import dji.ux.beta.core.base.OnStateChangeCallback -import dji.ux.beta.core.base.panelwidget.ListPanelWidget -import dji.ux.beta.core.base.panelwidget.PanelWidgetConfiguration -import dji.ux.beta.core.base.panelwidget.PanelWidgetType -import dji.ux.beta.core.base.panelwidget.WidgetID +import dji.ux.beta.core.R +import dji.ux.beta.core.base.panel.ListPanelWidget +import dji.ux.beta.core.base.panel.PanelWidgetConfiguration +import dji.ux.beta.core.base.panel.PanelWidgetType +import dji.ux.beta.core.base.panel.WidgetID +import dji.ux.beta.core.communication.OnStateChangeCallback import dji.ux.beta.core.extension.getIntegerAndUse import dji.ux.beta.core.extension.toggleVisibility -import dji.ux.beta.core.listitemwidget.emmcstatus.EMMCStatusListItemWidget -import dji.ux.beta.core.listitemwidget.flightmode.FlightModeListItemWidget -import dji.ux.beta.core.listitemwidget.maxaltitude.MaxAltitudeListItemWidget -import dji.ux.beta.core.listitemwidget.maxflightdistance.MaxFlightDistanceListItemWidget -import dji.ux.beta.core.listitemwidget.overviewstatus.OverviewListItemWidget -import dji.ux.beta.core.listitemwidget.rcbattery.RCBatteryListItemWidget -import dji.ux.beta.core.listitemwidget.rcstickmode.RCStickModeListItemWidget -import dji.ux.beta.core.listitemwidget.returntohomealtitude.ReturnToHomeAltitudeListItemWidget -import dji.ux.beta.core.listitemwidget.sdcardstatus.SDCardStatusListItemWidget -import dji.ux.beta.core.listitemwidget.travelmode.TravelModeListItemWidget +import dji.ux.beta.core.panel.listitem.aircraftbatterytemperature.AircraftBatteryTemperatureListItemWidget +import dji.ux.beta.core.panel.listitem.emmcstatus.EMMCStatusListItemWidget +import dji.ux.beta.core.panel.listitem.flightmode.FlightModeListItemWidget +import dji.ux.beta.core.panel.listitem.maxaltitude.MaxAltitudeListItemWidget +import dji.ux.beta.core.panel.listitem.maxflightdistance.MaxFlightDistanceListItemWidget +import dji.ux.beta.core.panel.listitem.overview.OverviewListItemWidget +import dji.ux.beta.core.panel.listitem.rcbattery.RCBatteryListItemWidget +import dji.ux.beta.core.panel.listitem.rcstickmode.RCStickModeListItemWidget +import dji.ux.beta.core.panel.listitem.returntohomealtitude.ReturnToHomeAltitudeListItemWidget +import dji.ux.beta.core.panel.listitem.sdcardstatus.SDCardStatusListItemWidget +import dji.ux.beta.core.panel.listitem.travelmode.TravelModeListItemWidget import dji.ux.beta.core.widget.systemstatus.SystemStatusWidget /** @@ -63,7 +64,7 @@ import dji.ux.beta.core.widget.systemstatus.SystemStatusWidget * travel_mode. * * Note that multiple flags can be used simultaneously by logically OR'ing - * them. For example, to hide sd card status and rc srtick mode, it can be done by the + * them. For example, to hide sd card status and rc stick mode, it can be done by the * following two steps. * Define custom xmlns in its layout file: * xmlns:app="http://schemas.android.com/apk/res-auto" @@ -77,6 +78,7 @@ import dji.ux.beta.core.widget.systemstatus.SystemStatusWidget * [FlightModeListItemWidget], * [RCStickModeListItemWidget], * [RCBatteryListItemWidget], + * [AircraftBatteryTemperatureListItemWidget], * [SDCardStatusListItemWidget], * [EMMCStatusListItemWidget], * [MaxAltitudeListItemWidget], @@ -104,10 +106,10 @@ class SystemStatusListPanelWidget @JvmOverloads constructor( } init { - val blacklistValue = attrs?.let { initAttributes(context, it) } - val blacklistSet = getBlacklistSet(blacklistValue) + val excludedItemsValue = attrs?.let { initAttributes(context, it) } + val excludedItemsSet = getExcludedItems(excludedItemsValue) - smartListModel = SystemStatusSmartListModel(context, attrs, blacklistSet) + smartListModel = SystemStatusSmartListModel(context, attrs, excludedItemsSet) } override fun onSmartListModelCreated() { @@ -138,10 +140,10 @@ class SystemStatusListPanelWidget @JvmOverloads constructor( //endregion //region Helpers - private fun getBlacklistSet(blacklistValue: Int?): Set? { - return if (blacklistValue != null) { - SystemStatusSmartListModel.SystemStatusListItem.values() - .filter { it.isItemExcluded(blacklistValue) } + private fun getExcludedItems(excludedItemsValue: Int?): Set? { + return if (excludedItemsValue != null) { + SystemStatusSmartListModel.SystemStatusListItem.values + .filter { it.isItemExcluded(excludedItemsValue) } .map { it.widgetID } .toSet() } else { diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panelwidget/systemstatus/SystemStatusSmartListModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/systemstatus/SystemStatusSmartListModel.kt similarity index 78% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panelwidget/systemstatus/SystemStatusSmartListModel.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/systemstatus/SystemStatusSmartListModel.kt index f2ef748f..2ec48ca8 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panelwidget/systemstatus/SystemStatusSmartListModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/systemstatus/SystemStatusSmartListModel.kt @@ -21,29 +21,30 @@ * */ -package dji.ux.beta.core.panelwidget.systemstatus +package dji.ux.beta.core.panel.systemstatus import android.content.Context import android.util.AttributeSet import android.view.View import dji.common.product.Model import dji.common.product.Model.* -import dji.ux.beta.core.base.panelwidget.SmartListModel -import dji.ux.beta.core.base.panelwidget.WidgetID -import dji.ux.beta.core.listitemwidget.emmcstatus.EMMCStatusListItemWidget -import dji.ux.beta.core.listitemwidget.flightmode.FlightModeListItemWidget -import dji.ux.beta.core.listitemwidget.maxaltitude.MaxAltitudeListItemWidget -import dji.ux.beta.core.listitemwidget.maxflightdistance.MaxFlightDistanceListItemWidget -import dji.ux.beta.core.listitemwidget.novicemode.NoviceModeListItemWidget -import dji.ux.beta.core.listitemwidget.overviewstatus.OverviewListItemWidget -import dji.ux.beta.core.listitemwidget.rcbattery.RCBatteryListItemWidget -import dji.ux.beta.core.listitemwidget.rcstickmode.RCStickModeListItemWidget -import dji.ux.beta.core.listitemwidget.returntohomealtitude.ReturnToHomeAltitudeListItemWidget -import dji.ux.beta.core.listitemwidget.sdcardstatus.SDCardStatusListItemWidget -import dji.ux.beta.core.listitemwidget.ssdstatus.SSDStatusListItemWidget -import dji.ux.beta.core.listitemwidget.travelmode.TravelModeListItemWidget -import dji.ux.beta.core.listitemwidget.unittype.UnitTypeListItemWidget -import dji.ux.beta.core.panelwidget.systemstatus.SystemStatusSmartListModel.SystemStatusListItem.* +import dji.ux.beta.core.base.panel.SmartListModel +import dji.ux.beta.core.base.panel.WidgetID +import dji.ux.beta.core.panel.listitem.aircraftbatterytemperature.AircraftBatteryTemperatureListItemWidget +import dji.ux.beta.core.panel.listitem.emmcstatus.EMMCStatusListItemWidget +import dji.ux.beta.core.panel.listitem.flightmode.FlightModeListItemWidget +import dji.ux.beta.core.panel.listitem.maxaltitude.MaxAltitudeListItemWidget +import dji.ux.beta.core.panel.listitem.maxflightdistance.MaxFlightDistanceListItemWidget +import dji.ux.beta.core.panel.listitem.novicemode.NoviceModeListItemWidget +import dji.ux.beta.core.panel.listitem.overview.OverviewListItemWidget +import dji.ux.beta.core.panel.listitem.rcbattery.RCBatteryListItemWidget +import dji.ux.beta.core.panel.listitem.rcstickmode.RCStickModeListItemWidget +import dji.ux.beta.core.panel.listitem.returntohomealtitude.ReturnToHomeAltitudeListItemWidget +import dji.ux.beta.core.panel.listitem.sdcardstatus.SDCardStatusListItemWidget +import dji.ux.beta.core.panel.listitem.ssdstatus.SSDStatusListItemWidget +import dji.ux.beta.core.panel.listitem.travelmode.TravelModeListItemWidget +import dji.ux.beta.core.panel.listitem.unittype.UnitModeListItemWidget +import dji.ux.beta.core.panel.systemstatus.SystemStatusSmartListModel.SystemStatusListItem.* private const val TAG = "systemstatuslist" @@ -53,8 +54,8 @@ private const val TAG = "systemstatuslist" open class SystemStatusSmartListModel @JvmOverloads constructor( context: Context, private val attrs: AttributeSet? = null, - blacklist: Set? = null -) : SmartListModel(context, attrs, blacklist) { + excludedItems: Set? = null +) : SmartListModel(context, attrs, excludedItems) { //region Default Items /** @@ -68,12 +69,13 @@ open class SystemStatusSmartListModel @JvmOverloads constructor( FLIGHT_MODE.widgetID, RC_STICK_MODE.widgetID, RC_BATTERY.widgetID, + AIRCRAFT_BATTERY_TEMPERATURE.widgetID, SD_CARD_STATUS.widgetID, EMMC_STATUS.widgetID, SSD_STATUS.widgetID, TRAVEL_MODE.widgetID, NOVICE_MODE.widgetID, - UNIT_TYPE.widgetID) + UNIT_MODE.widgetID) } /** @@ -117,6 +119,12 @@ open class SystemStatusSmartListModel @JvmOverloads constructor( } } + override fun onProductConnectionChanged(isConnected: Boolean) { + if (!isConnected) { + resetSystemStatusListToDefault() + } + } + override fun createWidget(widgetID: WidgetID): View { return when (SystemStatusListItem.from(widgetID)) { @@ -125,12 +133,13 @@ open class SystemStatusSmartListModel @JvmOverloads constructor( FLIGHT_MODE -> FlightModeListItemWidget(context, attrs) RC_STICK_MODE -> RCStickModeListItemWidget(context, attrs) RC_BATTERY -> RCBatteryListItemWidget(context, attrs) + AIRCRAFT_BATTERY_TEMPERATURE -> AircraftBatteryTemperatureListItemWidget(context, attrs) SD_CARD_STATUS -> SDCardStatusListItemWidget(context, attrs) EMMC_STATUS -> EMMCStatusListItemWidget(context, attrs) MAX_ALTITUDE -> MaxAltitudeListItemWidget(context, attrs) MAX_FLIGHT_DISTANCE -> MaxFlightDistanceListItemWidget(context, attrs) TRAVEL_MODE -> TravelModeListItemWidget(context, attrs) - UNIT_TYPE -> UnitTypeListItemWidget(context, attrs) + UNIT_MODE -> UnitModeListItemWidget(context, attrs) SSD_STATUS -> SSDStatusListItemWidget(context, attrs) NOVICE_MODE -> NoviceModeListItemWidget(context, attrs) null -> throw IllegalStateException("The WidgetID ($widgetID) is not recognized.") @@ -192,6 +201,11 @@ open class SystemStatusSmartListModel @JvmOverloads constructor( */ RC_BATTERY("rc_battery", 32), + /** + * Maps to [AircraftBatteryTemperatureListItemWidget]. + */ + AIRCRAFT_BATTERY_TEMPERATURE("aircraft_battery_temperature", 64), + /** * Maps to [SDCardStatusListItemWidget]. */ @@ -218,9 +232,9 @@ open class SystemStatusSmartListModel @JvmOverloads constructor( TRAVEL_MODE("travel_mode", 2048), /** - * Maps to [UnitTypeListItemWidget]. + * Maps to [UnitModeListItemWidget]. */ - UNIT_TYPE("unit_type", 4096), + UNIT_MODE("unit_mode", 4096), /** * Maps to [SSDStatusListItemWidget]. @@ -251,19 +265,22 @@ open class SystemStatusSmartListModel @JvmOverloads constructor( } companion object { + @JvmStatic + val values = values() + /** * Create a [SystemStatusListItem] from a [WidgetID]. */ @JvmStatic fun from(widgetID: WidgetID): SystemStatusListItem? = - values().find { it.widgetID == widgetID } + values.find { it.widgetID == widgetID } /** * Create a [SystemStatusListItem] from an int value. */ @JvmStatic fun from(value: Int): SystemStatusListItem? = - values().find { it.value == value } + values.find { it.value == value } } } } \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/telemetry/TelemetryPanelWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/telemetry/TelemetryPanelWidget.kt new file mode 100644 index 00000000..2e28d74f --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/telemetry/TelemetryPanelWidget.kt @@ -0,0 +1,319 @@ +/* + * 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.ux.beta.core.panel.telemetry + +import android.annotation.SuppressLint +import android.content.Context +import android.util.AttributeSet +import android.view.View +import androidx.core.content.res.use +import dji.ux.beta.core.R +import dji.ux.beta.core.base.panel.FreeFormPanelWidget +import dji.ux.beta.core.base.panel.PanelWidgetConfiguration +import dji.ux.beta.core.base.panel.PanelWidgetType +import dji.ux.beta.core.extension.getDimension +import dji.ux.beta.core.extension.getIntegerAndUse +import dji.ux.beta.core.extension.getResourceIdAndUse +import dji.ux.beta.core.widget.altitude.AGLAltitudeWidget +import dji.ux.beta.core.widget.altitude.AMSLAltitudeWidget +import dji.ux.beta.core.widget.distancehome.DistanceHomeWidget +import dji.ux.beta.core.widget.distancerc.DistanceRCWidget +import dji.ux.beta.core.widget.horizontalvelocity.HorizontalVelocityWidget +import dji.ux.beta.core.widget.location.LocationWidget +import dji.ux.beta.core.widget.verticalvelocity.VerticalVelocityWidget +import dji.ux.beta.core.widget.vps.VPSWidget + +typealias WidgetID = String + +/** + * Compound widget that aggregates important physical state information + * about the aircraft into a dashboard. + *

+ * It includes the [AMSLAltitudeWidget], [AGLAltitudeWidget], + * [DistanceHomeWidget], [DistanceRCWidget], [HorizontalVelocityWidget], + * [VerticalVelocityWidget], [VPSWidget] and the [LocationWidget]. + *

+ * This widget can be customized to exclude any of these widgets + * as required. A widget excluded will not be created. + * Individual widgets can be accessed using the paneID of each widget + * and the function [TelemetryPanelWidget.viewInPane]. + * + */ +open class TelemetryPanelWidget @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + configuration: PanelWidgetConfiguration = PanelWidgetConfiguration( + context = context, + panelWidgetType = PanelWidgetType.FREE_FORM), + private var widgetTheme: Int = 0 +) : FreeFormPanelWidget(context, attrs, defStyleAttr, configuration) { + + //region fields + + private var excludedItemSet = emptySet() + + /** + * Pane ID of the [AMSLAltitudeWidget] + */ + val amslAltitudeWidgetPaneID: Int + + /** + * Pane ID of the [AGLAltitudeWidget] + */ + val aglAltitudeWidgetPaneID: Int + + /** + * Pane ID of the [HorizontalVelocityWidget] + */ + val horizontalVelocityWidgetPaneID: Int + + /** + * Pane ID of the [DistanceRCWidget] + */ + val distanceRCWidgetPaneID: Int + + /** + * Pane ID of the [DistanceHomeWidget] + */ + val distanceHomeWidgetPaneID: Int + + /** + * Pane ID of the [VerticalVelocityWidget] + */ + val verticalVelocityWidgetPaneID: Int + + /** + * Pane ID of the [VPSWidget] + */ + val vpsWidgetPaneID: Int + + /** + * Pane ID of the [LocationWidget] + */ + val locationWidgetPaneID: Int + + //endregion + + //region constructor + init { + // Create 4 rows + val rowList = splitPane(rootID, SplitType.VERTICAL, arrayOf(0.25f, 0.25f, 0.25f, 0.25f)) + // Create 3 columns for second row + val columnList1 = splitPane(rowList[1], SplitType.HORIZONTAL, arrayOf(0.3f, 0.4f, 0.3f)) + // Create 3 columns for third row + val columnList2 = splitPane(rowList[2], SplitType.HORIZONTAL, arrayOf(0.3f, 0.4f, 0.3f)) + + //Assign the paneIds based on position in the panel + amslAltitudeWidgetPaneID = rowList[0] + aglAltitudeWidgetPaneID = columnList1[0] + horizontalVelocityWidgetPaneID = columnList1[1] + distanceRCWidgetPaneID = columnList1[2] + distanceHomeWidgetPaneID = columnList2[0] + verticalVelocityWidgetPaneID = columnList2[1] + vpsWidgetPaneID = columnList2[2] + locationWidgetPaneID = rowList[3] + + // Get the excluded items list. + attrs?.let { initAttributes(context, it) } + addWidgetsToPanel() + } + //endregion + + //region helpers + @SuppressLint("Recycle") + private fun initAttributes(context: Context, attrs: AttributeSet) { + context.obtainStyledAttributes(attrs, R.styleable.TelemetryPanelWidget).use { typedArray -> + typedArray.getIntegerAndUse(R.styleable.TelemetryPanelWidget_uxsdk_excludeTelemetryItem) { + excludedItemSet = getExcludeListSet(it) + } + typedArray.getResourceIdAndUse(R.styleable.TelemetryPanelWidget_uxsdk_telemetry_widget_theme) { + widgetTheme = it + } + } + + } + + private fun getExcludeListSet(excludeListValue: Int?): Set { + return if (excludeListValue != null) { + TelemetryPanelItem.values + .filter { it.isItemExcluded(excludeListValue) } + .map { it.widgetID } + .toSet() + } else { + emptySet() + } + } + + private fun addWidgetsToPanel() { + val widgetMargin = getDimension(R.dimen.uxsdk_telemetry_column_margin).toInt() + + setPane(TelemetryPanelItem.AMSL_ALTITUDE, amslAltitudeWidgetPaneID, ViewAlignment.LEFT, + createWidgetBlock = { + AMSLAltitudeWidget(context, widgetTheme = widgetTheme) + }) + + setPane(TelemetryPanelItem.AGL_ALTITUDE, aglAltitudeWidgetPaneID, ViewAlignment.LEFT, + createWidgetBlock = { + AGLAltitudeWidget(context, widgetTheme = widgetTheme) + }, rightMargin = widgetMargin) + + setPane(TelemetryPanelItem.HORIZONTAL_VELOCITY, horizontalVelocityWidgetPaneID, ViewAlignment.LEFT, + createWidgetBlock = { + HorizontalVelocityWidget(context, widgetTheme = widgetTheme) + }, leftMargin = widgetMargin, rightMargin = widgetMargin) + + setPane(TelemetryPanelItem.DISTANCE_RC, distanceRCWidgetPaneID, ViewAlignment.LEFT, + createWidgetBlock = { + DistanceRCWidget(context, widgetTheme = widgetTheme) + }, leftMargin = widgetMargin) + + setPane(TelemetryPanelItem.DISTANCE_HOME, distanceHomeWidgetPaneID, ViewAlignment.LEFT, + createWidgetBlock = { + DistanceHomeWidget(context, widgetTheme = widgetTheme) + }, rightMargin = widgetMargin) + + setPane(TelemetryPanelItem.VERTICAL_VELOCITY, verticalVelocityWidgetPaneID, ViewAlignment.LEFT, + createWidgetBlock = { + VerticalVelocityWidget(context, widgetTheme = widgetTheme) + }, leftMargin = widgetMargin, rightMargin = widgetMargin) + + setPane(TelemetryPanelItem.VPS, vpsWidgetPaneID, ViewAlignment.LEFT, + createWidgetBlock = { + VPSWidget(context, widgetTheme = widgetTheme) + }, leftMargin = widgetMargin) + + setPane(TelemetryPanelItem.LOCATION, locationWidgetPaneID, ViewAlignment.LEFT, + createWidgetBlock = { + LocationWidget(context, widgetTheme = widgetTheme) + }) + } + + private inline fun setPane( + panelItem: TelemetryPanelItem, + paneID: Int, + position: ViewAlignment = ViewAlignment.CENTER, + leftMargin: Int = 0, + topMargin: Int = 0, + rightMargin: Int = 0, + bottomMargin: Int = 0, + createWidgetBlock: () -> R + ) { + if (excludedItemSet.contains(panelItem.widgetID)) { + setPaneVisibility(paneID, false) + } else { + val widget = createWidgetBlock() + addView(paneID, widget, position, leftMargin, topMargin, rightMargin, bottomMargin) + setPaneVisibility(paneID, true) + } + } + //endregion + + //region lifecycle + override fun reactToModelChanges() { + // Empty method + } + + override fun getIdealDimensionRatioString(): String? { + return null + } + //endregion + + + /** + * Default Widgets for the [TelemetryPanelWidget] + * @property widgetID Identifier for the item + * @property value Int value for excluding items + */ + enum class TelemetryPanelItem(val widgetID: WidgetID, val value: Int) { + + /** + * Maps to [AMSLAltitudeWidget]. + */ + AMSL_ALTITUDE("amsl_altitude", 1), + + /** + * Maps to [AGL_ALTITUDE]. + */ + AGL_ALTITUDE("agl_altitude", 2), + + /** + * Maps to [HorizontalVelocityWidget]. + */ + HORIZONTAL_VELOCITY("horizontal_velocity", 4), + + /** + * Maps to [DistanceRCWidget]. + */ + DISTANCE_RC("distance_rc", 8), + + /** + * Maps to [DistanceHomeWidget]. + */ + DISTANCE_HOME("distance_home", 16), + + /** + * Maps to [VerticalVelocityWidget]. + */ + VERTICAL_VELOCITY("vertical_velocity", 32), + + /** + * Maps to [VPSWidget]. + */ + VPS("vps", 64), + + /** + * Maps to [LocationWidget]. + */ + LOCATION("location", 128); + + + /** + * Checks if the item is excluded given the flag [excludeItems]. + */ + fun isItemExcluded(excludeItems: Int): Boolean { + return excludeItems and this.value == this.value + } + + + companion object { + @JvmStatic + val values = values() + + /** + * Create a [TelemetryPanelItem] from a [WidgetID]. + */ + @JvmStatic + fun from(widgetID: WidgetID): TelemetryPanelItem? = + values.find { it.widgetID == widgetID } + + /** + * Create a [TelemetryPanelItem] from an int value. + */ + @JvmStatic + fun from(value: Int): TelemetryPanelItem? = + values.find { it.value == value } + } + } +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panelwidget/topbar/TopBarPanelWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/topbar/TopBarPanelWidget.kt similarity index 89% rename from android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panelwidget/topbar/TopBarPanelWidget.kt rename to android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/topbar/TopBarPanelWidget.kt index 525cbcb7..1439e067 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panelwidget/topbar/TopBarPanelWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/panel/topbar/TopBarPanelWidget.kt @@ -21,17 +21,17 @@ * */ -package dji.ux.beta.core.panelwidget.topbar +package dji.ux.beta.core.panel.topbar import android.annotation.SuppressLint import android.content.Context import android.util.AttributeSet import androidx.core.content.res.use -import dji.ux.beta.R +import dji.ux.beta.core.R import dji.ux.beta.core.base.WidgetSizeDescription -import dji.ux.beta.core.base.panelwidget.BarPanelWidget -import dji.ux.beta.core.base.panelwidget.PanelItem -import dji.ux.beta.core.base.panelwidget.PanelWidgetConfiguration +import dji.ux.beta.core.base.panel.BarPanelWidget +import dji.ux.beta.core.base.panel.PanelItem +import dji.ux.beta.core.base.panel.PanelWidgetConfiguration import dji.ux.beta.core.extension.getDimension import dji.ux.beta.core.extension.getIntegerAndUse import dji.ux.beta.core.widget.airsense.AirSenseWidget @@ -130,7 +130,7 @@ open class TopBarPanelWidget @JvmOverloads constructor( //endregion //region Private properties - private var blacklistValue = 0 + private var excludedItemsValue = 0 //endregion //region Lifecycle & Setup @@ -141,7 +141,7 @@ open class TopBarPanelWidget @JvmOverloads constructor( init { val leftPanelItems = ArrayList() - if (!WidgetValue.SYSTEM_STATUS.isItemExcluded(blacklistValue)) { + if (!WidgetValue.SYSTEM_STATUS.isItemExcluded(excludedItemsValue)) { systemStatusWidget = SystemStatusWidget(context, attrs) leftPanelItems.add(PanelItem(systemStatusWidget, itemMarginTop = 0, itemMarginBottom = 0)) } else { @@ -150,55 +150,55 @@ open class TopBarPanelWidget @JvmOverloads constructor( addLeftWidgets(leftPanelItems.toTypedArray()) val rightPanelItems = ArrayList() - if (!WidgetValue.FLIGHT_MODE.isItemExcluded(blacklistValue)) { + if (!WidgetValue.FLIGHT_MODE.isItemExcluded(excludedItemsValue)) { flightModeWidget = FlightModeWidget(context, attrs) rightPanelItems.add(PanelItem(flightModeWidget)) } else { flightModeWidget = null } - if (!WidgetValue.SIMULATOR_INDICATOR.isItemExcluded(blacklistValue)) { + if (!WidgetValue.SIMULATOR_INDICATOR.isItemExcluded(excludedItemsValue)) { simulatorIndicatorWidget = SimulatorIndicatorWidget(context, attrs) rightPanelItems.add(PanelItem(simulatorIndicatorWidget)) } else { simulatorIndicatorWidget = null } - if (!WidgetValue.AIR_SENSE.isItemExcluded(blacklistValue)) { + if (!WidgetValue.AIR_SENSE.isItemExcluded(excludedItemsValue)) { airSenseWidget = AirSenseWidget(context, attrs) rightPanelItems.add(PanelItem(airSenseWidget)) } else { airSenseWidget = null } - if (!WidgetValue.GPS_SIGNAL.isItemExcluded(blacklistValue)) { + if (!WidgetValue.GPS_SIGNAL.isItemExcluded(excludedItemsValue)) { gpsSignalWidget = GPSSignalWidget(context, attrs) rightPanelItems.add(PanelItem(gpsSignalWidget as GPSSignalWidget)) } else { gpsSignalWidget = null } - if (!WidgetValue.VISION.isItemExcluded(blacklistValue)) { + if (!WidgetValue.VISION.isItemExcluded(excludedItemsValue)) { visionWidget = VisionWidget(context, attrs) rightPanelItems.add(PanelItem(visionWidget)) } else { visionWidget = null } - if (!WidgetValue.RC_SIGNAL.isItemExcluded(blacklistValue)) { + if (!WidgetValue.RC_SIGNAL.isItemExcluded(excludedItemsValue)) { remoteControllerSignalWidget = RemoteControllerSignalWidget(context, attrs) rightPanelItems.add(PanelItem(remoteControllerSignalWidget)) } else { remoteControllerSignalWidget = null } - if (!WidgetValue.VIDEO_SIGNAL.isItemExcluded(blacklistValue)) { + if (!WidgetValue.VIDEO_SIGNAL.isItemExcluded(excludedItemsValue)) { videoSignalWidget = VideoSignalWidget(context, attrs) rightPanelItems.add(PanelItem(videoSignalWidget)) } else { videoSignalWidget = null } - if (!WidgetValue.BATTERY.isItemExcluded(blacklistValue)) { + if (!WidgetValue.BATTERY.isItemExcluded(excludedItemsValue)) { batteryWidget = BatteryWidget(context, attrs) rightPanelItems.add(PanelItem(batteryWidget)) } else { batteryWidget = null } - if (!WidgetValue.CONNECTION.isItemExcluded(blacklistValue)) { + if (!WidgetValue.CONNECTION.isItemExcluded(excludedItemsValue)) { connectionWidget = ConnectionWidget(context, attrs) rightPanelItems.add(PanelItem(connectionWidget)) } else { @@ -215,7 +215,7 @@ open class TopBarPanelWidget @JvmOverloads constructor( context.obtainStyledAttributes(attrs, R.styleable.TopBarPanelWidget).use { typedArray -> typedArray.getIntegerAndUse(R.styleable.TopBarPanelWidget_uxsdk_excludeTopBarItem) { - blacklistValue = it + excludedItemsValue = it } } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/ui/CenterPointView.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/ui/CenterPointView.kt index b5bdd729..430a4ecc 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/ui/CenterPointView.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/ui/CenterPointView.kt @@ -29,8 +29,8 @@ import android.view.View import androidx.annotation.ColorInt import androidx.annotation.DrawableRes import androidx.appcompat.widget.AppCompatImageView -import dji.ux.beta.R -import dji.ux.beta.core.base.GlobalPreferencesManager +import dji.ux.beta.core.R +import dji.ux.beta.core.communication.GlobalPreferencesManager import dji.ux.beta.core.ui.CenterPointView.CenterPointType import dji.ux.beta.core.util.ViewUtil @@ -82,7 +82,7 @@ class CenterPointView @JvmOverloads constructor( } //endregion - //region Constructors + //region Constructor init { initView() } @@ -153,16 +153,12 @@ class CenterPointView @JvmOverloads constructor( UNKNOWN(8, -1); companion object { + @JvmStatic + val values = values() + @JvmStatic fun find(value: Int): CenterPointType { - var result = UNKNOWN - for (i in values().indices) { - if (values()[i].value == (value)) { - result = values()[i] - break - } - } - return result + return values.find { it.value == value } ?: UNKNOWN } } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/ui/GridLineView.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/ui/GridLineView.kt index 292146c1..e378c813 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/ui/GridLineView.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/ui/GridLineView.kt @@ -28,8 +28,8 @@ import android.graphics.Paint import android.util.AttributeSet import android.view.View import androidx.annotation.ColorInt -import dji.ux.beta.R -import dji.ux.beta.core.base.GlobalPreferencesManager +import dji.ux.beta.core.R +import dji.ux.beta.core.communication.GlobalPreferencesManager import dji.ux.beta.core.extension.getColor private const val DISABLED = 0 @@ -106,7 +106,7 @@ class GridLineView @JvmOverloads constructor( //endregion - //region Constructors + //region Constructor init { if (!isInEditMode) { setWillNotDraw(false) @@ -211,16 +211,12 @@ class GridLineView @JvmOverloads constructor( UNKNOWN(3); companion object { + @JvmStatic + val values = values() + @JvmStatic fun find(value: Int): GridLineType { - var result = UNKNOWN - for (i in values().indices) { - if (values()[i].value == value) { - result = values()[i] - break - } - } - return result + return values.find { it.value == value } ?: UNKNOWN } } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/ui/RulerView.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/ui/RulerView.java new file mode 100644 index 00000000..0aed4a0d --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/ui/RulerView.java @@ -0,0 +1,605 @@ +/* + * 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.ux.beta.core.ui; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewParent; +import android.widget.Scroller; + +import androidx.annotation.ColorInt; + +import dji.ux.beta.core.R; + +/** + * + * A custom view used to display a scale with increase and decrease buttons + * + */ +public class RulerView extends View { + + private static final String TAG = "RulerView"; + + protected final static int DEFAULT_INTERVAL = 10; // Default scale interval value + protected final static int DEFAULT_NUMBER = 13; + + protected int width = 0; + protected int height = 0; + protected Paint drawPaint = null; + + protected Drawable selectDrawable = null; + protected int scaleColor = 0; + protected int scalePadding = 0; + + protected float density = 0; + protected int minVelocity = 0; + protected int maxVelocity = 0; + protected Scroller scroller = null; + protected VelocityTracker velocityTracker = null; + protected int offsetY = 0; + protected int lastTouchY = 0; + protected final RectF tmpRect = new RectF(); + + protected int maxSize = 2000; + protected int curSize = 0; + protected int interval = DEFAULT_INTERVAL; + + protected OnRulerScrollListener onScrollListener = null; + protected OnRulerChangeListener onChangeListener = null; + protected boolean isRulerEnabled = true; + + public RulerView(Context context, AttributeSet attrs) { + super(context, attrs); + + initDatas(context); + + if (isInEditMode()) { + return; + } + initDefaultAttrs(); + } + + /** + * Set ruler scroll listener + * + * @param listener instance of {@link OnRulerScrollListener} + */ + public void setOnScrollListener(final OnRulerScrollListener listener) { + onScrollListener = listener; + } + + /** + * Set ruler change listener + * + * @param listener instance of {@link OnRulerChangeListener} + */ + public void setOnChangeListener(final OnRulerChangeListener listener) { + onChangeListener = listener; + } + + /** + * Set the max size of the ruler + * + * @param max integer value + */ + public void setMaxSize(final int max) { + if (max != maxSize) { + maxSize = max; + if (curSize > max) { + final int beforeSize = curSize; + curSize = max; + if (null != onChangeListener) { + onChangeListener.onChanged(this, max, beforeSize, false); + } + offsetY = (int) ((max + 1) * density); + } + postInvalidate(); + } + } + + /** + * Get the max size of the ruler + * + * @return integer value + */ + public int getMaxSize() { + return maxSize; + } + + /** + * Check if the ruler cursor is at minimum position + * + * @return boolean value true - cursor at minimum false - cursor more than minimum + */ + public boolean isAtMin() { + return (curSize == 0); + } + + /** + * Check if the ruler cursor is at maximum position + * + * @return boolean value true - cursor at maximum false - cursor less than maximum + */ + public boolean isAtMax() { + return (curSize == maxSize); + } + + /** + * Sets the new value and updates to the new state immediately. + * + * @param size integer value + */ + public void setCurSizeNow(final int size) { + final int beforeSize = curSize; + curSize = size; + if (null != onChangeListener) { + onChangeListener.onChanged(this, size, beforeSize, false); + } + offsetY = (int) (size * density); + postInvalidate(); + } + + /** + * Sets the new value and uses a scrolling animation to update the new state. + * + * @param size integer value + */ + public void setCurSize(int size) { + if (size != curSize) { + if (size > maxSize) { + size = maxSize; + } else if (size < 0) { + size = 0; + } + final int step = (int) (Math.abs(curSize - size) * 1.0f / 8 + 1); + post(new ScrollRunnable(curSize, size, step)); + } + } + + /** + * Get the cursor position of the ruler + * + * @return integer value + */ + public int getCurSize() { + return curSize; + } + + /** + * Increase the value by the given step + * + * @param step integer delta to increase + * @return updated integer value + */ + public int stepUp(final int step) { + int size = curSize; + if (curSize < maxSize) { + size = (curSize + step); + if (size > maxSize) { + size = maxSize; + } + post(new ScrollRunnable(curSize, size)); + } + return size; + } + + /** + * Reduce the value by the given step + * + * @param step integer delta to decrease + * @return updated integer value + */ + public int stepDown(final int step) { + int size = curSize; + if (curSize > 0) { + size = (curSize - step); + if (size < 0) { + size = 0; + } + post(new ScrollRunnable(curSize, size)); + } + return size; + } + + /** + * Increase the value by the + * default interval size {@link #DEFAULT_INTERVAL}. + */ + public void stepNext() { + if (curSize < maxSize) { + int size = (curSize + interval); + if (size > maxSize) { + size = maxSize; + } + post(new ScrollRunnable(curSize, size)); + } + } + + /** + * Decrease the value by the + * default interval size {@link #DEFAULT_INTERVAL} + */ + public void stepPrev() { + if (curSize > 0) { + int size = (curSize - interval); + if (size < 0) { + size = 0; + } + post(new ScrollRunnable(curSize, size)); + } + } + + protected void initDefaultAttrs() { + final Resources res = getResources(); + scaleColor = res.getColor(R.color.uxsdk_white); + scalePadding = res.getDimensionPixelSize(R.dimen.uxsdk_gen_corner_radius); + drawPaint.setColor(scaleColor); + } + + protected void initDatas(final Context context) { + scroller = new Scroller(context); + + drawPaint = new Paint(); + drawPaint.setAntiAlias(true); + drawPaint.setStyle(Paint.Style.FILL); + + final ViewConfiguration configuration = ViewConfiguration.get(context); + minVelocity = configuration.getScaledMinimumFlingVelocity(); + maxVelocity = configuration.getScaledMaximumFlingVelocity(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int heightSize = MeasureSpec.getSize(heightMeasureSpec); + final int widthSize = MeasureSpec.getSize(widthMeasureSpec); + setMeasuredDimension(widthSize, heightSize); + + float targetDensity = getResources().getDisplayMetrics().density * 2; + if (targetDensity < 4.0f) { + targetDensity = 4.0f; + } + + int number = DEFAULT_NUMBER - 1; + final float fHeight = heightSize * 1.0f; + density = fHeight / (number * interval + 1); + while (density > targetDensity) { + number += 2; + density = fHeight / (number * interval + 1); + } + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + width = w; + height = h; + if (selectDrawable != null) { + final int selectHeight = selectDrawable.getIntrinsicHeight(); + final int selectWidth = selectDrawable.getIntrinsicWidth(); + selectDrawable.setBounds((w - selectWidth) / 2, (h - selectHeight) / 2, (w + selectWidth) / 2, (h + selectHeight) / 2); + } + } + + private void obtainTracker() { + if (null == velocityTracker) { + velocityTracker = VelocityTracker.obtain(); + } + } + + private void recycleTracker() { + if (null != velocityTracker) { + velocityTracker.clear(); + velocityTracker.recycle(); + velocityTracker = null; + } + } + + private void requestInterceptEvent() { + final ViewParent parent = getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(true); + } + } + + private void onOffsetChanged(final int offset) { + final int size = (int) (offset / density); + if (size != curSize) { + final int beforeSize = curSize; + curSize = size; + if (null != onChangeListener) { + onChangeListener.onChanged(this, size, beforeSize, true); + } + } + } + + private void scrollOverY(final int deltaY) { + final int maxOffset = (int) ((maxSize + 1) * density); + offsetY += deltaY; + if (offsetY < 0) { + offsetY = 0; + } else if (offsetY > maxOffset) { + offsetY = maxOffset; + } + onOffsetChanged(offsetY); + postInvalidate(); + } + + @Override + public void computeScroll() { + if (scroller.computeScrollOffset()) { + offsetY = scroller.getCurrY(); + onOffsetChanged(offsetY); + if (scroller.isFinished() && null != onScrollListener) { + onScrollListener.onScrollingFinished(this); + } + postInvalidateOnAnimation(); + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (isRulerEnabled) { + obtainTracker(); + velocityTracker.addMovement(event); + + int action = event.getAction(); + switch (action) { + case MotionEvent.ACTION_DOWN: + requestInterceptEvent(); + if (!scroller.isFinished()) { + scroller.abortAnimation(); + } + lastTouchY = (int) event.getY(); + if (null != onScrollListener) { + onScrollListener.onScrollingStarted(this); + } + break; + case MotionEvent.ACTION_MOVE: + final int y = (int) event.getY(); + int deltaY = lastTouchY - y; + lastTouchY = y; + scrollOverY(deltaY); + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + velocityTracker.computeCurrentVelocity(1000, maxVelocity); + int initialVelocity = (int) velocityTracker.getYVelocity(); + if ((Math.abs(initialVelocity) > minVelocity)) { + final int maxOffset = (int) ((maxSize + 1) * density); + scroller.fling(0, offsetY, 0, -initialVelocity, 0, 0, 0, maxOffset); + } else if (null != onScrollListener) { + onScrollListener.onScrollingFinished(this); + } + recycleTracker(); + break; + default: + break; + } + } + return true; + } + + @Override + protected void onDraw(Canvas canvas) { + if (selectDrawable != null) { + selectDrawable.draw(canvas); + } + + final float offset = offsetY; + final float maxOffset = (maxSize + 1) * density; + final float halfH = height * 1.0f / 2; + int offDensity = (int) ((offset / density) % interval); + if (offDensity != 0) { + offDensity = interval - offDensity; + } + + final float left = scalePadding; + final float right = width - scalePadding; + final float radius = density / 2; + + float top = offDensity * density; + final float halfDensity = density / 2; + + while (top < height) { + if ((halfH <= (top + offset + density)) && ((top + offset + halfDensity) <= (maxOffset + halfH))) { + tmpRect.set(left, top, right, top + density); + drawPaint.setAlpha(recalAlpha(top, halfH)); + canvas.drawRoundRect(tmpRect, radius, radius, drawPaint); + } + top += interval * density; + } + } + + private int recalAlpha(final float top, final float halfH) { + final float pos = (top + density * 1.0f / 2); + final float factor = Math.abs(pos - halfH) * 1.0f / halfH; + return (int) (((1 - factor) * (1 - factor) * 0.95f + 0.05f) * 255); + } + + private final class ScrollRunnable implements Runnable { + private int mStartSize; + private int mEndSize; + private int mStep; + private boolean mbAdd = false; + + private ScrollRunnable(final int start, final int end) { + this(start, end, 2); + } + + private ScrollRunnable(final int start, final int end, final int step) { + mStartSize = start; + mEndSize = end; + mStep = step; + if (start < end) { + mbAdd = true; + } + } + + @Override + public void run() { + if (mbAdd) { + if (mEndSize <= mStartSize + mStep + 1) { + final int beforeSize = curSize; + curSize = mEndSize; + if (null != onChangeListener) { + onChangeListener.onChanged(RulerView.this, mEndSize, beforeSize, true); + } + + offsetY = (int) (mEndSize * density); + postInvalidate(); + } else { + curSize += mStep; + if (curSize >= mEndSize) { + final int beforeSize = curSize; + curSize = mEndSize; + if (null != onChangeListener) { + onChangeListener.onChanged(RulerView.this, mEndSize, beforeSize, true); + } + + offsetY = (int) (mEndSize * density); + postInvalidate(); + } else { + offsetY = (int) (curSize * density); + invalidate(); + postDelayed(this, 10); + } + } + } else { + if (mEndSize + mStep + 1 >= mStartSize) { + final int beforeSize = curSize; + curSize = mEndSize; + if (null != onChangeListener) { + onChangeListener.onChanged(RulerView.this, mEndSize, beforeSize, true); + } + + offsetY = (int) (mEndSize * density); + postInvalidate(); + } else { + curSize -= mStep; + if (curSize <= mEndSize) { + final int beforeSize = curSize; + curSize = mEndSize; + if (null != onChangeListener) { + onChangeListener.onChanged(RulerView.this, mEndSize, beforeSize, true); + } + + offsetY = (int) (mEndSize * density); + postInvalidate(); + } else { + offsetY = (int) (curSize * density); + invalidate(); + postDelayed(this, 10); + } + } + } + } + } + + /** + * Interface to track updates of the ruler srcoll + */ + public interface OnRulerScrollListener { + /** + * Indicate start of scroll action + * + * @param rulerView current instance of {@link RulerView} + */ + void onScrollingStarted(final RulerView rulerView); + + /** + * Indicate stop of scroll action + * + * @param rulerView current instance of {@link RulerView} + */ + void onScrollingFinished(final RulerView rulerView); + } + + + /** + * Interface to track changes in ruler + * + */ + public interface OnRulerChangeListener { + + /** + * Indicate ruler changed + * + * @param rulerView current instance of {@link RulerView} + * @param newSize update size + * @param oldSize old size + * @param fromUser is the update from user interaction + */ + void onChanged(final RulerView rulerView, final int newSize, final int oldSize, final boolean fromUser); + } + + /** + * Enable or disable the scroll + * + * @param isEnabled true- scroll enabled false - scroll disabled + */ + public void setRulerEnabled(boolean isEnabled) { + isRulerEnabled = isEnabled; + } + + /** + * Check if ruler scrolling is enabled + * + * @return boolean value true- enabled false - disabled + */ + public boolean isRulerEnabled() { + return isRulerEnabled; + } + + + /** + * Set the ruler scale color + * + * @param scaleColor integer value representing color + */ + public void setScaleColor(@ColorInt int scaleColor) { + this.scaleColor = scaleColor; + drawPaint.setColor(scaleColor); + invalidate(); + } + + /** + * Get the ruler scale color + * + * @return integer value representing color + */ + @ColorInt + public int getScaleColor() { + return scaleColor; + } + +} diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/ui/SeekBarView.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/ui/SeekBarView.java index bba240b6..08fab729 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/ui/SeekBarView.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/ui/SeekBarView.java @@ -45,7 +45,7 @@ import java.util.LinkedList; import java.util.List; -import dji.ux.beta.R; +import dji.ux.beta.core.R; /** * Displays a customized seek bar that displays a value positioned above the thumb image. There are @@ -229,6 +229,13 @@ public void setText(@Nullable String text) { seekBarValueText.setText(text); } + /** + * Get the text above the seek bar progress indicator + */ + public String getText() { + return seekBarValueText.getText().toString(); + } + /** * Set the minimum value text * @@ -264,6 +271,21 @@ public int getProgress() { * @param progress The progress to set the seek bar to */ public void setProgress(@IntRange(from = 0) int progress) { + updateSeekBarProgress(progress, false); + } + + /** + * Set the progress of the seek bar to the value it was at before it was interacted with. + */ + public void restorePreviousProgress() { + setProgress(previousProgress); + updateTextAndThumbInProgress(previousProgress); + } + //endregion + + //region Seek Bar Internal Methods + + private void updateSeekBarProgress(int progress, boolean isFromUI) { synchronized (this) { if (progress >= progressMax) { currentProgress = progressMax; @@ -275,23 +297,13 @@ public void setProgress(@IntRange(from = 0) int progress) { if (onSeekBarChangeListeners != null) { for (int i = 0; i < onSeekBarChangeListeners.size(); i++) { - onSeekBarChangeListeners.get(i).onProgressChanged(this, currentProgress); + onSeekBarChangeListeners.get(i).onProgressChanged(this, currentProgress, isFromUI); } } updateTextAndThumbInProgress(currentProgress); } } - /** - * Set the progress of the seek bar to the value it was at before it was interacted with. - */ - public void restorePreviousProgress() { - setProgress(previousProgress); - updateTextAndThumbInProgress(previousProgress); - } - //endregion - - //region Seek Bar Internal Methods private void updateTextAndThumbInProgress(int progress) { float newX = boundaryLeft + getIncrement() * progress; updateTextAndThumbPosition(newX); @@ -325,6 +337,7 @@ private void setSeekbarThumbPosition(float newX) { private float getIncrement() { return (boundaryRight - boundaryLeft) / progressMax; } + //endregion //region OnTouchListener @@ -371,7 +384,7 @@ private void onStartTracking(@NonNull MotionEvent event) { private void onTrackMoving(@NonNull MotionEvent event) { float xDelta = event.getX() - xMoveStart; float newX = xThumbStartCenter + xDelta; - setProgress((int) ((newX - boundaryLeft) / getIncrement())); + updateSeekBarProgress((int) ((newX - boundaryLeft) / getIncrement()), true); } private void onEndTracking() { @@ -1057,7 +1070,7 @@ public interface OnSeekBarChangeListener { * @param progress The current progress level. This will be in the range 0..max where max * was set by {@link #setMax(int)}. */ - void onProgressChanged(@NonNull SeekBarView object, @IntRange(from = 0) int progress); + void onProgressChanged(@NonNull SeekBarView object, @IntRange(from = 0) int progress, boolean isFromUI); /** * Notification that the user has started a touch gesture. Clients may want to use this diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/ui/SlideAndFillSeekBar.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/ui/SlideAndFillSeekBar.kt index 1ecd8c32..9ea36a75 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/ui/SlideAndFillSeekBar.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/ui/SlideAndFillSeekBar.kt @@ -29,7 +29,7 @@ import android.graphics.Paint import android.util.AttributeSet import android.widget.SeekBar import androidx.appcompat.widget.AppCompatSeekBar -import dji.ux.beta.R +import dji.ux.beta.core.R import dji.ux.beta.core.extension.getColor /** diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/ui/SlidingDialog.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/ui/SlidingDialog.kt index d50a4b24..272e3a91 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/ui/SlidingDialog.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/ui/SlidingDialog.kt @@ -36,7 +36,7 @@ import android.view.WindowManager import android.widget.* import androidx.annotation.* import androidx.constraintlayout.widget.ConstraintLayout -import dji.ux.beta.R +import dji.ux.beta.core.R import dji.ux.beta.core.extension.textColor import dji.ux.beta.core.extension.textColorStateList import dji.ux.beta.core.util.ViewUtil @@ -56,7 +56,7 @@ class SlidingDialog @JvmOverloads constructor( @param:DimenRes private val widthId: Int = R.dimen.uxsdk_sliding_dialog_width ) : Dialog(context, styleId), View.OnClickListener, SeekBar.OnSeekBarChangeListener, CompoundButton.OnCheckedChangeListener { - //region fields + //region Fields private var container: ConstraintLayout private var iconImageView: ImageView private var cancelTextView: TextView @@ -307,7 +307,7 @@ class SlidingDialog @JvmOverloads constructor( fun onCheckBoxChecked(dialog: DialogInterface?, checked: Boolean) } - //region lifecycle + //region Lifecycle init { requestWindowFeature(Window.FEATURE_NO_TITLE) setContentView(R.layout.uxsdk_dialog_sliding_action) diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/AudioUtil.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/AudioUtil.java index 117fe5a2..946f5000 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/AudioUtil.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/AudioUtil.java @@ -51,9 +51,23 @@ private AudioUtil() { * @param resID The resource ID of the sound to play. */ public static void playSound(Context context, int resID) { + playSound(context, resID, false); + } + + /** + * Plays a sound. + * + * @param context A context object. + * @param resID The resource ID of the sound to play. + * @param ignoreWhenBusy If set to true, will do nothing if a sound is already being played. + */ + public static void playSound(Context context, int resID, boolean ignoreWhenBusy) { try { if (player != null) { if (player.isPlaying()) { + if (ignoreWhenBusy) { + return; + } player.stop(); } player.release(); @@ -84,14 +98,12 @@ public static void playSound(Context context, int resID) { /** * Plays a sound in the background. * - * @param schedulerProvider A scheduler provider which provides the background thread. - * @param context A context object. - * @param resID The resource ID of the sound to play. + * @param context A context object. + * @param resID The resource ID of the sound to play. */ - public static Disposable playSoundInBackground(SchedulerProvider schedulerProvider, - final Context context, final int resID) { + public static Disposable playSoundInBackground(final Context context, final int resID) { return Observable.just(true) - .subscribeOn(schedulerProvider.computation()) + .subscribeOn(SchedulerProvider.computation()) .subscribe(aBoolean -> playSound(context, resID), e -> DJILog.d("PlaySound", e.getMessage())); } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/CameraUtil.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/CameraUtil.java index 328471c0..05232e47 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/CameraUtil.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/CameraUtil.java @@ -31,8 +31,10 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import dji.common.camera.CameraVideoStreamSource; import dji.common.camera.SettingsDefinitions; -import dji.ux.beta.R; +import dji.sdk.camera.Camera; +import dji.ux.beta.core.R; /** * Utility class for displaying camera information. @@ -277,4 +279,26 @@ public static SettingsDefinitions.ISO convertIntToISO(int isoValue) { return SettingsDefinitions.ISO.UNKNOWN; } } + + /** + * Get the lens index based on the given stream source and camera name. + * + * @param streamSource The streamSource + * @param cameraName The name of the camera + * @return The lens index + */ + public static int getLensIndex(CameraVideoStreamSource streamSource, String cameraName) { + if (streamSource == CameraVideoStreamSource.WIDE) { + return SettingsDefinitions.LensType.WIDE.value(); + } else if (streamSource == CameraVideoStreamSource.INFRARED_THERMAL) { + if (Camera.DisplayNameXT2_VL.equals(cameraName) || + Camera.DisplayNameMavic2EnterpriseDual_VL.equals(cameraName)) { + return Camera.XT2_IR_CAMERA_INDEX; + } else { + return SettingsDefinitions.LensType.INFRARED_THERMAL.value(); + } + } else { + return SettingsDefinitions.LensType.ZOOM.value(); + } + } } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/DJIDeviceUtil.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/DJIDeviceUtil.kt new file mode 100644 index 00000000..ffef7c8f --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/DJIDeviceUtil.kt @@ -0,0 +1,117 @@ +/* + * 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.ux.beta.core.util + +import android.os.Build + +/** + * Contains util methods for android devices made by DJI + */ +object DJIDeviceUtil { + + /** + * Whether the current device is made by DJI + */ + @JvmStatic + fun isDJIDevice(): Boolean { + return getDJIDeviceType() == DJIDeviceType.NONE + } + + /** + * Whether the current device is a smart controller + */ + @JvmStatic + fun isSmartController(): Boolean { + val deviceType = getDJIDeviceType() + return deviceType == DJIDeviceType.SMART_CONTROLLER || + deviceType == DJIDeviceType.MATRICE_300_RTK + } + + /** + * Get the current device type + */ + @JvmStatic + fun getDJIDeviceType(): DJIDeviceType { + return DJIDeviceType.find(Build.PRODUCT) + } +} + +/** + * Type of android device made by DJI + */ +enum class DJIDeviceType(val modelName: String) { + /** + * Not a known DJI device + */ + NONE("Unknown"), + + /** + * Phantom 4 Pro Remote Controller Built-in Display Device + */ + PHANTOM_4_PRO("GL300E"), + + /** + * Phantom 4 Pro V2 Remote Controller Built-in Display Device + */ + PHANTOM_4_PRO_V2("GL300K"), + + /** + * CrystalSky 5.5 inch + */ + CRYSTAL_SKY_A("ZS600A"), + + /** + * CrystalSky 7.85 inch + */ + CRYSTAL_SKY_B("ZS600B"), + + /** + * Phantom 4 RTK Remote Controller Built-in Display Device + */ + PHANTOM_4_RTK("ag410"), + + /** + * Smart Controller Built-in Display Device + */ + SMART_CONTROLLER("rm500"), + + /** + * Matrice 300 RTK Smart Controller Built-in Display Device + */ + MATRICE_300_RTK("pm430"); + + companion object { + @JvmStatic + val values = values() + + @JvmStatic + fun find(modelName: String?): DJIDeviceType { + if (modelName != null && modelName.trim { it <= ' ' }.isNotEmpty()) { + return values.find { it.modelName == modelName } ?: NONE + } + + return NONE + } + } +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/DataProcessor.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/DataProcessor.java index d3c1f68e..4d4a04fc 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/DataProcessor.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/DataProcessor.java @@ -27,7 +27,7 @@ import dji.thirdparty.io.reactivex.Flowable; import dji.thirdparty.io.reactivex.processors.BehaviorProcessor; -import dji.thirdparty.io.reactivex.schedulers.Schedulers; +import dji.ux.beta.core.base.SchedulerProvider; /** * Processor that emits the most recent item it has observed and all subsequent observed items @@ -59,7 +59,6 @@ private DataProcessor(@NonNull T defaultValue) { * * @param data item to be emitted */ - @SuppressWarnings("unchecked") public void onNext(@NonNull Object data) { T newData = (T) data; processor.onNext(newData); @@ -98,7 +97,7 @@ public T getValue() { */ @NonNull public Flowable toFlowable() { - return processor.observeOn(Schedulers.computation()) + return processor.observeOn(SchedulerProvider.computation()) .onBackpressureLatest(); } } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/DisplayUtil.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/DisplayUtil.java index 9365b215..a32add46 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/DisplayUtil.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/DisplayUtil.java @@ -26,6 +26,7 @@ import android.content.Context; import androidx.annotation.NonNull; +import androidx.annotation.Px; /** * Utility class for converting display units. @@ -43,7 +44,7 @@ private DisplayUtil() { * @param pxValue The pixel value to convert. * @return A density-independent pixel value. */ - public static float pxToDip(@NonNull Context context, float pxValue) { + public static float pxToDip(@NonNull Context context, @Px float pxValue) { float scale = context.getResources().getDisplayMetrics().density; return pxValue / scale + 0.5f; } @@ -55,6 +56,7 @@ public static float pxToDip(@NonNull Context context, float pxValue) { * @param dipValue The density-independent pixel value to convert. * @return A pixel value. */ + @Px public static float dipToPx(@NonNull Context context, float dipValue) { float scale = context.getResources().getDisplayMetrics().density; return dipValue * scale + 0.5f; @@ -67,7 +69,7 @@ public static float dipToPx(@NonNull Context context, float dipValue) { * @param pxValue The pixel value to convert. * @return A scale-independent pixel value. */ - public static float pxToSp(@NonNull Context context, float pxValue) { + public static float pxToSp(@NonNull Context context, @Px float pxValue) { float fontScale = context.getResources().getDisplayMetrics().scaledDensity; return pxValue / fontScale + 0.5f; } @@ -79,6 +81,7 @@ public static float pxToSp(@NonNull Context context, float pxValue) { * @param spValue The scale-independent pixel value to convert. * @return A pixel value. */ + @Px public static float spToPx(@NonNull Context context, float spValue) { float fontScale = context.getResources().getDisplayMetrics().scaledDensity; return spValue * fontScale + 0.5f; diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/MobileGPSLocationUtil.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/MobileGPSLocationUtil.java index bb007f71..5237e834 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/MobileGPSLocationUtil.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/MobileGPSLocationUtil.java @@ -18,6 +18,7 @@ * 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.ux.beta.core.util; diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/ProductUtil.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/ProductUtil.java index 3c9b7cf0..f755f917 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/ProductUtil.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/ProductUtil.java @@ -24,6 +24,7 @@ package dji.ux.beta.core.util; import dji.common.product.Model; +import dji.sdk.accessory.speaker.Speaker; import dji.sdk.base.BaseProduct; import dji.sdk.products.Aircraft; import dji.sdk.sdkmanager.DJISDKManager; @@ -95,11 +96,11 @@ public static boolean isMatrice600Series(Model model) { } /** - * Determine whether the connected product is in the Matrice 200 series. + * Determine whether the connected product is in the Matrice 200 series. * * @param model The connected product. - * @return `true` if the connected product is in the Matrice 200 series. `false` if the - * connected product is not in the Matrice 200 series. + * @return `true` if the connected product is in the Matrice 200 series. `false` if the + * connected product is not in the Matrice 200 series. */ public static boolean isMatrice200Series(Model model) { return Model.MATRICE_200.equals(model) @@ -111,6 +112,21 @@ public static boolean isMatrice200Series(Model model) { || Model.MATRICE_300_RTK.equals(model); } + /** + * Determine whether the connected product is in the Matrice 200 V2 series or the Matrice 300 + * series. + * + * @param model The connected product. + * @return `true` if the connected product is in the Matrice 200 V2 or Matrice 300 series. + * `false` if the connected product is not in the Matrice 200 V2 or Matrice 300 series. + */ + public static boolean isM200V2OrM300(Model model) { + return Model.MATRICE_200_V2.equals(model) + || Model.MATRICE_210_V2.equals(model) + || Model.MATRICE_210_RTK_V2.equals(model) + || Model.MATRICE_300_RTK.equals(model); + } + /** * Determine whether the connected product is part of the Phantom 3 series. * @@ -172,7 +188,35 @@ public static boolean isExtPortSupportedProduct() { } return false; } - + + /** + * Get the Speaker object from the current product. + * + * @return The current product's speaker, or null if the current product does not have a + * speaker. + */ + public static Speaker getSpeaker() { + Aircraft aircraft = (Aircraft) DJISDKManager.getInstance().getProduct(); + if (aircraft != null && null != aircraft.getAccessoryAggregation() && null != aircraft.getAccessoryAggregation().getSpeaker()) { + return aircraft.getAccessoryAggregation().getSpeaker(); + } + return null; + } + + /** + * Determine whether the connected product supports Auto ISO. + * + * @return `true` if the connected product supports Auto ISO. `false` if there is + * no product connected or if the connected product does not support Auto ISO. + */ + public static boolean isAutoISOSupportedProduct() { + if (isProductAvailable()) { + Model model = DJISDKManager.getInstance().getProduct().getModel(); + return !Model.MAVIC_AIR.equals(model) && !Model.MAVIC_PRO.equals(model); + } + return false; + } + /** * Determine whether the connected product has vision sensors. * @@ -184,7 +228,8 @@ public static boolean isVisionSupportedProduct(Model model) { return !isMatrice600Series(model) && !Model.MATRICE_100.equals(model) && !isPhantom3Series(model) - && !isInspire1Series(model); + && !isInspire1Series(model) + && !Model.MAVIC_MINI.equals(model); } /** diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/SettingDefinitions.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/SettingDefinitions.java index dcdc448c..e6080e51 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/SettingDefinitions.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/SettingDefinitions.java @@ -37,7 +37,7 @@ private SettingDefinitions() { } /** - * The gimbal index to determine control over the dual gimbal drones + * The gimbal index to determine control over drones with multiple gimbals. */ public enum GimbalIndex { /** @@ -47,7 +47,11 @@ public enum GimbalIndex { /** * The gimbal index corresponds to the starboard side of the aircraft. */ - STARBOARD(1); + STARBOARD(1), + /** + * The gimbal index corresponds to the top of the aircraft. + */ + TOP(2); private int index; @@ -55,9 +59,18 @@ public enum GimbalIndex { this.index = index; } + private static GimbalIndex[] values; + + public static GimbalIndex[] getValues() { + if (values == null) { + values = values(); + } + return values; + } + @Nullable - public static GimbalIndex find(@IntRange(from = 0, to = 1) int index) { - for (GimbalIndex gimbalIndex : GimbalIndex.values()) { + public static GimbalIndex find(@IntRange(from = 0, to = 2) int index) { + for (GimbalIndex gimbalIndex : GimbalIndex.getValues()) { if (gimbalIndex.getIndex() == index) { return gimbalIndex; } @@ -89,7 +102,11 @@ public enum CameraIndex { /** * The camera has an index of 2 */ - CAMERA_INDEX_2(2); + CAMERA_INDEX_2(2), + /** + * The camera has an index of 4 + */ + CAMERA_INDEX_4(4); private int index; @@ -97,9 +114,18 @@ public enum CameraIndex { this.index = index; } + private static CameraIndex[] values; + + public static CameraIndex[] getValues() { + if (values == null) { + values = values(); + } + return values; + } + @NonNull - public static CameraIndex find(@IntRange(from = -1, to = 2) int index) { - for (CameraIndex cameraIndex : CameraIndex.values()) { + public static CameraIndex find(@IntRange(from = -1, to = 4) int index) { + for (CameraIndex cameraIndex : CameraIndex.getValues()) { if (cameraIndex.getIndex() == index) { return cameraIndex; } @@ -147,24 +173,34 @@ public enum VideoSource { this.value = value; } + public int value() { + return this.value; + } + + private boolean _equals(int b) { + return value == b; + } + + private static VideoSource[] values; + + public static VideoSource[] getValues() { + if (values == null) { + values = values(); + } + return values; + } + public static VideoSource find(int value) { VideoSource result = UNKNOWN; - for (int i = 0; i < values().length; i++) { - if (values()[i]._equals(value)) { - result = values()[i]; + for (int i = 0; i < getValues().length; i++) { + if (getValues()[i]._equals(value)) { + result = getValues()[i]; break; } } return result; } - public int value() { - return this.value; - } - - private boolean _equals(int b) { - return value == b; - } } /** @@ -179,6 +215,10 @@ public enum CameraSide { * The camera is on the starboard side of the aircraft. */ STARBOARD("Starboard-side"), + /** + * The camera is on top of the aircraft. + */ + TOP("Top-side"), /** * The camera is on an unknown side of the aircraft. */ @@ -190,11 +230,20 @@ public enum CameraSide { this.side = side; } + private static CameraSide[] values; + + public static CameraSide[] getValues() { + if (values == null) { + values = values(); + } + return values; + } + public static CameraSide find(String side) { CameraSide result = UNKNOWN; - for (int i = 0; i < values().length; i++) { - if (values()[i].side.equals(side)) { - result = values()[i]; + for (int i = 0; i < getValues().length; i++) { + if (getValues()[i].side.equals(side)) { + result = getValues()[i]; break; } } @@ -244,24 +293,34 @@ public enum ControlMode { this.value = value; } + public int value() { + return this.value; + } + + private boolean _equals(int b) { + return value == b; + } + + private static ControlMode[] values; + + public static ControlMode[] getValues() { + if (values == null) { + values = values(); + } + return values; + } + public static ControlMode find(int value) { ControlMode result = SPOT_METER; - for (int i = 0; i < values().length; i++) { - if (values()[i]._equals(value)) { - result = values()[i]; + for (int i = 0; i < getValues().length; i++) { + if (getValues()[i]._equals(value)) { + result = getValues()[i]; break; } } return result; } - public int value() { - return this.value; - } - - private boolean _equals(int b) { - return value == b; - } } /** @@ -296,9 +355,18 @@ public enum MapProvider { this.index = index; } + private static MapProvider[] values; + + public static MapProvider[] getValues() { + if (values == null) { + values = values(); + } + return values; + } + @NonNull public static MapProvider find(@IntRange(from = -1, to = 3) int index) { - for (MapProvider mapProvider : MapProvider.values()) { + for (MapProvider mapProvider : MapProvider.getValues()) { if (mapProvider.getIndex() == index) { return mapProvider; } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/UnitConversionUtil.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/UnitConversionUtil.java index 44864fb2..2e490e81 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/UnitConversionUtil.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/util/UnitConversionUtil.java @@ -27,12 +27,13 @@ import androidx.annotation.NonNull; -import dji.ux.beta.R; +import dji.ux.beta.core.R; /** * Utility class for unit conversions */ public final class UnitConversionUtil { + public static final float TEMPERATURE_K2C = 273.15f; //region Constants private static final float METER_TO_FOOT = 3.2808f; private static final float METER_PER_SEC_TO_MILE_PER_HR = 2.2369f; @@ -50,6 +51,7 @@ private UnitConversionUtil() { //region Length or Distance Conversion Functions + /** * Utility function to convert meters to feet * @@ -60,6 +62,16 @@ public static float convertMetersToFeet(float value) { return (value * METER_TO_FOOT); } + /** + * Utility function to convert meters to feet + * + * @param value in meters + * @return double value of the input meter value converted to feet + */ + public static double convertMetersToFeet(double value) { + return (value * METER_TO_FOOT); + } + /** * Utility function to convert feet to meters * @@ -130,6 +142,46 @@ public static int[] formatSecondToHour(int second) { return time; } + /** + * Convert temperature value from kelvin to Celsius + * + * @param value temperature in kelvin + * @return float temperature in Celsius + */ + public static float kelvinToCelsius(final float value) { + return (value - TEMPERATURE_K2C); + } + + /** + * Convert temperature value from Celsius to kelvin + * + * @param value temperature in Celsius + * @return float temperature in kelvin + */ + public static float celsiusToKelvin(final float value) { + return (value + TEMPERATURE_K2C); + } + + /** + * Convert temperature value from Celsius to fahrenheit + * + * @param value temperature in Celsius + * @return float temperature in Fahrenheit + */ + public static float celsiusToFahrenheit(final float value) { + return (value * 1.8f + 32); + } + + /** + * Convert temperature value from Fahrenheit to Celsius + * + * @param value temperature in Fahrenheit + * @return float temperature in Celsius + */ + public static float fahrenheitToCelsius(final float value) { + return (value - 32) / 1.8f; + } + public static String getSpaceWithUnit(@NonNull Context context, int space) { String result = String.format(context.getString(R.string.uxsdk_storage_status_remaining_space_mb), space); if (space > 1024) { @@ -163,11 +215,20 @@ public enum UnitType { intValue = value; } + private static UnitType[] values; + + public static UnitType[] getValues() { + if (values == null) { + values = values(); + } + return values; + } + public static UnitType find(int value) { UnitType result = METRIC; - for (int i = 0; i < values().length; i++) { - if (values()[i].intValue == value) { - result = values()[i]; + for (int i = 0; i < getValues().length; i++) { + if (getValues()[i].intValue == value) { + result = getValues()[i]; break; } } @@ -206,11 +267,20 @@ public enum SpeedMetricUnitType { intValue = value; } + private static SpeedMetricUnitType[] values; + + public static SpeedMetricUnitType[] getValues() { + if (values == null) { + values = values(); + } + return values; + } + public static SpeedMetricUnitType find(int value) { SpeedMetricUnitType result = METERS_PER_SECOND; - for (int i = 0; i < values().length; i++) { - if (values()[i].intValue == value) { - result = values()[i]; + for (int i = 0; i < getValues().length; i++) { + if (getValues()[i].intValue == value) { + result = getValues()[i]; break; } } @@ -228,5 +298,62 @@ public int value() { } } + + /** + * Specify the temperature unit + */ + public enum TemperatureUnitType { + /** + * The unit type degrees CELSIUS + */ + CELSIUS("CELSIUS", 0), + /** + * The unit type degrees FAHRENHEIT + */ + FAHRENHEIT("FAHRENHEIT", 1), + /** + * The unit type KELVIN + */ + KELVIN("KELVIN", 2); + + private String stringValue; + private int intValue; + + TemperatureUnitType(String toString, int value) { + stringValue = toString; + intValue = value; + } + + private static TemperatureUnitType[] values; + + public static TemperatureUnitType[] getValues() { + if (values == null) { + values = values(); + } + return values; + } + + public static TemperatureUnitType find(int value) { + TemperatureUnitType result = CELSIUS; + for (int i = 0; i < getValues().length; i++) { + if (getValues()[i].intValue == value) { + result = getValues()[i]; + break; + } + } + return result; + } + + @Override + @NonNull + public String toString() { + return stringValue; + } + + public int value() { + return this.intValue; + } + + } //endregion } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/airsense/AirSenseWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/airsense/AirSenseWidget.kt index 7bf5b8d8..761024c7 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/airsense/AirSenseWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/airsense/AirSenseWidget.kt @@ -53,22 +53,21 @@ import androidx.appcompat.view.ContextThemeWrapper import androidx.core.content.res.use import dji.common.flightcontroller.adsb.AirSenseWarningLevel import dji.thirdparty.io.reactivex.Flowable -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers +import dji.ux.beta.core.base.SchedulerProvider import dji.thirdparty.io.reactivex.functions.Consumer import dji.thirdparty.io.reactivex.processors.PublishProcessor -import dji.ux.beta.R -import dji.ux.beta.core.base.ConstraintLayoutWidget +import dji.ux.beta.core.R import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.GlobalPreferencesManager -import dji.ux.beta.core.base.SchedulerProvider -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget +import dji.ux.beta.core.communication.GlobalPreferencesManager +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.* import dji.ux.beta.core.util.DisplayUtil import dji.ux.beta.core.util.ViewUtil -import dji.ux.beta.core.widget.airsense.AirSenseWidget.AirSenseWidgetState -import dji.ux.beta.core.widget.airsense.AirSenseWidget.AirSenseWidgetState.* -import dji.ux.beta.core.widget.airsense.AirSenseWidget.AirSenseWidgetUIState.* -import dji.ux.beta.core.widget.airsense.AirSenseWidgetModel.AirSenseStatus +import dji.ux.beta.core.widget.airsense.AirSenseWidget.ModelState +import dji.ux.beta.core.widget.airsense.AirSenseWidget.ModelState.* +import dji.ux.beta.core.widget.airsense.AirSenseWidget.UIState.* +import dji.ux.beta.core.widget.airsense.AirSenseWidgetModel.AirSenseState /** * Widget that displays an icon representing whether there are any aircraft nearby and how likely @@ -92,27 +91,26 @@ open class AirSenseWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ConstraintLayoutWidget(context, attrs, defStyleAttr) { +) : ConstraintLayoutWidget(context, attrs, defStyleAttr) { //region Fields private val airSenseImageView: ImageView = findViewById(R.id.imageview_air_sense) - private val colorMap: MutableMap = + private val colorMap: MutableMap = mutableMapOf( - AirSenseStatus.DISCONNECTED to getColor(R.color.uxsdk_gray_58), - AirSenseStatus.NO_AIR_SENSE_CONNECTED to getColor(R.color.uxsdk_gray_58), - AirSenseStatus.NO_AIRPLANES_NEARBY to getColor(R.color.uxsdk_gray_58), - AirSenseStatus.WARNING_LEVEL_0 to getColor(R.color.uxsdk_white), - AirSenseStatus.WARNING_LEVEL_1 to getColor(R.color.uxsdk_blue_highlight), - AirSenseStatus.WARNING_LEVEL_2 to getColor(R.color.uxsdk_yellow_500), - AirSenseStatus.WARNING_LEVEL_3 to getColor(R.color.uxsdk_red_500), - AirSenseStatus.WARNING_LEVEL_4 to getColor(R.color.uxsdk_red_500)) + AirSenseState.DISCONNECTED to getColor(R.color.uxsdk_gray_58), + AirSenseState.NO_AIR_SENSE_CONNECTED to getColor(R.color.uxsdk_gray_58), + AirSenseState.NO_AIRPLANES_NEARBY to getColor(R.color.uxsdk_gray_58), + AirSenseState.WARNING_LEVEL_0 to getColor(R.color.uxsdk_white), + AirSenseState.WARNING_LEVEL_1 to getColor(R.color.uxsdk_blue_highlight), + AirSenseState.WARNING_LEVEL_2 to getColor(R.color.uxsdk_yellow_500), + AirSenseState.WARNING_LEVEL_3 to getColor(R.color.uxsdk_red_500), + AirSenseState.WARNING_LEVEL_4 to getColor(R.color.uxsdk_red_500)) private val blinkAnimation: Animation = AnimationUtils.loadAnimation(context, R.anim.uxsdk_anim_blink) private var warningDialogDisplayed: Boolean = false - private val uiUpdateStateProcessor: PublishProcessor = PublishProcessor.create() + private val uiUpdateStateProcessor: PublishProcessor = PublishProcessor.create() private val widgetModel by lazy { AirSenseWidgetModel(DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - SchedulerProvider.getInstance()) + ObservableInMemoryKeyedStore.getInstance()) } var airSenseDisconnectedStateIcon: Drawable? = getDrawable(R.drawable.uxsdk_ic_topbar_adsb_disconnected) @@ -200,7 +198,7 @@ open class AirSenseWidget @JvmOverloads constructor( var checkBoxTextSize: Float = 0f //endregion - //region Constructors + //region Constructor override fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { View.inflate(context, R.layout.uxsdk_widget_air_sense, this) } @@ -227,13 +225,13 @@ open class AirSenseWidget @JvmOverloads constructor( override fun reactToModelChanges() { addReaction(widgetModel.airSenseWarningLevel - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { updateAirSenseWarningLevel(it) }) - addReaction(widgetModel.airSenseStatus - .observeOn(AndroidSchedulers.mainThread()) + addReaction(widgetModel.airSenseState + .observeOn(SchedulerProvider.ui()) .subscribe { updateIcon(it) }) addReaction(widgetModel.productConnection - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) } @@ -247,27 +245,27 @@ open class AirSenseWidget @JvmOverloads constructor( && warningLevel != AirSenseWarningLevel.UNKNOWN) { showWarningDialog() } - widgetStateDataProcessor.onNext(AirSenseWarningLevelUpdate(warningLevel)) + widgetStateDataProcessor.onNext(AirSenseWarningLevelUpdated(warningLevel)) } - private fun updateIcon(status: AirSenseStatus) { - visibility = if (status == AirSenseStatus.NO_AIR_SENSE_CONNECTED) GONE else VISIBLE + private fun updateIcon(state: AirSenseState) { + visibility = if (state == AirSenseState.NO_AIR_SENSE_CONNECTED) GONE else VISIBLE airSenseImageView.imageDrawable = - if (status == AirSenseStatus.DISCONNECTED) { + if (state == AirSenseState.DISCONNECTED) { airSenseDisconnectedStateIcon } else { airSenseConnectedStateIcon } - if (colorMap.containsKey(status)) { - ViewUtil.tintImage(airSenseImageView, getAirSenseIconTintColor(status)) + if (colorMap.containsKey(state)) { + ViewUtil.tintImage(airSenseImageView, getAirSenseIconTintColor(state)) } - if (status == AirSenseStatus.WARNING_LEVEL_4) { + if (state == AirSenseState.WARNING_LEVEL_4) { airSenseImageView.startAnimation(blinkAnimation) } else { airSenseImageView.clearAnimation() } - widgetStateDataProcessor.onNext(AirSenseStatusUpdate(status)) + widgetStateDataProcessor.onNext(AirSenseStateUpdated(state)) } private fun updateWarningMessages(warningLevel: AirSenseWarningLevel) { @@ -301,21 +299,22 @@ open class AirSenseWidget @JvmOverloads constructor( private fun onWarningDialogClosed() { warningDialogDisplayed = false - uiUpdateStateProcessor.onNext(WarningDialogDismiss) + uiUpdateStateProcessor.onNext(WarningDialogDismissed) } + @SuppressLint("InflateParams") private fun createTermsView(): View { val termsView = if (warningDialogTheme != 0) { val ctw = ContextThemeWrapper(context, warningDialogTheme) val inflater: LayoutInflater = ctw.getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater - inflater.inflate(R.layout.uxsdk_layout_terms_view, null) + inflater.inflate(R.layout.uxsdk_layout_dialog_checkbox, null) } else { - View.inflate(context, R.layout.uxsdk_layout_terms_view, null) + View.inflate(context, R.layout.uxsdk_layout_dialog_checkbox, null) } val dontShowAgainCheckBox = termsView.findViewById(R.id.checkbox_dont_show_again) dontShowAgainCheckBox.setOnCheckedChangeListener { _: CompoundButton?, checked: Boolean -> GlobalPreferencesManager.getInstance().isAirSenseTermsNeverShown = checked - uiUpdateStateProcessor.onNext(DontShowAgainCheckBoxTap(checked)) + uiUpdateStateProcessor.onNext(NeverShowAgainCheckChanged(checked)) } if (checkBoxTextAppearance != INVALID_RESOURCE) { dontShowAgainCheckBox.setTextAppearance(context, checkBoxTextAppearance) @@ -329,12 +328,12 @@ open class AirSenseWidget @JvmOverloads constructor( if (checkBoxTextSize != INVALID_DIMENSION) { dontShowAgainCheckBox.textSize = checkBoxTextSize } - val termsLinkTextView = termsView.findViewById(R.id.textview_terms_link) + val termsLinkTextView = termsView.findViewById(R.id.textview_dialog_content) val termsLink = SpannableString(getString(R.string.uxsdk_air_sense_terms_content)) val clickableSpan: ClickableSpan = object : ClickableSpan() { override fun onClick(view: View) { showTermsDialog() - uiUpdateStateProcessor.onNext(TermsLinkTap) + uiUpdateStateProcessor.onNext(TermsLinkClicked) } override fun updateDrawState(ds: TextPaint) { @@ -379,13 +378,13 @@ open class AirSenseWidget @JvmOverloads constructor( } private fun onTermsDialogClosed() { - uiUpdateStateProcessor.onNext(TermsDialogDismiss) + uiUpdateStateProcessor.onNext(TermsDialogDismissed) } private fun checkAndUpdateIcon() { if (!isInEditMode) { - addDisposable(widgetModel.airSenseStatus.firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + addDisposable(widgetModel.airSenseState.firstOrError() + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { this.updateIcon(it) }, logErrorConsumer(TAG, "Update Icon "))) } } @@ -397,27 +396,27 @@ open class AirSenseWidget @JvmOverloads constructor( } /** - * Tints the AirSense icon to the given color when the AirSense status is the given value. + * Tints the AirSense icon to the given color when the AirSense state is the given value. * - * @param status The status for which to tint the AirSense icon. + * @param state The state for which to tint the AirSense icon. * @param color The color to tint the AirSense icon. */ - fun setAirSenseIconTintColor(status: AirSenseStatus, @ColorInt color: Int) { - colorMap[status] = color + fun setAirSenseIconTintColor(state: AirSenseState, @ColorInt color: Int) { + colorMap[state] = color checkAndUpdateIcon() } /** - * Returns the color that the AirSense icon will be tinted when the AirSense status is + * Returns the color that the AirSense icon will be tinted when the AirSense state is * the given value. * - * @param status The status for which the AirSense icon will be tinted the returned + * @param state The state for which the AirSense icon will be tinted the returned * color. * @return The color the AirSense icon will be tinted. */ @ColorInt - fun getAirSenseIconTintColor(status: AirSenseStatus): Int { - return (colorMap[status]?.let { it } ?: getColor(R.color.uxsdk_white)) + fun getAirSenseIconTintColor(state: AirSenseState): Int { + return (colorMap[state]?.let { it } ?: getColor(R.color.uxsdk_white)) } /** @@ -489,25 +488,25 @@ open class AirSenseWidget @JvmOverloads constructor( airSenseIconBackground = it } typedArray.getColorAndUse(R.styleable.AirSenseWidget_uxsdk_airSenseIconDisconnectedTint) { - setAirSenseIconTintColor(AirSenseStatus.DISCONNECTED, it) + setAirSenseIconTintColor(AirSenseState.DISCONNECTED, it) } typedArray.getColorAndUse(R.styleable.AirSenseWidget_uxsdk_airSenseIconNoAirplanesNearbyTint) { - setAirSenseIconTintColor(AirSenseStatus.NO_AIRPLANES_NEARBY, it) + setAirSenseIconTintColor(AirSenseState.NO_AIRPLANES_NEARBY, it) } typedArray.getColorAndUse(R.styleable.AirSenseWidget_uxsdk_airSenseIconWarningLevel0Tint) { - setAirSenseIconTintColor(AirSenseStatus.WARNING_LEVEL_0, it) + setAirSenseIconTintColor(AirSenseState.WARNING_LEVEL_0, it) } typedArray.getColorAndUse(R.styleable.AirSenseWidget_uxsdk_airSenseIconWarningLevel1Tint) { - setAirSenseIconTintColor(AirSenseStatus.WARNING_LEVEL_1, it) + setAirSenseIconTintColor(AirSenseState.WARNING_LEVEL_1, it) } typedArray.getColorAndUse(R.styleable.AirSenseWidget_uxsdk_airSenseIconWarningLevel2Tint) { - setAirSenseIconTintColor(AirSenseStatus.WARNING_LEVEL_2, it) + setAirSenseIconTintColor(AirSenseState.WARNING_LEVEL_2, it) } typedArray.getColorAndUse(R.styleable.AirSenseWidget_uxsdk_airSenseIconWarningLevel3Tint) { - setAirSenseIconTintColor(AirSenseStatus.WARNING_LEVEL_3, it) + setAirSenseIconTintColor(AirSenseState.WARNING_LEVEL_3, it) } typedArray.getColorAndUse(R.styleable.AirSenseWidget_uxsdk_airSenseIconWarningLevel4Tint) { - setAirSenseIconTintColor(AirSenseStatus.WARNING_LEVEL_4, it) + setAirSenseIconTintColor(AirSenseState.WARNING_LEVEL_4, it) } typedArray.getResourceIdAndUse(R.styleable.AirSenseWidget_uxsdk_linkTextAppearance) { termsLinkTextAppearance = it @@ -545,63 +544,64 @@ open class AirSenseWidget @JvmOverloads constructor( //region Hooks /** - * Get the [AirSenseWidgetUIState] updates + * Get the [UIState] updates */ - fun getUIStateUpdates(): Flowable { - return uiUpdateStateProcessor + fun getUIStateUpdates(): Flowable { + return uiUpdateStateProcessor.onBackpressureBuffer() } /** - * Get the [AirSenseWidgetState] updates + * Get the [ModelState] updates */ - override fun getWidgetStateUpdate(): Flowable { + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { return super.getWidgetStateUpdate() } /** * Widget UI update State */ - sealed class AirSenseWidgetUIState { + sealed class UIState { /** * Update when warning dialog is dismissed */ - object WarningDialogDismiss : AirSenseWidgetUIState() + object WarningDialogDismissed : UIState() /** * Update when terms link is tapped */ - object TermsLinkTap : AirSenseWidgetUIState() + object TermsLinkClicked : UIState() /** * Update when terms dialog is dismissed */ - object TermsDialogDismiss : AirSenseWidgetUIState() + object TermsDialogDismissed : UIState() /** * Update when "Don't show again" checkbox is tapped */ - data class DontShowAgainCheckBoxTap(val isChecked: Boolean) : AirSenseWidgetUIState() + data class NeverShowAgainCheckChanged(val isChecked: Boolean) : UIState() } /** * Class defines the widget state updates */ - sealed class AirSenseWidgetState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : AirSenseWidgetState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * AirSense warning level update */ - data class AirSenseWarningLevelUpdate(val airSenseWarningLevel: AirSenseWarningLevel) : AirSenseWidgetState() + data class AirSenseWarningLevelUpdated(val airSenseWarningLevel: AirSenseWarningLevel) : ModelState() /** * AirSense status update */ - data class AirSenseStatusUpdate(val airSenseStatus: AirSenseStatus) : AirSenseWidgetState() + data class AirSenseStateUpdated(val airSenseState: AirSenseState) : ModelState() } //endregion diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/airsense/AirSenseWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/airsense/AirSenseWidgetModel.kt index 8de086d4..0d271fbd 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/airsense/AirSenseWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/airsense/AirSenseWidgetModel.kt @@ -30,12 +30,11 @@ import dji.keysdk.FlightControllerKey import dji.thirdparty.io.reactivex.Completable import dji.thirdparty.io.reactivex.Flowable import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.SchedulerProviderInterface import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.MessagingKeys -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.base.uxsdkkeys.UXKey -import dji.ux.beta.core.base.uxsdkkeys.UXKeys +import dji.ux.beta.core.communication.MessagingKeys +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.UXKey +import dji.ux.beta.core.communication.UXKeys import dji.ux.beta.core.model.WarningMessage import dji.ux.beta.core.model.WarningMessageError import dji.ux.beta.core.util.DataProcessor @@ -44,15 +43,16 @@ import dji.ux.beta.core.util.DataProcessor * Widget Model for the [AirSenseWidget] used to define * the underlying logic and communication */ -class AirSenseWidgetModel(djiSdkModel: DJISDKModel, - private val keyedStore: ObservableInMemoryKeyedStore, - private val schedulerProvider: SchedulerProviderInterface) : WidgetModel(djiSdkModel, keyedStore) { +class AirSenseWidgetModel @JvmOverloads constructor( + djiSdkModel: DJISDKModel, + private val keyedStore: ObservableInMemoryKeyedStore +) : WidgetModel(djiSdkModel, keyedStore) { //region Fields private val airSenseConnectedProcessor: DataProcessor = DataProcessor.create(false) private val airSenseWarningLevelProcessor: DataProcessor = DataProcessor.create(AirSenseWarningLevel.UNKNOWN) private val airSenseAirplaneStatesProcessor: DataProcessor> = DataProcessor.create(emptyArray()) private val sendWarningMessageKey: UXKey = UXKeys.create(MessagingKeys.SEND_WARNING_MESSAGE) - private val airSenseStatusProcessor: DataProcessor = DataProcessor.create(AirSenseStatus.DISCONNECTED) + private val airSenseStateProcessor: DataProcessor = DataProcessor.create(AirSenseState.DISCONNECTED) //endregion //region Data @@ -65,8 +65,8 @@ class AirSenseWidgetModel(djiSdkModel: DJISDKModel, /** * Get the number of airplanes detected by AirSense */ - val airSenseStatus: Flowable - get() = airSenseStatusProcessor.toFlowable() + val airSenseState: Flowable + get() = airSenseStateProcessor.toFlowable() //endregion //region Actions @@ -103,7 +103,6 @@ class AirSenseWidgetModel(djiSdkModel: DJISDKModel, .action(action) val warningMessage = builder.build() return keyedStore.setValue(sendWarningMessageKey, warningMessage) - .subscribeOn(schedulerProvider.io()) } //endregion @@ -123,21 +122,21 @@ class AirSenseWidgetModel(djiSdkModel: DJISDKModel, } override fun updateStates() { - airSenseStatusProcessor.onNext( + airSenseStateProcessor.onNext( if (!productConnectionProcessor.value) { - AirSenseStatus.DISCONNECTED + AirSenseState.DISCONNECTED } else if (!airSenseConnectedProcessor.value) { - AirSenseStatus.NO_AIR_SENSE_CONNECTED + AirSenseState.NO_AIR_SENSE_CONNECTED } else if (airSenseAirplaneStatesProcessor.value.isEmpty()) { - AirSenseStatus.NO_AIRPLANES_NEARBY + AirSenseState.NO_AIRPLANES_NEARBY } else { when (airSenseWarningLevelProcessor.value) { - AirSenseWarningLevel.LEVEL_0 -> AirSenseStatus.WARNING_LEVEL_0 - AirSenseWarningLevel.LEVEL_1 -> AirSenseStatus.WARNING_LEVEL_1 - AirSenseWarningLevel.LEVEL_2 -> AirSenseStatus.WARNING_LEVEL_2 - AirSenseWarningLevel.LEVEL_3 -> AirSenseStatus.WARNING_LEVEL_3 - AirSenseWarningLevel.LEVEL_4 -> AirSenseStatus.WARNING_LEVEL_4 - else -> AirSenseStatus.UNKNOWN + AirSenseWarningLevel.LEVEL_0 -> AirSenseState.WARNING_LEVEL_0 + AirSenseWarningLevel.LEVEL_1 -> AirSenseState.WARNING_LEVEL_1 + AirSenseWarningLevel.LEVEL_2 -> AirSenseState.WARNING_LEVEL_2 + AirSenseWarningLevel.LEVEL_3 -> AirSenseState.WARNING_LEVEL_3 + AirSenseWarningLevel.LEVEL_4 -> AirSenseState.WARNING_LEVEL_4 + else -> AirSenseState.UNKNOWN } } ) @@ -148,7 +147,7 @@ class AirSenseWidgetModel(djiSdkModel: DJISDKModel, /** * The status of the AirSense system. */ - enum class AirSenseStatus { + enum class AirSenseState { /** * There is no product connected. */ diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/altitude/AGLAltitudeWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/altitude/AGLAltitudeWidget.kt new file mode 100644 index 00000000..fdb78c7e --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/altitude/AGLAltitudeWidget.kt @@ -0,0 +1,152 @@ +/* + * 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.ux.beta.core.widget.altitude + +import android.content.Context +import android.util.AttributeSet +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.R +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.SchedulerProvider +import dji.ux.beta.core.base.WidgetSizeDescription +import dji.ux.beta.core.base.widget.BaseTelemetryWidget +import dji.ux.beta.core.communication.GlobalPreferencesManager +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.extension.getDistanceString +import dji.ux.beta.core.extension.getString +import dji.ux.beta.core.util.UnitConversionUtil +import dji.ux.beta.core.widget.altitude.AGLAltitudeWidget.ModelState +import dji.ux.beta.core.widget.altitude.AltitudeWidgetModel.AltitudeState +import java.text.DecimalFormat + +/** + * Widget displays the above ground level altitude + */ +open class AGLAltitudeWidget @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + widgetTheme: Int = 0 +) : BaseTelemetryWidget( + context, + attrs, + defStyleAttr, + WidgetType.TEXT, + widgetTheme, + R.style.UXSDKAGLAltitudeWidget +) { + + //region Fields + override val metricDecimalFormat: DecimalFormat = DecimalFormat("###0.0") + + override val imperialDecimalFormat: DecimalFormat = DecimalFormat("###0") + + private val widgetModel: AltitudeWidgetModel by lazy { + AltitudeWidgetModel( + DJISDKModel.getInstance(), + ObservableInMemoryKeyedStore.getInstance(), + GlobalPreferencesManager.getInstance()) + } + //endregion + + //region Lifecycle + override fun onAttachedToWindow() { + super.onAttachedToWindow() + if (!isInEditMode) { + widgetModel.setup() + } + } + + override fun onDetachedFromWindow() { + if (!isInEditMode) { + widgetModel.cleanup() + } + super.onDetachedFromWindow() + } + + override fun reactToModelChanges() { + addReaction(widgetModel.productConnection + .observeOn(SchedulerProvider.ui()) + .subscribe { widgetStateDataProcessor.onNext(ModelState.ProductConnected(it)) }) + addReaction(widgetModel.altitudeState + .observeOn(SchedulerProvider.ui()) + .subscribe { updateUI(it) }) + } + //endregion + + //region Reactions to model + private fun updateUI(altitudeState: AltitudeState) { + widgetStateDataProcessor.onNext(ModelState.AltitudeStateUpdated(altitudeState)) + if (altitudeState is AltitudeState.CurrentAltitude) { + if(altitudeState.unitType == UnitConversionUtil.UnitType.IMPERIAL) { + setValueTextViewMinWidthByText("8888") + } else { + setValueTextViewMinWidthByText("888.8") + } + unitString = getDistanceString(altitudeState.unitType) + valueString = getDecimalFormat(altitudeState.unitType) + .format(altitudeState.altitudeAGL).toString() + } else { + unitString = null + valueString = getString(R.string.uxsdk_string_default_value) + } + } + //endregion + + //region customizations + override fun getIdealDimensionRatioString(): String? = null + + + override val widgetSizeDescription: WidgetSizeDescription = + WidgetSizeDescription(WidgetSizeDescription.SizeType.OTHER, + widthDimension = WidgetSizeDescription.Dimension.EXPAND, + heightDimension = WidgetSizeDescription.Dimension.WRAP) + + //endregion + + //region Hooks + /** + * Get the [ModelState] updates + */ + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { + return super.getWidgetStateUpdate() + } + + /** + * Class defines widget state updates + */ + sealed class ModelState { + /** + * Product connection update + */ + data class ProductConnected(val boolean: Boolean) : ModelState() + + /** + * Altitude widget state updated + */ + data class AltitudeStateUpdated(val altitudeState: AltitudeState) : ModelState() + } + //endregion +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/altitude/AMSLAltitudeWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/altitude/AMSLAltitudeWidget.kt new file mode 100644 index 00000000..6a0faad2 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/altitude/AMSLAltitudeWidget.kt @@ -0,0 +1,153 @@ +/* + * 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.ux.beta.core.widget.altitude + +import android.content.Context +import android.util.AttributeSet +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.R +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.SchedulerProvider +import dji.ux.beta.core.base.WidgetSizeDescription +import dji.ux.beta.core.base.widget.BaseTelemetryWidget +import dji.ux.beta.core.communication.GlobalPreferencesManager +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.extension.getDistanceString +import dji.ux.beta.core.extension.getString +import dji.ux.beta.core.util.UnitConversionUtil +import dji.ux.beta.core.widget.altitude.AMSLAltitudeWidget.ModelState +import java.text.DecimalFormat + +/** + * Widget displays the above mean sea level altitude + */ +open class AMSLAltitudeWidget @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + widgetTheme: Int = 0 +) : BaseTelemetryWidget( + context, + attrs, + defStyleAttr, + WidgetType.TEXT, + widgetTheme, + R.style.UXSDKAMSLAltitudeWidget +) { + + //region Fields + override val metricDecimalFormat: DecimalFormat = DecimalFormat("###0.0") + + override val imperialDecimalFormat: DecimalFormat = DecimalFormat("###0") + + private val widgetModel: AltitudeWidgetModel by lazy { + AltitudeWidgetModel( + DJISDKModel.getInstance(), + ObservableInMemoryKeyedStore.getInstance(), + GlobalPreferencesManager.getInstance()) + } + //endregion + + //region Lifecycle + override fun onAttachedToWindow() { + super.onAttachedToWindow() + if (!isInEditMode) { + widgetModel.setup() + } + } + + override fun onDetachedFromWindow() { + if (!isInEditMode) { + widgetModel.cleanup() + } + super.onDetachedFromWindow() + } + + override fun reactToModelChanges() { + addReaction(widgetModel.productConnection + .observeOn(SchedulerProvider.ui()) + .subscribe { widgetStateDataProcessor.onNext(ModelState.ProductConnected(it)) }) + addReaction(widgetModel.altitudeState + .observeOn(SchedulerProvider.ui()) + .subscribe { updateUI(it) }) + } + //endregion + + //region Reactions to model + private fun updateUI(altitudeState: AltitudeWidgetModel.AltitudeState) { + widgetStateDataProcessor.onNext(ModelState.AltitudeStateUpdated(altitudeState)) + if (altitudeState is AltitudeWidgetModel.AltitudeState.CurrentAltitude) { + if(altitudeState.unitType == UnitConversionUtil.UnitType.IMPERIAL) { + setValueTextViewMinWidthByText("8888") + } else { + setValueTextViewMinWidthByText("888.8") + } + unitString = getDistanceString(altitudeState.unitType) + valueString = getDecimalFormat(altitudeState.unitType) + .format(altitudeState.altitudeAMSL).toString() + } else { + unitString = null + valueString = getString(R.string.uxsdk_string_default_value) + } + } + //endregion + + //region customizations + override fun getIdealDimensionRatioString(): String? = null + + + override val widgetSizeDescription: WidgetSizeDescription = + WidgetSizeDescription(WidgetSizeDescription.SizeType.OTHER, + widthDimension = WidgetSizeDescription.Dimension.EXPAND, + heightDimension = WidgetSizeDescription.Dimension.WRAP) + + //endregion + + //region Hooks + + /** + * Get the [ModelState] updates + */ + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { + return super.getWidgetStateUpdate() + } + + /** + * Class defines widget state updates + */ + sealed class ModelState { + /** + * Product connection update + */ + data class ProductConnected(val boolean: Boolean) : ModelState() + + /** + * Altitude widget state updated + */ + data class AltitudeStateUpdated(val altitudeState: AltitudeWidgetModel.AltitudeState) : ModelState() + } + + //endregion +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/altitude/AltitudeWidget.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/altitude/AltitudeWidget.java deleted file mode 100644 index daf42f30..00000000 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/altitude/AltitudeWidget.java +++ /dev/null @@ -1,515 +0,0 @@ -/* - * 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.ux.beta.core.widget.altitude; - -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.widget.TextView; - -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 java.text.DecimalFormat; - -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers; -import dji.ux.beta.R; -import dji.ux.beta.core.base.ConstraintLayoutWidget; -import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.GlobalPreferencesInterface; -import dji.ux.beta.core.base.GlobalPreferencesManager; -import dji.ux.beta.core.base.uxsdkkeys.GlobalPreferenceKeys; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; -import dji.ux.beta.core.util.DisplayUtil; -import dji.ux.beta.core.util.UnitConversionUtil; - -/** - * Shows the current altitude of the aircraft. - * Uses the unit set in the UNIT_TYPE global preferences - * {@link GlobalPreferencesInterface#getUnitType()} and the - * {@link GlobalPreferenceKeys#UNIT_TYPE} UX Key - * and defaults to meters. - */ -public class AltitudeWidget extends ConstraintLayoutWidget { - //region Fields - private static final int EMS = 3; - private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("##0.0"); - private TextView altitudeTitleTextView; - private TextView altitudeValueTextView; - private TextView altitudeUnitTextView; - private AltitudeWidgetModel widgetModel; - //endregion - - //region Constructors - public AltitudeWidget(Context context) { - super(context); - } - - public AltitudeWidget(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public AltitudeWidget(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_base_dashboard_text_only, this); - altitudeTitleTextView = findViewById(R.id.textview_title); - altitudeValueTextView = findViewById(R.id.textview_value); - altitudeUnitTextView = findViewById(R.id.textview_unit); - - if (!isInEditMode()) { - widgetModel = new AltitudeWidgetModel(DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - GlobalPreferencesManager.getInstance()); - altitudeTitleTextView.setText(getResources().getString(R.string.uxsdk_altitude_title)); - altitudeValueTextView.setMinEms(EMS); - } - - if (attrs != null) { - initAttributes(context, attrs); - } - } - //endregion - - //region Lifecycle - @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.getAltitude() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::updateValueText)); - addReaction(widgetModel.getUnitType() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::updateUnitText)); - } - //endregion - - //region Reactions to model - private void updateValueText(float altitude) { - altitudeValueTextView.setText(DECIMAL_FORMAT.format(altitude)); - } - - private void updateUnitText(UnitConversionUtil.UnitType unitType) { - if (unitType == UnitConversionUtil.UnitType.IMPERIAL) { - altitudeUnitTextView.setText(getResources().getString(R.string.uxsdk_unit_feet)); - } else { - altitudeUnitTextView.setText(getResources().getString(R.string.uxsdk_unit_meters)); - } - } - //endregion - - //region Customization - @NonNull - @Override - public String getIdealDimensionRatioString() { - return getResources().getString(R.string.uxsdk_widget_base_dashboard_distance_ratio); - } - //endregion - - //region Customization Helpers - - /** - * Set text appearance of the altitude title text view - * - * @param textAppearance Style resource for text appearance - */ - public void setAltitudeTitleTextAppearance(@StyleRes int textAppearance) { - altitudeTitleTextView.setTextAppearance(getContext(), textAppearance); - } - - /** - * Get current text color state list of the altitude title text view - * - * @return ColorStateList resource - */ - @Nullable - public ColorStateList getAltitudeTitleTextColors() { - return altitudeTitleTextView.getTextColors(); - } - - /** - * Get current text color of the altitude title text view - * - * @return color integer resource - */ - @ColorInt - public int getAltitudeTitleTextColor() { - return altitudeTitleTextView.getCurrentTextColor(); - } - - /** - * Set text color state list for the altitude title text view - * - * @param colorStateList ColorStateList resource - */ - public void setAltitudeTitleTextColor(@NonNull ColorStateList colorStateList) { - altitudeTitleTextView.setTextColor(colorStateList); - } - - /** - * Set the text color for the altitude title text view - * - * @param color color integer resource - */ - public void setAltitudeTitleTextColor(@ColorInt int color) { - altitudeTitleTextView.setTextColor(color); - } - - /** - * Get current text size of the altitude title text view - * - * @return text size of the text view - */ - @Dimension - public float getAltitudeTitleTextSize() { - return altitudeTitleTextView.getTextSize(); - } - - /** - * Set the text size of the altitude title text view - * - * @param textSize text size float value - */ - public void setAltitudeTitleTextSize(@Dimension float textSize) { - altitudeTitleTextView.setTextSize(textSize); - } - - /** - * Get current background of the altitude title text view - * - * @return Drawable resource of the background - */ - @Nullable - public Drawable getAltitudeTitleTextBackground() { - return altitudeTitleTextView.getBackground(); - } - - /** - * Set the background of the altitude title text view - * - * @param drawable Drawable resource for the background - */ - public void setAltitudeTitleTextBackground(@Nullable Drawable drawable) { - altitudeTitleTextView.setBackground(drawable); - } - - /** - * Set the resource ID for the background of the altitude title text view - * - * @param resourceId Integer ID of the text view's background resource - */ - public void setAltitudeTitleTextBackground(@DrawableRes int resourceId) { - altitudeTitleTextView.setBackgroundResource(resourceId); - } - - /** - * Set text appearance of the altitude value text view - * - * @param textAppearance Style resource for text appearance - */ - public void setAltitudeValueTextAppearance(@StyleRes int textAppearance) { - altitudeValueTextView.setTextAppearance(getContext(), textAppearance); - } - - /** - * Get current text color state list of the altitude value text view - * - * @return ColorStateList resource - */ - @Nullable - public ColorStateList getAltitudeValueTextColors() { - return altitudeValueTextView.getTextColors(); - } - - /** - * Get current text color of the altitude value text view - * - * @return color integer resource - */ - @ColorInt - public int getAltitudeValueTextColor() { - return altitudeValueTextView.getCurrentTextColor(); - } - - /** - * Set text color state list for the altitude value text view - * - * @param colorStateList ColorStateList resource - */ - public void setAltitudeValueTextColor(@NonNull ColorStateList colorStateList) { - altitudeValueTextView.setTextColor(colorStateList); - } - - /** - * Set the text color for the altitude value text view - * - * @param color color integer resource - */ - public void setAltitudeValueTextColor(@ColorInt int color) { - altitudeValueTextView.setTextColor(color); - } - - /** - * Get current text size of the altitude value text view - * - * @return text size of the text view - */ - @Dimension - public float getAltitudeValueTextSize() { - return altitudeValueTextView.getTextSize(); - } - - /** - * Set the text size of the altitude value text view - * - * @param textSize text size float value - */ - public void setAltitudeValueTextSize(@Dimension float textSize) { - altitudeValueTextView.setTextSize(textSize); - } - - /** - * Get current background of the altitude value text view - * - * @return Drawable resource of the background - */ - @Nullable - public Drawable getAltitudeValueTextBackground() { - return altitudeValueTextView.getBackground(); - } - - /** - * Set the background for the altitude value text view - * - * @param drawable Drawable resource for the background - */ - public void setAltitudeValueTextBackground(@Nullable Drawable drawable) { - altitudeValueTextView.setBackground(drawable); - } - - /** - * Set the resource ID for the background of the altitude value text view - * - * @param resourceId Integer ID of the text view's background resource - */ - public void setAltitudeValueTextBackground(@DrawableRes int resourceId) { - altitudeValueTextView.setBackgroundResource(resourceId); - } - - /** - * Set text appearance of the altitude unit text view - * - * @param textAppearance Style resource for text appearance - */ - public void setAltitudeUnitTextAppearance(@StyleRes int textAppearance) { - altitudeUnitTextView.setTextAppearance(getContext(), textAppearance); - } - - /** - * Get current text color state list of the altitude unit text view - * - * @return ColorStateList resource - */ - @Nullable - public ColorStateList getAltitudeUnitTextColors() { - return altitudeUnitTextView.getTextColors(); - } - - /** - * Get current text color of the altitude unit text view - * - * @return color integer resource - */ - @ColorInt - public int getAltitudeUnitTextColor() { - return altitudeUnitTextView.getCurrentTextColor(); - } - - /** - * Set text color state list for the altitude unit text view - * - * @param colorStateList ColorStateList resource - */ - public void setAltitudeUnitTextColor(@NonNull ColorStateList colorStateList) { - altitudeUnitTextView.setTextColor(colorStateList); - } - - /** - * Set the text color for the altitude unit text view - * - * @param color color integer resource - */ - public void setAltitudeUnitTextColor(@ColorInt int color) { - altitudeUnitTextView.setTextColor(color); - } - - /** - * Get current text size of the altitude unit text view - * - * @return text size of the text view - */ - @Dimension - public float getAltitudeUnitTextSize() { - return altitudeUnitTextView.getTextSize(); - } - - /** - * Set the text size of the altitude unit text view - * - * @param textSize text size float value - */ - public void setAltitudeUnitTextSize(@Dimension float textSize) { - altitudeUnitTextView.setTextSize(textSize); - } - - /** - * Get current background of the altitude unit text view - * - * @return Drawable resource of the background - */ - @Nullable - public Drawable getAltitudeUnitTextBackground() { - return altitudeUnitTextView.getBackground(); - } - - /** - * Set the background for the altitude unit text view - * - * @param drawable Drawable resource for the background - */ - public void setAltitudeUnitTextBackground(@Nullable Drawable drawable) { - altitudeUnitTextView.setBackground(drawable); - } - - /** - * Set the resource ID for the background of the altitude unit text view - * - * @param resourceId Integer ID of the text view's background resource - */ - public void setAltitudeUnitTextBackground(@DrawableRes int resourceId) { - altitudeUnitTextView.setBackgroundResource(resourceId); - } - - //Initialize all customizable attributes - private void initAttributes(@NonNull Context context, @NonNull AttributeSet attrs) { - TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.AltitudeWidget); - int altitudeTitleTextAppearanceId = - typedArray.getResourceId(R.styleable.AltitudeWidget_uxsdk_altitudeTitleTextAppearance, INVALID_RESOURCE); - if (altitudeTitleTextAppearanceId != INVALID_RESOURCE) { - setAltitudeTitleTextAppearance(altitudeTitleTextAppearanceId); - } - - float altitudeTitleTextSize = - typedArray.getDimension(R.styleable.AltitudeWidget_uxsdk_altitudeTitleTextSize, INVALID_RESOURCE); - if (altitudeTitleTextSize != INVALID_RESOURCE) { - setAltitudeTitleTextSize(DisplayUtil.pxToSp(context, altitudeTitleTextSize)); - } - - int altitudeTitleTextColor = - typedArray.getColor(R.styleable.AltitudeWidget_uxsdk_altitudeTitleTextColor, INVALID_COLOR); - if (altitudeTitleTextColor != INVALID_COLOR) { - setAltitudeTitleTextColor(altitudeTitleTextColor); - } - - Drawable altitudeTitleTextBackgroundDrawable = - typedArray.getDrawable(R.styleable.AltitudeWidget_uxsdk_altitudeTitleBackgroundDrawable); - if (altitudeTitleTextBackgroundDrawable != null) { - setAltitudeTitleTextBackground(altitudeTitleTextBackgroundDrawable); - } - - int altitudeValueTextAppearanceId = - typedArray.getResourceId(R.styleable.AltitudeWidget_uxsdk_altitudeValueTextAppearance, INVALID_RESOURCE); - if (altitudeValueTextAppearanceId != INVALID_RESOURCE) { - setAltitudeValueTextAppearance(altitudeValueTextAppearanceId); - } - - float altitudeValueTextSize = - typedArray.getDimension(R.styleable.AltitudeWidget_uxsdk_altitudeValueTextSize, INVALID_RESOURCE); - if (altitudeValueTextSize != INVALID_RESOURCE) { - setAltitudeValueTextSize(DisplayUtil.pxToSp(context, altitudeValueTextSize)); - } - - int altitudeValueTextColor = - typedArray.getColor(R.styleable.AltitudeWidget_uxsdk_altitudeValueTextColor, INVALID_COLOR); - if (altitudeValueTextColor != INVALID_COLOR) { - setAltitudeValueTextColor(altitudeValueTextColor); - } - - Drawable altitudeValueTextBackgroundDrawable = - typedArray.getDrawable(R.styleable.AltitudeWidget_uxsdk_altitudeValueBackgroundDrawable); - if (altitudeValueTextBackgroundDrawable != null) { - setAltitudeValueTextBackground(altitudeValueTextBackgroundDrawable); - } - - int altitudeUnitTextAppearanceId = - typedArray.getResourceId(R.styleable.AltitudeWidget_uxsdk_altitudeUnitTextAppearance, INVALID_RESOURCE); - if (altitudeUnitTextAppearanceId != INVALID_RESOURCE) { - setAltitudeUnitTextAppearance(altitudeUnitTextAppearanceId); - } - - float altitudeUnitTextSize = - typedArray.getDimension(R.styleable.AltitudeWidget_uxsdk_altitudeUnitTextSize, INVALID_RESOURCE); - if (altitudeUnitTextSize != INVALID_RESOURCE) { - setAltitudeUnitTextSize(DisplayUtil.pxToSp(context, altitudeUnitTextSize)); - } - - int altitudeUnitTextColor = - typedArray.getColor(R.styleable.AltitudeWidget_uxsdk_altitudeUnitTextColor, INVALID_COLOR); - if (altitudeUnitTextColor != INVALID_COLOR) { - setAltitudeUnitTextColor(altitudeUnitTextColor); - } - - Drawable altitudeUnitTextBackgroundDrawable = - typedArray.getDrawable(R.styleable.AltitudeWidget_uxsdk_altitudeUnitBackgroundDrawable); - if (altitudeUnitTextBackgroundDrawable != null) { - setAltitudeUnitTextBackground(altitudeUnitTextBackgroundDrawable); - } - typedArray.recycle(); - } - //endregion -} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/altitude/AltitudeWidgetModel.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/altitude/AltitudeWidgetModel.java deleted file mode 100644 index f6ffded3..00000000 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/altitude/AltitudeWidgetModel.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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.ux.beta.core.widget.altitude; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import dji.keysdk.FlightControllerKey; -import dji.thirdparty.io.reactivex.Flowable; -import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.GlobalPreferencesInterface; -import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.GlobalPreferenceKeys; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; -import dji.ux.beta.core.base.uxsdkkeys.UXKey; -import dji.ux.beta.core.base.uxsdkkeys.UXKeys; -import dji.ux.beta.core.util.DataProcessor; -import dji.ux.beta.core.util.UnitConversionUtil; - -/** - * Widget Model for the {@link AltitudeWidget} used to define - * the underlying logic and communication - */ -public class AltitudeWidgetModel extends WidgetModel { - - //region Fields - private final GlobalPreferencesInterface preferencesManager; - private final DataProcessor rawAltitudeProcessor; - private final DataProcessor altitudeProcessor; - private final DataProcessor unitTypeProcessor; - //endregion - - //region Constructor - public AltitudeWidgetModel(@NonNull DJISDKModel djiSdkModel, - @NonNull ObservableInMemoryKeyedStore keyedStore, - @Nullable GlobalPreferencesInterface preferencesManager) { - super(djiSdkModel, keyedStore); - this.preferencesManager = preferencesManager; - altitudeProcessor = DataProcessor.create(0.0f); - rawAltitudeProcessor = DataProcessor.create(0.0f); - unitTypeProcessor = DataProcessor.create(UnitConversionUtil.UnitType.METRIC); - if (preferencesManager != null) { - unitTypeProcessor.onNext(preferencesManager.getUnitType()); - } - } - //endregion - - //region Data - - /** - * Get the altitude of the aircraft. - * - * @return Flowable for the DataProcessor that user should subscribe to. - */ - public Flowable getAltitude() { - return altitudeProcessor.toFlowable(); - } - - /** - * Get the unit type of the altitude value received. - * - * @return Flowable for the DataProcessor that user should subscribe to. - */ - public Flowable getUnitType() { - return unitTypeProcessor.toFlowable(); - } - //endregion - - //region Lifecycle - @Override - protected void inSetup() { - FlightControllerKey altitudeKey = FlightControllerKey.create(FlightControllerKey.ALTITUDE); - bindDataProcessor(altitudeKey, rawAltitudeProcessor, altitude -> convertValueByUnit((float) altitude)); - - UXKey unitKey = UXKeys.create(GlobalPreferenceKeys.UNIT_TYPE); - bindDataProcessor(unitKey, unitTypeProcessor); - - if (preferencesManager != null) { - preferencesManager.setUpListener(); - } - } - - @Override - protected void inCleanup() { - if (preferencesManager != null) { - preferencesManager.cleanup(); - } - } - - @Override - protected void updateStates() { - //Nothing to update - } - //endregion - - //region Helpers - private void convertValueByUnit(float altitude) { - if (unitTypeProcessor.getValue() == UnitConversionUtil.UnitType.IMPERIAL) { - altitudeProcessor.onNext(UnitConversionUtil.convertMetersToFeet(altitude)); - } else { - altitudeProcessor.onNext(altitude); - } - } - //endregion -} diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/altitude/AltitudeWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/altitude/AltitudeWidgetModel.kt new file mode 100644 index 00000000..104e1f3c --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/altitude/AltitudeWidgetModel.kt @@ -0,0 +1,110 @@ +/* + * 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.ux.beta.core.widget.altitude + +import dji.keysdk.FlightControllerKey +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.WidgetModel +import dji.ux.beta.core.communication.GlobalPreferenceKeys +import dji.ux.beta.core.communication.GlobalPreferencesInterface +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.extension.toDistance +import dji.ux.beta.core.util.DataProcessor +import dji.ux.beta.core.util.UnitConversionUtil.UnitType + +/** + * Widget Model for the [AMSLAltitudeWidget] and [AGLAltitudeWidget] used to define + * the underlying logic and communication. + */ +class AltitudeWidgetModel @JvmOverloads constructor( + djiSdkModel: DJISDKModel, + keyedStore: ObservableInMemoryKeyedStore, + private val preferencesManager: GlobalPreferencesInterface? +) : WidgetModel(djiSdkModel, keyedStore) { + + private val altitudeProcessor: DataProcessor = DataProcessor.create(0.0f) + private val takeOffLocationAltitudeProcessor: DataProcessor = DataProcessor.create(0.0f) + private val unitTypeDataProcessor: DataProcessor = DataProcessor.create(UnitType.METRIC) + private val altitudeStateProcessor: DataProcessor = DataProcessor.create(AltitudeState.ProductDisconnected) + + /** + * Value of the altitude state of the aircraft + */ + val altitudeState: Flowable + get() = altitudeStateProcessor.toFlowable() + + override fun inSetup() { + val altitudeKey = FlightControllerKey.create(FlightControllerKey.ALTITUDE) + bindDataProcessor(altitudeKey, altitudeProcessor) + + val takeoffLocationAltitudeKey = FlightControllerKey.create(FlightControllerKey.TAKEOFF_LOCATION_ALTITUDE) + bindDataProcessor(takeoffLocationAltitudeKey, takeOffLocationAltitudeProcessor) + + val unitTypeKey = GlobalPreferenceKeys.create(GlobalPreferenceKeys.UNIT_TYPE) + bindDataProcessor(unitTypeKey, unitTypeDataProcessor) + preferencesManager?.setUpListener() + preferencesManager?.let { unitTypeDataProcessor.onNext(it.unitType) } + } + + override fun updateStates() { + if (productConnectionProcessor.value) { + altitudeStateProcessor.onNext( + AltitudeState.CurrentAltitude( + altitudeAGL = altitudeProcessor.value.toDistance(unitTypeDataProcessor.value), + altitudeAMSL = (altitudeProcessor.value + takeOffLocationAltitudeProcessor.value) + .toDistance(unitTypeDataProcessor.value), + unitType = unitTypeDataProcessor.value)) + } else { + altitudeStateProcessor.onNext(AltitudeState.ProductDisconnected) + } + } + + override fun inCleanup() { + preferencesManager?.cleanup() + } + + /** + * Class to represent states of Altitude + */ + sealed class AltitudeState { + + /** + * When product is disconnected + */ + object ProductDisconnected : AltitudeState() + + /** + * When product is connected and altitude level is available + * + * @property altitudeAGL - Above Ground Level Altitude + * @property altitudeAMSL - Above Mean Sea Level Altitude + * @property unitType - Unit of altitude + */ + data class CurrentAltitude(val altitudeAGL: Float, + val altitudeAMSL: Float, + val unitType: UnitType) : AltitudeState() + } + +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/battery/BatteryWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/battery/BatteryWidget.kt index bf65ee4e..79dff2ac 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/battery/BatteryWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/battery/BatteryWidget.kt @@ -38,17 +38,18 @@ import androidx.annotation.DrawableRes import androidx.annotation.StyleRes import androidx.constraintlayout.widget.ConstraintSet import androidx.core.content.res.use -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.base.SchedulerProvider import dji.thirdparty.io.reactivex.functions.Consumer -import dji.ux.beta.R -import dji.ux.beta.core.base.ConstraintLayoutWidget +import dji.ux.beta.core.R import dji.ux.beta.core.base.DJISDKModel import dji.ux.beta.core.base.WidgetSizeDescription -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.* -import dji.ux.beta.core.widget.battery.BatteryWidget.BatteryWidgetState -import dji.ux.beta.core.widget.battery.BatteryWidget.BatteryWidgetState.BatteryStateUpdated -import dji.ux.beta.core.widget.battery.BatteryWidget.BatteryWidgetState.ProductConnected +import dji.ux.beta.core.widget.battery.BatteryWidget.ModelState +import dji.ux.beta.core.widget.battery.BatteryWidget.ModelState.BatteryStateUpdated +import dji.ux.beta.core.widget.battery.BatteryWidget.ModelState.ProductConnected import dji.ux.beta.core.widget.battery.BatteryWidgetModel.BatteryState import dji.ux.beta.core.widget.battery.BatteryWidgetModel.BatteryStatus @@ -62,9 +63,9 @@ open class BatteryWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ConstraintLayoutWidget(context, attrs, defStyleAttr) { - +) : ConstraintLayoutWidget(context, attrs, defStyleAttr) { + //region Fields private val widgetModel by lazy { BatteryWidgetModel( DJISDKModel.getInstance(), @@ -182,7 +183,9 @@ open class BatteryWidget @JvmOverloads constructor( dualBattery2ValueTextView.background = value } + //endregion + //region Constructors override fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { View.inflate(context, R.layout.uxsdk_widget_battery, this) } @@ -190,27 +193,20 @@ open class BatteryWidget @JvmOverloads constructor( init { attrs?.let { initAttributes(context, it) } } + //endregion + //region Lifecycle override fun reactToModelChanges() { addReaction(widgetModel.batteryState - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { updateUI(it) }) addReaction(widgetModel.productConnection - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) } - override fun getIdealDimensionRatioString(): String? { - return null - } - - override val widgetSizeDescription: WidgetSizeDescription = - WidgetSizeDescription(WidgetSizeDescription.SizeType.OTHER, - widthDimension = WidgetSizeDescription.Dimension.WRAP, - heightDimension = WidgetSizeDescription.Dimension.EXPAND) - override fun onAttachedToWindow() { super.onAttachedToWindow() if (!isInEditMode) { @@ -225,7 +221,9 @@ open class BatteryWidget @JvmOverloads constructor( super.onDetachedFromWindow() } + //endregion + //region Reactions to model private fun updateUI(batteryState: BatteryState) { widgetStateDataProcessor.onNext(BatteryStateUpdated(batteryState)) when (batteryState) { @@ -296,7 +294,9 @@ open class BatteryWidget @JvmOverloads constructor( } + //endregion + //region private helpers private fun setPercentageTextColorByState(textView: TextView, batteryStatus: BatteryStatus) { percentColorStates[batteryStatus]?.let { textView.textColorStateList = it @@ -331,7 +331,7 @@ open class BatteryWidget @JvmOverloads constructor( private fun checkAndUpdateUI() { if (!isInEditMode) { addDisposable(widgetModel.batteryState.firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { this.updateUI(it) }, logErrorConsumer(TAG, "Update UI "))) } } @@ -339,7 +339,7 @@ open class BatteryWidget @JvmOverloads constructor( private fun checkAndUpdateIconDimensionRatio() { if (!isInEditMode) { addDisposable(widgetModel.batteryState.firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { this.updateIconRatio(it) }, logErrorConsumer(TAG, "Update icon dimension ratio "))) } } @@ -352,9 +352,17 @@ open class BatteryWidget @JvmOverloads constructor( if (batteryState is BatteryState.DualBatteryState) dualIconDimensionRatio else singleIconDimensionRatio) set.applyTo(this) } + //endregion //region customizations + override fun getIdealDimensionRatioString(): String? { + return null + } + override val widgetSizeDescription: WidgetSizeDescription = + WidgetSizeDescription(WidgetSizeDescription.SizeType.OTHER, + widthDimension = WidgetSizeDescription.Dimension.WRAP, + heightDimension = WidgetSizeDescription.Dimension.EXPAND) /** * Set the single battery icon drawable for the given batteryStatus @@ -687,21 +695,31 @@ open class BatteryWidget @JvmOverloads constructor( //endregion + //region Hooks + /** + * Get the [ModelState] updates + */ + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { + return super.getWidgetStateUpdate() + } + /** * * Class defines widget state updates */ - sealed class BatteryWidgetState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : BatteryWidgetState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * Battery state update */ - data class BatteryStateUpdated(val batteryState: BatteryState) : BatteryWidgetState() + data class BatteryStateUpdated(val batteryState: BatteryState) : ModelState() } + //endregion companion object { private const val TAG = "BatteryWidget" diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/battery/BatteryWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/battery/BatteryWidgetModel.kt index 1f65fe2b..af074b54 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/battery/BatteryWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/battery/BatteryWidgetModel.kt @@ -32,7 +32,7 @@ import dji.keysdk.FlightControllerKey import dji.thirdparty.io.reactivex.Flowable import dji.ux.beta.core.base.DJISDKModel import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.milliVoltsToVolts import dji.ux.beta.core.util.DataProcessor @@ -72,9 +72,10 @@ class BatteryWidgetModel( private val batteryAggregationProcessor = DataProcessor.create(AggregationState.Builder().build()) - private val batteryStateProcessor: DataProcessor = DataProcessor.create( - BatteryState.SingleBatteryState(0, 0f, BatteryStatus.UNKNOWN)) + private val batteryStateProcessor: DataProcessor = DataProcessor.create(BatteryState.DisconnectedState) private val batteryThresholdBehaviorProcessor = DataProcessor.create(BatteryThresholdBehavior.UNKNOWN) + private val batteryNeededToGoHomeProcessor: DataProcessor = DataProcessor.create(0) + private val isAircraftFlyingDataProcessor: DataProcessor = DataProcessor.create(false) /** * Get the current state of the battery of the connected product @@ -103,7 +104,10 @@ class BatteryWidgetModel( val batteryThresholdBehaviorKey = FlightControllerKey.create(FlightControllerKey.BATTERY_THRESHOLD_BEHAVIOR) bindDataProcessor(batteryThresholdBehaviorKey, batteryThresholdBehaviorProcessor) - + val batteryNeededToGoHomeKey = FlightControllerKey.create(FlightControllerKey.BATTERY_PERCENTAGE_NEEDED_TO_GO_HOME) + bindDataProcessor(batteryNeededToGoHomeKey, batteryNeededToGoHomeProcessor) + val isFlyingKey = FlightControllerKey.create(FlightControllerKey.IS_FLYING) + bindDataProcessor(isFlyingKey, isAircraftFlyingDataProcessor) } override fun updateStates() { @@ -125,12 +129,16 @@ class BatteryWidgetModel( calculateBatteryStatus(batteryWarningRecordProcessor1.value, batteryThresholdBehaviorProcessor.value, batteryPercentageProcessor1.value, + batteryNeededToGoHomeProcessor.value, + isAircraftFlyingDataProcessor.value, battery1Voltage), batteryPercentageProcessor2.value, battery2Voltage, calculateBatteryStatus(batteryWarningRecordProcessor2.value, batteryThresholdBehaviorProcessor.value, batteryPercentageProcessor2.value, + batteryNeededToGoHomeProcessor.value, + isAircraftFlyingDataProcessor.value, battery2Voltage) )) } @@ -142,6 +150,8 @@ class BatteryWidgetModel( calculateBatteryStatus(batteryWarningRecordProcessor1.value, batteryThresholdBehaviorProcessor.value, batteryPercentageProcessor1.value, + batteryNeededToGoHomeProcessor.value, + isAircraftFlyingDataProcessor.value, voltage) )) } @@ -180,6 +190,8 @@ class BatteryWidgetModel( val currentBatteryStatus = calculateBatteryStatus(currentWarningRecord, batteryThresholdBehaviorProcessor.value, aggregationState.batteryOverviews[i].chargeRemainingInPercent, + batteryNeededToGoHomeProcessor.value, + isAircraftFlyingDataProcessor.value, currentVoltage) if (currentBatteryStatus > priorityStatus) { @@ -191,12 +203,8 @@ class BatteryWidgetModel( } private fun calculateAverageVoltage(cellVoltages: Array?): Float { - var sum = 0f return if (cellVoltages != null && cellVoltages.isNotEmpty()) { - for (element in cellVoltages) { - sum += element - } - (sum / cellVoltages.size).milliVoltsToVolts() + cellVoltages.average().toFloat().milliVoltsToVolts() } else 0f } @@ -204,6 +212,8 @@ class BatteryWidgetModel( private fun calculateBatteryStatus(warningRecord: WarningRecord, batteryThresholdBehavior: BatteryThresholdBehavior, percentage: Int, + goHomeBattery: Int, + isFlying: Boolean, voltage: Float): BatteryStatus { if (percentage < 0 || voltage < 0f) { return BatteryStatus.UNKNOWN @@ -213,7 +223,8 @@ class BatteryWidgetModel( return BatteryStatus.ERROR } else if (BatteryThresholdBehavior.LAND_IMMEDIATELY == batteryThresholdBehavior) { return BatteryStatus.WARNING_LEVEL_2 - } else if (BatteryThresholdBehavior.GO_HOME == batteryThresholdBehavior) { + } else if (BatteryThresholdBehavior.GO_HOME == batteryThresholdBehavior + || (percentage <= goHomeBattery && isFlying)) { return BatteryStatus.WARNING_LEVEL_1 } return BatteryStatus.NORMAL @@ -309,14 +320,11 @@ class BatteryWidgetModel( companion object { @JvmStatic - fun find(@IntRange(from = 0, to = 5) index: Int): BatteryStatus { - for (batteryStatus in values()) { - if (batteryStatus.index == index) { - return batteryStatus - } - } + val values = values() - return UNKNOWN + @JvmStatic + fun find(@IntRange(from = 0, to = 5) index: Int): BatteryStatus { + return values.find { it.index == index } ?: UNKNOWN } } } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/CompassWidget.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/CompassWidget.java deleted file mode 100644 index eb4db7df..00000000 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/CompassWidget.java +++ /dev/null @@ -1,1046 +0,0 @@ -/* - * 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.ux.beta.core.widget.compass; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; -import android.hardware.SensorManager; -import android.util.AttributeSet; -import android.util.Pair; -import android.view.WindowManager; -import android.widget.ImageView; -import android.widget.ProgressBar; - -import androidx.annotation.ColorInt; -import androidx.annotation.DrawableRes; -import androidx.annotation.FloatRange; -import androidx.annotation.IntRange; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.constraintlayout.widget.ConstraintLayout; - -import dji.thirdparty.io.reactivex.Flowable; -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers; -import dji.thirdparty.io.reactivex.disposables.Disposable; -import dji.thirdparty.io.reactivex.functions.Function3; -import dji.thirdparty.io.reactivex.functions.Function4; -import dji.ux.beta.R; -import dji.ux.beta.core.base.ConstraintLayoutWidget; -import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; -import dji.ux.beta.core.util.MobileGPSLocationUtil; - -/** - * This widget aggregates the attitude and location data of the aircraft - * into one widget. This includes - - * - Position of the aircraft relative to the pilot - * - Distance of the aircraft from the pilot - * - Heading of the aircraft relative to the pilot - * - True north relative to the pilot and the aircraft - * - The aircraft's last recorded home location - * - Attitude of the aircraft - * - Yaw of the gimbal - */ -public class CompassWidget extends ConstraintLayoutWidget { - - //region Constants - private static final String TAG = "CompassWidget"; - private final static int MAX_DISTANCE = 400; - private final static int MAX_SCALE_DISTANCE = 2000; - private final static float MIN_SCALE = 0.6f; - private final static int MAX_PROGRESS = 100; - private final static int MIN_PROGRESS = 0; - private static final int FULL_TURN = 360; - private static final int HALF_TURN = 180; - private static final int QUARTER_TURN = 90; - //endregion - - //region Fields - private CompassWidgetModel widgetModel; - private float halfNorthIconWidth; - private float halfAttitudeBallWidth; - private float paddingWidth; - private float paddingHeight; - //endregion - - //region Views - private ImageView homeImageView; - private ImageView rcImageView; - private ImageView aircraftImageView; - private ImageView gimbalYawImageView; - private ImageView innerCirclesImageView; - private ImageView northImageView; - private ImageView compassBackgroundImageView; - private ProgressBar aircraftAttitudeProgressBar; - private VisualCompassView visualCompassView; - private GimbalYawView gimbalYawView; - //endregion - - public CompassWidget(Context context) { - super(context); - } - - public CompassWidget(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public CompassWidget(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_compass, this); - - compassBackgroundImageView = findViewById(R.id.imageview_compass_background); - homeImageView = findViewById(R.id.imageview_compass_home); - rcImageView = findViewById(R.id.imageview_compass_rc); - northImageView = findViewById(R.id.imageview_north); - innerCirclesImageView = findViewById(R.id.imageview_inner_circles); - aircraftImageView = findViewById(R.id.imageview_compass_aircraft); - gimbalYawImageView = findViewById(R.id.imageview_gimbal_heading); - aircraftAttitudeProgressBar = findViewById(R.id.progressbar_compass_attitude); - visualCompassView = findViewById(R.id.visual_compass_view); - gimbalYawView = findViewById(R.id.gimbal_yaw_view); - - if (!isInEditMode()) { - widgetModel = new CompassWidgetModel(DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - (SensorManager) context.getSystemService(Context.SENSOR_SERVICE), - (WindowManager) context.getSystemService(Context.WINDOW_SERVICE)); - widgetModel.setMobileGPSLocationUtil(new MobileGPSLocationUtil(context, widgetModel)); - } - - if (attrs != null) { - initAttributes(context, attrs); - } - } - - //region Lifecycle - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - if (!isInEditMode()) { - widgetModel.setup(); - } - } - - @Override - protected void onDetachedFromWindow() { - if (!isInEditMode()) { - widgetModel.cleanup(); - } - super.onDetachedFromWindow(); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - if (!isInEditMode()) { - synchronized (this) { - gimbalYawImageView.setPivotX(gimbalYawImageView.getMeasuredWidth() / 2f); - gimbalYawImageView.setPivotY(gimbalYawImageView.getMeasuredHeight()); - } - halfNorthIconWidth = (float) northImageView.getWidth() / 2; - halfAttitudeBallWidth = (float) compassBackgroundImageView.getWidth() / 2; - paddingWidth = (float) getWidth() - compassBackgroundImageView.getWidth(); - paddingHeight = (float) getHeight() - compassBackgroundImageView.getHeight(); - } - } - - @Override - protected void reactToModelChanges() { - addReaction(widgetModel.getAircraftAttitude() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::updateAircraftAttitudeUI)); - addReaction(widgetModel.getMobileDeviceAzimuth() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::updateNorthHeadingUI)); - addReaction(reactToUpdateAircraftHeading()); - addReaction(reactToUpdateAircraftLocation()); - addReaction(reactToUpdateGimbalHeading()); - addReaction(reactToUpdateSecondGPSLocation()); - } - //endregion - - //region reaction Helpers - private Disposable reactToUpdateAircraftHeading() { - // Use the mobile device azimuth and the aircraft attitude to update the aircraft's heading - return Flowable.combineLatest(widgetModel.getMobileDeviceAzimuth(), - widgetModel.getAircraftAttitude(), - Pair::new) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(values -> updateAircraftHeadingUI(values.first, values.second), - logErrorConsumer(TAG, "reactToUpdateAircraftHeading: ")); - } - - private Disposable reactToUpdateAircraftLocation() { - // Use the mobile device azimuth, and the aircraft and current location states to update the aircraft location UI - return Flowable.combineLatest(widgetModel.getMobileDeviceAzimuth(), - widgetModel.getAircraftState(), - widgetModel.getCurrentLocationState(), - (Function3) (phoneAzimuth, aircraftState, state) -> { - if (aircraftState != null && state != null) { - ViewCoordinates viewCoordinates = - getAircraftLocationCoordinates(phoneAzimuth, aircraftState, state); - return Pair.create(Pair.create(getMaxDistance(aircraftState, state), - calculateScale(aircraftState.getDistance())), - viewCoordinates); - } - return null; - }).observeOn(AndroidSchedulers.mainThread()).subscribe(values -> { - if (values != null) { - Pair pair = (Pair) values.first; - updateAircraftLocationUI((float) pair.first, (float) pair.second, (ViewCoordinates) values.second); - } - }, logErrorConsumer(TAG, "reactToUpdateAircraftLocation: ")); - } - - private Disposable reactToUpdateGimbalHeading() { - // Use the mobile device azimuth, the aircraft attitude and the gimbal heading to update the gimbal heading UI - return Flowable.combineLatest(widgetModel.getMobileDeviceAzimuth(), - widgetModel.getAircraftAttitude(), - widgetModel.getGimbalHeading(), - (phoneAzimuth, aircraftAttitude, gimbalHeading) -> { - if (aircraftAttitude != null) { - return Pair.create(gimbalHeading, - (float) aircraftAttitude.getYaw() - phoneAzimuth); - } - return null; - }).observeOn(AndroidSchedulers.mainThread()).subscribe(values -> { - if (values != null) { - updateGimbalHeadingUI(values.first, values.second); - } - }, logErrorConsumer(TAG, "reactToUpdateGimbalHeading: ")); - } - - private Disposable reactToUpdateSecondGPSLocation() { - // Use the center type, mobile device azimuth, and the aircraft and current location states - // to update the home or RC/Mobile device location UI - return Flowable.combineLatest(widgetModel.getCenterType(), - widgetModel.getMobileDeviceAzimuth(), - widgetModel.getCurrentLocationState(), - widgetModel.getAircraftState(), - (Function4) (centerType, phoneAzimuth, state, aircraftState) -> { - if (aircraftState != null && state != null) { - ViewCoordinates viewCoordinates = - getSecondGPSLocationCoordinates(phoneAzimuth, state, aircraftState); - return Pair.create(centerType, viewCoordinates); - } - return null; - }).observeOn(AndroidSchedulers.mainThread()).subscribe(values -> { - if (values != null) { - updateSecondGPSLocationUI((CompassWidgetModel.CenterType) values.first, - (ViewCoordinates) values.second); - } - }, logErrorConsumer(TAG, "reactToUpdateSecondGPSLocation: ")); - } - //endregion - - //region Calculations - private ViewCoordinates getSecondGPSLocationCoordinates(float phoneAzimuth, - @NonNull CompassWidgetModel.CurrentLocationState state, - @NonNull CompassWidgetModel.AircraftState aircraftState) { - final double radians = Math.toRadians(state.getAngle() + phoneAzimuth); - final float maxDistance = getMaxDistance(aircraftState, state); - float rcHomeDistance = state.getDistance(); - float x, y; - if (rcHomeDistance == maxDistance) { - x = (float) Math.cos(radians); - y = (float) Math.sin(radians); - } else { - x = (float) (rcHomeDistance * Math.cos(radians) / maxDistance); - y = (float) (rcHomeDistance * Math.sin(radians) / maxDistance); - } - return new ViewCoordinates(x, y); - } - - private float getMaxDistance(@NonNull CompassWidgetModel.AircraftState aircraftState, - @NonNull CompassWidgetModel.CurrentLocationState state) { - float maxDistance = aircraftState.getDistance(); - if (maxDistance < state.getDistance()) { - maxDistance = state.getDistance(); - } - if (maxDistance < MAX_DISTANCE) { - maxDistance = MAX_DISTANCE; - } - return maxDistance; - } - - private ViewCoordinates getAircraftLocationCoordinates(float phoneAzimuth, - @NonNull CompassWidgetModel.AircraftState aircraftState, - @NonNull CompassWidgetModel.CurrentLocationState state) { - float maxDistance = getMaxDistance(aircraftState, state); - final double radians = Math.toRadians(aircraftState.getAngle() + phoneAzimuth); - float aircraftDistance = aircraftState.getDistance(); - float x, y; - if (aircraftDistance >= maxDistance) { - x = (float) Math.cos(radians); - y = (float) Math.sin(radians); - } else { - x = (float) (aircraftDistance * Math.cos(radians) / maxDistance); - y = (float) (aircraftDistance * Math.sin(radians) / maxDistance); - } - return new ViewCoordinates(x, y); - } - - private float calculateScale(final float distance) { - float scale = 1.0f; - if (distance >= MAX_SCALE_DISTANCE) { - scale = MIN_SCALE; - } else if (distance > MAX_DISTANCE) { - scale = 1 - MIN_SCALE + ((MAX_SCALE_DISTANCE - distance) / (MAX_SCALE_DISTANCE - MAX_DISTANCE) * MIN_SCALE); - } - return scale; - } - //endregion - - //region update UI - private void updateNorthHeadingUI(float phoneAzimuth) { - // update north image - double northRadian = Math.toRadians((FULL_TURN - phoneAzimuth) % FULL_TURN); - final float moveX = - (float) (halfAttitudeBallWidth + paddingWidth / 2 + halfAttitudeBallWidth * Math.sin(northRadian)); - final float moveY = - (float) (halfAttitudeBallWidth + paddingHeight / 2 - halfAttitudeBallWidth * Math.cos(northRadian)); - northImageView.setX(moveX - halfNorthIconWidth); - northImageView.setY(moveY - halfNorthIconWidth); - } - - private void updateAircraftAttitudeUI(@NonNull CompassWidgetModel.AircraftAttitude aircraftAttitude) { - //Update aircraft roll - if (aircraftAttitudeProgressBar != null) { - aircraftAttitudeProgressBar.setRotation((float) aircraftAttitude.getRoll()); - } - //Update aircraft pitch - float tempPitch = (float) -aircraftAttitude.getPitch() + QUARTER_TURN; - int progress = (int) ((tempPitch * 100) / HALF_TURN); - if (progress < MIN_PROGRESS) { - progress = MIN_PROGRESS; - } else if (progress > MAX_PROGRESS) { - progress = MAX_PROGRESS; - } - if (aircraftAttitudeProgressBar != null) { - if (aircraftAttitudeProgressBar.getProgress() != progress) { - aircraftAttitudeProgressBar.setProgress(progress); - } - } - } - - private void updateAircraftHeadingUI(float phoneAzimuth, - @NonNull CompassWidgetModel.AircraftAttitude aircraftAttitude) { - if (aircraftImageView != null) { - aircraftImageView.setRotation((float) aircraftAttitude.getYaw() - phoneAzimuth); - } - } - - private void updateAircraftLocationUI(float maxDistance, float scale, @NonNull ViewCoordinates viewCoordinates) { - final float wRadius = (getMeasuredWidth() - paddingWidth - aircraftImageView.getWidth()) / 2.0f; - final float hRadius = (getMeasuredHeight() - paddingHeight - aircraftImageView.getHeight()) / 2.0f; - - //update the size and heading of the aircraft - aircraftImageView.setX((paddingWidth / 2.0f) + wRadius + viewCoordinates.getX() * wRadius); - aircraftImageView.setY((paddingHeight / 2.0f) + hRadius - viewCoordinates.getY() * hRadius); - aircraftImageView.setScaleX(scale); - aircraftImageView.setScaleY(scale); - - // Update the size and heading of the gimbal - gimbalYawImageView.setX(aircraftImageView.getX() + aircraftImageView.getWidth() / 2f - - gimbalYawImageView.getWidth() / 2f); - gimbalYawImageView.setY(aircraftImageView.getY() + aircraftImageView.getHeight() / 2f - - gimbalYawImageView.getHeight()); - gimbalYawImageView.setScaleX(scale); - gimbalYawImageView.setScaleY(scale); - - //update the compass view - if (visualCompassView != null) { - visualCompassView.setVisibility(VISIBLE); - innerCirclesImageView.setVisibility(GONE); - visualCompassView.setDistance(maxDistance); - } - } - - private void updateGimbalHeadingUI(float gimbalHeading, float rotationOffset) { - gimbalYawView.setYaw(gimbalHeading); - if (gimbalYawImageView != null) { - gimbalYawImageView.setRotation(gimbalHeading + rotationOffset); - } - } - - private void updateSecondGPSLocationUI(@NonNull CompassWidgetModel.CenterType type, - @NonNull ViewCoordinates viewCoordinates) { - // Calculate the second GPS image's parameters using the center point's parameters - ImageView centerGPSImage, secondGPSImage; - if (type == CompassWidgetModel.CenterType.HOME_GPS) { - centerGPSImage = homeImageView; - secondGPSImage = rcImageView; - } else { - centerGPSImage = rcImageView; - secondGPSImage = homeImageView; - } - - centerGPSImage.setVisibility(VISIBLE); - final ConstraintLayout.LayoutParams centerParam = - (ConstraintLayout.LayoutParams) centerGPSImage.getLayoutParams(); - centerParam.leftMargin = 0; - centerParam.topMargin = 0; - centerGPSImage.setLayoutParams(centerParam); - - //Updating second GPS location and show the second GPS image if both exist - if (secondGPSImage == null) return; - if (type != CompassWidgetModel.CenterType.HOME_GPS) { - secondGPSImage.setVisibility(VISIBLE); - final float wRadius = (getMeasuredWidth() - paddingWidth - secondGPSImage.getWidth()) / 2.0f; - final float hRadius = (getMeasuredHeight() - paddingHeight - secondGPSImage.getHeight()) / 2.0f; - secondGPSImage.setX((paddingWidth / 2.0f) + wRadius + viewCoordinates.getX() * wRadius); - secondGPSImage.setY((paddingHeight / 2.0f) + hRadius - viewCoordinates.getY() * hRadius); - } else { - secondGPSImage.setVisibility(GONE); - } - } - //endregion - - //region Customization - @NonNull - @Override - public String getIdealDimensionRatioString() { - return getResources().getString(R.string.uxsdk_widget_compass_ratio); - } - - /** - * Get the drawable resource for the home icon - * - * @return Drawable resource for the icon - */ - public Drawable getHomeIcon() { - return homeImageView.getDrawable(); - } - - /** - * Set the resource ID for the home icon - * - * @param resourceId Integer ID of the drawable resource - */ - public void setHomeIcon(@DrawableRes int resourceId) { - homeImageView.setImageResource(resourceId); - } - - /** - * Set the drawable resource for the home icon - * - * @param icon Drawable resource for the image - */ - public void setHomeIcon(@Nullable Drawable icon) { - homeImageView.setImageDrawable(icon); - } - - /** - * Get the drawable resource for the home icon's background - * - * @return Drawable resource of the icon's background - */ - public Drawable getHomeIconBackground() { - return homeImageView.getBackground(); - } - - /** - * Set the resource ID for the home icon's background - * - * @param resourceId Integer ID of the icon's background resource - */ - public void setHomeIconBackground(@DrawableRes int resourceId) { - homeImageView.setBackgroundResource(resourceId); - } - - /** - * Set the drawable resource for the home icon's background - * - * @param background Drawable resource for the icon's background - */ - public void setHomeIconBackground(@Nullable Drawable background) { - homeImageView.setBackground(background); - } - - /** - * Get the drawable resource for the RC location icon - * - * @return Drawable resource for the icon - */ - public Drawable getRCLocationIcon() { - return rcImageView.getDrawable(); - } - - /** - * Set the resource ID for the RC location icon - * - * @param resourceId Integer ID of the drawable resource - */ - public void setRCLocationIcon(@DrawableRes int resourceId) { - rcImageView.setImageResource(resourceId); - } - - /** - * Set the drawable resource for the RC location icon - * - * @param icon Drawable resource for the image - */ - public void setRCLocationIcon(@Nullable Drawable icon) { - rcImageView.setImageDrawable(icon); - } - - /** - * Get the drawable resource for the RC location icon's background - * - * @return Drawable resource for the icon's background - */ - public Drawable getRCLocationIconBackground() { - return rcImageView.getBackground(); - } - - /** - * Set the resource ID for the RC location icon's background - * - * @param resourceId Integer ID of the icon's background resource - */ - public void setRCLocationIconBackground(@DrawableRes int resourceId) { - rcImageView.setBackgroundResource(resourceId); - } - - /** - * Set the drawable resource for the RC location icon's background - * - * @param background Drawable resource for the icon's background - */ - public void setRCLocationIconBackground(@Nullable Drawable background) { - rcImageView.setBackground(background); - } - - /** - * Get the drawable resource for the aircraft icon - * - * @return Drawable resource for the icon - */ - public Drawable getAircraftIcon() { - return aircraftImageView.getDrawable(); - } - - /** - * Set the resource ID for the aircraft icon - * - * @param resourceId Integer ID of the drawable resource - */ - public void setAircraftIcon(@DrawableRes int resourceId) { - aircraftImageView.setImageResource(resourceId); - } - - /** - * Set the drawable resource for the aircraft icon - * - * @param icon Drawable resource for the image - */ - public void setAircraftIcon(@Nullable Drawable icon) { - aircraftImageView.setImageDrawable(icon); - } - - /** - * Get the drawable resource for the aircraft icon's background - * - * @return Drawable resource of the icon's background - */ - public Drawable getAircraftIconBackground() { - return aircraftImageView.getBackground(); - } - - /** - * Set the resource ID for the aircraft icon's background - * - * @param resourceId Integer ID of the icon's background resource - */ - public void setAircraftIconBackground(@DrawableRes int resourceId) { - aircraftImageView.setBackgroundResource(resourceId); - } - - /** - * Set the drawable resource for the aircraft icon's background - * - * @param background Drawable resource for the icon's background - */ - public void setAircraftIconBackground(@Nullable Drawable background) { - aircraftImageView.setBackground(background); - } - - /** - * Get the drawable resource for the gimbal yaw icon - * - * @return Drawable resource for the icon - */ - public Drawable getGimbalYawIcon() { - return gimbalYawImageView.getDrawable(); - } - - /** - * Set the resource ID for the gimbal yaw icon - * - * @param resourceId Integer ID of the drawable resource - */ - public void setGimbalYawIcon(@DrawableRes int resourceId) { - gimbalYawImageView.setImageResource(resourceId); - } - - /** - * Set the drawable resource for the gimbal yaw icon - * - * @param icon Drawable resource for the image - */ - public void setGimbalYawIcon(@Nullable Drawable icon) { - gimbalYawImageView.setImageDrawable(icon); - } - - /** - * Get the drawable resource for the gimbal yaw icon's background - * - * @return Drawable resource of the icon's background - */ - public Drawable getGimbalYawIconBackground() { - return gimbalYawImageView.getBackground(); - } - - /** - * Set the resource ID for the gimbal yaw icon's background - * - * @param resourceId Integer ID of the icon's background resource - */ - public void setGimbalYawIconBackground(@DrawableRes int resourceId) { - gimbalYawImageView.setBackgroundResource(resourceId); - } - - /** - * Set the drawable resource for the gimbal yaw icon's background - * - * @param background Drawable resource for the icon's background - */ - public void setGimbalYawIconBackground(@Nullable Drawable background) { - gimbalYawImageView.setBackground(background); - } - - /** - * Get the drawable resource for the north icon - * - * @return Drawable resource for the icon - */ - public Drawable getNorthIcon() { - return northImageView.getDrawable(); - } - - /** - * Set the resource ID for the north icon - * - * @param resourceId Integer ID of the drawable resource - */ - public void setNorthIcon(@DrawableRes int resourceId) { - northImageView.setImageResource(resourceId); - } - - /** - * Set the drawable resource for the north icon - * - * @param icon Drawable resource for the image - */ - public void setNorthIcon(@Nullable Drawable icon) { - northImageView.setImageDrawable(icon); - } - - /** - * Get the drawable resource for the north icon's background - * - * @return Drawable resource of the icon's background - */ - public Drawable getNorthIconBackground() { - return northImageView.getBackground(); - } - - /** - * Set the resource ID for the north icon's background - * - * @param resourceId Integer ID of the icon's background resource - */ - public void setNorthIconBackground(@DrawableRes int resourceId) { - northImageView.setBackgroundResource(resourceId); - } - - /** - * Set the drawable resource for the north icon's background - * - * @param background Drawable resource for the icon's background - */ - public void setNorthIconBackground(@Nullable Drawable background) { - northImageView.setBackground(background); - } - - /** - * Get the drawable resource for the inner circles icon - * - * @return Drawable resource for the icon - */ - public Drawable getInnerCirclesIcon() { - return innerCirclesImageView.getDrawable(); - } - - /** - * Set the resource ID for the inner circles icon - * - * @param resourceId Integer ID of the drawable resource - */ - public void setInnerCirclesIcon(@DrawableRes int resourceId) { - innerCirclesImageView.setImageResource(resourceId); - } - - /** - * Set the drawable resource for the inner circles icon - * - * @param icon Drawable resource for the image - */ - public void setInnerCirclesIcon(@Nullable Drawable icon) { - innerCirclesImageView.setImageDrawable(icon); - } - - /** - * Get the drawable resource for the inner circles icon's background - * - * @return Drawable resource of the icon's background - */ - public Drawable getInnerCirclesIconBackground() { - return innerCirclesImageView.getBackground(); - } - - /** - * Set the resource ID for the inner circles icon's background - * - * @param resourceId Integer ID of the icon's background resource - */ - public void setInnerCirclesIconBackground(@DrawableRes int resourceId) { - innerCirclesImageView.setBackgroundResource(resourceId); - } - - /** - * Set the drawable resource for the inner circles icon's background - * - * @param background Drawable resource for the icon's background - */ - public void setInnerCirclesIconBackground(@Nullable Drawable background) { - innerCirclesImageView.setBackground(background); - } - - /** - * Get the drawable resource for the compass background icon - * - * @return Drawable resource for the icon - */ - public Drawable getCompassBackgroundIcon() { - return compassBackgroundImageView.getDrawable(); - } - - /** - * Set the resource ID for the compass background icon - * - * @param resourceId Integer ID of the drawable resource - */ - public void setCompassBackgroundIcon(@DrawableRes int resourceId) { - compassBackgroundImageView.setImageResource(resourceId); - } - - /** - * Set the drawable resource for the compass background icon - * - * @param icon Drawable resource for the image - */ - public void setCompassBackgroundIcon(@Nullable Drawable icon) { - compassBackgroundImageView.setImageDrawable(icon); - } - - /** - * Get the drawable resource for the compass background icon's background - * - * @return Drawable resource of the icon's background - */ - public Drawable getCompassBackgroundIconBackground() { - return compassBackgroundImageView.getBackground(); - } - - /** - * Set the resource ID for the compass background icon's background - * - * @param resourceId Integer ID of the icon's background resource - */ - public void setCompassBackgroundIconBackground(@DrawableRes int resourceId) { - compassBackgroundImageView.setBackgroundResource(resourceId); - } - - /** - * Set the drawable resource for the compass background icon's background - * - * @param background Drawable resource for the icon's background - */ - public void setCompassBackgroundIconBackground(@Nullable Drawable background) { - compassBackgroundImageView.setBackground(background); - } - - /** - * Get the drawable resource for the aircraft attitude icon - * - * @return Drawable resource for the progress bar - */ - public Drawable getAircraftAttitudeIcon() { - return aircraftAttitudeProgressBar.getProgressDrawable(); - } - - /** - * Set the drawable resource for the aircraft attitude icon - * - * @param icon Drawable resource for the progress bar - */ - public void setAircraftAttitudeIcon(@Nullable Drawable icon) { - aircraftAttitudeProgressBar.setProgressDrawable(icon); - } - - /** - * Get the drawable resource for the aircraft attitude icon's background - * - * @return Drawable resource of the icon's background - */ - public Drawable getAircraftAttitudeIconBackground() { - return aircraftAttitudeProgressBar.getBackground(); - } - - /** - * Set the resource ID for the aircraft attitude icon's background - * - * @param resourceId Integer ID of the icon's background resource - */ - public void setAircraftAttitudeIconBackground(@DrawableRes int resourceId) { - aircraftAttitudeProgressBar.setBackgroundResource(resourceId); - } - - /** - * Set the drawable resource for the aircraft attitude icon's background - * - * @param background Drawable resource for the icon's background - */ - public void setAircraftAttitudeIconBackground(@Nullable Drawable background) { - aircraftAttitudeProgressBar.setBackground(background); - } - - /** - * Set the stroke width for the lines in the visual compass view - * - * @param strokeWidth Float value of stroke width in px - */ - public void setVisualCompassViewStrokeWidth( - @FloatRange(from = 1.0, to = VisualCompassView.MAX_LINE_WIDTH) float strokeWidth) { - visualCompassView.setStrokeWidth(strokeWidth); - } - - /** - * Set the color for the lines in the visual compass view - * - * @param color Color integer resource - */ - public void setVisualCompassViewLineColor(@ColorInt int color) { - visualCompassView.setLineColor(color); - } - - /** - * Set the interval between the lines in the visual compass view - * - * @param interval Integer value of the interval - */ - public void setVisualCompassViewLineInterval(@IntRange(from = 1) int interval) { - visualCompassView.setLineInterval(interval); - } - - /** - * Set the number of lines to be drawn in the visual compass view - * - * @param numberOfLines Number of lines as an integer value - */ - public void setVisualCompassViewNumberOfLines(@IntRange(from = 3) int numberOfLines) { - visualCompassView.setNumberOfLines(numberOfLines); - } - - /** - * Set the stroke width for the lines in the gimbal yaw view - * - * @param strokeWidth Float value of stroke width in px - */ - public void setGimbalYawViewStrokeWidth( - @FloatRange(from = 1.0, to = GimbalYawView.MAX_LINE_WIDTH) float strokeWidth) { - gimbalYawView.setStrokeWidth(strokeWidth); - } - - /** - * Set the yaw color in the gimbal yaw view - * - * @param color Color integer resource - */ - public void setGimbalYawViewYawColor(@ColorInt int color) { - gimbalYawView.setYawColor(color); - } - - /** - * Set the invalid color in the gimbal yaw view - * - * @param color Color integer resource - */ - public void setGimbalYawViewInvalidColor(@ColorInt int color) { - gimbalYawView.setInvalidColor(color); - } - - /** - * Set the blink color in the gimbal yaw view - * - * @param color Color integer resource - */ - public void setGimbalYawViewBlinkColor(@ColorInt int color) { - gimbalYawView.setBlinkColor(color); - } - //endregion - - //region Customization Helpers - private void initAttributes(@NonNull Context context, @NonNull AttributeSet attrs) { - - TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CompassWidget); - - Drawable homeIcon = typedArray.getDrawable(R.styleable.CompassWidget_uxsdk_homeIcon); - if (homeIcon != null) { - setHomeIcon(homeIcon); - } - - Drawable rcLocationIcon = typedArray.getDrawable(R.styleable.CompassWidget_uxsdk_rcLocationIcon); - if (rcLocationIcon != null) { - setRCLocationIcon(rcLocationIcon); - } - - Drawable aircraftIcon = typedArray.getDrawable(R.styleable.CompassWidget_uxsdk_aircraftIcon); - if (aircraftIcon != null) { - setAircraftIcon(aircraftIcon); - } - - Drawable gimbalYawIcon = typedArray.getDrawable(R.styleable.CompassWidget_uxsdk_gimbalYawIcon); - if (gimbalYawIcon != null) { - setGimbalYawIcon(gimbalYawIcon); - } - - Drawable northIcon = typedArray.getDrawable(R.styleable.CompassWidget_uxsdk_northIcon); - if (northIcon != null) { - setNorthIcon(northIcon); - } - - Drawable innerCirclesIcon = typedArray.getDrawable(R.styleable.CompassWidget_uxsdk_innerCirclesIcon); - if (innerCirclesIcon != null) { - setInnerCirclesIcon(innerCirclesIcon); - } - - Drawable compassBackgroundIcon = typedArray.getDrawable(R.styleable.CompassWidget_uxsdk_compassBackgroundIcon); - if (compassBackgroundIcon != null) { - setCompassBackgroundIcon(compassBackgroundIcon); - } - - Drawable aircraftAttitudeIcon = typedArray.getDrawable(R.styleable.CompassWidget_uxsdk_aircraftAttitudeIcon); - if (aircraftAttitudeIcon != null) { - setAircraftAttitudeIcon(aircraftAttitudeIcon); - } - - float visualCompassViewStrokeWidth = - typedArray.getDimension(R.styleable.CompassWidget_uxsdk_visualCompassViewStrokeWidth, INVALID_RESOURCE); - if (visualCompassViewStrokeWidth != INVALID_RESOURCE) { - setVisualCompassViewStrokeWidth(visualCompassViewStrokeWidth); - } - - int visualCompassViewLineColor = - typedArray.getColor(R.styleable.CompassWidget_uxsdk_visualCompassViewLineColor, INVALID_COLOR); - if (visualCompassViewLineColor != INVALID_COLOR) { - setVisualCompassViewLineColor(visualCompassViewLineColor); - } - - int visualCompassViewLineInterval = - typedArray.getInteger(R.styleable.CompassWidget_uxsdk_visualCompassViewLineInterval, INVALID_RESOURCE); - if (visualCompassViewLineInterval != INVALID_RESOURCE) { - setVisualCompassViewLineInterval(visualCompassViewLineInterval); - } - - int visualCompassViewNumberOfLines = - typedArray.getInteger(R.styleable.CompassWidget_uxsdk_visualCompassViewNumberOfLines, INVALID_RESOURCE); - if (visualCompassViewNumberOfLines != INVALID_RESOURCE) { - setVisualCompassViewNumberOfLines(visualCompassViewNumberOfLines); - } - - float gimbalYawViewStrokeWidth = - typedArray.getDimension(R.styleable.CompassWidget_uxsdk_gimbalYawViewStrokeWidth, INVALID_RESOURCE); - if (gimbalYawViewStrokeWidth != INVALID_RESOURCE) { - setGimbalYawViewStrokeWidth(gimbalYawViewStrokeWidth); - } - - int gimbalYawViewYawColor = - typedArray.getColor(R.styleable.CompassWidget_uxsdk_gimbalYawViewYawColor, INVALID_COLOR); - if (gimbalYawViewYawColor != INVALID_COLOR) { - setGimbalYawViewYawColor(gimbalYawViewYawColor); - } - - int gimbalYawViewInvalidColor = - typedArray.getColor(R.styleable.CompassWidget_uxsdk_gimbalYawViewInvalidColor, INVALID_COLOR); - if (gimbalYawViewInvalidColor != INVALID_COLOR) { - setGimbalYawViewInvalidColor(gimbalYawViewInvalidColor); - } - - int gimbalYawViewBlinkColor = - typedArray.getColor(R.styleable.CompassWidget_uxsdk_gimbalYawViewBlinkColor, INVALID_COLOR); - if (gimbalYawViewBlinkColor != INVALID_COLOR) { - setGimbalYawViewBlinkColor(gimbalYawViewBlinkColor); - } - - typedArray.recycle(); - } - //endregion - - /** - * Wrapper that holds the x and y values of the view coordinates - */ - private class ViewCoordinates { - private float x, y; - - ViewCoordinates(float x, float y) { - this.x = x; - this.y = y; - } - - private float getX() { - return x; - } - - private float getY() { - return y; - } - } -} diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/CompassWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/CompassWidget.kt new file mode 100644 index 00000000..7427ffd3 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/CompassWidget.kt @@ -0,0 +1,807 @@ +/* + * 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.ux.beta.core.widget.compass + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.drawable.Drawable +import android.hardware.SensorManager +import android.util.AttributeSet +import android.view.View +import android.view.WindowManager +import android.widget.ImageView +import android.widget.ProgressBar +import androidx.annotation.ColorInt +import androidx.annotation.DrawableRes +import androidx.annotation.FloatRange +import androidx.annotation.IntRange +import androidx.core.content.res.use +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.R +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.SchedulerProvider +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.extension.* +import dji.ux.beta.core.util.MobileGPSLocationUtil +import dji.ux.beta.core.util.SettingDefinitions.GimbalIndex +import dji.ux.beta.core.widget.compass.CompassWidget.ModelState +import dji.ux.beta.core.widget.compass.CompassWidget.ModelState.CompassStateUpdated +import dji.ux.beta.core.widget.compass.CompassWidget.ModelState.ProductConnected +import dji.ux.beta.core.widget.compass.CompassWidgetModel.* +import kotlin.math.cos +import kotlin.math.sin + +private const val TAG = "CompassWidget" +private const val MAX_DISTANCE = 400 +private const val MAX_SCALE_DISTANCE = 2000 +private const val MIN_SCALE = 0.6f +private const val MAX_PROGRESS = 100 +private const val MIN_PROGRESS = 0 +private const val FULL_TURN = 360 +private const val HALF_TURN = 180 +private const val QUARTER_TURN = 90 + +/** + * This widget aggregates the attitude and location data of the aircraft + * into one widget. This includes - + * - Position of the aircraft relative to the pilot + * - Distance of the aircraft from the pilot + * - Heading of the aircraft relative to the pilot + * - True north relative to the pilot and the aircraft + * - The aircraft's last recorded home location + * - Attitude of the aircraft + * - Yaw of the gimbal + */ +open class CompassWidget @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ConstraintLayoutWidget(context, attrs, defStyleAttr) { + + //region Fields + private var halfNorthIconWidth = 0f + private var halfAttitudeBallWidth = 0f + private var paddingWidth = 0f + private var paddingHeight = 0f + + private val homeImageView: ImageView = findViewById(R.id.imageview_compass_home) + private val rcImageView: ImageView = findViewById(R.id.imageview_compass_rc) + private val aircraftImageView: ImageView = findViewById(R.id.imageview_compass_aircraft) + private val gimbalYawImageView: ImageView = findViewById(R.id.imageview_gimbal_heading) + private val innerCirclesImageView: ImageView = findViewById(R.id.imageview_inner_circles) + private val northImageView: ImageView = findViewById(R.id.imageview_north) + private val compassBackgroundImageView: ImageView = findViewById(R.id.imageview_compass_background) + private val aircraftAttitudeProgressBar: ProgressBar = findViewById(R.id.progressbar_compass_attitude) + private val visualCompassView: VisualCompassView = findViewById(R.id.visual_compass_view) + private val gimbalYawView: GimbalYawView = findViewById(R.id.gimbal_yaw_view) + + private val widgetModel: CompassWidgetModel by lazy { + CompassWidgetModel(DJISDKModel.getInstance(), + ObservableInMemoryKeyedStore.getInstance(), + context.getSystemService(Context.SENSOR_SERVICE) as SensorManager, + context.getSystemService(Context.WINDOW_SERVICE) as WindowManager) + } + + /** + * The drawable resource for the home icon + */ + var homeIcon: Drawable + get() = homeImageView.drawable + set(icon) { + homeImageView.setImageDrawable(icon) + } + + /** + * The drawable resource for the home icon's background + */ + var homeIconBackground: Drawable + get() = homeImageView.background + set(background) { + homeImageView.background = background + } + + /** + * The drawable resource for the RC location icon + */ + var rcLocationIcon: Drawable + @JvmName("getRCLocationIcon") + get() = rcImageView.drawable + @JvmName("setRCLocationIcon") + set(icon) { + rcImageView.setImageDrawable(icon) + } + + /** + * The drawable resource for the RC location icon's background + */ + var rcLocationIconBackground: Drawable + @JvmName("getRCLocationIconBackground") + get() = rcImageView.background + @JvmName("setRCLocationIconBackground") + set(background) { + rcImageView.background = background + } + + /** + * The drawable resource for the aircraft icon + */ + var aircraftIcon: Drawable + get() = aircraftImageView.drawable + set(icon) { + aircraftImageView.setImageDrawable(icon) + } + + /** + * The drawable resource for the aircraft icon's background + */ + var aircraftIconBackground: Drawable + get() = aircraftImageView.background + set(background) { + aircraftImageView.background = background + } + + /** + * The drawable resource for the gimbal yaw icon + */ + var gimbalYawIcon: Drawable + get() = gimbalYawImageView.drawable + set(icon) { + gimbalYawImageView.setImageDrawable(icon) + } + + /** + * The drawable resource for the gimbal yaw icon's background + */ + var gimbalYawIconBackground: Drawable + get() = gimbalYawImageView.background + set(background) { + gimbalYawImageView.background = background + } + + /** + * The drawable resource for the north icon + */ + var northIcon: Drawable + get() = northImageView.drawable + set(icon) { + northImageView.setImageDrawable(icon) + } + + /** + * The drawable resource for the north icon's background + */ + var northIconBackground: Drawable + get() = northImageView.background + set(background) { + northImageView.background = background + } + + /** + * The drawable resource for the inner circles icon + */ + var innerCirclesIcon: Drawable + get() = innerCirclesImageView.drawable + set(icon) { + innerCirclesImageView.setImageDrawable(icon) + } + + /** + * The drawable resource for the inner circles icon's background + */ + var innerCirclesIconBackground: Drawable + get() = innerCirclesImageView.background + set(background) { + innerCirclesImageView.background = background + } + + /** + * The drawable resource for the compass background icon + */ + var compassBackgroundIcon: Drawable + get() = compassBackgroundImageView.drawable + set(icon) { + compassBackgroundImageView.setImageDrawable(icon) + } + + /** + * The drawable resource for the compass background icon's background + */ + var compassBackgroundIconBackground: Drawable + get() = compassBackgroundImageView.background + set(background) { + compassBackgroundImageView.background = background + } + + /** + * The drawable resource for the aircraft attitude icon + */ + var aircraftAttitudeIcon: Drawable? + get() = aircraftAttitudeProgressBar.progressDrawable + set(icon) { + aircraftAttitudeProgressBar.progressDrawable = icon + } + + /** + * The drawable resource for the aircraft attitude icon's background + */ + var aircraftAttitudeIconBackground: Drawable + get() = aircraftAttitudeProgressBar.background + set(background) { + aircraftAttitudeProgressBar.background = background + } + + /** + * The stroke width in px for the lines in the visual compass view + */ + var visualCompassViewStrokeWidth: Float + @FloatRange(from = 1.0, to = VisualCompassView.MAX_LINE_WIDTH.toDouble()) + get() = visualCompassView.strokeWidth + set(@FloatRange(from = 1.0, to = VisualCompassView.MAX_LINE_WIDTH.toDouble()) strokeWidth) { + visualCompassView.strokeWidth = strokeWidth + } + + /** + * The color for the lines in the visual compass view + */ + var visualCompassViewLineColor: Int + @ColorInt + get() = visualCompassView.lineColor + set(@ColorInt color) { + visualCompassView.lineColor = color + } + + /** + * The interval between the lines in the visual compass view + */ + var visualCompassViewLineInterval: Int + get() = visualCompassView.lineInterval + set(@IntRange(from = 1) interval) { + visualCompassView.lineInterval = interval + } + + /** + * The number of lines to be drawn in the visual compass view + */ + var visualCompassViewNumberOfLines: Int + get() = visualCompassView.numberOfLines + set(@IntRange(from = 3) numberOfLines) { + visualCompassView.numberOfLines = numberOfLines + } + + /** + * The stroke width in px for the lines in the gimbal yaw view + */ + var gimbalYawViewStrokeWidth: Float + get() = gimbalYawView.strokeWidth + set(@FloatRange(from = 1.0, to = GimbalYawView.MAX_LINE_WIDTH.toDouble()) strokeWidth) { + gimbalYawView.strokeWidth = strokeWidth + } + + /** + * The yaw color in the gimbal yaw view + */ + var gimbalYawViewYawColor: Int + @ColorInt + get() = gimbalYawView.yawColor + set(@ColorInt color) { + gimbalYawView.yawColor = color + } + + /** + * The invalid color in the gimbal yaw view + */ + var gimbalYawViewInvalidColor: Int + @ColorInt + get() = gimbalYawView.invalidColor + set(@ColorInt color) { + gimbalYawView.invalidColor = color + } + + /** + * Set the blink color in the gimbal yaw view + */ + var gimbalYawViewBlinkColor: Int + @ColorInt + get() = gimbalYawView.blinkColor + set(@ColorInt color) { + gimbalYawView.blinkColor = color + } + + //endregion + + //region Constructor + override fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { + View.inflate(context, R.layout.uxsdk_widget_compass, this) + } + + init { + if (!isInEditMode) { + widgetModel.mobileGPSLocationUtil = MobileGPSLocationUtil(context, widgetModel) + } + 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 onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + super.onLayout(changed, left, top, right, bottom) + if (!isInEditMode) { + synchronized(this) { + gimbalYawImageView.pivotX = gimbalYawImageView.measuredWidth / 2f + gimbalYawImageView.pivotY = gimbalYawImageView.measuredHeight.toFloat() + } + halfNorthIconWidth = northImageView.width.toFloat() / 2 + halfAttitudeBallWidth = compassBackgroundImageView.width.toFloat() / 2 + paddingWidth = width.toFloat() - compassBackgroundImageView.width + paddingHeight = height.toFloat() - compassBackgroundImageView.height + } + } + + override fun reactToModelChanges() { + addReaction(widgetModel.compassWidgetState + .observeOn(SchedulerProvider.ui()) + .subscribe { compassWidgetState -> onCompassStateUpdated(compassWidgetState) }) + addReaction(widgetModel.productConnection + .observeOn(SchedulerProvider.ui()) + .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) + } + //endregion + + //region Reaction Helpers + private fun onCompassStateUpdated(compassWidgetState: CompassWidgetState) { + widgetStateDataProcessor.onNext(CompassStateUpdated(compassWidgetState)) + updateAircraftAttitudeUI(compassWidgetState.aircraftAttitude) + updateNorthHeadingUI(compassWidgetState.phoneAzimuth) + updateAircraftHeadingUI(compassWidgetState.phoneAzimuth, compassWidgetState.aircraftAttitude) + + val viewCoordinates = getAircraftLocationCoordinates(compassWidgetState.phoneAzimuth, compassWidgetState.aircraftState, compassWidgetState.currentLocationState) + updateAircraftLocationUI(getMaxDistance(compassWidgetState.aircraftState, compassWidgetState.currentLocationState), + calculateScale(compassWidgetState.aircraftState.distance), + viewCoordinates) + + updateGimbalHeadingUI(compassWidgetState.gimbalHeading, compassWidgetState.aircraftAttitude.yaw.toFloat() - compassWidgetState.phoneAzimuth) + + val secondViewCoordinates = getSecondGPSLocationCoordinates(compassWidgetState.phoneAzimuth, compassWidgetState.currentLocationState, compassWidgetState.aircraftState) + updateSecondGPSLocationUI(compassWidgetState.centerType, secondViewCoordinates) + } + //endregion + + //region Calculations + private fun getSecondGPSLocationCoordinates(phoneAzimuth: Float, + state: CurrentLocationState, + aircraftState: AircraftState): ViewCoordinates { + val radians = Math.toRadians(state.angle + phoneAzimuth.toDouble()) + val maxDistance = getMaxDistance(aircraftState, state) + val rcHomeDistance = state.distance + val x: Float + val y: Float + if (rcHomeDistance == maxDistance) { + x = cos(radians).toFloat() + y = sin(radians).toFloat() + } else { + x = (rcHomeDistance * cos(radians) / maxDistance).toFloat() + y = (rcHomeDistance * sin(radians) / maxDistance).toFloat() + } + return ViewCoordinates(x, y) + } + + private fun getMaxDistance(aircraftState: AircraftState, + state: CurrentLocationState): Float { + var maxDistance = aircraftState.distance + if (maxDistance < state.distance) { + maxDistance = state.distance + } + if (maxDistance < MAX_DISTANCE) { + maxDistance = MAX_DISTANCE.toFloat() + } + return maxDistance + } + + private fun getAircraftLocationCoordinates(phoneAzimuth: Float, + aircraftState: AircraftState, + state: CurrentLocationState): ViewCoordinates { + val maxDistance = getMaxDistance(aircraftState, state) + val radians = Math.toRadians(aircraftState.angle + phoneAzimuth.toDouble()) + val aircraftDistance = aircraftState.distance + val x: Float + val y: Float + if (aircraftDistance >= maxDistance) { + x = cos(radians).toFloat() + y = sin(radians).toFloat() + } else { + x = (aircraftDistance * cos(radians) / maxDistance).toFloat() + y = (aircraftDistance * sin(radians) / maxDistance).toFloat() + } + return ViewCoordinates(x, y) + } + + private fun calculateScale(distance: Float): Float { + var scale = 1.0f + if (distance >= MAX_SCALE_DISTANCE) { + scale = MIN_SCALE + } else if (distance > MAX_DISTANCE) { + scale = 1 - MIN_SCALE + (MAX_SCALE_DISTANCE - distance) / (MAX_SCALE_DISTANCE - MAX_DISTANCE) * MIN_SCALE + } + return scale + } + + //endregion + //region update UI + private fun updateNorthHeadingUI(phoneAzimuth: Float) { + // update north image + val northRadian = Math.toRadians((FULL_TURN - phoneAzimuth) % FULL_TURN.toDouble()) + val moveX = (halfAttitudeBallWidth + paddingWidth / 2 + halfAttitudeBallWidth * sin(northRadian)).toFloat() + val moveY = (halfAttitudeBallWidth + paddingHeight / 2 - halfAttitudeBallWidth * cos(northRadian)).toFloat() + northImageView.x = moveX - halfNorthIconWidth + northImageView.y = moveY - halfNorthIconWidth + } + + private fun updateAircraftAttitudeUI(aircraftAttitude: AircraftAttitude) { + //Update aircraft roll + aircraftAttitudeProgressBar.rotation = aircraftAttitude.roll.toFloat() + + //Update aircraft pitch + val tempPitch = (-aircraftAttitude.pitch).toFloat() + QUARTER_TURN + var progress = (tempPitch * 100 / HALF_TURN).toInt() + if (progress < MIN_PROGRESS) { + progress = MIN_PROGRESS + } else if (progress > MAX_PROGRESS) { + progress = MAX_PROGRESS + } + if (aircraftAttitudeProgressBar.progress != progress) { + aircraftAttitudeProgressBar.progress = progress + } + } + + private fun updateAircraftHeadingUI(phoneAzimuth: Float, + aircraftAttitude: AircraftAttitude) { + aircraftImageView.rotation = aircraftAttitude.yaw.toFloat() - phoneAzimuth + } + + private fun updateAircraftLocationUI(maxDistance: Float, scale: Float, viewCoordinates: ViewCoordinates) { + val wRadius = (measuredWidth - paddingWidth - aircraftImageView.width) / 2.0f + val hRadius = (measuredHeight - paddingHeight - aircraftImageView.height) / 2.0f + + //update the size and heading of the aircraft + aircraftImageView.x = paddingWidth / 2.0f + wRadius + viewCoordinates.x * wRadius + aircraftImageView.y = paddingHeight / 2.0f + hRadius - viewCoordinates.y * hRadius + aircraftImageView.scaleX = scale + aircraftImageView.scaleY = scale + + // Update the size and heading of the gimbal + gimbalYawImageView.x = (aircraftImageView.x + aircraftImageView.width / 2f + - gimbalYawImageView.width / 2f) + gimbalYawImageView.y = (aircraftImageView.y + aircraftImageView.height / 2f + - gimbalYawImageView.height) + gimbalYawImageView.scaleX = scale + gimbalYawImageView.scaleY = scale + + //update the compass view + visualCompassView.visibility = View.VISIBLE + innerCirclesImageView.visibility = View.GONE + visualCompassView.setDistance(maxDistance) + } + + private fun updateGimbalHeadingUI(gimbalHeading: Float, rotationOffset: Float) { + gimbalYawView.setYaw(gimbalHeading) + gimbalYawImageView.rotation = gimbalHeading + rotationOffset + } + + private fun updateSecondGPSLocationUI(type: CenterType, + viewCoordinates: ViewCoordinates) { + // Calculate the second GPS image's parameters using the center point's parameters + val centerGPSImage: ImageView? + val secondGPSImage: ImageView? + if (type == CenterType.HOME_GPS) { + centerGPSImage = homeImageView + secondGPSImage = rcImageView + } else { + centerGPSImage = rcImageView + secondGPSImage = homeImageView + } + centerGPSImage.visibility = View.VISIBLE + val centerParam = centerGPSImage.layoutParams as LayoutParams + centerParam.leftMargin = 0 + centerParam.topMargin = 0 + centerGPSImage.layoutParams = centerParam + + //Updating second GPS location and show the second GPS image if both exist + if (type != CenterType.HOME_GPS) { + secondGPSImage.visibility = View.VISIBLE + val wRadius = (measuredWidth - paddingWidth - secondGPSImage.width) / 2.0f + val hRadius = (measuredHeight - paddingHeight - secondGPSImage.height) / 2.0f + secondGPSImage.x = paddingWidth / 2.0f + wRadius + viewCoordinates.x * wRadius + secondGPSImage.y = paddingHeight / 2.0f + hRadius - viewCoordinates.y * hRadius + } else { + secondGPSImage.visibility = View.GONE + } + } + //endregion + + //region Customization + override fun getIdealDimensionRatioString(): String { + return getString(R.string.uxsdk_widget_compass_ratio) + } + + /** + * Get the index of the gimbal to which the widget is reacting + * + * @return [GimbalIndex] + */ + fun getGimbalIndex(): GimbalIndex? { + return widgetModel.getGimbalIndex() + } + + /** + * Set the index of gimbal to which the widget should react + * + * @param gimbalIndex index of the gimbal. + */ + fun setGimbalIndex(gimbalIndex: GimbalIndex?) { + if (!isInEditMode) { + widgetModel.setGimbalIndex(gimbalIndex) + } + } + + /** + * Set the resource ID for the home icon + * + * @param resourceId Integer ID of the drawable resource + */ + fun setHomeIcon(@DrawableRes resourceId: Int) { + homeImageView.setImageResource(resourceId) + } + + /** + * Set the resource ID for the home icon's background + * + * @param resourceId Integer ID of the icon's background resource + */ + fun setHomeIconBackground(@DrawableRes resourceId: Int) { + homeImageView.setBackgroundResource(resourceId) + } + + /** + * Set the resource ID for the RC location icon + * + * @param resourceId Integer ID of the drawable resource + */ + fun setRCLocationIcon(@DrawableRes resourceId: Int) { + rcImageView.setImageResource(resourceId) + } + + /** + * Set the resource ID for the RC location icon's background + * + * @param resourceId Integer ID of the icon's background resource + */ + fun setRCLocationIconBackground(@DrawableRes resourceId: Int) { + rcImageView.setBackgroundResource(resourceId) + } + + /** + * Set the resource ID for the aircraft icon + * + * @param resourceId Integer ID of the drawable resource + */ + fun setAircraftIcon(@DrawableRes resourceId: Int) { + aircraftImageView.setImageResource(resourceId) + } + + /** + * Set the resource ID for the aircraft icon's background + * + * @param resourceId Integer ID of the icon's background resource + */ + fun setAircraftIconBackground(@DrawableRes resourceId: Int) { + aircraftImageView.setBackgroundResource(resourceId) + } + + /** + * Set the resource ID for the gimbal yaw icon + * + * @param resourceId Integer ID of the drawable resource + */ + fun setGimbalYawIcon(@DrawableRes resourceId: Int) { + gimbalYawImageView.setImageResource(resourceId) + } + + /** + * Set the resource ID for the gimbal yaw icon's background + * + * @param resourceId Integer ID of the icon's background resource + */ + fun setGimbalYawIconBackground(@DrawableRes resourceId: Int) { + gimbalYawImageView.setBackgroundResource(resourceId) + } + + /** + * Set the resource ID for the north icon + * + * @param resourceId Integer ID of the drawable resource + */ + fun setNorthIcon(@DrawableRes resourceId: Int) { + northImageView.setImageResource(resourceId) + } + + /** + * Set the resource ID for the north icon's background + * + * @param resourceId Integer ID of the icon's background resource + */ + fun setNorthIconBackground(@DrawableRes resourceId: Int) { + northImageView.setBackgroundResource(resourceId) + } + + /** + * Set the resource ID for the inner circles icon + * + * @param resourceId Integer ID of the drawable resource + */ + fun setInnerCirclesIcon(@DrawableRes resourceId: Int) { + innerCirclesImageView.setImageResource(resourceId) + } + + /** + * Set the resource ID for the inner circles icon's background + * + * @param resourceId Integer ID of the icon's background resource + */ + fun setInnerCirclesIconBackground(@DrawableRes resourceId: Int) { + innerCirclesImageView.setBackgroundResource(resourceId) + } + + /** + * Set the resource ID for the compass background icon + * + * @param resourceId Integer ID of the drawable resource + */ + fun setCompassBackgroundIcon(@DrawableRes resourceId: Int) { + compassBackgroundImageView.setImageResource(resourceId) + } + + /** + * Set the resource ID for the compass background icon's background + * + * @param resourceId Integer ID of the icon's background resource + */ + fun setCompassBackgroundIconBackground(@DrawableRes resourceId: Int) { + compassBackgroundImageView.setBackgroundResource(resourceId) + } + + /** + * Set the resource ID for the aircraft attitude icon's background + * + * @param resourceId Integer ID of the icon's background resource + */ + fun setAircraftAttitudeIconBackground(@DrawableRes resourceId: Int) { + aircraftAttitudeProgressBar.setBackgroundResource(resourceId) + } + //endregion + + //region Customization Helpers + @SuppressLint("Recycle") + private fun initAttributes(context: Context, attrs: AttributeSet) { + context.obtainStyledAttributes(attrs, R.styleable.CompassWidget).use { typedArray -> + typedArray.getIntAndUse(R.styleable.CompassWidget_uxsdk_gimbalIndex) { + setGimbalIndex(GimbalIndex.find(it)) + } + typedArray.getDrawableAndUse(R.styleable.CompassWidget_uxsdk_homeIcon) { + homeIcon = it + } + typedArray.getDrawableAndUse(R.styleable.CompassWidget_uxsdk_rcLocationIcon) { + rcLocationIcon = it + } + typedArray.getDrawableAndUse(R.styleable.CompassWidget_uxsdk_aircraftIcon) { + aircraftIcon = it + } + typedArray.getDrawableAndUse(R.styleable.CompassWidget_uxsdk_gimbalYawIcon) { + gimbalYawIcon = it + } + typedArray.getDrawableAndUse(R.styleable.CompassWidget_uxsdk_northIcon) { + northIcon = it + } + typedArray.getDrawableAndUse(R.styleable.CompassWidget_uxsdk_innerCirclesIcon) { + innerCirclesIcon = it + } + typedArray.getDrawableAndUse(R.styleable.CompassWidget_uxsdk_compassBackgroundIcon) { + compassBackgroundIcon = it + } + typedArray.getDrawableAndUse(R.styleable.CompassWidget_uxsdk_aircraftAttitudeIcon) { + aircraftAttitudeIcon = it + } + typedArray.getDimensionAndUse(R.styleable.CompassWidget_uxsdk_visualCompassViewStrokeWidth) { + visualCompassViewStrokeWidth = it + } + typedArray.getColorAndUse(R.styleable.CompassWidget_uxsdk_visualCompassViewLineColor) { + visualCompassViewLineColor = it + } + typedArray.getIntegerAndUse(R.styleable.CompassWidget_uxsdk_visualCompassViewLineInterval) { + visualCompassViewLineInterval = it + } + typedArray.getIntegerAndUse(R.styleable.CompassWidget_uxsdk_visualCompassViewNumberOfLines) { + visualCompassViewNumberOfLines = it + } + typedArray.getDimensionAndUse(R.styleable.CompassWidget_uxsdk_gimbalYawViewStrokeWidth) { + gimbalYawViewStrokeWidth = it + } + typedArray.getColorAndUse(R.styleable.CompassWidget_uxsdk_gimbalYawViewYawColor) { + gimbalYawViewYawColor = it + } + typedArray.getColorAndUse(R.styleable.CompassWidget_uxsdk_gimbalYawViewInvalidColor) { + gimbalYawViewInvalidColor = it + } + typedArray.getColorAndUse(R.styleable.CompassWidget_uxsdk_gimbalYawViewBlinkColor) { + gimbalYawViewBlinkColor = it + } + } + } + //endregion + + //region Classes + /** + * Wrapper that holds the x and y values of the view coordinates + */ + private inner class ViewCoordinates internal constructor(val x: Float, val y: Float) + //endregion + + //region Hooks + /** + * Get the [ModelState] updates + */ + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { + return super.getWidgetStateUpdate() + } + + /** + * Class defines the widget state updates + */ + sealed class ModelState { + /** + * Product connection update + */ + data class ProductConnected(val isConnected: Boolean) : ModelState() + + /** + * Compass state update + */ + data class CompassStateUpdated(val compassWidgetState: CompassWidgetState) : ModelState() + } + //endregion +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/CompassWidgetModel.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/CompassWidgetModel.java deleted file mode 100644 index f407187b..00000000 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/CompassWidgetModel.java +++ /dev/null @@ -1,531 +0,0 @@ -/* - * 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.ux.beta.core.widget.compass; - -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; -import android.location.Location; -import android.location.LocationListener; -import android.os.Bundle; -import android.view.Surface; -import android.view.WindowManager; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import dji.common.remotecontroller.GPSData; -import dji.keysdk.DJIKey; -import dji.keysdk.FlightControllerKey; -import dji.keysdk.GimbalKey; -import dji.keysdk.RemoteControllerKey; -import dji.thirdparty.io.reactivex.Flowable; -import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; -import dji.ux.beta.core.util.DataProcessor; -import dji.ux.beta.core.util.LocationUtil; -import dji.ux.beta.core.util.MathUtil; -import dji.ux.beta.core.util.MobileGPSLocationUtil; - -/** - * Widget Model for the {@link CompassWidget} used to define - * the underlying logic and communication - */ -public class CompassWidgetModel extends WidgetModel implements SensorEventListener, LocationListener { - - //region Fields - private static final int SENSOR_SENSITIVE_PARAM = 2; - private static final int HALF_TURN = 180; - private static final int QUARTER_TURN = 90; - - private final DataProcessor attitudePitchProcessor; - private final DataProcessor attitudeRollProcessor; - private final DataProcessor attitudeYawProcessor; - private final DataProcessor homeLocationLatitudeProcessor; - private final DataProcessor homeLocationLongitudeProcessor; - private final DataProcessor aircraftLocationLatitudeProcessor; - private final DataProcessor aircraftLocationLongitudeProcessor; - private final DataProcessor rcGPSDataProcessor; - private final DataProcessor gimbalYawProcessor; - private final DataProcessor centerTypeProcessor; - private final DataProcessor mobileDeviceAzimuthProcessor; - private final DataProcessor aircraftAttitudeProcessor; - private final DataProcessor aircraftStateProcessor; - private final DataProcessor currentLocationStateProcessor; - - private CenterType centerType = CenterType.HOME_GPS; - private AircraftAttitude latestAircraftAttitude; - private MobileGPSLocationUtil mobileGPSLocationUtil = null; - private SensorManager sensorManager; - private WindowManager windowManager; - private Sensor rotationVector; - private double rcOrMobileLatitude; - private double rcOrMobileLongitude; - private double aircraftLatitude; - private double aircraftLongitude; - private double homeLatitude; - private double homeLongitude; - private float latestSensorValue; - - /** - * values[0]: azimuth, rotation around the Z axis. - * values[1]: pitch, rotation around the X axis. - * values[2]: roll, rotation around the Y axis. - */ - private float[] values = new float[3]; - private float[] rotations = new float[9]; - //endregion - - //region Constructor - public CompassWidgetModel(@NonNull DJISDKModel djiSdkModel, - @NonNull ObservableInMemoryKeyedStore keyedStore, - @Nullable SensorManager sensorManager, - @Nullable WindowManager windowManager) { - super(djiSdkModel, keyedStore); - this.sensorManager = sensorManager; - this.windowManager = windowManager; - attitudePitchProcessor = DataProcessor.create(0f); - attitudeRollProcessor = DataProcessor.create(0f); - attitudeYawProcessor = DataProcessor.create(0f); - homeLocationLatitudeProcessor = DataProcessor.create(0.0); - homeLocationLongitudeProcessor = DataProcessor.create(0.0); - aircraftLocationLatitudeProcessor = DataProcessor.create(0.0); - aircraftLocationLongitudeProcessor = DataProcessor.create(0.0); - rcGPSDataProcessor = DataProcessor.create(new GPSData.Builder().build()); - gimbalYawProcessor = DataProcessor.create(0f); - - centerTypeProcessor = DataProcessor.create(CenterType.HOME_GPS); - mobileDeviceAzimuthProcessor = DataProcessor.create(0f); - latestAircraftAttitude = new AircraftAttitude(0f, 0f, 0f); - aircraftAttitudeProcessor = DataProcessor.create(latestAircraftAttitude); - aircraftStateProcessor = DataProcessor.create(new AircraftState(0f, 0f)); - currentLocationStateProcessor = DataProcessor.create(new CurrentLocationState(0f, 0f)); - } - //endregion - - //region Data - - /** - * Get the center type for the compass widget - * - * @return CenterType enum value - */ - public Flowable getCenterType() { - return centerTypeProcessor.toFlowable(); - } - - /** - * Get the mobile device's azimuth - * - * @return Float value representing the azimuth - */ - public Flowable getMobileDeviceAzimuth() { - return mobileDeviceAzimuthProcessor.toFlowable(); - } - - /** - * Get the state of the aircraft - * - * @return {@link AircraftState} value for distance and angle of the aircraft - */ - public Flowable getAircraftState() { - return aircraftStateProcessor.toFlowable(); - } - - /** - * Get the aircraft's attitude (roll, pitch and yaw) - * - * @return {@link AircraftAttitude} value for the attitude - */ - public Flowable getAircraftAttitude() { - return aircraftAttitudeProcessor.toFlowable(); - } - - /** - * Get the state of the current location of the aircraft. - * - * @return {@link CurrentLocationState} value for distance and angle of current location - */ - public Flowable getCurrentLocationState() { - return currentLocationStateProcessor.toFlowable(); - } - - /** - * Get the aircraft gimbal's heading - * - * @return Float value representing the gimbal's yaw position - */ - public Flowable getGimbalHeading() { - return gimbalYawProcessor.toFlowable(); - } - //endregion - - //region Lifecycle - @Override - protected void inSetup() { - DJIKey attitudePitchKey = FlightControllerKey.create(FlightControllerKey.ATTITUDE_PITCH); - DJIKey attitudeRollKey = FlightControllerKey.create(FlightControllerKey.ATTITUDE_ROLL); - DJIKey attitudeYawKey = FlightControllerKey.create(FlightControllerKey.ATTITUDE_YAW); - DJIKey homeLatitudeKey = FlightControllerKey.create(FlightControllerKey.HOME_LOCATION_LATITUDE); - DJIKey homeLongitudeKey = FlightControllerKey.create(FlightControllerKey.HOME_LOCATION_LONGITUDE); - DJIKey aircraftLatitudeKey = FlightControllerKey.create(FlightControllerKey.AIRCRAFT_LOCATION_LATITUDE); - DJIKey aircraftLongitudeKey = FlightControllerKey.create(FlightControllerKey.AIRCRAFT_LOCATION_LONGITUDE); - DJIKey rcGpsDataKey = RemoteControllerKey.create(RemoteControllerKey.GPS_DATA); - DJIKey gimbalYawKey = GimbalKey.create(GimbalKey.YAW_ANGLE_WITH_AIRCRAFT_IN_DEGREE); - - // Set AircraftAttitude using roll, pitch and yaw keys - bindDataProcessor(attitudePitchKey, - attitudePitchProcessor, - pitch -> latestAircraftAttitude.setPitch((double) pitch)); - bindDataProcessor(attitudeRollKey, - attitudeRollProcessor, - roll -> latestAircraftAttitude.setRoll((double) roll)); - bindDataProcessor(attitudeYawKey, attitudeYawProcessor, yaw -> latestAircraftAttitude.setYaw((double) yaw)); - - // Set the home location when changed and update the various calculations - bindDataProcessor(homeLatitudeKey, homeLocationLatitudeProcessor, latitude -> { - homeLatitude = (double) latitude; - updateCalculations(); - }); - bindDataProcessor(homeLongitudeKey, homeLocationLongitudeProcessor, longitude -> { - homeLongitude = (double) longitude; - updateCalculations(); - }); - - // Update the aircraft's location - bindDataProcessor(aircraftLatitudeKey, - aircraftLocationLatitudeProcessor, - latitude -> updateAircraftLatitude((double) latitude)); - bindDataProcessor(aircraftLongitudeKey, - aircraftLocationLongitudeProcessor, - longitude -> updateAircraftLongitude((double) longitude)); - - // Update the RC's location - bindDataProcessor(rcGpsDataKey, rcGPSDataProcessor, gpsData -> updateGPSData((GPSData) gpsData)); - - // Update the gimbal heading - bindDataProcessor(gimbalYawKey, gimbalYawProcessor); - - registerMobileDeviceSensorListener(); - - // Start mobile device's location updates if available - if (mobileGPSLocationUtil != null) { - mobileGPSLocationUtil.startUpdateLocation(); - } - } - - @Override - protected void inCleanup() { - unregisterMobileDeviceSensorListener(); - - // Stop mobile device's location updates if available - if (mobileGPSLocationUtil != null) { - mobileGPSLocationUtil.stopUpdateLocation(); - } - } - //endregion - - //region Updates - @Override - protected void updateStates() { - aircraftAttitudeProcessor.onNext(latestAircraftAttitude); - } - //endregion - - //region Mobile Device Sensor listener - private void registerMobileDeviceSensorListener() { - if (sensorManager != null) { - // Register the mobile device's rotation sensor to start listening for updates - rotationVector = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); - if (rotationVector != null) { - sensorManager.registerListener(this, rotationVector, SensorManager.SENSOR_DELAY_UI); - } - } - } - - private void unregisterMobileDeviceSensorListener() { - sensorManager.unregisterListener(this, rotationVector); - } - - @Override - public void onSensorChanged(SensorEvent event) { - // Update the mobile device azimuth when updated by the sensor - float sensorValue = latestSensorValue; - if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) { - MathUtil.getRotationMatrixFromVector(rotations, event.values); - SensorManager.getOrientation(rotations, values); - sensorValue = (float) Math.toDegrees(values[0]); - } - - if (Math.abs(sensorValue - latestSensorValue) > SENSOR_SENSITIVE_PARAM) { - latestSensorValue = sensorValue; - if (windowManager != null && windowManager.getDefaultDisplay().getRotation() == Surface.ROTATION_270) { - sensorValue += HALF_TURN; - } - float mobileDeviceAzimuth = sensorValue + QUARTER_TURN; - mobileDeviceAzimuthProcessor.onNext(mobileDeviceAzimuth); - } - } - - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { - // Do nothing - } - - @Override - public void onLocationChanged(Location location) { - if (location != null) { - // Update the center type to be the RC/Mobile device type - centerType = CenterType.RC_MOBILE_GPS; - centerTypeProcessor.onNext(centerType); - // Update location using received location of the mobile device - rcOrMobileLatitude = location.getLatitude(); - rcOrMobileLongitude = location.getLongitude(); - - updateCalculations(); - } - } - - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { - // Do nothing - } - - @Override - public void onProviderEnabled(String provider) { - // Do nothing - } - - @Override - public void onProviderDisabled(String provider) { - // Do nothing - } - //endregion - - //region Helpers - - /** - * Initialize the MobileGPSLocationUtil class that has the `startUpdateLocation()` - * and `stopUpdateLocation()` functions for the mobile device's location - * - * @param mobileGPSLocationUtil Instance of the MobileGPSLocationUtil class - */ - public void setMobileGPSLocationUtil(@NonNull MobileGPSLocationUtil mobileGPSLocationUtil) { - this.mobileGPSLocationUtil = mobileGPSLocationUtil; - } - - private void updateGPSData(@NonNull GPSData data) { - if (data.isValid()) { - // Update the center type to be the RC/Mobile device type - centerType = CenterType.RC_MOBILE_GPS; - centerTypeProcessor.onNext(centerType); - - // Update location using received location of the RC - rcOrMobileLatitude = data.getLocation().getLatitude(); - rcOrMobileLongitude = data.getLocation().getLongitude(); - - // Stop updating mobile device location once RC location is received - mobileGPSLocationUtil.stopUpdateLocation(); - - updateCalculations(); - } - } - - private void updateAircraftLatitude(double latitude) { - if (LocationUtil.checkLatitude(latitude)) { - aircraftLatitude = latitude; - calculateAircraftAngleAndDistanceFromCenterLocation(); - } - } - - private void updateAircraftLongitude(double longitude) { - if (LocationUtil.checkLongitude(longitude)) { - aircraftLongitude = longitude; - calculateAircraftAngleAndDistanceFromCenterLocation(); - } - } - - private void updateCalculations() { - calculateAircraftAngleAndDistanceFromCenterLocation(); - calculateAngleAndDistanceBetweenRCAndHome(); - } - - private void calculateAircraftAngleAndDistanceFromCenterLocation() { - float[] tempCalculatedLocation; - AircraftState latestAircraftState = new AircraftState(0.0f, 0.0f); - if (centerType == CenterType.HOME_GPS) { - if (LocationUtil.checkLatitude(homeLatitude) && LocationUtil.checkLongitude(homeLongitude)) { - tempCalculatedLocation = LocationUtil.calculateAngleAndDistance(homeLatitude, - homeLongitude, - aircraftLatitude, - aircraftLongitude); - latestAircraftState.setAngle(tempCalculatedLocation[0]); - latestAircraftState.setDistance(tempCalculatedLocation[1]); - aircraftStateProcessor.onNext(latestAircraftState); - } - } else if (centerType == CenterType.RC_MOBILE_GPS) { - if (LocationUtil.checkLatitude(rcOrMobileLatitude) && LocationUtil.checkLongitude(rcOrMobileLongitude)) { - tempCalculatedLocation = LocationUtil.calculateAngleAndDistance(rcOrMobileLatitude, - rcOrMobileLongitude, - aircraftLatitude, - aircraftLongitude); - latestAircraftState.setAngle(tempCalculatedLocation[0]); - latestAircraftState.setDistance(tempCalculatedLocation[1]); - aircraftStateProcessor.onNext(latestAircraftState); - } - } - } - - private void calculateAngleAndDistanceBetweenRCAndHome() { - if (centerType != CenterType.HOME_GPS) { - float[] tempCalculatedLocation = LocationUtil.calculateAngleAndDistance(rcOrMobileLatitude, - rcOrMobileLongitude, - homeLatitude, - homeLongitude); - CurrentLocationState latestCurrentLocationState = new CurrentLocationState(0.0f, 0.0f); - latestCurrentLocationState.setAngle(tempCalculatedLocation[0]); - latestCurrentLocationState.setDistance(tempCalculatedLocation[1]); - currentLocationStateProcessor.onNext(latestCurrentLocationState); - } - } - //endregion - - /** - * Enum for the center type used in the calculations - */ - public enum CenterType { - /** - * The center is determined by RC location data or mobile device - * location data - */ - RC_MOBILE_GPS, - - /** - * The center is determined by the home location's data - */ - HOME_GPS - } - - /** - * Class that holds the aircraft's attitude with getters and setters - * for the roll, pitch and yaw of the aircraft - */ - public class AircraftAttitude { - private double roll; - private double pitch; - private double yaw; - - public AircraftAttitude(double roll, double pitch, double yaw) { - this.roll = roll; - this.pitch = pitch; - this.yaw = yaw; - } - - public double getRoll() { - return roll; - } - - public void setRoll(double roll) { - this.roll = roll; - } - - public double getPitch() { - return pitch; - } - - public void setPitch(double pitch) { - this.pitch = pitch; - } - - public double getYaw() { - return yaw; - } - - public void setYaw(double yaw) { - this.yaw = yaw; - } - } - - /** - * Class that holds the angle and distance between the aircraft and the - * home/RC/Mobile device's location. - */ - public class AircraftState { - private float angle; - private float distance; - - public AircraftState(float angle, float distance) { - this.angle = angle; - this.distance = distance; - } - - public float getAngle() { - return angle; - } - - public void setAngle(float angle) { - this.angle = angle; - } - - public float getDistance() { - return distance; - } - - public void setDistance(float distance) { - this.distance = distance; - } - } - - /** - * Class that holds the angle and distance between current home and RC/Mobile device - * locations - */ - public class CurrentLocationState { - private float angle; - private float distance; - - public CurrentLocationState(float angle, float distance) { - this.angle = angle; - this.distance = distance; - } - - public float getAngle() { - return angle; - } - - public void setAngle(float angle) { - this.angle = angle; - } - - public float getDistance() { - return distance; - } - - public void setDistance(float distance) { - this.distance = distance; - } - } -} diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/CompassWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/CompassWidgetModel.kt new file mode 100644 index 00000000..674c6d47 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/CompassWidgetModel.kt @@ -0,0 +1,425 @@ +/* + * 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.ux.beta.core.widget.compass + +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener +import android.hardware.SensorManager +import android.location.Location +import android.location.LocationListener +import android.os.Bundle +import android.view.Surface +import android.view.WindowManager +import dji.common.remotecontroller.GPSData +import dji.keysdk.DJIKey +import dji.keysdk.FlightControllerKey +import dji.keysdk.GimbalKey +import dji.keysdk.RemoteControllerKey +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.WidgetModel +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.util.* +import dji.ux.beta.core.util.SettingDefinitions.GimbalIndex +import kotlin.math.abs + +private const val SENSOR_SENSITIVE_PARAM = 2 +private const val HALF_TURN = 180 +private const val QUARTER_TURN = 90 + +/** + * Widget Model for the [CompassWidget] used to define + * the underlying logic and communication + */ +class CompassWidgetModel(djiSdkModel: DJISDKModel, + keyedStore: ObservableInMemoryKeyedStore, + private val sensorManager: SensorManager?, + private val windowManager: WindowManager? +) : WidgetModel(djiSdkModel, keyedStore), SensorEventListener, LocationListener { + + //region Fields + private val attitudePitchProcessor: DataProcessor = DataProcessor.create(0f) + private val attitudeRollProcessor: DataProcessor = DataProcessor.create(0f) + private val attitudeYawProcessor: DataProcessor = DataProcessor.create(0f) + private val homeLocationLatitudeProcessor: DataProcessor = DataProcessor.create(0.0) + private val homeLocationLongitudeProcessor: DataProcessor = DataProcessor.create(0.0) + private val aircraftLocationLatitudeProcessor: DataProcessor = DataProcessor.create(0.0) + private val aircraftLocationLongitudeProcessor: DataProcessor = DataProcessor.create(0.0) + private val rcGPSDataProcessor: DataProcessor = DataProcessor.create(GPSData.Builder().build()) + private val gimbalYawProcessor: DataProcessor = DataProcessor.create(0f) + private val centerTypeProcessor: DataProcessor = DataProcessor.create(CenterType.HOME_GPS) + private val mobileDeviceAzimuthProcessor: DataProcessor = DataProcessor.create(0f) + + private val aircraftAttitudeProcessor: DataProcessor = DataProcessor.create(AircraftAttitude(0.0, 0.0, 0.0)) + private val aircraftStateProcessor: DataProcessor = DataProcessor.create(AircraftState(0f, 0f)) + private val currentLocationStateProcessor: DataProcessor = DataProcessor.create(CurrentLocationState(0f, 0f)) + + private val compassWidgetStateProcessor: DataProcessor = DataProcessor.create( + CompassWidgetState(0f, + AircraftAttitude(0.0, 0.0, 0.0), + AircraftState(0f, 0f), + CurrentLocationState(0f, 0f), + 0f, + CenterType.HOME_GPS)) + + private var rotationVector: Sensor? = null + private var rcOrMobileLatitude = 0.0 + private var rcOrMobileLongitude = 0.0 + private var aircraftLatitude = 0.0 + private var aircraftLongitude = 0.0 + private var homeLatitude = 0.0 + private var homeLongitude = 0.0 + private var latestSensorValue = 0f + private var gimbalIndex = GimbalIndex.PORT.index + + /** + * values[0]: azimuth, rotation around the Z axis. + * values[1]: pitch, rotation around the X axis. + * values[2]: roll, rotation around the Y axis. + */ + private val values = FloatArray(3) + private val rotations = FloatArray(9) + + /** + * The MobileGPSLocationUtil class that has the `startUpdateLocation()` + * and `stopUpdateLocation()` functions for the mobile device's location + */ + var mobileGPSLocationUtil: MobileGPSLocationUtil? = null + + /** + * The state of the compass widget + */ + val compassWidgetState: Flowable + get() = compassWidgetStateProcessor.toFlowable() + + //endregion + + //region Lifecycle + override fun inSetup() { + val attitudePitchKey: DJIKey = FlightControllerKey.create(FlightControllerKey.ATTITUDE_PITCH) + val attitudeRollKey: DJIKey = FlightControllerKey.create(FlightControllerKey.ATTITUDE_ROLL) + val attitudeYawKey: DJIKey = FlightControllerKey.create(FlightControllerKey.ATTITUDE_YAW) + val homeLatitudeKey: DJIKey = FlightControllerKey.create(FlightControllerKey.HOME_LOCATION_LATITUDE) + val homeLongitudeKey: DJIKey = FlightControllerKey.create(FlightControllerKey.HOME_LOCATION_LONGITUDE) + val aircraftLatitudeKey: DJIKey = FlightControllerKey.create(FlightControllerKey.AIRCRAFT_LOCATION_LATITUDE) + val aircraftLongitudeKey: DJIKey = FlightControllerKey.create(FlightControllerKey.AIRCRAFT_LOCATION_LONGITUDE) + val rcGpsDataKey: DJIKey = RemoteControllerKey.create(RemoteControllerKey.GPS_DATA) + val gimbalYawKey: DJIKey = GimbalKey.create(GimbalKey.YAW_ANGLE_WITH_AIRCRAFT_IN_DEGREE, gimbalIndex) + + // Set AircraftAttitude using roll, pitch and yaw keys + bindDataProcessor(attitudePitchKey, attitudePitchProcessor) { pitch: Any -> + aircraftAttitudeProcessor.onNext(AircraftAttitude(aircraftAttitudeProcessor.value.roll, + pitch as Double, + aircraftAttitudeProcessor.value.yaw)) + } + bindDataProcessor(attitudeRollKey, attitudeRollProcessor) { roll: Any -> + aircraftAttitudeProcessor.onNext(AircraftAttitude(roll as Double, + aircraftAttitudeProcessor.value.pitch, + aircraftAttitudeProcessor.value.yaw)) + } + bindDataProcessor(attitudeYawKey, attitudeYawProcessor) { yaw: Any -> + aircraftAttitudeProcessor.onNext(AircraftAttitude(aircraftAttitudeProcessor.value.roll, + aircraftAttitudeProcessor.value.pitch, + yaw as Double)) + } + + // Set the home location when changed and update the various calculations + bindDataProcessor(homeLatitudeKey, homeLocationLatitudeProcessor) { latitude: Any -> + homeLatitude = latitude as Double + updateCalculations() + } + bindDataProcessor(homeLongitudeKey, homeLocationLongitudeProcessor) { longitude: Any -> + homeLongitude = longitude as Double + updateCalculations() + } + + // Update the aircraft's location + bindDataProcessor(aircraftLatitudeKey, aircraftLocationLatitudeProcessor) { latitude: Any -> + updateAircraftLatitude(latitude as Double) + } + bindDataProcessor(aircraftLongitudeKey, aircraftLocationLongitudeProcessor) { longitude: Any -> + updateAircraftLongitude(longitude as Double) + } + + // Update the RC's location + bindDataProcessor(rcGpsDataKey, rcGPSDataProcessor) { gpsData: Any -> + updateGPSData(gpsData as GPSData) + } + + // Update the gimbal heading + bindDataProcessor(gimbalYawKey, gimbalYawProcessor) + registerMobileDeviceSensorListener() + + // Start mobile device's location updates if available + mobileGPSLocationUtil?.startUpdateLocation() + } + + override fun inCleanup() { + unregisterMobileDeviceSensorListener() + + // Stop mobile device's location updates if available + mobileGPSLocationUtil?.stopUpdateLocation() + } + //endregion + + //region Updates + override fun updateStates() { + compassWidgetStateProcessor.onNext(CompassWidgetState( + mobileDeviceAzimuthProcessor.value, + aircraftAttitudeProcessor.value, + aircraftStateProcessor.value, + currentLocationStateProcessor.value, + gimbalYawProcessor.value, + centerTypeProcessor.value)) + } + //endregion + + //region Mobile Device Sensor listener + private fun registerMobileDeviceSensorListener() { + if (sensorManager != null) { + // Register the mobile device's rotation sensor to start listening for updates + // DJI devices cannot get rotation from TYPE_ROTATION_VECTOR, so use TYPE_ORIENTATION + rotationVector = if (DJIDeviceUtil.isDJIDevice()) { + sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION) + } else { + sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR) + } + if (rotationVector != null) { + sensorManager.registerListener(this, rotationVector, SensorManager.SENSOR_DELAY_UI) + } + } + } + + private fun unregisterMobileDeviceSensorListener() { + sensorManager?.unregisterListener(this, rotationVector) + } + + override fun onSensorChanged(event: SensorEvent) { + // Update the mobile device azimuth when updated by the sensor + var sensorValue = latestSensorValue + if (event.sensor.type == Sensor.TYPE_ORIENTATION) { + sensorValue = event.values[0] + } else if (event.sensor.type == Sensor.TYPE_ROTATION_VECTOR) { + MathUtil.getRotationMatrixFromVector(rotations, event.values) + SensorManager.getOrientation(rotations, values) + sensorValue = Math.toDegrees(values[0].toDouble()).toFloat() + } + if (abs(sensorValue - latestSensorValue) > SENSOR_SENSITIVE_PARAM) { + latestSensorValue = sensorValue + val rotation: Int = getDisplayRotation() + if (rotation == Surface.ROTATION_270) { + sensorValue += HALF_TURN.toFloat() + } + if (DJIDeviceUtil.isSmartController()) { + sensorValue += QUARTER_TURN.toFloat() + } + val mobileDeviceAzimuth = sensorValue + QUARTER_TURN + mobileDeviceAzimuthProcessor.onNext(mobileDeviceAzimuth) + } + updateStates() + } + + override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) { + // Do nothing + } + + override fun onLocationChanged(location: Location?) { + if (location != null) { + // Update the center type to be the RC/Mobile device type + centerTypeProcessor.onNext(CenterType.RC_MOBILE_GPS) + // Update location using received location of the mobile device + rcOrMobileLatitude = location.latitude + rcOrMobileLongitude = location.longitude + updateCalculations() + updateStates() + } + } + + override fun onStatusChanged(provider: String, status: Int, extras: Bundle) { + // Do nothing + } + + override fun onProviderEnabled(provider: String) { + // Do nothing + } + + override fun onProviderDisabled(provider: String) { + // Do nothing + } + //endregion + + //region Helpers + private fun updateGPSData(data: GPSData) { + if (data.isValid) { + // Update the center type to be the RC/Mobile device type + centerTypeProcessor.onNext(CenterType.RC_MOBILE_GPS) + + // Update location using received location of the RC + rcOrMobileLatitude = data.location.latitude + rcOrMobileLongitude = data.location.longitude + + // Stop updating mobile device location once RC location is received + mobileGPSLocationUtil?.stopUpdateLocation() + updateCalculations() + } + } + + private fun updateAircraftLatitude(latitude: Double) { + if (LocationUtil.checkLatitude(latitude)) { + aircraftLatitude = latitude + calculateAircraftAngleAndDistanceFromCenterLocation() + } + } + + private fun updateAircraftLongitude(longitude: Double) { + if (LocationUtil.checkLongitude(longitude)) { + aircraftLongitude = longitude + calculateAircraftAngleAndDistanceFromCenterLocation() + } + } + + private fun updateCalculations() { + calculateAircraftAngleAndDistanceFromCenterLocation() + calculateAngleAndDistanceBetweenRCAndHome() + } + + private fun calculateAircraftAngleAndDistanceFromCenterLocation() { + val tempCalculatedLocation: FloatArray + val latestAircraftState = AircraftState(0.0f, 0.0f) + if (centerTypeProcessor.value == CenterType.HOME_GPS) { + if (LocationUtil.checkLatitude(homeLatitude) && LocationUtil.checkLongitude(homeLongitude)) { + tempCalculatedLocation = LocationUtil.calculateAngleAndDistance(homeLatitude, + homeLongitude, + aircraftLatitude, + aircraftLongitude) + latestAircraftState.angle = tempCalculatedLocation[0] + latestAircraftState.distance = tempCalculatedLocation[1] + aircraftStateProcessor.onNext(latestAircraftState) + } + } else if (centerTypeProcessor.value == CenterType.RC_MOBILE_GPS) { + if (LocationUtil.checkLatitude(rcOrMobileLatitude) && LocationUtil.checkLongitude(rcOrMobileLongitude)) { + tempCalculatedLocation = LocationUtil.calculateAngleAndDistance(rcOrMobileLatitude, + rcOrMobileLongitude, + aircraftLatitude, + aircraftLongitude) + latestAircraftState.angle = tempCalculatedLocation[0] + latestAircraftState.distance = tempCalculatedLocation[1] + aircraftStateProcessor.onNext(latestAircraftState) + } + } + } + + private fun calculateAngleAndDistanceBetweenRCAndHome() { + if (centerTypeProcessor.value != CenterType.HOME_GPS) { + val tempCalculatedLocation = LocationUtil.calculateAngleAndDistance(rcOrMobileLatitude, + rcOrMobileLongitude, + homeLatitude, + homeLongitude) + val latestCurrentLocationState = CurrentLocationState(0.0f, 0.0f) + latestCurrentLocationState.angle = tempCalculatedLocation[0] + latestCurrentLocationState.distance = tempCalculatedLocation[1] + currentLocationStateProcessor.onNext(latestCurrentLocationState) + } + } + + private fun getDisplayRotation(): Int { + var rotation = 0 + if (windowManager != null) { + rotation = windowManager.defaultDisplay.rotation + } + if (DJIDeviceUtil.isDJIDevice()) { + rotation = (rotation + 1) % 4 // DJI device default offset is 90, which is modified to support the turn screen function. + } + return rotation + } + //endregion + + //region Customization + /** + * Get the gimbal index for which the model is reacting. + * + * @return current gimbal index. + */ + fun getGimbalIndex(): GimbalIndex? { + return GimbalIndex.find(gimbalIndex) + } + + /** + * Set gimbal index to which the model should react. + * + * @param gimbalIndex index of the gimbal. + */ + fun setGimbalIndex(gimbalIndex: GimbalIndex?) { + if (gimbalIndex != null) { + this.gimbalIndex = gimbalIndex.index + } + restart() + } + //endregion + + //region Classes + /** + * Enum for the center type used in the calculations + */ + enum class CenterType { + /** + * The center is determined by RC location data or mobile device + * location data + */ + RC_MOBILE_GPS, + + /** + * The center is determined by the home location's data + */ + HOME_GPS + } + + /** + * Class that holds the aircraft's attitude with getters and setters + * for the [roll], [pitch] and [yaw] of the aircraft + */ + data class AircraftAttitude(var roll: Double, var pitch: Double, var yaw: Double) + + /** + * Class that holds the [angle] and [distance] between the aircraft and the + * home/RC/Mobile device's location. + */ + data class AircraftState(var angle: Float, var distance: Float) + + /** + * Class that holds the [angle] and [distance] between current home and RC/Mobile device + * locations + */ + data class CurrentLocationState(var angle: Float, var distance: Float) + + data class CompassWidgetState( + var phoneAzimuth: Float, + var aircraftAttitude: AircraftAttitude, + var aircraftState: AircraftState, + var currentLocationState: CurrentLocationState, + var gimbalHeading: Float, + var centerType: CenterType + ) + //endregion +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/GimbalYawView.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/GimbalYawView.java deleted file mode 100644 index 081c9229..00000000 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/GimbalYawView.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * 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.ux.beta.core.widget.compass; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; -import android.util.AttributeSet; -import android.view.View; - -import androidx.annotation.ColorInt; -import androidx.annotation.FloatRange; - -import dji.ux.beta.R; -import dji.ux.beta.core.util.DisplayUtil; - -/** - * Custom view to display the aircraft gimbal's heading - */ -public class GimbalYawView extends View { - - //region Properties - public static final int MAX_LINE_WIDTH = 4; - - private static final int DEAD_ANGLE = 30; - private static final int BLINK_ANGLE = 270; - private static final int SHOW_ANGLE = 190; - private static final int HIDE_ANGLE = 90; - private static final long DURATION_BLINK = 200; - - private final RectF rect = new RectF(); - private Paint paint = null; - private float strokeWidth; - - private int curBlinkColor; - private int yawColor; - private int invalidColor; - private int blinkColor; - - private boolean beforeShow; - private float yaw; - private float absYaw; - private float yawStartAngle; - private float yawSweepAngle; - private float invalidStartAngle; - private float invalidSweepAngle; - //endregion - - //region Constructor - public GimbalYawView(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - public GimbalYawView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(); - } - - public GimbalYawView(Context context) { - super(context); - init(); - } - //endregion - - //region UI Logic - private void init() { - if (isInEditMode()) { - return; - } - final Context context = getContext(); - strokeWidth = DisplayUtil.dipToPx(context, getContext().getResources().getDimension(R.dimen.uxsdk_line_width)); - paint = new Paint(); - paint.setStrokeWidth(strokeWidth); - paint.setStyle(Paint.Style.STROKE); - paint.setAntiAlias(true); - - yawColor = context.getResources().getColor(R.color.uxsdk_blue_material_A400); - invalidColor = context.getResources().getColor(R.color.uxsdk_red); - blinkColor = context.getResources().getColor(R.color.uxsdk_red_material_900_30_percent); - } - - /** - * Set the yaw for the view - * - * @param yaw Yaw of the gimbal - */ - public void setYaw(final float yaw) { - if (this.yaw != yaw) { - this.yaw = yaw; - absYaw = (yaw >= 0) ? yaw : (0 - yaw); - if (absYaw >= SHOW_ANGLE) { - beforeShow = true; - } else if (absYaw < HIDE_ANGLE) { - beforeShow = false; - } - - yawStartAngle = 0.0f; - yawSweepAngle = 0.0f; - - invalidStartAngle = 0.0f; - invalidSweepAngle = DEAD_ANGLE; - - if (this.yaw < 0) { - yawStartAngle = this.yaw; - yawSweepAngle = 0 - this.yaw; - } else { - invalidStartAngle = 0 - DEAD_ANGLE; - yawSweepAngle = this.yaw; - } - postInvalidate(); - } - } - - @Override - protected void onDraw(Canvas canvas) { - if (isInEditMode()) { - return; - } - final float width; - if (getWidth() < getHeight()) { - width = getWidth(); - } else { - width = getHeight(); - } - final float halfStroke = strokeWidth / 2.0f; - final float radius = width / 2; - - rect.set(halfStroke, halfStroke, width - halfStroke, width - halfStroke); - canvas.save(); - canvas.translate(radius, radius); - canvas.rotate(-90.0f); - canvas.translate(-radius, -radius); - - if (absYaw >= BLINK_ANGLE) { - curBlinkColor = (curBlinkColor == invalidColor) ? blinkColor : invalidColor; - paint.setColor(curBlinkColor); - canvas.drawArc(rect, invalidStartAngle, invalidSweepAngle, false, paint); - postInvalidateDelayed(DURATION_BLINK); - } else if (beforeShow) { - curBlinkColor = invalidColor; - paint.setColor(invalidColor); - canvas.drawArc(rect, invalidStartAngle, invalidSweepAngle, false, paint); - } - - paint.setColor(yawColor); - canvas.drawArc(rect, yawStartAngle, yawSweepAngle, false, paint); - - canvas.restore(); - } - //endregion - - //region Customizations - - /** - * Set the stroke width for the lines - * - * @param strokeWidth Float value of stroke width in px - */ - public void setStrokeWidth(@FloatRange(from = 1.0, to = MAX_LINE_WIDTH) final float strokeWidth) { - this.strokeWidth = strokeWidth; - postInvalidate(); - } - - /** - * Set the yaw color - * - * @param yawColor Color integer resource - */ - public void setYawColor(@ColorInt final int yawColor) { - this.yawColor = yawColor; - postInvalidate(); - } - - /** - * Set the invalid color - * - * @param invalidColor Color integer resource - */ - public void setInvalidColor(@ColorInt final int invalidColor) { - this.invalidColor = invalidColor; - postInvalidate(); - } - - /** - * Set the blink color - * - * @param blinkColor Color integer resource - */ - public void setBlinkColor(@ColorInt final int blinkColor) { - this.blinkColor = blinkColor; - postInvalidate(); - } - - //endregion -} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/GimbalYawView.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/GimbalYawView.kt new file mode 100644 index 00000000..4d132462 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/GimbalYawView.kt @@ -0,0 +1,192 @@ +/* + * 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.ux.beta.core.widget.compass + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.RectF +import android.util.AttributeSet +import android.view.View +import androidx.annotation.ColorInt +import androidx.annotation.FloatRange +import androidx.annotation.Px +import dji.ux.beta.core.R +import dji.ux.beta.core.extension.getColor +import dji.ux.beta.core.extension.getDimension + +private const val DEAD_ANGLE = 30 +private const val BLINK_ANGLE = 270 +private const val SHOW_ANGLE = 190 +private const val HIDE_ANGLE = 90 +private const val DURATION_BLINK: Long = 200 + +/** + * Custom view to display the aircraft gimbal's heading + */ +class GimbalYawView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : View(context, attrs, defStyleAttr) { + //region Fields + private val rect = RectF() + private val paint: Paint = Paint() + private var curBlinkColor = 0 + private var beforeShow = false + private var yaw = 0f + private var absYaw = 0f + private var yawStartAngle = 0f + private var yawSweepAngle = 0f + private var invalidStartAngle = 0f + private var invalidSweepAngle = 0f + + /** + * The stroke width for the lines in pixels + */ + @Px + @FloatRange(from = 1.0, to = MAX_LINE_WIDTH.toDouble()) + var strokeWidth = getDimension(R.dimen.uxsdk_gimbal_line_width) + set(@FloatRange(from = 1.0, to = MAX_LINE_WIDTH.toDouble()) strokeWidth) { + field = strokeWidth + postInvalidate() + } + + /** + * The yaw color + */ + @ColorInt + var yawColor = getColor(R.color.uxsdk_blue_material_A400) + set(@ColorInt yawColor) { + field = yawColor + postInvalidate() + } + + /** + * The invalid color + */ + @ColorInt + var invalidColor = getColor(R.color.uxsdk_red) + set(@ColorInt invalidColor) { + field = invalidColor + postInvalidate() + } + + /** + * The blink color + */ + @ColorInt + var blinkColor = getColor(R.color.uxsdk_red_material_900_30_percent) + set(@ColorInt blinkColor) { + field = blinkColor + postInvalidate() + } + //endregion + + //region Constructor + init { + init() + } + + private fun init() { + if (isInEditMode) { + return + } + + paint.strokeWidth = strokeWidth + paint.style = Paint.Style.STROKE + paint.isAntiAlias = true + } + //endregion + + //region UI Logic + + /** + * Set the yaw for the view + * + * @param yaw Yaw of the gimbal + */ + fun setYaw(yaw: Float) { + if (this.yaw != yaw) { + this.yaw = yaw + absYaw = if (yaw >= 0) yaw else 0 - yaw + if (absYaw >= SHOW_ANGLE) { + beforeShow = true + } else if (absYaw < HIDE_ANGLE) { + beforeShow = false + } + yawStartAngle = 0.0f + yawSweepAngle = 0.0f + invalidStartAngle = 0.0f + invalidSweepAngle = DEAD_ANGLE.toFloat() + if (this.yaw < 0) { + yawStartAngle = this.yaw + yawSweepAngle = 0 - this.yaw + } else { + invalidStartAngle = 0 - DEAD_ANGLE.toFloat() + yawSweepAngle = this.yaw + } + postInvalidate() + } + } + + override fun onDraw(canvas: Canvas) { + if (isInEditMode) { + return + } + val width: Float = if (width < height) { + width.toFloat() + } else { + height.toFloat() + } + val halfStroke = strokeWidth / 2.0f + val radius = width / 2 + rect[halfStroke, halfStroke, width - halfStroke] = width - halfStroke + canvas.save() + canvas.translate(radius, radius) + canvas.rotate(-90.0f) + canvas.translate(-radius, -radius) + if (absYaw >= BLINK_ANGLE) { + curBlinkColor = if (curBlinkColor == invalidColor) blinkColor else invalidColor + paint.color = curBlinkColor + canvas.drawArc(rect, invalidStartAngle, invalidSweepAngle, false, paint) + postInvalidateDelayed(DURATION_BLINK) + } else if (beforeShow) { + curBlinkColor = invalidColor + paint.color = invalidColor + canvas.drawArc(rect, invalidStartAngle, invalidSweepAngle, false, paint) + } + paint.color = yawColor + canvas.drawArc(rect, yawStartAngle, yawSweepAngle, false, paint) + canvas.restore() + } + //endregion + + companion object { + + /** + * The maximum width of the lines + */ + const val MAX_LINE_WIDTH = 4 + } +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/VisualCompassView.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/VisualCompassView.java deleted file mode 100644 index ea0f8d39..00000000 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/VisualCompassView.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * 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.ux.beta.core.widget.compass; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.util.AttributeSet; -import android.view.View; - -import androidx.annotation.ColorInt; -import androidx.annotation.FloatRange; -import androidx.annotation.IntRange; - -import dji.ux.beta.R; -import dji.ux.beta.core.util.DisplayUtil; - -/** - * Custom view to display the compass view for the aircraft - */ -public class VisualCompassView extends View { - - //region Properties - public static final int MAX_LINE_WIDTH = 4; - private static final int DEFAULT_INTERVAL = 100; - private static final int DEFAULT_DISTANCE = 400; - private static final int DEFAULT_NUMBER_OF_LINES = 4; - - private final Paint paint = new Paint(); - - private float distance; - private int interval; - private int lines; - private int color; - private float strokeWidth; - //endregion - - //region Constructor - public VisualCompassView(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - //endregion - - //region UI Logic - private void init() { - if (isInEditMode()) { - return; - } - interval = DEFAULT_INTERVAL; - distance = DEFAULT_DISTANCE; - lines = DEFAULT_NUMBER_OF_LINES; - color = getContext().getResources().getColor(R.color.uxsdk_white_47_percent); - paint.setAntiAlias(true); - paint.setColor(getContext().getResources().getColor(R.color.uxsdk_white_20_percent)); - paint.setStyle(Paint.Style.STROKE); - strokeWidth = - DisplayUtil.dipToPx(getContext(), getContext().getResources().getDimension(R.dimen.uxsdk_line_width)); - if (strokeWidth > MAX_LINE_WIDTH) { - strokeWidth = MAX_LINE_WIDTH; - } - paint.setStrokeWidth(strokeWidth); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - drawObstacleInfo(canvas); - } - - private float findMod(final float distance) { - final float radarRadius = interval * lines; - float result = 0.0f; - if (distance > radarRadius) { - result = (float) (Math.log(distance / (interval * lines)) / Math.log(2)); - result = result - ((int) result); - } - return result; - } - - private int getVirtualColor(final float mod) { - int color = this.color; - int alpha = Color.alpha(color); - alpha = (int) (alpha * (1.0f - mod)); - color = Color.argb(alpha, Color.red(this.color), Color.green(this.color), Color.blue(this.color)); - return color; - } - - private void drawDistance(final Canvas canvas) { - float mod = findMod(distance); - - final int width = getWidth() - 2; - final float radius = width * 0.5f; - final float center = radius + 1; - float unitRadius = radius / (4.0f * (mod + 1.0f)); - - final int vColor = getVirtualColor(mod); - float dRadius; - - paint.setStyle(Paint.Style.STROKE); - paint.setColor(color); - canvas.drawCircle(center, center, radius, paint); - - for (int i = 1; i * unitRadius < radius; i++) { - if ((i % 2) == 0) { - paint.setColor(color); - } else { - paint.setColor(vColor); - } - dRadius = i * unitRadius; - canvas.drawCircle(center, center, dRadius, paint); - } - } - - private void drawObstacleInfo(final Canvas canvas) { - drawDistance(canvas); - } - - protected void setDistance(final float distance) { - if (this.distance != distance) { - this.distance = distance; - postInvalidate(); - } - } - - /** - * Set the stroke width for the lines - * - * @param strokeWidth Float value of stroke width in px - */ - public void setStrokeWidth(@FloatRange(from = 1.0, to = MAX_LINE_WIDTH) final float strokeWidth) { - this.strokeWidth = strokeWidth; - postInvalidate(); - } - - /** - * Set the color for the lines - * - * @param color Color integer resource - */ - public void setLineColor(@ColorInt final int color) { - this.color = color; - postInvalidate(); - } - - /** - * Set the interval between the lines - * - * @param interval Integer value of the interval - */ - public void setLineInterval(@IntRange(from = 1) final int interval) { - this.interval = interval; - postInvalidate(); - } - - /** - * Set the number of lines to be drawn - * - * @param lines Number of lines as an integer value - */ - public void setNumberOfLines(@IntRange(from = 3) final int lines) { - this.lines = lines; - postInvalidate(); - } - - //endregion -} - diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/VisualCompassView.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/VisualCompassView.kt new file mode 100644 index 00000000..a85e941b --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/compass/VisualCompassView.kt @@ -0,0 +1,187 @@ +/* + * 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.ux.beta.core.widget.compass + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View +import androidx.annotation.ColorInt +import androidx.annotation.FloatRange +import androidx.annotation.Px +import dji.ux.beta.core.R +import dji.ux.beta.core.extension.getColor +import dji.ux.beta.core.extension.getDimension +import kotlin.math.ln + +private const val DEFAULT_INTERVAL = 100 +private const val DEFAULT_DISTANCE = 400 +private const val DEFAULT_NUMBER_OF_LINES = 4 + +/** + * Custom view to display the compass view for the aircraft + */ +class VisualCompassView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : View(context, attrs, defStyleAttr) { + //region Fields + private val paint = Paint() + private var distance = DEFAULT_DISTANCE.toFloat() + + /** + * The interval between the lines + */ + var lineInterval = DEFAULT_INTERVAL + set(interval) { + field = interval + postInvalidate() + } + + /** + * The number of lines to be drawn + */ + var numberOfLines = DEFAULT_NUMBER_OF_LINES + set(lines) { + field = lines + postInvalidate() + } + + /** + * The stroke width for the lines in pixels + */ + @Px + @FloatRange(from = 1.0, to = MAX_LINE_WIDTH.toDouble()) + var strokeWidth = getDimension(R.dimen.uxsdk_line_width) + set(@FloatRange(from = 1.0, to = MAX_LINE_WIDTH.toDouble()) strokeWidth) { + field = strokeWidth + postInvalidate() + } + + /** + * The color for the lines + */ + @get:ColorInt + var lineColor: Int = getColor(R.color.uxsdk_white_47_percent) + set(color) { + field = color + postInvalidate() + } + + //endregion + + //region Constructor + init { + init() + } + + private fun init() { + if (isInEditMode) { + return + } + + paint.isAntiAlias = true + paint.color = getColor(R.color.uxsdk_white_20_percent) + paint.style = Paint.Style.STROKE + if (strokeWidth > MAX_LINE_WIDTH) { + strokeWidth = MAX_LINE_WIDTH.toFloat() + } + paint.strokeWidth = strokeWidth + } + //endregion + + //region UI Logic + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + drawObstacleInfo(canvas) + } + + private fun findMod(distance: Float): Float { + val radarRadius = lineInterval * numberOfLines.toFloat() + var result = 0.0f + if (distance > radarRadius) { + result = (ln(distance / (lineInterval * numberOfLines).toDouble()) / ln(2.0)).toFloat() + result -= result.toInt() + } + return result + } + + private fun getVirtualColor(mod: Float): Int { + var color = lineColor + var alpha = Color.alpha(color) + alpha = (alpha * (1.0f - mod)).toInt() + color = Color.argb(alpha, Color.red(lineColor), Color.green(lineColor), Color.blue(lineColor)) + return color + } + + private fun drawDistance(canvas: Canvas) { + val mod = findMod(distance) + val width = width - 2 + val radius = width * 0.5f + val center = radius + 1 + val unitRadius = radius / (4.0f * (mod + 1.0f)) + val vColor = getVirtualColor(mod) + var dRadius: Float + paint.style = Paint.Style.STROKE + paint.color = lineColor + canvas.drawCircle(center, center, radius, paint) + var i = 1 + while (i * unitRadius < radius) { + if (i % 2 == 0) { + paint.color = lineColor + } else { + paint.color = vColor + } + dRadius = i * unitRadius + canvas.drawCircle(center, center, dRadius, paint) + i++ + } + } + + private fun drawObstacleInfo(canvas: Canvas) { + drawDistance(canvas) + } + + /** + * Set the real-world distance that is represented by the length from the center to the edge + * of the view. + */ + fun setDistance(distance: Float) { + if (this.distance != distance) { + this.distance = distance + postInvalidate() + } + } + //endregion + + companion object { + + /** + * The maximum width of the lines + */ + const val MAX_LINE_WIDTH = 4 + } +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/connection/ConnectionWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/connection/ConnectionWidget.kt index 20c1cf01..91e02c41 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/connection/ConnectionWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/connection/ConnectionWidget.kt @@ -32,18 +32,18 @@ import android.widget.ImageView import androidx.annotation.DrawableRes import androidx.core.content.res.use import dji.thirdparty.io.reactivex.Flowable -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers +import dji.ux.beta.core.base.SchedulerProvider import dji.thirdparty.io.reactivex.functions.Consumer -import dji.ux.beta.R +import dji.ux.beta.core.R import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.FrameLayoutWidget -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.base.widget.FrameLayoutWidget +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.getDrawable import dji.ux.beta.core.extension.getDrawableAndUse import dji.ux.beta.core.extension.getString import dji.ux.beta.core.extension.imageDrawable -import dji.ux.beta.core.widget.connection.ConnectionWidget.ConnectionWidgetState -import dji.ux.beta.core.widget.connection.ConnectionWidget.ConnectionWidgetState.ProductConnected +import dji.ux.beta.core.widget.connection.ConnectionWidget.ModelState +import dji.ux.beta.core.widget.connection.ConnectionWidget.ModelState.ProductConnected private const val TAG = "ConnectionWidget" @@ -54,11 +54,7 @@ open class ConnectionWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : FrameLayoutWidget( - context, - attrs, - defStyleAttr -) { +) : FrameLayoutWidget(context, attrs, defStyleAttr) { //region Fields /** @@ -96,7 +92,7 @@ open class ConnectionWidget @JvmOverloads constructor( private val connectivityImageView: ImageView = findViewById(R.id.image_view_connection_status) //endregion - //region Constructors + //region Constructor override fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { View.inflate(context, R.layout.uxsdk_widget_connection, this) } @@ -123,12 +119,13 @@ open class ConnectionWidget @JvmOverloads constructor( override fun reactToModelChanges() { addReaction(widgetModel.productConnection - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::updateUI)) } //endregion - //region private functions + + //region Reactions to model private fun updateUI(isConnected: Boolean) { widgetStateDataProcessor.onNext(ProductConnected(isConnected)) connectivityImageView.imageDrawable = if (isConnected) { @@ -137,11 +134,13 @@ open class ConnectionWidget @JvmOverloads constructor( disconnectedIcon } } + //endregion + //region private helpers private fun checkAndUpdateIcon() { if (!isInEditMode) { addDisposable(widgetModel.productConnection.lastOrError() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { updateUI(it) }, logErrorConsumer(TAG, "product connection"))) } } @@ -178,9 +177,7 @@ open class ConnectionWidget @JvmOverloads constructor( fun setConnectivityIconBackground(@DrawableRes resourceId: Int) { connectivityImageView.setBackgroundResource(resourceId) } - //endregion - //region Customization helpers @SuppressLint("Recycle") private fun initAttributes(context: Context, attrs: AttributeSet) { context.obtainStyledAttributes(attrs, R.styleable.ConnectionWidget).use { typedArray -> @@ -200,20 +197,21 @@ open class ConnectionWidget @JvmOverloads constructor( //region Hooks /** - * Get the [ConnectionWidgetState] updates + * Get the [ModelState] updates */ - override fun getWidgetStateUpdate(): Flowable { + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { return super.getWidgetStateUpdate() } /** * Class defines the widget state updates */ - sealed class ConnectionWidgetState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : ConnectionWidgetState() + data class ProductConnected(val isConnected: Boolean) : ModelState() } //endregion } \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/connection/ConnectionWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/connection/ConnectionWidgetModel.kt index fcca947a..a474d76d 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/connection/ConnectionWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/connection/ConnectionWidgetModel.kt @@ -18,14 +18,14 @@ * 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.ux.beta.core.widget.connection import dji.ux.beta.core.base.DJISDKModel import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore /** * Widget Model for the [ConnectionWidget] diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/dashboard/DashboardWidget.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/dashboard/DashboardWidget.java deleted file mode 100644 index 6cd3611d..00000000 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/dashboard/DashboardWidget.java +++ /dev/null @@ -1,324 +0,0 @@ -/* - * 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.ux.beta.core.widget.dashboard; - -import android.content.Context; -import android.content.res.TypedArray; -import android.util.AttributeSet; -import android.view.View; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import dji.ux.beta.R; -import dji.ux.beta.core.base.ConstraintLayoutWidget; -import dji.ux.beta.core.widget.altitude.AltitudeWidget; -import dji.ux.beta.core.widget.compass.CompassWidget; -import dji.ux.beta.core.widget.distancehome.DistanceHomeWidget; -import dji.ux.beta.core.widget.distancerc.DistanceRCWidget; -import dji.ux.beta.core.widget.horizontalvelocity.HorizontalVelocityWidget; -import dji.ux.beta.core.widget.verticalvelocity.VerticalVelocityWidget; -import dji.ux.beta.core.widget.vps.VPSWidget; - -/** - * Compound widget that aggregates important physical state information - * about the aircraft into a dashboard. - *

- * It includes the {@link CompassWidget}, {@link AltitudeWidget}, - * {@link DistanceHomeWidget}, {@link DistanceRCWidget}, - * {@link HorizontalVelocityWidget}, {@link VerticalVelocityWidget} - * and the {@link VPSWidget}. - *

- * This widget can be customized to include or exclude any of these widgets - * as required. - */ -public class DashboardWidget extends ConstraintLayoutWidget { - //region Fields - private CompassWidget compassWidget; - private AltitudeWidget altitudeWidget; - private DistanceHomeWidget distanceHomeWidget; - private DistanceRCWidget distanceRCWidget; - private HorizontalVelocityWidget horizontalVelocityWidget; - private VerticalVelocityWidget verticalVelocityWidget; - private VPSWidget vpsWidget; - //endregion - - //region Constructors - public DashboardWidget(Context context) { - super(context); - } - - public DashboardWidget(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public DashboardWidget(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_dashboard, this); - - compassWidget = findViewById(R.id.widget_compass); - altitudeWidget = findViewById(R.id.widget_altitude); - distanceHomeWidget = findViewById(R.id.widget_distance_home); - distanceRCWidget = findViewById(R.id.widget_distance_rc); - horizontalVelocityWidget = findViewById(R.id.widget_horizontal_velocity); - verticalVelocityWidget = findViewById(R.id.widget_vertical_velocity); - vpsWidget = findViewById(R.id.widget_vps); - - if (attrs != null) { - initAttributes(context, attrs); - } - } - //endregion - - //region Lifecycle - @Override - protected void reactToModelChanges() { - //No reactions - } - //endregion - - //region Customizations - @NonNull - @Override - public String getIdealDimensionRatioString() { - return getResources().getString(R.string.uxsdk_widget_dashboard_ratio); - } - - /** - * Get the compass widget object - * - * @return CompassWidget object - */ - public CompassWidget getCompassWidget() { - return compassWidget; - } - - /** - * Get the altitude widget object - * - * @return AltitudeWidget object - */ - public AltitudeWidget getAltitudeWidget() { - return altitudeWidget; - } - - /** - * Get the distance from home widget object - * - * @return DistanceHomeWidget object - */ - public DistanceHomeWidget getDistanceHomeWidget() { - return distanceHomeWidget; - } - - /** - * Get the distance from RC widget object - * - * @return DistanceRCWidget object - */ - public DistanceRCWidget getDistanceRCWidget() { - return distanceRCWidget; - } - - /** - * Get the horizontal velocity widget object - * - * @return HorizontalVelocityWidget object - */ - public HorizontalVelocityWidget getHorizontalVelocityWidget() { - return horizontalVelocityWidget; - } - - /** - * Get the vertical velocity widget object - * - * @return VerticalVelocityWidget object - */ - public VerticalVelocityWidget getVerticalVelocityWidget() { - return verticalVelocityWidget; - } - - /** - * Get the VPS widget object - * - * @return VPSWidget object - */ - public VPSWidget getVPSWidget() { - return vpsWidget; - } - - /** - * Set the visibility of the compass widget object - * - * @param isVisible Boolean value for visibility - */ - public void setCompassWidgetVisibility(boolean isVisible) { - if (isVisible) { - compassWidget.setVisibility(View.VISIBLE); - } else { - compassWidget.setVisibility(View.GONE); - } - } - - /** - * Set the visibility of the altitude widget object - * - * @param isVisible Boolean value for visibility - */ - public void setAltitudeWidgetVisibility(boolean isVisible) { - if (isVisible) { - altitudeWidget.setVisibility(View.VISIBLE); - } else { - altitudeWidget.setVisibility(View.GONE); - } - } - - /** - * Set the visibility of the distance from home widget object - * - * @param isVisible Boolean value for visibility - */ - public void setDistanceHomeWidgetVisibility(boolean isVisible) { - if (isVisible) { - distanceHomeWidget.setVisibility(View.VISIBLE); - } else { - distanceHomeWidget.setVisibility(View.GONE); - } - } - - /** - * Set the visibility of the distance from RC widget object - * - * @param isVisible Boolean value for visibility - */ - public void setDistanceRCWidgetVisibility(boolean isVisible) { - if (isVisible) { - distanceRCWidget.setVisibility(View.VISIBLE); - } else { - distanceRCWidget.setVisibility(View.GONE); - } - } - - /** - * Set the visibility of the horizontal velocity widget object - * - * @param isVisible Boolean value for visibility - */ - public void setHorizontalVelocityWidgetVisibility(boolean isVisible) { - if (isVisible) { - horizontalVelocityWidget.setVisibility(View.VISIBLE); - } else { - horizontalVelocityWidget.setVisibility(View.GONE); - } - } - - /** - * Set the visibility of the vertical velocity widget object - * - * @param isVisible Boolean value for visibility - */ - public void setVerticalVelocityWidgetVisibility(boolean isVisible) { - if (isVisible) { - verticalVelocityWidget.setVisibility(View.VISIBLE); - } else { - verticalVelocityWidget.setVisibility(View.GONE); - } - } - - /** - * Set the visibility of the VPS widget object - * - * @param isVisible Boolean value for visibility - */ - public void setVPSWidgetVisibility(boolean isVisible) { - if (isVisible) { - vpsWidget.setVisibility(View.VISIBLE); - } else { - vpsWidget.setVisibility(View.GONE); - } - } - - //Initialize all customizable attributes - private void initAttributes(@NonNull Context context, @NonNull AttributeSet attrs) { - TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DashboardWidget); - - int hiddenWidgets = typedArray.getInteger(R.styleable.DashboardWidget_uxsdk_hideWidgets, HideWidget.NONE.value()); - if (HideWidget.isWidgetHidden(hiddenWidgets, HideWidget.COMPASS)) { - setCompassWidgetVisibility(false); - } - if (HideWidget.isWidgetHidden(hiddenWidgets, HideWidget.ALTITUDE)) { - setAltitudeWidgetVisibility(false); - } - if (HideWidget.isWidgetHidden(hiddenWidgets, HideWidget.DISTANCE_HOME)) { - setDistanceHomeWidgetVisibility(false); - } - if (HideWidget.isWidgetHidden(hiddenWidgets, HideWidget.DISTANCE_RC)) { - setDistanceRCWidgetVisibility(false); - } - if (HideWidget.isWidgetHidden(hiddenWidgets, HideWidget.HORIZONTAL_VELOCITY)) { - setHorizontalVelocityWidgetVisibility(false); - } - if (HideWidget.isWidgetHidden(hiddenWidgets, HideWidget.VERTICAL_VELOCITY)) { - setVerticalVelocityWidgetVisibility(false); - } - if (HideWidget.isWidgetHidden(hiddenWidgets, HideWidget.VPS)) { - setVPSWidgetVisibility(false); - } - - typedArray.recycle(); - } - - /** - * Enum of all the widgets in the dashboard to set their visibility - */ - private enum HideWidget { - NONE(0), - COMPASS(1), - ALTITUDE(2), - DISTANCE_HOME(4), - DISTANCE_RC(8), - HORIZONTAL_VELOCITY(16), - VERTICAL_VELOCITY(32), - VPS(64); - - private final int value; - - HideWidget(int value) { - this.value = value; - } - - protected static boolean isWidgetHidden(int hiddenWidgets, HideWidget widget) { - return (hiddenWidgets & widget.value()) == widget.value; - } - - public int value() { - return this.value; - } - } - //endregion -} diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancehome/DistanceHomeWidget.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancehome/DistanceHomeWidget.java deleted file mode 100644 index 5d8ecc2a..00000000 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancehome/DistanceHomeWidget.java +++ /dev/null @@ -1,520 +0,0 @@ -/* - * 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.ux.beta.core.widget.distancehome; - -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.widget.TextView; - -import androidx.annotation.ColorInt; -import androidx.annotation.Dimension; -import androidx.annotation.DrawableRes; -import androidx.annotation.FloatRange; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StyleRes; - -import java.text.DecimalFormat; - -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers; -import dji.ux.beta.R; -import dji.ux.beta.core.base.ConstraintLayoutWidget; -import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.GlobalPreferencesInterface; -import dji.ux.beta.core.base.GlobalPreferencesManager; -import dji.ux.beta.core.base.uxsdkkeys.GlobalPreferenceKeys; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; -import dji.ux.beta.core.util.DisplayUtil; -import dji.ux.beta.core.util.UnitConversionUtil; - -/** - * Shows the distance between the current location of the aircraft - * and home. - * Uses the unit set in the UNIT_TYPE global preferences - * {@link GlobalPreferencesInterface#getUnitType()} and the - * {@link GlobalPreferenceKeys#UNIT_TYPE} UX Key - * and defaults to meters. - */ -public class DistanceHomeWidget extends ConstraintLayoutWidget { - //region Fields - private static final int EMS = 3; - private static DecimalFormat decimalFormat = new DecimalFormat("###0.0"); - private TextView distanceHomeTitleTextView; - private TextView distanceHomeValueTextView; - private TextView distanceHomeUnitTextView; - private DistanceHomeWidgetModel widgetModel; - //endregion - - //region Constructors - public DistanceHomeWidget(Context context) { - super(context); - } - - public DistanceHomeWidget(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public DistanceHomeWidget(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_base_dashboard_text_only, this); - distanceHomeTitleTextView = findViewById(R.id.textview_title); - distanceHomeValueTextView = findViewById(R.id.textview_value); - distanceHomeUnitTextView = findViewById(R.id.textview_unit); - - if (!isInEditMode()) { - widgetModel = new DistanceHomeWidgetModel(DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - GlobalPreferencesManager.getInstance()); - distanceHomeTitleTextView.setText(getResources().getString(R.string.uxsdk_distance_home_title)); - distanceHomeValueTextView.setMinEms(EMS); - } - - if (attrs != null) { - initAttributes(context, attrs); - } - } - //endregion - - //region Lifecycle - @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.getDistanceFromHome() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::updateValueText)); - addReaction(widgetModel.getUnitType() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::updateUnitText)); - } - //endregion - - //region Reactions to model - private void updateValueText(@FloatRange(from = 0.0f) float distanceFromHome) { - distanceHomeValueTextView.setText(decimalFormat.format(distanceFromHome)); - } - - private void updateUnitText(UnitConversionUtil.UnitType unitType) { - if (unitType == UnitConversionUtil.UnitType.IMPERIAL) { - distanceHomeUnitTextView.setText(getResources().getString(R.string.uxsdk_unit_feet)); - } else { - distanceHomeUnitTextView.setText(getResources().getString(R.string.uxsdk_unit_meters)); - } - } - //endregion - - //region Customization - @NonNull - @Override - public String getIdealDimensionRatioString() { - return getResources().getString(R.string.uxsdk_widget_base_dashboard_distance_ratio); - } - //endregion - - //region Customization Helpers - - /** - * Set text appearance of the distance from home title text view - * - * @param textAppearance Style resource for text appearance - */ - public void setDistanceHomeTitleTextAppearance(@StyleRes int textAppearance) { - distanceHomeTitleTextView.setTextAppearance(getContext(), textAppearance); - } - - /** - * Get current text color state list of the distance from home title text view - * - * @return ColorStateList resource - */ - @Nullable - public ColorStateList getDistanceHomeTitleTextColors() { - return distanceHomeTitleTextView.getTextColors(); - } - - /** - * Get current text color of the distance from home title text view - * - * @return color integer resource - */ - @ColorInt - public int getDistanceHomeTitleTextColor() { - return distanceHomeTitleTextView.getCurrentTextColor(); - } - - /** - * Set text color state list for the distance from home title text view - * - * @param colorStateList ColorStateList resource - */ - public void setDistanceHomeTitleTextColor(@NonNull ColorStateList colorStateList) { - distanceHomeTitleTextView.setTextColor(colorStateList); - } - - /** - * Set the text color for the distance from home title text view - * - * @param color color integer resource - */ - public void setDistanceHomeTitleTextColor(@ColorInt int color) { - distanceHomeTitleTextView.setTextColor(color); - } - - /** - * Get current text size of the distance from home title text view - * - * @return text size of the text view - */ - @Dimension - public float getDistanceHomeTitleTextSize() { - return distanceHomeTitleTextView.getTextSize(); - } - - /** - * Set the text size of the distance from home title text view - * - * @param textSize text size float value - */ - public void setDistanceHomeTitleTextSize(@Dimension float textSize) { - distanceHomeTitleTextView.setTextSize(textSize); - } - - /** - * Get current background of the distance from home title text view - * - * @return Drawable resource of the background - */ - @Nullable - public Drawable getDistanceHomeTitleTextBackground() { - return distanceHomeTitleTextView.getBackground(); - } - - /** - * Set the background of the distance from home title text view - * - * @param drawable Drawable resource for the background - */ - public void setDistanceHomeTitleTextBackground(@Nullable Drawable drawable) { - distanceHomeTitleTextView.setBackground(drawable); - } - - /** - * Set the resource ID for the background of the distance from home title text view - * - * @param resourceId Integer ID of the text view's background resource - */ - public void setDistanceHomeTitleTextBackground(@DrawableRes int resourceId) { - distanceHomeTitleTextView.setBackgroundResource(resourceId); - } - - /** - * Set text appearance of the distance from home value text view - * - * @param textAppearance Style resource for text appearance - */ - public void setDistanceHomeValueTextAppearance(@StyleRes int textAppearance) { - distanceHomeValueTextView.setTextAppearance(getContext(), textAppearance); - } - - /** - * Get current text color state list of the distance from home value text view - * - * @return ColorStateList resource - */ - @Nullable - public ColorStateList getDistanceHomeValueTextColors() { - return distanceHomeValueTextView.getTextColors(); - } - - /** - * Get current text color of the distance from home value text view - * - * @return color integer resource - */ - @ColorInt - public int getDistanceHomeValueTextColor() { - return distanceHomeValueTextView.getCurrentTextColor(); - } - - /** - * Set text color state list for the distance from home value text view - * - * @param colorStateList ColorStateList resource - */ - public void setDistanceHomeValueTextColor(@NonNull ColorStateList colorStateList) { - distanceHomeValueTextView.setTextColor(colorStateList); - } - - /** - * Set the text color for the distance from home value text view - * - * @param color color integer resource - */ - public void setDistanceHomeValueTextColor(@ColorInt int color) { - distanceHomeValueTextView.setTextColor(color); - } - - /** - * Get current text size of the distance from home value text view - * - * @return text size of the text view - */ - @Dimension - public float getDistanceHomeValueTextSize() { - return distanceHomeValueTextView.getTextSize(); - } - - /** - * Set the text size of the distance from home value text view - * - * @param textSize text size float value - */ - public void setDistanceHomeValueTextSize(@Dimension float textSize) { - distanceHomeValueTextView.setTextSize(textSize); - } - - /** - * Get current background of the distance from home value text view - * - * @return Drawable resource of the background - */ - @Nullable - public Drawable getDistanceHomeValueTextBackground() { - return distanceHomeValueTextView.getBackground(); - } - - /** - * Set the background for the distance from home value text view - * - * @param drawable Drawable resource for the background - */ - public void setDistanceHomeValueTextBackground(@Nullable Drawable drawable) { - distanceHomeValueTextView.setBackground(drawable); - } - - /** - * Set the resource ID for the background of the distance from home value text view - * - * @param resourceId Integer ID of the text view's background resource - */ - public void setDistanceHomeValueTextBackground(@DrawableRes int resourceId) { - distanceHomeValueTextView.setBackgroundResource(resourceId); - } - - /** - * Set text appearance of the distance from home unit text view - * - * @param textAppearance Style resource for text appearance - */ - public void setDistanceHomeUnitTextAppearance(@StyleRes int textAppearance) { - distanceHomeUnitTextView.setTextAppearance(getContext(), textAppearance); - } - - /** - * Get current text color state list of the distance from home unit text view - * - * @return ColorStateList resource - */ - @Nullable - public ColorStateList getDistanceHomeUnitTextColors() { - return distanceHomeUnitTextView.getTextColors(); - } - - /** - * Get current text color of the distance from home unit text view - * - * @return color integer resource - */ - @ColorInt - public int getDistanceHomeUnitTextColor() { - return distanceHomeUnitTextView.getCurrentTextColor(); - } - - /** - * Set text color state list for the distance from home unit text view - * - * @param colorStateList ColorStateList resource - */ - public void setDistanceHomeUnitTextColor(@NonNull ColorStateList colorStateList) { - distanceHomeUnitTextView.setTextColor(colorStateList); - } - - /** - * Set the text color for the distance from home unit text view - * - * @param color color integer resource - */ - public void setDistanceHomeUnitTextColor(@ColorInt int color) { - distanceHomeUnitTextView.setTextColor(color); - } - - /** - * Get current text size of the distance from home unit text view - * - * @return text size of the text view - */ - @Dimension - public float getDistanceHomeUnitTextSize() { - return distanceHomeUnitTextView.getTextSize(); - } - - /** - * Set the text size of the distance from home unit text view - * - * @param textSize text size float value - */ - public void setDistanceHomeUnitTextSize(@Dimension float textSize) { - distanceHomeUnitTextView.setTextSize(textSize); - } - - /** - * Get current background of the distance from home unit text view - * - * @return Drawable resource of the background - */ - @Nullable - public Drawable getDistanceHomeUnitTextBackground() { - return distanceHomeUnitTextView.getBackground(); - } - - /** - * Set the background for the distance from home unit text view - * - * @param drawable Drawable resource for the background - */ - public void setDistanceHomeUnitTextBackground(@Nullable Drawable drawable) { - distanceHomeUnitTextView.setBackground(drawable); - } - - /** - * Set the resource ID for the background of the distance from home unit text view - * - * @param resourceId Integer ID of the text view's background resource - */ - public void setDistanceHomeUnitTextBackground(@DrawableRes int resourceId) { - distanceHomeTitleTextView.setBackgroundResource(resourceId); - } - - //Initialize all customizable attributes - private void initAttributes(@NonNull Context context, @NonNull AttributeSet attrs) { - TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DistanceHomeWidget); - int distanceHomeTitleTextAppearanceId = - typedArray.getResourceId(R.styleable.DistanceHomeWidget_uxsdk_distanceHomeTitleTextAppearance, - INVALID_RESOURCE); - if (distanceHomeTitleTextAppearanceId != INVALID_RESOURCE) { - setDistanceHomeTitleTextAppearance(distanceHomeTitleTextAppearanceId); - } - - float distanceHomeTitleTextSize = - typedArray.getDimension(R.styleable.DistanceHomeWidget_uxsdk_distanceHomeTitleTextSize, INVALID_RESOURCE); - if (distanceHomeTitleTextSize != INVALID_RESOURCE) { - setDistanceHomeTitleTextSize(DisplayUtil.pxToSp(context, distanceHomeTitleTextSize)); - } - - int distanceHomeTitleTextColor = - typedArray.getColor(R.styleable.DistanceHomeWidget_uxsdk_distanceHomeTitleTextColor, INVALID_COLOR); - if (distanceHomeTitleTextColor != INVALID_COLOR) { - setDistanceHomeTitleTextColor(distanceHomeTitleTextColor); - } - - Drawable distanceHomeTitleTextBackgroundDrawable = - typedArray.getDrawable(R.styleable.DistanceHomeWidget_uxsdk_distanceHomeTitleBackgroundDrawable); - if (distanceHomeTitleTextBackgroundDrawable != null) { - setDistanceHomeTitleTextBackground(distanceHomeTitleTextBackgroundDrawable); - } - - int distanceHomeValueTextAppearanceId = - typedArray.getResourceId(R.styleable.DistanceHomeWidget_uxsdk_distanceHomeValueTextAppearance, - INVALID_RESOURCE); - if (distanceHomeValueTextAppearanceId != INVALID_RESOURCE) { - setDistanceHomeValueTextAppearance(distanceHomeValueTextAppearanceId); - } - - float distanceHomeValueTextSize = - typedArray.getDimension(R.styleable.DistanceHomeWidget_uxsdk_distanceHomeValueTextSize, INVALID_RESOURCE); - if (distanceHomeValueTextSize != INVALID_RESOURCE) { - setDistanceHomeValueTextSize(DisplayUtil.pxToSp(context, distanceHomeValueTextSize)); - } - - int distanceHomeValueTextColor = - typedArray.getColor(R.styleable.DistanceHomeWidget_uxsdk_distanceHomeValueTextColor, INVALID_COLOR); - if (distanceHomeValueTextColor != INVALID_COLOR) { - setDistanceHomeValueTextColor(distanceHomeValueTextColor); - } - - Drawable distanceHomeValueTextBackgroundDrawable = - typedArray.getDrawable(R.styleable.DistanceHomeWidget_uxsdk_distanceHomeValueBackgroundDrawable); - if (distanceHomeValueTextBackgroundDrawable != null) { - setDistanceHomeValueTextBackground(distanceHomeValueTextBackgroundDrawable); - } - - int distanceHomeUnitTextAppearanceId = - typedArray.getResourceId(R.styleable.DistanceHomeWidget_uxsdk_distanceHomeUnitTextAppearance, - INVALID_RESOURCE); - if (distanceHomeUnitTextAppearanceId != INVALID_RESOURCE) { - setDistanceHomeUnitTextAppearance(distanceHomeUnitTextAppearanceId); - } - - float distanceHomeUnitTextSize = - typedArray.getDimension(R.styleable.DistanceHomeWidget_uxsdk_distanceHomeUnitTextSize, INVALID_RESOURCE); - if (distanceHomeUnitTextSize != INVALID_RESOURCE) { - setDistanceHomeUnitTextSize(DisplayUtil.pxToSp(context, distanceHomeUnitTextSize)); - } - - int distanceHomeUnitTextColor = - typedArray.getColor(R.styleable.DistanceHomeWidget_uxsdk_distanceHomeUnitTextColor, INVALID_COLOR); - if (distanceHomeUnitTextColor != INVALID_COLOR) { - setDistanceHomeUnitTextColor(distanceHomeUnitTextColor); - } - - Drawable distanceHomeUnitTextBackgroundDrawable = - typedArray.getDrawable(R.styleable.DistanceHomeWidget_uxsdk_distanceHomeUnitBackgroundDrawable); - if (distanceHomeUnitTextBackgroundDrawable != null) { - setDistanceHomeUnitTextBackground(distanceHomeUnitTextBackgroundDrawable); - } - typedArray.recycle(); - } - //endregion -} diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancehome/DistanceHomeWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancehome/DistanceHomeWidget.kt new file mode 100644 index 00000000..d9b2a648 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancehome/DistanceHomeWidget.kt @@ -0,0 +1,157 @@ +/* + * 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.ux.beta.core.widget.distancehome + +import android.content.Context +import android.util.AttributeSet +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.R +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.SchedulerProvider +import dji.ux.beta.core.base.WidgetSizeDescription +import dji.ux.beta.core.base.widget.BaseTelemetryWidget +import dji.ux.beta.core.communication.GlobalPreferencesManager +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.extension.getDistanceString +import dji.ux.beta.core.extension.getString +import dji.ux.beta.core.util.UnitConversionUtil +import dji.ux.beta.core.widget.distancehome.DistanceHomeWidget.ModelState +import dji.ux.beta.core.widget.distancehome.DistanceHomeWidget.ModelState.DistanceHomeStateUpdated +import dji.ux.beta.core.widget.distancehome.DistanceHomeWidget.ModelState.ProductConnected +import dji.ux.beta.core.widget.distancehome.DistanceHomeWidgetModel.DistanceHomeState +import java.text.DecimalFormat + +/** + * Widget displays the distance between the current location of the aircraft + * and the recorded home point. + */ +open class DistanceHomeWidget @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + widgetTheme: Int = 0 +) : BaseTelemetryWidget( + context, + attrs, + defStyleAttr, + WidgetType.TEXT, + widgetTheme, + R.style.UXSDKDistanceHomeWidget +) { + + //region Fields + override val metricDecimalFormat: DecimalFormat = DecimalFormat("###0.0") + + override val imperialDecimalFormat: DecimalFormat = DecimalFormat("###0") + + private val widgetModel: DistanceHomeWidgetModel by lazy { + DistanceHomeWidgetModel( + DJISDKModel.getInstance(), + ObservableInMemoryKeyedStore.getInstance(), + GlobalPreferencesManager.getInstance()) + } + //endregion + + //region Lifecycle + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + if (!isInEditMode) { + widgetModel.setup() + } + } + + override fun onDetachedFromWindow() { + if (!isInEditMode) { + widgetModel.cleanup() + } + super.onDetachedFromWindow() + } + + override fun reactToModelChanges() { + addReaction(widgetModel.productConnection + .observeOn(SchedulerProvider.ui()) + .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) + addReaction(widgetModel.distanceHomeState + .observeOn(SchedulerProvider.ui()) + .subscribe { updateUI(it) }) + } + //endregion + + //region Reactions to model + private fun updateUI(distanceHomeState: DistanceHomeState) { + widgetStateDataProcessor.onNext(DistanceHomeStateUpdated(distanceHomeState)) + if (distanceHomeState is DistanceHomeState.CurrentDistanceToHome) { + if(distanceHomeState.unitType == UnitConversionUtil.UnitType.IMPERIAL) { + setValueTextViewMinWidthByText("8888") + } else { + setValueTextViewMinWidthByText("888.8") + } + valueString = getDecimalFormat(distanceHomeState.unitType) + .format(distanceHomeState.distance).toString() + unitString = getDistanceString(distanceHomeState.unitType) + } else { + valueString = getString(R.string.uxsdk_string_default_value) + unitString = null + } + } + //endregion + + //region customizations + override fun getIdealDimensionRatioString(): String? = null + + + override val widgetSizeDescription: WidgetSizeDescription = + WidgetSizeDescription(WidgetSizeDescription.SizeType.OTHER, + widthDimension = WidgetSizeDescription.Dimension.EXPAND, + heightDimension = WidgetSizeDescription.Dimension.WRAP) + + //endregion + + //region Hooks + /** + * Get the [ModelState] updates + */ + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { + return super.getWidgetStateUpdate() + } + + /** + * Class defines widget state updates + */ + sealed class ModelState { + /** + * Product connection update + */ + data class ProductConnected(val boolean: Boolean) : ModelState() + + /** + * Distance to home state update + */ + data class DistanceHomeStateUpdated(val distanceHomeState: DistanceHomeState) : ModelState() + } + //endregion + +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancehome/DistanceHomeWidgetModel.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancehome/DistanceHomeWidgetModel.java deleted file mode 100644 index a2fead8f..00000000 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancehome/DistanceHomeWidgetModel.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * 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.ux.beta.core.widget.distancehome; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import dji.keysdk.FlightControllerKey; -import dji.thirdparty.io.reactivex.Flowable; -import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.GlobalPreferencesInterface; -import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.GlobalPreferenceKeys; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; -import dji.ux.beta.core.base.uxsdkkeys.UXKey; -import dji.ux.beta.core.base.uxsdkkeys.UXKeys; -import dji.ux.beta.core.util.DataProcessor; -import dji.ux.beta.core.util.LocationUtil; -import dji.ux.beta.core.util.UnitConversionUtil; - -/** - * Widget Model for the {@link DistanceHomeWidget} used to define - * the underlying logic and communication - */ -public class DistanceHomeWidgetModel extends WidgetModel { - - //region Fields - private final GlobalPreferencesInterface preferencesManager; - private final DataProcessor distanceFromHomeProcessor; - private final DataProcessor aircraftLatitudeProcessor; - private final DataProcessor aircraftLongitudeProcessor; - private final DataProcessor homeLatitudeProcessor; - private final DataProcessor homeLongitudeProcessor; - private final DataProcessor unitTypeProcessor; - //endregion - - //region Constructor - public DistanceHomeWidgetModel(@NonNull DJISDKModel djiSdkModel, - @NonNull ObservableInMemoryKeyedStore keyedStore, - @Nullable GlobalPreferencesInterface preferencesManager) { - super(djiSdkModel, keyedStore); - this.preferencesManager = preferencesManager; - distanceFromHomeProcessor = DataProcessor.create(0.0f); - aircraftLatitudeProcessor = DataProcessor.create(0.0); - aircraftLongitudeProcessor = DataProcessor.create(0.0); - homeLatitudeProcessor = DataProcessor.create(0.0); - homeLongitudeProcessor = DataProcessor.create(0.0); - unitTypeProcessor = DataProcessor.create(UnitConversionUtil.UnitType.METRIC); - if (preferencesManager != null) { - unitTypeProcessor.onNext(preferencesManager.getUnitType()); - } - } - //endregion - - //region Data - - /** - * Get the distance of the aircraft from the home location - * - * @return Flowable for the DataProcessor that user should subscribe to. - */ - public Flowable getDistanceFromHome() { - return distanceFromHomeProcessor.toFlowable(); - } - - /** - * Get the unit type of the distance value received. - * - * @return Flowable for the DataProcessor that user should subscribe to. - */ - public Flowable getUnitType() { - return unitTypeProcessor.toFlowable(); - } - //endregion - - //region Lifecycle - @Override - protected void inSetup() { - FlightControllerKey aircraftLatitudeKey = - FlightControllerKey.create(FlightControllerKey.AIRCRAFT_LOCATION_LATITUDE); - FlightControllerKey aircraftLongitudeKey = - FlightControllerKey.create(FlightControllerKey.AIRCRAFT_LOCATION_LONGITUDE); - FlightControllerKey homeLatitudeKey = FlightControllerKey.create(FlightControllerKey.HOME_LOCATION_LATITUDE); - FlightControllerKey homeLongitudeKey = FlightControllerKey.create(FlightControllerKey.HOME_LOCATION_LONGITUDE); - - bindDataProcessor(aircraftLatitudeKey, aircraftLatitudeProcessor); - bindDataProcessor(aircraftLongitudeKey, aircraftLongitudeProcessor); - bindDataProcessor(homeLatitudeKey, homeLatitudeProcessor); - bindDataProcessor(homeLongitudeKey, homeLongitudeProcessor); - - UXKey unitKey = UXKeys.create(GlobalPreferenceKeys.UNIT_TYPE); - bindDataProcessor(unitKey, unitTypeProcessor); - - if (preferencesManager != null) { - preferencesManager.setUpListener(); - } - } - - @Override - protected void inCleanup() { - if (preferencesManager != null) { - preferencesManager.cleanup(); - } - } - - @Override - protected void updateStates() { - // Check if location coordinates are valid and update - if (LocationUtil.checkLatitude(aircraftLatitudeProcessor.getValue()) - && LocationUtil.checkLongitude(aircraftLongitudeProcessor.getValue()) - && LocationUtil.checkLatitude(homeLatitudeProcessor.getValue()) - && LocationUtil.checkLongitude(homeLongitudeProcessor.getValue())) { - convertValueByUnit(LocationUtil.distanceBetween(homeLatitudeProcessor.getValue(), - homeLongitudeProcessor.getValue(), - aircraftLatitudeProcessor.getValue(), - aircraftLongitudeProcessor.getValue())); - } - } - //endregion - - //region Helpers - private void convertValueByUnit(float distanceFromHome) { - if (unitTypeProcessor.getValue() == UnitConversionUtil.UnitType.IMPERIAL) { - distanceFromHomeProcessor.onNext(UnitConversionUtil.convertMetersToFeet(distanceFromHome)); - } else { - distanceFromHomeProcessor.onNext(distanceFromHome); - } - } - //endregion -} diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancehome/DistanceHomeWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancehome/DistanceHomeWidgetModel.kt new file mode 100644 index 00000000..698b2135 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancehome/DistanceHomeWidgetModel.kt @@ -0,0 +1,122 @@ +/* + * 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.ux.beta.core.widget.distancehome + +import dji.keysdk.FlightControllerKey +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.WidgetModel +import dji.ux.beta.core.communication.GlobalPreferenceKeys +import dji.ux.beta.core.communication.GlobalPreferencesInterface +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.extension.toDistance +import dji.ux.beta.core.util.DataProcessor +import dji.ux.beta.core.util.LocationUtil.* +import dji.ux.beta.core.util.UnitConversionUtil +import dji.ux.beta.core.widget.distancehome.DistanceHomeWidgetModel.DistanceHomeState.CurrentDistanceToHome + +/** + * Widget Model for the [DistanceHomeWidget] used to define + * the underlying logic and communication + */ +class DistanceHomeWidgetModel(djiSdkModel: DJISDKModel, + keyedStore: ObservableInMemoryKeyedStore, + private val preferencesManager: GlobalPreferencesInterface? +) : WidgetModel(djiSdkModel, keyedStore) { + + private val aircraftLatitudeProcessor: DataProcessor = DataProcessor.create(0.0) + private val aircraftLongitudeProcessor: DataProcessor = DataProcessor.create(0.0) + private val homeLatitudeProcessor: DataProcessor = DataProcessor.create(0.0) + private val homeLongitudeProcessor: DataProcessor = DataProcessor.create(0.0) + private val unitTypeDataProcessor: DataProcessor = DataProcessor.create(UnitConversionUtil.UnitType.METRIC) + private val distanceHomeStateProcessor: DataProcessor = DataProcessor.create(DistanceHomeState.ProductDisconnected) + + /** + * Value of the distance to home state of the aircraft + */ + val distanceHomeState: Flowable + get() = distanceHomeStateProcessor.toFlowable() + + override fun inSetup() { + val aircraftLatitudeKey = FlightControllerKey.create(FlightControllerKey.AIRCRAFT_LOCATION_LATITUDE) + val aircraftLongitudeKey = FlightControllerKey.create(FlightControllerKey.AIRCRAFT_LOCATION_LONGITUDE) + val homeLatitudeKey = FlightControllerKey.create(FlightControllerKey.HOME_LOCATION_LATITUDE) + val homeLongitudeKey = FlightControllerKey.create(FlightControllerKey.HOME_LOCATION_LONGITUDE) + + bindDataProcessor(aircraftLatitudeKey, aircraftLatitudeProcessor) + bindDataProcessor(aircraftLongitudeKey, aircraftLongitudeProcessor) + bindDataProcessor(homeLatitudeKey, homeLatitudeProcessor) + bindDataProcessor(homeLongitudeKey, homeLongitudeProcessor) + + val unitTypeKey = GlobalPreferenceKeys.create(GlobalPreferenceKeys.UNIT_TYPE) + bindDataProcessor(unitTypeKey, unitTypeDataProcessor) + preferencesManager?.setUpListener() + preferencesManager?.let { unitTypeDataProcessor.onNext(it.unitType) } + } + + override fun updateStates() { + if (productConnectionProcessor.value) { + if (checkLatitude(aircraftLatitudeProcessor.value) + && checkLongitude(aircraftLongitudeProcessor.value) + && checkLatitude(homeLatitudeProcessor.value) + && checkLongitude(homeLongitudeProcessor.value)) { + distanceHomeStateProcessor.onNext(CurrentDistanceToHome( + distanceBetween(homeLatitudeProcessor.value, + homeLongitudeProcessor.value, + aircraftLatitudeProcessor.value, + aircraftLongitudeProcessor.value).toDistance(unitTypeDataProcessor.value), + unitTypeDataProcessor.value)) + } else { + distanceHomeStateProcessor.onNext(DistanceHomeState.LocationUnavailable) + } + } else { + distanceHomeStateProcessor.onNext(DistanceHomeState.ProductDisconnected) + } + } + + override fun inCleanup() { + preferencesManager?.cleanup() + } + + /** + * Class to represent states distance of aircraft from the home point + */ + sealed class DistanceHomeState { + /** + * Product is disconnected + */ + object ProductDisconnected : DistanceHomeState() + + /** + * Product is connected but gps location fix is unavailable + */ + object LocationUnavailable : DistanceHomeState() + + /** + * Reflecting the distance to the home point + */ + data class CurrentDistanceToHome(val distance: Float, val unitType: UnitConversionUtil.UnitType) : DistanceHomeState() + + } +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancerc/DistanceRCWidget.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancerc/DistanceRCWidget.java deleted file mode 100644 index 7866b32e..00000000 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancerc/DistanceRCWidget.java +++ /dev/null @@ -1,520 +0,0 @@ -/* - * 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.ux.beta.core.widget.distancerc; - -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.widget.TextView; - -import androidx.annotation.ColorInt; -import androidx.annotation.Dimension; -import androidx.annotation.DrawableRes; -import androidx.annotation.FloatRange; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StyleRes; - -import java.text.DecimalFormat; - -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers; -import dji.ux.beta.R; -import dji.ux.beta.core.base.ConstraintLayoutWidget; -import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.GlobalPreferencesInterface; -import dji.ux.beta.core.base.GlobalPreferencesManager; -import dji.ux.beta.core.base.uxsdkkeys.GlobalPreferenceKeys; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; -import dji.ux.beta.core.util.DisplayUtil; -import dji.ux.beta.core.util.UnitConversionUtil; - -/** - * Shows the distance between the current location of the aircraft - * and the RC (pilot). - * Uses the unit set in the UNIT_TYPE global preferences - * {@link GlobalPreferencesInterface#getUnitType()} and the - * {@link GlobalPreferenceKeys#UNIT_TYPE} UX Key - * and defaults to meters. - */ -public class DistanceRCWidget extends ConstraintLayoutWidget { - //region Fields - private static final int EMS = 3; - private static DecimalFormat decimalFormat = new DecimalFormat("###0.0"); - private TextView distanceRCTitleTextView; - private TextView distanceRCValueTextView; - private TextView distanceRCUnitTextView; - private DistanceRCWidgetModel widgetModel; - //endregion - - //region Constructors - public DistanceRCWidget(Context context) { - super(context); - } - - public DistanceRCWidget(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public DistanceRCWidget(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_base_dashboard_text_only, this); - distanceRCTitleTextView = findViewById(R.id.textview_title); - distanceRCValueTextView = findViewById(R.id.textview_value); - distanceRCUnitTextView = findViewById(R.id.textview_unit); - - if (!isInEditMode()) { - widgetModel = new DistanceRCWidgetModel(DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - GlobalPreferencesManager.getInstance()); - distanceRCTitleTextView.setText(getResources().getString(R.string.uxsdk_distance_rc_title)); - distanceRCValueTextView.setMinEms(EMS); - } - - if (attrs != null) { - initAttributes(context, attrs); - } - } - //endregion - - //region Lifecycle - @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.getDistanceFromRC() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::updateValueText)); - addReaction(widgetModel.getUnitType() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::updateUnitText)); - } - //endregion - - //region Reactions to model - private void updateValueText(@FloatRange(from = 0.0f) float distanceFromRC) { - distanceRCValueTextView.setText(decimalFormat.format(distanceFromRC)); - } - - private void updateUnitText(UnitConversionUtil.UnitType unitType) { - if (unitType == UnitConversionUtil.UnitType.IMPERIAL) { - distanceRCUnitTextView.setText(getResources().getString(R.string.uxsdk_unit_feet)); - } else { - distanceRCUnitTextView.setText(getResources().getString(R.string.uxsdk_unit_meters)); - } - } - //endregion - - //region Customization - @NonNull - @Override - public String getIdealDimensionRatioString() { - return getResources().getString(R.string.uxsdk_widget_base_dashboard_distance_ratio); - } - //endregion - - //region Customization Helpers - - /** - * Set text appearance of the distance from RC title text view - * - * @param textAppearance Style resource for text appearance - */ - public void setDistanceRCTitleTextAppearance(@StyleRes int textAppearance) { - distanceRCTitleTextView.setTextAppearance(getContext(), textAppearance); - } - - /** - * Get current text color state list of the distance from RC title text view - * - * @return ColorStateList resource - */ - @Nullable - public ColorStateList getDistanceRCTitleTextColors() { - return distanceRCTitleTextView.getTextColors(); - } - - /** - * Get current text color of the distance from RC title text view - * - * @return color integer resource - */ - @ColorInt - public int getDistanceRCTitleTextColor() { - return distanceRCTitleTextView.getCurrentTextColor(); - } - - /** - * Set text color state list for the distance from RC title text view - * - * @param colorStateList ColorStateList resource - */ - public void setDistanceRCTitleTextColor(@NonNull ColorStateList colorStateList) { - distanceRCTitleTextView.setTextColor(colorStateList); - } - - /** - * Set the text color for the distance from RC title text view - * - * @param color color integer resource - */ - public void setDistanceRCTitleTextColor(@ColorInt int color) { - distanceRCTitleTextView.setTextColor(color); - } - - /** - * Get current text size of the distance from RC title text view - * - * @return text size of the text view - */ - @Dimension - public float getDistanceRCTitleTextSize() { - return distanceRCTitleTextView.getTextSize(); - } - - /** - * Set the text size of the distance from RC title text view - * - * @param textSize text size float value - */ - public void setDistanceRCTitleTextSize(@Dimension float textSize) { - distanceRCTitleTextView.setTextSize(textSize); - } - - /** - * Get current background of the distance from RC title text view - * - * @return Drawable resource of the background - */ - @Nullable - public Drawable getDistanceRCTitleTextBackground() { - return distanceRCTitleTextView.getBackground(); - } - - /** - * Set the background of the distance from RC title text view - * - * @param drawable Drawable resource for the background - */ - public void setDistanceRCTitleTextBackground(@Nullable Drawable drawable) { - distanceRCTitleTextView.setBackground(drawable); - } - - /** - * Set the resource ID for the background of the distance from RC title text view - * - * @param resourceId Integer ID of the text view's background resource - */ - public void setDistanceRCTitleTextBackground(@DrawableRes int resourceId) { - distanceRCTitleTextView.setBackgroundResource(resourceId); - } - - /** - * Set text appearance of the distance from RC value text view - * - * @param textAppearance Style resource for text appearance - */ - public void setDistanceRCValueTextAppearance(@StyleRes int textAppearance) { - distanceRCValueTextView.setTextAppearance(getContext(), textAppearance); - } - - /** - * Get current text color state list of the distance from RC value text view - * - * @return ColorStateList resource - */ - @Nullable - public ColorStateList getDistanceRCValueTextColors() { - return distanceRCValueTextView.getTextColors(); - } - - /** - * Get current text color of the distance from RC value text view - * - * @return color integer resource - */ - @ColorInt - public int getDistanceRCValueTextColor() { - return distanceRCValueTextView.getCurrentTextColor(); - } - - /** - * Set text color state list for the distance from RC value text view - * - * @param colorStateList ColorStateList resource - */ - public void setDistanceRCValueTextColor(@NonNull ColorStateList colorStateList) { - distanceRCValueTextView.setTextColor(colorStateList); - } - - /** - * Set the text color for the distance from RC value text view - * - * @param color color integer resource - */ - public void setDistanceRCValueTextColor(@ColorInt int color) { - distanceRCValueTextView.setTextColor(color); - } - - /** - * Get current text size of the distance from RC value text view - * - * @return text size of the text view - */ - @Dimension - public float getDistanceRCValueTextSize() { - return distanceRCValueTextView.getTextSize(); - } - - /** - * Set the text size of the distance from RC value text view - * - * @param textSize text size float value - */ - public void setDistanceRCValueTextSize(@Dimension float textSize) { - distanceRCValueTextView.setTextSize(textSize); - } - - /** - * Get current background of the distance from RC value text view - * - * @return Drawable resource of the background - */ - @Nullable - public Drawable getDistanceRCValueTextBackground() { - return distanceRCValueTextView.getBackground(); - } - - /** - * Set the background for the distance from RC value text view - * - * @param drawable Drawable resource for the background - */ - public void setDistanceRCValueTextBackground(@Nullable Drawable drawable) { - distanceRCValueTextView.setBackground(drawable); - } - - /** - * Set the resource ID for the background of the distance from RC value text view - * - * @param resourceId Integer ID of the text view's background resource - */ - public void setDistanceRCValueTextBackground(@DrawableRes int resourceId) { - distanceRCValueTextView.setBackgroundResource(resourceId); - } - - /** - * Set text appearance of the distance from RC unit text view - * - * @param textAppearance Style resource for text appearance - */ - public void setDistanceRCUnitTextAppearance(@StyleRes int textAppearance) { - distanceRCUnitTextView.setTextAppearance(getContext(), textAppearance); - } - - /** - * Get current text color state list of the distance from RC unit text view - * - * @return ColorStateList resource - */ - @Nullable - public ColorStateList getDistanceRCUnitTextColors() { - return distanceRCUnitTextView.getTextColors(); - } - - /** - * Get current text color of the distance from RC unit text view - * - * @return color integer resource - */ - @ColorInt - public int getDistanceRCUnitTextColor() { - return distanceRCUnitTextView.getCurrentTextColor(); - } - - /** - * Set text color state list for the distance from RC unit text view - * - * @param colorStateList ColorStateList resource - */ - public void setDistanceRCUnitTextColor(@NonNull ColorStateList colorStateList) { - distanceRCUnitTextView.setTextColor(colorStateList); - } - - /** - * Set the text color for the distance from RC unit text view - * - * @param color color integer resource - */ - public void setDistanceRCUnitTextColor(@ColorInt int color) { - distanceRCUnitTextView.setTextColor(color); - } - - /** - * Get current text size of the distance from RC unit text view - * - * @return text size of the text view - */ - @Dimension - public float getDistanceRCUnitTextSize() { - return distanceRCUnitTextView.getTextSize(); - } - - /** - * Set the text size of the distance from RC unit text view - * - * @param textSize text size float value - */ - public void setDistanceRCUnitTextSize(@Dimension float textSize) { - distanceRCUnitTextView.setTextSize(textSize); - } - - /** - * Get current background of the distance from RC unit text view - * - * @return Drawable resource of the background - */ - @Nullable - public Drawable getDistanceRCUnitTextBackground() { - return distanceRCUnitTextView.getBackground(); - } - - /** - * Set the background for the distance from RC unit text view - * - * @param drawable Drawable resource for the background - */ - public void setDistanceRCUnitTextBackground(@Nullable Drawable drawable) { - distanceRCUnitTextView.setBackground(drawable); - } - - /** - * Set the resource ID for the background of the distance from RC unit text view - * - * @param resourceId Integer ID of the text view's background resource - */ - public void setDistanceRCUnitTextBackground(@DrawableRes int resourceId) { - distanceRCUnitTextView.setBackgroundResource(resourceId); - } - - //Initialize all customizable attributes - private void initAttributes(@NonNull Context context, @NonNull AttributeSet attrs) { - TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DistanceRCWidget); - int distanceRCTitleTextAppearanceId = - typedArray.getResourceId(R.styleable.DistanceRCWidget_uxsdk_distanceRCTitleTextAppearance, - INVALID_RESOURCE); - if (distanceRCTitleTextAppearanceId != INVALID_RESOURCE) { - setDistanceRCTitleTextAppearance(distanceRCTitleTextAppearanceId); - } - - float distanceRCTitleTextSize = - typedArray.getDimension(R.styleable.DistanceRCWidget_uxsdk_distanceRCTitleTextSize, INVALID_RESOURCE); - if (distanceRCTitleTextSize != INVALID_RESOURCE) { - setDistanceRCTitleTextSize(DisplayUtil.pxToSp(context, distanceRCTitleTextSize)); - } - - int distanceRCTitleTextColor = - typedArray.getColor(R.styleable.DistanceRCWidget_uxsdk_distanceRCTitleTextColor, INVALID_COLOR); - if (distanceRCTitleTextColor != INVALID_COLOR) { - setDistanceRCTitleTextColor(distanceRCTitleTextColor); - } - - Drawable distanceRCTitleTextBackgroundDrawable = - typedArray.getDrawable(R.styleable.DistanceRCWidget_uxsdk_distanceRCTitleBackgroundDrawable); - if (distanceRCTitleTextBackgroundDrawable != null) { - setDistanceRCTitleTextBackground(distanceRCTitleTextBackgroundDrawable); - } - - int distanceRCValueTextAppearanceId = - typedArray.getResourceId(R.styleable.DistanceRCWidget_uxsdk_distanceRCValueTextAppearance, - INVALID_RESOURCE); - if (distanceRCValueTextAppearanceId != INVALID_RESOURCE) { - setDistanceRCValueTextAppearance(distanceRCValueTextAppearanceId); - } - - float distanceRCValueTextSize = - typedArray.getDimension(R.styleable.DistanceRCWidget_uxsdk_distanceRCValueTextSize, INVALID_RESOURCE); - if (distanceRCValueTextSize != INVALID_RESOURCE) { - setDistanceRCValueTextSize(DisplayUtil.pxToSp(context, distanceRCValueTextSize)); - } - - int distanceRCValueTextColor = - typedArray.getColor(R.styleable.DistanceRCWidget_uxsdk_distanceRCValueTextColor, INVALID_COLOR); - if (distanceRCValueTextColor != INVALID_COLOR) { - setDistanceRCValueTextColor(distanceRCValueTextColor); - } - - Drawable distanceRCValueTextBackgroundDrawable = - typedArray.getDrawable(R.styleable.DistanceRCWidget_uxsdk_distanceRCValueBackgroundDrawable); - if (distanceRCValueTextBackgroundDrawable != null) { - setDistanceRCValueTextBackground(distanceRCValueTextBackgroundDrawable); - } - - int distanceRCUnitTextAppearanceId = - typedArray.getResourceId(R.styleable.DistanceRCWidget_uxsdk_distanceRCUnitTextAppearance, - INVALID_RESOURCE); - if (distanceRCUnitTextAppearanceId != INVALID_RESOURCE) { - setDistanceRCUnitTextAppearance(distanceRCUnitTextAppearanceId); - } - - float distanceRCUnitTextSize = - typedArray.getDimension(R.styleable.DistanceRCWidget_uxsdk_distanceRCUnitTextSize, INVALID_RESOURCE); - if (distanceRCUnitTextSize != INVALID_RESOURCE) { - setDistanceRCUnitTextSize(DisplayUtil.pxToSp(context, distanceRCUnitTextSize)); - } - - int distanceRCUnitTextColor = - typedArray.getColor(R.styleable.DistanceRCWidget_uxsdk_distanceRCUnitTextColor, INVALID_COLOR); - if (distanceRCUnitTextColor != INVALID_COLOR) { - setDistanceRCUnitTextColor(distanceRCUnitTextColor); - } - - Drawable distanceRCUnitTextBackgroundDrawable = - typedArray.getDrawable(R.styleable.DistanceRCWidget_uxsdk_distanceRCUnitBackgroundDrawable); - if (distanceRCUnitTextBackgroundDrawable != null) { - setDistanceRCUnitTextBackground(distanceRCUnitTextBackgroundDrawable); - } - typedArray.recycle(); - } - //endregion -} diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancerc/DistanceRCWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancerc/DistanceRCWidget.kt new file mode 100644 index 00000000..700f7d2b --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancerc/DistanceRCWidget.kt @@ -0,0 +1,154 @@ +/* + * 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.ux.beta.core.widget.distancerc + +import android.content.Context +import android.util.AttributeSet +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.R +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.SchedulerProvider +import dji.ux.beta.core.base.WidgetSizeDescription +import dji.ux.beta.core.base.widget.BaseTelemetryWidget +import dji.ux.beta.core.communication.GlobalPreferencesManager +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.extension.getDistanceString +import dji.ux.beta.core.extension.getString +import dji.ux.beta.core.util.UnitConversionUtil +import dji.ux.beta.core.widget.distancerc.DistanceRCWidget.ModelState +import dji.ux.beta.core.widget.distancerc.DistanceRCWidget.ModelState.ProductConnected +import dji.ux.beta.core.widget.distancerc.DistanceRCWidgetModel.DistanceRCState +import java.text.DecimalFormat + +/** + * Widget displays the distance between the current location of the aircraft + * and the remote controller location. + */ +open class DistanceRCWidget @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + widgetTheme: Int = 0 +) : BaseTelemetryWidget( + context, + attrs, + defStyleAttr, + WidgetType.TEXT, + widgetTheme, + R.style.UXSDKDistanceRCWidget +) { + + //region Fields + override val metricDecimalFormat: DecimalFormat = DecimalFormat("###0.0") + + override val imperialDecimalFormat: DecimalFormat = DecimalFormat("###0") + + private val widgetModel: DistanceRCWidgetModel by lazy { + DistanceRCWidgetModel( + DJISDKModel.getInstance(), + ObservableInMemoryKeyedStore.getInstance(), + GlobalPreferencesManager.getInstance()) + } + //endregion + + //region Lifecycle + override fun onAttachedToWindow() { + super.onAttachedToWindow() + if (!isInEditMode) { + widgetModel.setup() + } + } + + override fun onDetachedFromWindow() { + if (!isInEditMode) { + widgetModel.cleanup() + } + super.onDetachedFromWindow() + } + + override fun reactToModelChanges() { + addReaction(widgetModel.productConnection + .observeOn(SchedulerProvider.ui()) + .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) + addReaction(widgetModel.distanceRCState + .observeOn(SchedulerProvider.ui()) + .subscribe { updateUI(it) }) + } + //endregion + + //region Reactions to model + private fun updateUI(distanceRCState: DistanceRCState) { + widgetStateDataProcessor.onNext(ModelState.DistanceRCStateUpdated(distanceRCState)) + if (distanceRCState is DistanceRCState.CurrentDistanceToRC) { + if(distanceRCState.unitType == UnitConversionUtil.UnitType.IMPERIAL) { + setValueTextViewMinWidthByText("8888") + } else { + setValueTextViewMinWidthByText("888.8") + } + valueString = getDecimalFormat(distanceRCState.unitType) + .format(distanceRCState.distance).toString() + unitString = getDistanceString(distanceRCState.unitType) + } else { + valueString = getString(R.string.uxsdk_string_default_value) + unitString = null + } + } + //endregion + + //region customizations + override fun getIdealDimensionRatioString(): String? = null + + + override val widgetSizeDescription: WidgetSizeDescription = + WidgetSizeDescription(WidgetSizeDescription.SizeType.OTHER, + widthDimension = WidgetSizeDescription.Dimension.EXPAND, + heightDimension = WidgetSizeDescription.Dimension.WRAP) + + //endregion + + //region Hooks + /** + * Get the [ModelState] updates + */ + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { + return super.getWidgetStateUpdate() + } + + /** + * Class defines widget state updates + */ + sealed class ModelState { + /** + * Product connection update + */ + data class ProductConnected(val boolean: Boolean) : ModelState() + + /** + * Distance RC model state + */ + data class DistanceRCStateUpdated(val distanceRCState: DistanceRCState) : ModelState() + } + //endregion +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancerc/DistanceRCWidgetModel.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancerc/DistanceRCWidgetModel.java deleted file mode 100644 index 26e7a718..00000000 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancerc/DistanceRCWidgetModel.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * 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.ux.beta.core.widget.distancerc; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import dji.common.remotecontroller.GPSData; -import dji.keysdk.FlightControllerKey; -import dji.keysdk.RemoteControllerKey; -import dji.thirdparty.io.reactivex.Flowable; -import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.GlobalPreferencesInterface; -import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.GlobalPreferenceKeys; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; -import dji.ux.beta.core.base.uxsdkkeys.UXKey; -import dji.ux.beta.core.base.uxsdkkeys.UXKeys; -import dji.ux.beta.core.util.DataProcessor; -import dji.ux.beta.core.util.LocationUtil; -import dji.ux.beta.core.util.UnitConversionUtil; - -/** - * Widget Model for the {@link DistanceRCWidget} used to define - * the underlying logic and communication - */ -public class DistanceRCWidgetModel extends WidgetModel { - - //region Fields - private final GlobalPreferencesInterface preferencesManager; - private final DataProcessor distanceFromRCProcessor; - private final DataProcessor aircraftLatitudeProcessor; - private final DataProcessor aircraftLongitudeProcessor; - private final DataProcessor rcGPSDataProcessor; - private final DataProcessor unitTypeProcessor; - - //endregion - - //region Constructor - public DistanceRCWidgetModel(@NonNull DJISDKModel djiSdkModel, - @NonNull ObservableInMemoryKeyedStore keyedStore, - @Nullable GlobalPreferencesInterface preferencesManager) { - super(djiSdkModel, keyedStore); - this.preferencesManager = preferencesManager; - distanceFromRCProcessor = DataProcessor.create(0.0f); - aircraftLatitudeProcessor = DataProcessor.create(0.0); - aircraftLongitudeProcessor = DataProcessor.create(0.0); - rcGPSDataProcessor = DataProcessor.create(new GPSData.Builder().build()); - unitTypeProcessor = DataProcessor.create(UnitConversionUtil.UnitType.METRIC); - if (preferencesManager != null) { - unitTypeProcessor.onNext(preferencesManager.getUnitType()); - } - } - //endregion - - //region Data - - /** - * Get the distance of the aircraft from the RC (pilot). - * - * @return Flowable for the DataProcessor that user should subscribe to. - */ - public Flowable getDistanceFromRC() { - return distanceFromRCProcessor.toFlowable(); - } - - /** - * Get the unit type of the distance value received. - * - * @return Flowable for the DataProcessor that user should subscribe to. - */ - public Flowable getUnitType() { - return unitTypeProcessor.toFlowable(); - } - //endregion - - //region Lifecycle - @Override - protected void inSetup() { - FlightControllerKey aircraftLatitudeKey = - FlightControllerKey.create(FlightControllerKey.AIRCRAFT_LOCATION_LATITUDE); - FlightControllerKey aircraftLongitudeKey = - FlightControllerKey.create(FlightControllerKey.AIRCRAFT_LOCATION_LONGITUDE); - RemoteControllerKey rcGPSDataKey = RemoteControllerKey.create(RemoteControllerKey.GPS_DATA); - - bindDataProcessor(aircraftLatitudeKey, aircraftLatitudeProcessor); - bindDataProcessor(aircraftLongitudeKey, aircraftLongitudeProcessor); - bindDataProcessor(rcGPSDataKey, rcGPSDataProcessor); - - UXKey unitKey = UXKeys.create(GlobalPreferenceKeys.UNIT_TYPE); - bindDataProcessor(unitKey, unitTypeProcessor); - - if (preferencesManager != null) { - preferencesManager.setUpListener(); - } - } - - @Override - protected void inCleanup() { - if (preferencesManager != null) { - preferencesManager.cleanup(); - } - } - - @Override - protected void updateStates() { - //Check if the GPS data is valid. The data is not valid if there are too few - //satellites or the signal strength is too low. - if (rcGPSDataProcessor.getValue().isValid()) { - double rcLatitude = rcGPSDataProcessor.getValue().getLocation().getLatitude(); - double rcLongitude = rcGPSDataProcessor.getValue().getLocation().getLongitude(); - if (LocationUtil.checkLatitude(aircraftLatitudeProcessor.getValue()) - && LocationUtil.checkLongitude(aircraftLongitudeProcessor.getValue()) - && LocationUtil.checkLatitude(rcLatitude) - && LocationUtil.checkLongitude(rcLongitude)) { - convertValueByUnit(LocationUtil.distanceBetween(rcLatitude, - rcLongitude, - aircraftLatitudeProcessor.getValue(), - aircraftLongitudeProcessor.getValue())); - } - } - } - //endregion - - //region Helpers - private void convertValueByUnit(float distanceFromRC) { - if (unitTypeProcessor.getValue() == UnitConversionUtil.UnitType.IMPERIAL) { - distanceFromRCProcessor.onNext(UnitConversionUtil.convertMetersToFeet(distanceFromRC)); - } else { - distanceFromRCProcessor.onNext(distanceFromRC); - } - } - //endregion -} diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancerc/DistanceRCWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancerc/DistanceRCWidgetModel.kt new file mode 100644 index 00000000..400d925a --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/distancerc/DistanceRCWidgetModel.kt @@ -0,0 +1,124 @@ +/* + * 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.ux.beta.core.widget.distancerc + +import dji.common.remotecontroller.GPSData +import dji.keysdk.FlightControllerKey +import dji.keysdk.RemoteControllerKey +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.WidgetModel +import dji.ux.beta.core.communication.GlobalPreferenceKeys +import dji.ux.beta.core.communication.GlobalPreferencesInterface +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.extension.toDistance +import dji.ux.beta.core.util.DataProcessor +import dji.ux.beta.core.util.LocationUtil +import dji.ux.beta.core.util.LocationUtil.distanceBetween +import dji.ux.beta.core.util.UnitConversionUtil +import dji.ux.beta.core.widget.distancerc.DistanceRCWidgetModel.DistanceRCState.* + +/** + * Widget Model for the [DistanceRCWidget] used to define + * the underlying logic and communication + */ +class DistanceRCWidgetModel(djiSdkModel: DJISDKModel, + keyedStore: ObservableInMemoryKeyedStore, + private val preferencesManager: GlobalPreferencesInterface? +) : WidgetModel(djiSdkModel, keyedStore) { + + private val rcGPSLocationProcessor: DataProcessor = DataProcessor.create(GPSData.Builder().build()) + private val unitTypeDataProcessor: DataProcessor = DataProcessor.create(UnitConversionUtil.UnitType.METRIC) + private val aircraftLatitudeProcessor: DataProcessor = DataProcessor.create(0.0) + private val aircraftLongitudeProcessor: DataProcessor = DataProcessor.create(0.0) + private val distanceRCStateProcessor: DataProcessor = DataProcessor.create(ProductDisconnected) + + /** + * Value of the distance to RC state of the aircraft + */ + val distanceRCState: Flowable + get() = distanceRCStateProcessor.toFlowable() + + override fun inSetup() { + val aircraftLatitudeKey = FlightControllerKey.create(FlightControllerKey.AIRCRAFT_LOCATION_LATITUDE) + val aircraftLongitudeKey = FlightControllerKey.create(FlightControllerKey.AIRCRAFT_LOCATION_LONGITUDE) + bindDataProcessor(aircraftLatitudeKey, aircraftLatitudeProcessor) + bindDataProcessor(aircraftLongitudeKey, aircraftLongitudeProcessor) + + val rcGPSKey = RemoteControllerKey.create(RemoteControllerKey.GPS_DATA) + bindDataProcessor(rcGPSKey, rcGPSLocationProcessor) + + val unitTypeKey = GlobalPreferenceKeys.create(GlobalPreferenceKeys.UNIT_TYPE) + bindDataProcessor(unitTypeKey, unitTypeDataProcessor) + preferencesManager?.setUpListener() + preferencesManager?.let { unitTypeDataProcessor.onNext(it.unitType) } + } + + override fun updateStates() { + if (productConnectionProcessor.value) { + if (LocationUtil.checkLatitude(aircraftLatitudeProcessor.value) + && LocationUtil.checkLongitude(aircraftLongitudeProcessor.value) + && rcGPSLocationProcessor.value.isValid) { + distanceRCStateProcessor.onNext(CurrentDistanceToRC( + distanceBetween(aircraftLatitudeProcessor.value, + aircraftLongitudeProcessor.value, + rcGPSLocationProcessor.value.location.latitude, + rcGPSLocationProcessor.value.location.longitude) + .toDistance(unitTypeDataProcessor.value), + unitTypeDataProcessor.value)) + } else { + distanceRCStateProcessor.onNext(LocationUnavailable) + } + } else { + distanceRCStateProcessor.onNext(ProductDisconnected) + } + + } + + override fun inCleanup() { + preferencesManager?.cleanup() + } + + /** + * Class to represent states distance of aircraft from the remote controller + */ + sealed class DistanceRCState { + /** + * Product is disconnected + */ + object ProductDisconnected : DistanceRCState() + + /** + * Product is connected but GPS location fix is unavailable + */ + object LocationUnavailable : DistanceRCState() + + /** + * Reflecting the distance to the remote controller + */ + data class CurrentDistanceToRC(val distance: Float, val unitType: UnitConversionUtil.UnitType) : DistanceRCState() + + } + +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/flightmode/FlightModeWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/flightmode/FlightModeWidget.kt index e4abbfe0..d58ba0f7 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/flightmode/FlightModeWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/flightmode/FlightModeWidget.kt @@ -37,18 +37,18 @@ import androidx.annotation.StyleRes import androidx.constraintlayout.widget.ConstraintSet import androidx.core.content.res.use import dji.thirdparty.io.reactivex.Flowable -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers +import dji.ux.beta.core.base.SchedulerProvider import dji.thirdparty.io.reactivex.functions.Consumer -import dji.ux.beta.R -import dji.ux.beta.core.base.ConstraintLayoutWidget +import dji.ux.beta.core.R import dji.ux.beta.core.base.DJISDKModel import dji.ux.beta.core.base.WidgetSizeDescription -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.* import dji.ux.beta.core.util.DisplayUtil -import dji.ux.beta.core.widget.flightmode.FlightModeWidget.FlightModeWidgetState -import dji.ux.beta.core.widget.flightmode.FlightModeWidget.FlightModeWidgetState.FlightModeTextUpdated -import dji.ux.beta.core.widget.flightmode.FlightModeWidget.FlightModeWidgetState.ProductConnected +import dji.ux.beta.core.widget.flightmode.FlightModeWidget.ModelState +import dji.ux.beta.core.widget.flightmode.FlightModeWidget.ModelState.FlightModeUpdated +import dji.ux.beta.core.widget.flightmode.FlightModeWidget.ModelState.ProductConnected import dji.ux.beta.core.widget.flightmode.FlightModeWidgetModel.FlightModeState private const val TAG = "FlightModeWidget" @@ -60,7 +60,7 @@ open class FlightModeWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ConstraintLayoutWidget(context, attrs, defStyleAttr) { +) : ConstraintLayoutWidget(context, attrs, defStyleAttr) { //region Fields private val iconImageView: ImageView = findViewById(R.id.imageview_flight_mode_icon) @@ -150,7 +150,7 @@ open class FlightModeWidget @JvmOverloads constructor( } //endregion - //region Constructors + //region Constructor override fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { inflate(context, R.layout.uxsdk_widget_flight_mode, this) } @@ -177,10 +177,10 @@ open class FlightModeWidget @JvmOverloads constructor( override fun reactToModelChanges() { addReaction(widgetModel.flightModeState - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { this.updateUI(it) }) addReaction(widgetModel.productConnection - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) } @@ -192,7 +192,7 @@ open class FlightModeWidget @JvmOverloads constructor( flightModeTextView.text = flightModeState.flightModeString iconImageView.setColorFilter(connectedStateIconColor, PorterDuff.Mode.SRC_IN) flightModeTextView.setTextColor(connectedStateTextColor) - widgetStateDataProcessor.onNext(FlightModeTextUpdated(flightModeState.flightModeString)) + widgetStateDataProcessor.onNext(FlightModeUpdated(flightModeState.flightModeString)) } else { flightModeTextView.text = getString(R.string.uxsdk_string_default_value) iconImageView.setColorFilter(disconnectedStateIconColor, PorterDuff.Mode.SRC_IN) @@ -207,7 +207,7 @@ open class FlightModeWidget @JvmOverloads constructor( private fun checkAndUpdateUI() { if (!isInEditMode) { addDisposable(widgetModel.flightModeState - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { this.updateUI(it) }, logErrorConsumer(TAG, "Update UI "))) } } @@ -321,27 +321,28 @@ open class FlightModeWidget @JvmOverloads constructor( } //endregion - //region hooks + //region Hooks /** - * Get the [FlightModeWidgetState] updates + * Get the [ModelState] updates */ - override fun getWidgetStateUpdate(): Flowable { + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { return super.getWidgetStateUpdate() } /** * Class defines the widget state updates */ - sealed class FlightModeWidgetState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : FlightModeWidgetState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * Flight mode text update */ - data class FlightModeTextUpdated(val flightModeText: String) : FlightModeWidgetState() + data class FlightModeUpdated(val flightModeText: String) : ModelState() } //endregion diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/flightmode/FlightModeWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/flightmode/FlightModeWidgetModel.kt index 00ffab7c..dc81504d 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/flightmode/FlightModeWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/flightmode/FlightModeWidgetModel.kt @@ -27,7 +27,7 @@ import dji.keysdk.FlightControllerKey import dji.thirdparty.io.reactivex.Flowable import dji.ux.beta.core.base.DJISDKModel import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.util.DataProcessor import dji.ux.beta.core.widget.flightmode.FlightModeWidgetModel.FlightModeState.FlightModeUpdated import dji.ux.beta.core.widget.flightmode.FlightModeWidgetModel.FlightModeState.ProductDisconnected diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/fpv/FPVWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/fpv/FPVWidget.kt index f72a0fbe..c0ea36b2 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/fpv/FPVWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/fpv/FPVWidget.kt @@ -37,6 +37,7 @@ import androidx.annotation.FloatRange import androidx.annotation.StyleRes import androidx.constraintlayout.widget.Guideline import androidx.core.content.res.use +import dji.common.camera.CameraVideoStreamSource import dji.common.camera.SettingsDefinitions import dji.keysdk.KeyManager import dji.log.DJILog @@ -44,20 +45,22 @@ import dji.sdk.camera.VideoFeeder.VideoDataListener import dji.sdk.codec.DJICodecManager import dji.sdk.util.VideoSizeCalculatorUtil import dji.thirdparty.io.reactivex.Flowable -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers +import dji.thirdparty.io.reactivex.functions.Action import dji.thirdparty.io.reactivex.functions.Consumer -import dji.ux.beta.R -import dji.ux.beta.core.base.ConstraintLayoutWidget +import dji.ux.beta.core.R import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.base.SchedulerProvider +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.* +import dji.ux.beta.core.module.FlatCameraModule import dji.ux.beta.core.ui.CenterPointView import dji.ux.beta.core.ui.GridLineView import dji.ux.beta.core.util.DisplayUtil import dji.ux.beta.core.util.SettingDefinitions import dji.ux.beta.core.util.SettingDefinitions.CameraSide -import dji.ux.beta.core.widget.fpv.FPVWidget.FPVWidgetState -import dji.ux.beta.core.widget.fpv.FPVWidget.FPVWidgetState.* +import dji.ux.beta.core.widget.fpv.FPVWidget.ModelState +import dji.ux.beta.core.widget.fpv.FPVWidget.ModelState.* import java.util.* private const val TAG = "FPVWidget" @@ -73,7 +76,7 @@ open class FPVWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ConstraintLayoutWidget(context, attrs, defStyleAttr), TextureView.SurfaceTextureListener { +) : ConstraintLayoutWidget(context, attrs, defStyleAttr), TextureView.SurfaceTextureListener { //region Fields private var codecManager: DJICodecManager? = null private val videoSizeCalculator: VideoSizeCalculatorUtil = VideoSizeCalculatorUtil() @@ -94,13 +97,14 @@ open class FPVWidget @JvmOverloads constructor( private val widgetModel by lazy { val videoDataListener = VideoDataListener { videoBuffer: ByteArray?, size: Int -> - widgetStateDataProcessor.onNext(VideoFeedUpdate(videoBuffer, size)) + widgetStateDataProcessor.onNext(VideoFeedUpdated(videoBuffer, size)) codecManager?.sendDataToDecoder(videoBuffer, size, videoFeed) } FPVWidgetModel(DJISDKModel.getInstance(), ObservableInMemoryKeyedStore.getInstance(), - videoDataListener) + videoDataListener, + FlatCameraModule()) } /** @@ -128,7 +132,7 @@ open class FPVWidget @JvmOverloads constructor( var isGridLinesEnabled = true set(isGridLinesEnabled) { field = isGridLinesEnabled - gridLineView.visibility = if (isGridLinesEnabled) View.VISIBLE else View.GONE + updateGridLineVisibility() } /** @@ -284,7 +288,7 @@ open class FPVWidget @JvmOverloads constructor( //endregion - //region Constructors + //region Constructor override fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { inflate(context, R.layout.uxsdk_widget_fpv, this) } @@ -319,7 +323,7 @@ open class FPVWidget @JvmOverloads constructor( override fun reactToModelChanges() { addReaction(widgetModel.model - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { if (codecManager == null) { videoSurface?.let { @@ -329,26 +333,27 @@ open class FPVWidget @JvmOverloads constructor( } }) addReaction(widgetModel.videoFeedSource - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { videoFeed: DJICodecManager.VideoSource -> - widgetStateDataProcessor.onNext(VideoFeedSourceUpdate(videoFeed)) + widgetStateDataProcessor.onNext(VideoFeedSourceUpdated(videoFeed)) this.videoFeed = videoFeed + updateGridLineVisibility() codecManager?.switchSource(videoFeed) }) addReaction(widgetModel.orientation - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { orientation: SettingsDefinitions.Orientation -> updateOrientation(orientation) }) addReaction(widgetModel.cameraName - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { cameraName: String -> updateCameraName(cameraName) }) addReaction(widgetModel.cameraSide - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { cameraSide: CameraSide -> updateCameraSide(cameraSide) }) addReaction(widgetModel.hasVideoViewChanged - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { delayCalculator() }) addReaction(widgetModel.productConnection - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { updateConnectionState(it) }) } @@ -421,6 +426,20 @@ open class FPVWidget @JvmOverloads constructor( return getString(R.string.uxsdk_widget_fpv_ratio) } + /** + * Set the [cameraVideoStreamSource] for multi-lens cameras. + * + * @return Disposable + */ + fun setCameraVideoStreamSource(cameraVideoStreamSource: CameraVideoStreamSource) { + addDisposable(widgetModel.setCameraVideoStreamSource(cameraVideoStreamSource) + .observeOn(SchedulerProvider.ui()) + .subscribe(Action { + // do nothing + }, logErrorConsumer(TAG, "set camera video stream source "))) + stateChangeCallback?.onStreamSourceChange(cameraVideoStreamSource) + } + /** * Sets a callback to retrieve the [DJICodecManager] object. * @@ -458,7 +477,7 @@ open class FPVWidget @JvmOverloads constructor( } gridLineView.adjustDimensions(relativeWidth, relativeHeight) stateChangeCallback?.onFPVSizeChange(FPVSize(relativeWidth, relativeHeight)) - widgetStateDataProcessor.onNext(FPVSizeUpdate(relativeWidth, relativeHeight)) + widgetStateDataProcessor.onNext(FPVSizeUpdated(relativeWidth, relativeHeight)) } private fun delayCalculator() { @@ -482,7 +501,7 @@ open class FPVWidget @JvmOverloads constructor( } private fun updateOrientation(orientation: SettingsDefinitions.Orientation) { - widgetStateDataProcessor.onNext(OrientationUpdate(orientation)) + widgetStateDataProcessor.onNext(OrientationUpdated(orientation)) videoSizeCalculator.setVideoIsRotated(orientation == SettingsDefinitions.Orientation.PORTRAIT) rotationAngle = if (orientation == SettingsDefinitions.Orientation.PORTRAIT) { PORTRAIT_ROTATION_ANGLE @@ -493,7 +512,7 @@ open class FPVWidget @JvmOverloads constructor( } private fun updateCameraName(cameraName: String) { - widgetStateDataProcessor.onNext(CameraNameUpdate(cameraName)) + widgetStateDataProcessor.onNext(CameraNameUpdated(cameraName)) cameraNameTextView.text = cameraName if (cameraName.isNotEmpty() && isCameraSourceNameVisible) { cameraNameTextView.visibility = View.VISIBLE @@ -504,7 +523,7 @@ open class FPVWidget @JvmOverloads constructor( } private fun updateCameraSide(cameraSide: CameraSide) { - widgetStateDataProcessor.onNext(CameraSideUpdate(cameraSide)) + widgetStateDataProcessor.onNext(CameraSideUpdated(cameraSide)) if (cameraSide == CameraSide.UNKNOWN) { cameraSideTextView.text = "" cameraSideTextView.visibility = View.GONE @@ -530,7 +549,7 @@ open class FPVWidget @JvmOverloads constructor( if (!isInEditMode) { addDisposable(widgetModel.cameraName .firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { cameraName: String -> updateCameraName(cameraName) }, logErrorConsumer(TAG, "updateCameraName"))) } @@ -540,11 +559,16 @@ open class FPVWidget @JvmOverloads constructor( if (!isInEditMode) { addDisposable(widgetModel.cameraSide .firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { cameraSide: CameraSide -> updateCameraSide(cameraSide) }, logErrorConsumer(TAG, "updateCameraSide"))) } } + + private fun updateGridLineVisibility() { + gridLineView.visibility = if (isGridLinesEnabled + && videoFeed != DJICodecManager.VideoSource.FPV) View.VISIBLE else View.GONE + } //endregion //region Customization helpers @@ -679,6 +703,11 @@ open class FPVWidget @JvmOverloads constructor( */ fun onCameraSideChange(cameraSide: CameraSide?) + /** + * Called when the camera stream source has changed + */ + fun onStreamSourceChange(streamSource: CameraVideoStreamSource?) + /** * Called when the size of the video feed has changed */ @@ -687,46 +716,48 @@ open class FPVWidget @JvmOverloads constructor( //endregion //region Hooks + /** - * Get the [FPVWidgetState] updates + * Get the [ModelState] updates */ - override fun getWidgetStateUpdate(): Flowable { + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { return super.getWidgetStateUpdate() } /** * Class defines the widget state updates */ - sealed class FPVWidgetState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : FPVWidgetState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * Orientation update */ - data class OrientationUpdate(val orientation: SettingsDefinitions.Orientation) : FPVWidgetState() + data class OrientationUpdated(val orientation: SettingsDefinitions.Orientation) : ModelState() /** * Video feed update */ - data class VideoFeedSourceUpdate(val videoFeed: DJICodecManager.VideoSource) : FPVWidgetState() + data class VideoFeedSourceUpdated(val videoFeed: DJICodecManager.VideoSource) : ModelState() /** * Video feed size update */ - data class FPVSizeUpdate(val width: Int, val height: Int) : FPVWidgetState() + data class FPVSizeUpdated(val width: Int, val height: Int) : ModelState() /** * Camera name update */ - data class CameraNameUpdate(val cameraName: String) : FPVWidgetState() + data class CameraNameUpdated(val cameraName: String) : ModelState() /** * Camera side update */ - data class CameraSideUpdate(val cameraSide: CameraSide) : FPVWidgetState() + data class CameraSideUpdated(val cameraSide: CameraSide) : ModelState() /** * Video feed update @@ -734,11 +765,11 @@ open class FPVWidget @JvmOverloads constructor( * @property videoBuffer H.264 or H.265 raw video data. See [SettingsDefinitions.VideoFileCompressionStandard] * @property size The data size */ - data class VideoFeedUpdate(val videoBuffer: ByteArray?, val size: Int) : FPVWidgetState() { + data class VideoFeedUpdated(val videoBuffer: ByteArray?, val size: Int) : ModelState() { override fun equals(other: Any?): Boolean { if (this === other) return true if (other?.javaClass != javaClass) return false - other as VideoFeedUpdate + other as VideoFeedUpdated return Arrays.equals(videoBuffer, other.videoBuffer) && size == other.size } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/fpv/FPVWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/fpv/FPVWidgetModel.kt index 6a243de8..50cd8459 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/fpv/FPVWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/fpv/FPVWidgetModel.kt @@ -23,25 +23,32 @@ package dji.ux.beta.core.widget.fpv import dji.common.airlink.PhysicalSource +import dji.common.camera.CameraVideoStreamSource import dji.common.camera.ResolutionAndFrameRate import dji.common.camera.SettingsDefinitions -import dji.common.camera.SettingsDefinitions.CameraMode import dji.common.camera.SettingsDefinitions.PhotoAspectRatio import dji.common.product.Model -import dji.keysdk.AirLinkKey -import dji.keysdk.CameraKey -import dji.keysdk.ProductKey +import dji.keysdk.* +import dji.log.DJILog +import dji.sdk.base.BaseProduct +import dji.sdk.camera.Camera import dji.sdk.camera.VideoFeeder import dji.sdk.camera.VideoFeeder.VideoDataListener import dji.sdk.camera.VideoFeeder.VideoFeed import dji.sdk.codec.DJICodecManager +import dji.sdk.products.Aircraft +import dji.sdk.sdkmanager.DJISDKManager +import dji.thirdparty.io.reactivex.Completable import dji.thirdparty.io.reactivex.Flowable import dji.thirdparty.io.reactivex.functions.Action import dji.thirdparty.io.reactivex.functions.Consumer -import dji.thirdparty.io.reactivex.schedulers.Schedulers import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.SchedulerProvider +import dji.ux.beta.core.base.UXSDKError import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.module.FlatCameraModule +import dji.ux.beta.core.util.CameraUtil import dji.ux.beta.core.util.DataProcessor import dji.ux.beta.core.util.ProductUtil import dji.ux.beta.core.util.SettingDefinitions @@ -56,14 +63,14 @@ private const val TAG = "FPVWidgetModel" */ class FPVWidgetModel(djiSdkModel: DJISDKModel, keyedStore: ObservableInMemoryKeyedStore, - private val videoDataListener: VideoDataListener? + private val videoDataListener: VideoDataListener?, + private val flatCameraModule: FlatCameraModule ) : WidgetModel(djiSdkModel, keyedStore) { //region Fields private val modelNameDataProcessor: DataProcessor = DataProcessor.create(Model.UNKNOWN_AIRCRAFT) private val orientationProcessor: DataProcessor = DataProcessor.create(SettingsDefinitions.Orientation.UNKNOWN) private val photoAspectRatioProcessor: DataProcessor = DataProcessor.create(PhotoAspectRatio.UNKNOWN) - private val cameraModeProcessor: DataProcessor = DataProcessor.create(CameraMode.UNKNOWN) private val resolutionAndFrameRateProcessor: DataProcessor = DataProcessor.create(ResolutionAndFrameRate(SettingsDefinitions.VideoResolution.UNKNOWN, SettingsDefinitions.VideoFrameRate.UNKNOWN)) @@ -74,11 +81,12 @@ class FPVWidgetModel(djiSdkModel: DJISDKModel, private var currentVideoFeed: VideoFeed? = null private var currentModel: Model? = null + private var cameraVideoStreamSource: CameraVideoStreamSource = CameraVideoStreamSource.ZOOM //endregion //region Data /** - * Get the current camera index. This value should only be used for video size calculation. + * The current camera index. This value should only be used for video size calculation. * To get the camera side, use [FPVWidgetModel.cameraSide] instead. */ var currentCameraIndex: CameraIndex = CameraIndex.CAMERA_INDEX_UNKNOWN @@ -133,24 +141,59 @@ class FPVWidgetModel(djiSdkModel: DJISDKModel, //endregion + //region Constructor + init { + addModule(flatCameraModule) + } + //endregion + //region Lifecycle override fun inSetup() { - //TODO: Add peak threshold, overexposure and dimensions event for grid display once inter-widget communication is done + //TODO: Add peak threshold, overexposure val modelNameKey = ProductKey.create(ProductKey.MODEL_NAME) val orientationKey = CameraKey.create(CameraKey.ORIENTATION) - val photoAspectRatioKey = CameraKey.create(CameraKey.PHOTO_ASPECT_RATIO) - val cameraModeKey = CameraKey.create(CameraKey.MODE) - val videoResolutionAndFrameRateKey = CameraKey.create(CameraKey.RESOLUTION_FRAME_RATE) + val photoAspectRatioKey = djiSdkModel.createLensKey(CameraKey.PHOTO_ASPECT_RATIO, + currentCameraIndex.index, + CameraUtil.getLensIndex(cameraVideoStreamSource, cameraNameProcessor.value)) + val videoResolutionAndFrameRateKey = djiSdkModel.createLensKey(CameraKey.RESOLUTION_FRAME_RATE, + currentCameraIndex.index, + CameraUtil.getLensIndex(cameraVideoStreamSource, cameraNameProcessor.value)) bindDataProcessor(modelNameKey, modelNameDataProcessor) { model: Any? -> currentModel = model as Model? - updateVideoFeed() - updateCameraDisplay() + if (model == Model.MATRICE_300_RTK) { + updateSources() + } else { + updateVideoFeed() + updateCameraDisplay() + } } val videoViewChangedConsumer = Consumer { _: Any? -> videoViewChangedProcessor.onNext(true) } bindDataProcessor(orientationKey, orientationProcessor, videoViewChangedConsumer) bindDataProcessor(photoAspectRatioKey, photoAspectRatioProcessor, videoViewChangedConsumer) - bindDataProcessor(cameraModeKey, cameraModeProcessor, videoViewChangedConsumer) bindDataProcessor(videoResolutionAndFrameRateKey, resolutionAndFrameRateProcessor, videoViewChangedConsumer) + addDisposable(flatCameraModule.cameraModeDataProcessor.toFlowable() + .doOnNext(videoViewChangedConsumer) + .subscribe(Consumer { }, logErrorConsumer(TAG, "camera mode: "))) + + val primaryVideoFeedPhysicalSourceKey = AirLinkKey.createOcuSyncLinkKey(AirLinkKey.PRIMARY_VIDEO_FEED_PHYSICAL_SOURCE) + addDisposable(djiSdkModel.addListener(primaryVideoFeedPhysicalSourceKey, this) + .subscribe(Consumer { + updateVideoFeed() + updateCameraDisplay() + }, logErrorConsumer(TAG, "Error listening to primary video feed physical source key "))) + val secondaryVideoFeedPhysicalSourceKey = AirLinkKey.createOcuSyncLinkKey(AirLinkKey.SECONDARY_VIDEO_FEED_PHYSICAL_SOURCE) + addDisposable(djiSdkModel.addListener(secondaryVideoFeedPhysicalSourceKey, this) + .subscribe(Consumer { + updateVideoFeed() + updateCameraDisplay() + }, logErrorConsumer(TAG, "Error listening to secondary video feed physical source key "))) + val rcModeKey = RemoteControllerKey.create(RemoteControllerKey.MODE) + addDisposable(djiSdkModel.addListener(rcModeKey, this) + .subscribe(Consumer { + if (currentModel == Model.MATRICE_300_RTK) { + updateSources() + } + }, logErrorConsumer(TAG, "Error listening to RC Mode key "))) } override fun inCleanup() { @@ -160,15 +203,73 @@ class FPVWidgetModel(djiSdkModel: DJISDKModel, } } } - //endregion + //region Updates public override fun updateStates() { updateCameraDisplay() } + //endregion + //region User interaction + /** + * Set the [cameraVideoStreamSource] for multi-lens cameras. + * + * @return Completable representing the success/failure of set action + */ + fun setCameraVideoStreamSource(cameraVideoStreamSource: CameraVideoStreamSource): Completable { + this.cameraVideoStreamSource = cameraVideoStreamSource + val cameraVideoStreamSourceKey: DJIKey = CameraKey.create(CameraKey.CAMERA_VIDEO_STREAM_SOURCE, currentCameraIndex.index) + return djiSdkModel.setValue(cameraVideoStreamSourceKey, cameraVideoStreamSource).also { + restart() + } + } //endregion + //region Helpers + private fun updateSources() { + var mainVideoStream = PhysicalSource.UNKNOWN + var secondaryVideoStream = PhysicalSource.UNKNOWN + val product: BaseProduct? = DJISDKManager.getInstance().product + if (product is Aircraft) { + val portCamera: Camera? = product.getCameraWithComponentIndex(0) + val starboardCamera: Camera? = product.getCameraWithComponentIndex(1) + val topCamera: Camera? = product.getCameraWithComponentIndex(4) + if (portCamera != null && portCamera.isConnected) { + mainVideoStream = PhysicalSource.LEFT_CAM + secondaryVideoStream = if (starboardCamera != null && starboardCamera.isConnected) { + PhysicalSource.RIGHT_CAM + } else if (topCamera != null && topCamera.isConnected) { + PhysicalSource.TOP_CAM + } else { + PhysicalSource.FPV_CAM + } + } else if (starboardCamera != null && starboardCamera.isConnected) { + mainVideoStream = PhysicalSource.RIGHT_CAM + secondaryVideoStream = if (topCamera != null && topCamera.isConnected) { + PhysicalSource.TOP_CAM + } else { + PhysicalSource.FPV_CAM + } + } else if (topCamera != null && topCamera.isConnected) { + mainVideoStream = PhysicalSource.TOP_CAM + secondaryVideoStream = PhysicalSource.FPV_CAM + } else { + mainVideoStream = PhysicalSource.FPV_CAM + } + } + + val assignSourceKey = AirLinkKey.createOcuSyncLinkKey(AirLinkKey.ASSIGN_SOURCE_TO_PRIMARY_CHANNEL) + addDisposable(djiSdkModel.performAction(assignSourceKey, + mainVideoStream, secondaryVideoStream) + .observeOn(SchedulerProvider.io()) + .subscribe({ }, { error -> + if (error is UXSDKError) { + DJILog.e(TAG, "assign source to primary channel failure: $error") + } + })) + } + private fun updateVideoFeed() { getVideoFeeder()?.let { if (videoSource == SettingDefinitions.VideoSource.AUTO) { @@ -213,7 +314,7 @@ class FPVWidgetModel(djiSdkModel: DJISDKModel, val extEnabled = djiSdkModel.getCacheValue(extVideoInputPortEnabledKey) as Boolean? if (extEnabled == null || !extEnabled) { addDisposable(djiSdkModel.setValue(extVideoInputPortEnabledKey, true) - .subscribeOn(Schedulers.io()) + .subscribeOn(SchedulerProvider.io()) .subscribe(Action { setCameraChannelBandwidth() }, logErrorConsumer(TAG, "SetExtVideoInputPortEnabled: "))) } else { @@ -224,15 +325,17 @@ class FPVWidgetModel(djiSdkModel: DJISDKModel, private fun setCameraChannelBandwidth() { val bandwidthAllocationForLBVideoInputPort = AirLinkKey.createLightbridgeLinkKey(AirLinkKey.BANDWIDTH_ALLOCATION_FOR_LB_VIDEO_INPUT_PORT) addDisposable(djiSdkModel.setValue(bandwidthAllocationForLBVideoInputPort, 0.0f) - .subscribeOn(Schedulers.io()) + .subscribeOn(SchedulerProvider.io()) .subscribe(Action {}, logErrorConsumer(TAG, "SetBandwidthAllocationForLBVideoInputPort: "))) } private fun updateCameraDisplay() { val displayName0Key = CameraKey.create(CameraKey.DISPLAY_NAME, 0) val displayName1Key = CameraKey.create(CameraKey.DISPLAY_NAME, 1) + val displayName4Key = CameraKey.create(CameraKey.DISPLAY_NAME, 4) var displayName0 = djiSdkModel.getCacheValue(displayName0Key) as String? var displayName1 = djiSdkModel.getCacheValue(displayName1Key) as String? + var displayName4 = djiSdkModel.getCacheValue(displayName4Key) as String? var displayName = "" var displaySide = CameraSide.UNKNOWN currentCameraIndex = CameraIndex.CAMERA_INDEX_UNKNOWN @@ -242,6 +345,9 @@ class FPVWidgetModel(djiSdkModel: DJISDKModel, if (displayName1 == null) { displayName1 = PhysicalSource.UNKNOWN.toString() } + if (displayName4 == null) { + displayName4 = PhysicalSource.UNKNOWN.toString() + } val physicalVideoSource: PhysicalSource? if (currentModel != null && currentVideoFeed != null) { physicalVideoSource = currentVideoFeed?.videoSource @@ -275,6 +381,10 @@ class FPVWidgetModel(djiSdkModel: DJISDKModel, displayName = displayName1 displaySide = CameraSide.STARBOARD currentCameraIndex = CameraIndex.CAMERA_INDEX_2 + } else if (physicalVideoSource == PhysicalSource.TOP_CAM) { + displayName = displayName4 + displaySide = CameraSide.TOP + currentCameraIndex = CameraIndex.CAMERA_INDEX_4 } else if (physicalVideoSource == PhysicalSource.FPV_CAM) { displayName = PhysicalSource.FPV_CAM.toString() currentCameraIndex = CameraIndex.CAMERA_INDEX_UNKNOWN //Index will be assigned as required diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/gpssignal/GPSSignalWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/gpssignal/GPSSignalWidget.kt index 06fd0b7e..3d21ba44 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/gpssignal/GPSSignalWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/gpssignal/GPSSignalWidget.kt @@ -39,19 +39,18 @@ import androidx.annotation.StyleRes import androidx.core.content.res.use import dji.common.flightcontroller.GPSSignalLevel import dji.thirdparty.io.reactivex.Flowable -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers import dji.thirdparty.io.reactivex.functions.Consumer import dji.thirdparty.io.reactivex.processors.PublishProcessor -import dji.ux.beta.R -import dji.ux.beta.core.base.ConstraintLayoutWidget +import dji.ux.beta.core.R import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.OnStateChangeCallback import dji.ux.beta.core.base.SchedulerProvider -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.OnStateChangeCallback import dji.ux.beta.core.extension.* import dji.ux.beta.core.util.DisplayUtil -import dji.ux.beta.core.widget.gpssignal.GPSSignalWidget.GPSSignalWidgetState -import dji.ux.beta.core.widget.gpssignal.GPSSignalWidget.GPSSignalWidgetState.* +import dji.ux.beta.core.widget.gpssignal.GPSSignalWidget.ModelState +import dji.ux.beta.core.widget.gpssignal.GPSSignalWidget.ModelState.* import java.util.* /** @@ -62,7 +61,7 @@ open class GPSSignalWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ConstraintLayoutWidget(context, attrs, defStyleAttr), View.OnClickListener { +) : ConstraintLayoutWidget(context, attrs, defStyleAttr), View.OnClickListener { //region Fields private val gpsIconImageView: ImageView = findViewById(R.id.imageview_gps_icon) @@ -70,13 +69,12 @@ open class GPSSignalWidget @JvmOverloads constructor( private val satelliteCountTextView: TextView = findViewById(R.id.textview_satellite_count) private val rtkEnabledTextView: TextView = findViewById(R.id.textview_rtk_enabled) private var stateChangeResourceId: Int = 0 - private val uiUpdateStateProcessor: PublishProcessor = PublishProcessor.create() + private val uiUpdateStateProcessor: PublishProcessor = PublishProcessor.create() private val widgetModel by lazy { GPSSignalWidgetModel( DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - SchedulerProvider.getInstance()) + ObservableInMemoryKeyedStore.getInstance()) } /** @@ -240,7 +238,7 @@ open class GPSSignalWidget @JvmOverloads constructor( var stateChangeCallback: OnStateChangeCallback? = null //endregion - //region Constructors + //region Constructor override fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { inflate(context, R.layout.uxsdk_widget_gps_signal, this) } @@ -269,28 +267,28 @@ open class GPSSignalWidget @JvmOverloads constructor( } override fun onClick(v: View?) { - uiUpdateStateProcessor.onNext(GPSSignalWidgetUIState.WidgetClick) + uiUpdateStateProcessor.onNext(UIState.WidgetClicked) stateChangeCallback?.onStateChange(null) } override fun reactToModelChanges() { addReaction(widgetModel.gpsSignalQuality - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { updateGPSSignal(it) }) addReaction(widgetModel.satelliteNumber - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { updateSatelliteCount(it) }) addReaction(widgetModel.rtkEnabled - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { updateRtk(it) }) addReaction(widgetModel.isRTKAccurate - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { updateRTKColor(it) }) addReaction(widgetModel.productConnection - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { updateIconColor(it) }) addReaction(widgetModel.isExternalGPSUsed - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { updateIcon(it) }) } //endregion @@ -302,22 +300,22 @@ open class GPSSignalWidget @JvmOverloads constructor( } else { 0 } - widgetStateDataProcessor.onNext(GPSSignalQualityUpdate(gpsLevel)) + widgetStateDataProcessor.onNext(GPSSignalQualityUpdated(gpsLevel)) gpsSignalImageView.setImageLevel(gpsLevel) } private fun updateSatelliteCount(satelliteCount: Int) { - widgetStateDataProcessor.onNext(SatelliteCountUpdate(satelliteCount)) + widgetStateDataProcessor.onNext(SatelliteCountUpdated(satelliteCount)) satelliteCountTextView.text = String.format(Locale.getDefault(), "%d", satelliteCount) } private fun updateRtk(rtkEnabled: Boolean) { - widgetStateDataProcessor.onNext(RTKEnabledUpdate(rtkEnabled)) + widgetStateDataProcessor.onNext(RTKEnabledUpdated(rtkEnabled)) rtkEnabledTextView.text = if (rtkEnabled) getString(R.string.uxsdk_gps_rtk_enabled) else "" } private fun updateRTKColor(isRTKAccurate: Boolean) { - widgetStateDataProcessor.onNext(IsRTKAccurateUpdate(isRTKAccurate)) + widgetStateDataProcessor.onNext(RTKAccurateUpdated(isRTKAccurate)) rtkEnabledTextView.textColor = if (isRTKAccurate) rtkAccurateTextColor else rtkInaccurateTextColor } @@ -333,14 +331,14 @@ open class GPSSignalWidget @JvmOverloads constructor( } private fun updateIcon(isExternalGPSUsed: Boolean) { - widgetStateDataProcessor.onNext(IsExternalGPSUsedUpdate(isExternalGPSUsed)) + widgetStateDataProcessor.onNext(ExternalGPSUsedUpdated(isExternalGPSUsed)) gpsIconImageView.imageDrawable = if (isExternalGPSUsed) externalGPSIcon else gpsIcon } private fun checkAndUpdateIconColor() { if (!isInEditMode) { addDisposable(widgetModel.productConnection.firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { this.updateIconColor(it) }, logErrorConsumer(TAG, "Update Icon Color "))) } } @@ -348,7 +346,7 @@ open class GPSSignalWidget @JvmOverloads constructor( private fun checkAndUpdateRTKColor() { if (!isInEditMode) { addDisposable(widgetModel.isRTKAccurate.firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { this.updateRTKColor(it) }, logErrorConsumer(TAG, "Update RTK Color "))) } } @@ -356,7 +354,7 @@ open class GPSSignalWidget @JvmOverloads constructor( private fun checkAndUpdateIcon() { if (!isInEditMode) { addDisposable(widgetModel.isExternalGPSUsed.firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { this.updateIcon(it) }, logErrorConsumer(TAG, "Update Icon "))) } } @@ -500,63 +498,65 @@ open class GPSSignalWidget @JvmOverloads constructor( //endregion //region Hooks + /** - * Get the [GPSSignalWidgetUIState] updates + * Get the [ModelState] updates */ - fun getUIStateUpdates(): Flowable { - return uiUpdateStateProcessor + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { + return super.getWidgetStateUpdate() } /** - * Widget UI update State + * Get the [UIState] updates */ - sealed class GPSSignalWidgetUIState { - /** - * Widget click update - */ - object WidgetClick : GPSSignalWidgetUIState() + fun getUIStateUpdates(): Flowable { + return uiUpdateStateProcessor.onBackpressureBuffer() } /** - * Get the [GPSSignalWidgetState] updates + * Widget UI update State */ - override fun getWidgetStateUpdate(): Flowable { - return super.getWidgetStateUpdate() + sealed class UIState { + /** + * Widget click update + */ + object WidgetClicked : UIState() } /** * Class defines the widget state updates */ - sealed class GPSSignalWidgetState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : GPSSignalWidgetState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * GPS signal quality / strength update */ - data class GPSSignalQualityUpdate(val signalQuality: Int) : GPSSignalWidgetState() + data class GPSSignalQualityUpdated(val signalQuality: Int) : ModelState() /** * Satellite count update */ - data class SatelliteCountUpdate(val satelliteCount: Int) : GPSSignalWidgetState() + data class SatelliteCountUpdated(val satelliteCount: Int) : ModelState() /** * RTK enabled state update */ - data class RTKEnabledUpdate(val isRTKEnabled: Boolean) : GPSSignalWidgetState() + data class RTKEnabledUpdated(val isRTKEnabled: Boolean) : ModelState() /** * RTK accuracy update */ - data class IsRTKAccurateUpdate(val isRTKAccurate: Boolean) : GPSSignalWidgetState() + data class RTKAccurateUpdated(val isRTKAccurate: Boolean) : ModelState() /** * External GPS usage update */ - data class IsExternalGPSUsedUpdate(val isExternalGPSUsed: Boolean) : GPSSignalWidgetState() + data class ExternalGPSUsedUpdated(val isExternalGPSUsed: Boolean) : ModelState() } //endregion diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/gpssignal/GPSSignalWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/gpssignal/GPSSignalWidgetModel.kt index e746a5eb..e3fdfe57 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/gpssignal/GPSSignalWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/gpssignal/GPSSignalWidgetModel.kt @@ -18,7 +18,7 @@ * 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.ux.beta.core.widget.gpssignal @@ -31,9 +31,9 @@ import dji.keysdk.FlightControllerKey import dji.thirdparty.io.reactivex.Flowable import dji.thirdparty.io.reactivex.functions.Consumer import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.SchedulerProviderInterface +import dji.ux.beta.core.base.SchedulerProvider import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.util.DataProcessor /** @@ -41,8 +41,7 @@ import dji.ux.beta.core.util.DataProcessor * the underlying logic and communication */ class GPSSignalWidgetModel(djiSdkModel: DJISDKModel, - keyedStore: ObservableInMemoryKeyedStore, - private val schedulerProvider: SchedulerProviderInterface + keyedStore: ObservableInMemoryKeyedStore ) : WidgetModel(djiSdkModel, keyedStore) { //region Fields @@ -105,7 +104,7 @@ class GPSSignalWidgetModel(djiSdkModel: DJISDKModel, //Use the supported key to begin getting the RTK Enabled values bindDataProcessor(rtkSupportedKey, rtkSupportedProcessor) { addDisposable(djiSdkModel.getValue(rtkEnabledKey) - .observeOn(schedulerProvider.io()) + .observeOn(SchedulerProvider.io()) .subscribe(Consumer { }, logErrorConsumer("GPSSignalWidget", "isRTKSupported: "))) } bindDataProcessor(rtkStateKey, rtkStateProcessor) diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/horizontalvelocity/HorizontalVelocityWidget.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/horizontalvelocity/HorizontalVelocityWidget.java deleted file mode 100644 index 935da49f..00000000 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/horizontalvelocity/HorizontalVelocityWidget.java +++ /dev/null @@ -1,554 +0,0 @@ -/* - * 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.ux.beta.core.widget.horizontalvelocity; - -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.util.Pair; -import android.widget.TextView; - -import androidx.annotation.ColorInt; -import androidx.annotation.Dimension; -import androidx.annotation.DrawableRes; -import androidx.annotation.FloatRange; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StyleRes; - -import java.text.DecimalFormat; - -import dji.thirdparty.io.reactivex.Flowable; -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers; -import dji.thirdparty.io.reactivex.disposables.Disposable; -import dji.ux.beta.R; -import dji.ux.beta.core.base.ConstraintLayoutWidget; -import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.GlobalPreferencesInterface; -import dji.ux.beta.core.base.GlobalPreferencesManager; -import dji.ux.beta.core.base.uxsdkkeys.GlobalPreferenceKeys; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; -import dji.ux.beta.core.util.DisplayUtil; -import dji.ux.beta.core.util.UnitConversionUtil; - -/** - * Shows the horizontal velocity of the aircraft. - *

- * Uses the unit set in the UNIT_TYPE global preferences - * {@link GlobalPreferencesInterface#getUnitType()} and the - * {@link GlobalPreferenceKeys#UNIT_TYPE} UX Key - * and defaults to m/s if nothing is set. - */ -public class HorizontalVelocityWidget extends ConstraintLayoutWidget { - //region Constants - private static final int EMS = 2; - private static final float MINIMUM_VELOCITY = 0.0f; - private static final float MAXIMUM_VELOCITY = 50.0f; - //endregion - - //region Fields - private static DecimalFormat decimalFormat = new DecimalFormat("#0.0"); - private TextView horizontalVelocityTitleTextView; - private TextView horizontalVelocityValueTextView; - private TextView horizontalVelocityUnitTextView; - private UnitConversionUtil.SpeedMetricUnitType speedMetricUnitType; - private HorizontalVelocityWidgetModel widgetModel; - //endregion - - //region Constructor - public HorizontalVelocityWidget(Context context) { - super(context); - } - - public HorizontalVelocityWidget(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public HorizontalVelocityWidget(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_base_dashboard_text_only, this); - horizontalVelocityTitleTextView = findViewById(R.id.textview_title); - horizontalVelocityValueTextView = findViewById(R.id.textview_value); - horizontalVelocityUnitTextView = findViewById(R.id.textview_unit); - - if (!isInEditMode()) { - widgetModel = new HorizontalVelocityWidgetModel(DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - GlobalPreferencesManager.getInstance()); - horizontalVelocityTitleTextView.setText(getResources().getString(R.string.uxsdk_horizontal_velocity_title)); - horizontalVelocityValueTextView.setMinEms(EMS); - } - - if (attrs != null) { - initAttributes(context, attrs); - } - } - //endregion - - //region Lifecycle - @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(reactToHorizontalVelocityChange()); - } - //endregion - - //region reaction helpers - private Disposable reactToHorizontalVelocityChange() { - return Flowable.combineLatest(widgetModel.getHorizontalVelocity(), widgetModel.getUnitType(), Pair::new) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(values -> updateUI(values.first, values.second)); - } - - private void updateUI(@FloatRange(from = MINIMUM_VELOCITY, to = MAXIMUM_VELOCITY) float horizontalVelocity, - UnitConversionUtil.UnitType unitType) { - if (unitType == UnitConversionUtil.UnitType.METRIC - && speedMetricUnitType == UnitConversionUtil.SpeedMetricUnitType.KM_PER_HOUR) { - horizontalVelocityValueTextView.setText(decimalFormat.format(UnitConversionUtil.convertMetersPerSecToKmPerHr( - horizontalVelocity))); - } else { - //Metric m/s or imperial mph will come through already converted - horizontalVelocityValueTextView.setText(decimalFormat.format(horizontalVelocity)); - } - updateUnitText(unitType); - } - - private void updateUnitText(UnitConversionUtil.UnitType unitType) { - if (unitType == UnitConversionUtil.UnitType.IMPERIAL) { - horizontalVelocityUnitTextView.setText(getResources().getString(R.string.uxsdk_unit_mile_per_hr)); - } else { - if (speedMetricUnitType == UnitConversionUtil.SpeedMetricUnitType.KM_PER_HOUR) { - horizontalVelocityUnitTextView.setText(getResources().getString(R.string.uxsdk_unit_km_per_hr)); - } else { - horizontalVelocityUnitTextView.setText(getResources().getString(R.string.uxsdk_unit_meter_per_second)); - } - } - } - //endregion - - //region Customization - @NonNull - @Override - public String getIdealDimensionRatioString() { - return getResources().getString(R.string.uxsdk_widget_base_dashboard_distance_ratio); - } - //endregion - - //region Customization Helpers - - /** - * Set text appearance of the horizontal velocity title text view - * - * @param textAppearance Style resource for text appearance - */ - public void setHorizontalVelocityTitleTextAppearance(@StyleRes int textAppearance) { - horizontalVelocityTitleTextView.setTextAppearance(getContext(), textAppearance); - } - - /** - * Get current text color state list of the horizontal velocity title text view - * - * @return ColorStateList resource - */ - @Nullable - public ColorStateList getHorizontalVelocityTitleTextColors() { - return horizontalVelocityTitleTextView.getTextColors(); - } - - /** - * Get current text color of the horizontal velocity title text view - * - * @return color integer resource - */ - @ColorInt - public int getHorizontalVelocityTitleTextColor() { - return horizontalVelocityTitleTextView.getCurrentTextColor(); - } - - /** - * Set text color state list for the horizontal velocity title text view - * - * @param colorStateList ColorStateList resource - */ - public void setHorizontalVelocityTitleTextColor(@NonNull ColorStateList colorStateList) { - horizontalVelocityTitleTextView.setTextColor(colorStateList); - } - - /** - * Set the text color for the horizontal velocity title text view - * - * @param color color integer resource - */ - public void setHorizontalVelocityTitleTextColor(@ColorInt int color) { - horizontalVelocityTitleTextView.setTextColor(color); - } - - /** - * Get current text size of the horizontal velocity title text view - * - * @return text size of the text view - */ - @Dimension - public float getHorizontalVelocityTitleTextSize() { - return horizontalVelocityTitleTextView.getTextSize(); - } - - /** - * Set the text size of the horizontal velocity title text view - * - * @param textSize text size float value - */ - public void setHorizontalVelocityTitleTextSize(@Dimension float textSize) { - horizontalVelocityTitleTextView.setTextSize(textSize); - } - - /** - * Get current background of the horizontal velocity title text view - * - * @return Drawable resource of the background - */ - @Nullable - public Drawable getHorizontalVelocityTitleTextBackground() { - return horizontalVelocityTitleTextView.getBackground(); - } - - /** - * Set the background of the horizontal velocity title text view - * - * @param drawable Drawable resource for the background - */ - public void setHorizontalVelocityTitleTextBackground(@Nullable Drawable drawable) { - horizontalVelocityTitleTextView.setBackground(drawable); - } - - /** - * Set the resource ID for the background of the horizontal velocity title text view - * - * @param resourceId Integer ID of the text view's background resource - */ - public void setHorizontalVelocityTitleTextBackground(@DrawableRes int resourceId) { - horizontalVelocityTitleTextView.setBackgroundResource(resourceId); - } - - /** - * Set text appearance of the horizontal velocity value text view - * - * @param textAppearance Style resource for text appearance - */ - public void setHorizontalVelocityValueTextAppearance(@StyleRes int textAppearance) { - horizontalVelocityValueTextView.setTextAppearance(getContext(), textAppearance); - } - - /** - * Get current text color state list of the horizontal velocity value text view - * - * @return ColorStateList resource - */ - @Nullable - public ColorStateList getHorizontalVelocityValueTextColors() { - return horizontalVelocityValueTextView.getTextColors(); - } - - /** - * Get current text color of the horizontal velocity value text view - * - * @return color integer resource - */ - @ColorInt - public int getHorizontalVelocityValueTextColor() { - return horizontalVelocityValueTextView.getCurrentTextColor(); - } - - /** - * Set text color state list for the horizontal velocity value text view - * - * @param colorStateList ColorStateList resource - */ - public void setHorizontalVelocityValueTextColor(@NonNull ColorStateList colorStateList) { - horizontalVelocityValueTextView.setTextColor(colorStateList); - } - - /** - * Set the text color for the horizontal velocity value text view - * - * @param color color integer resource - */ - public void setHorizontalVelocityValueTextColor(@ColorInt int color) { - horizontalVelocityValueTextView.setTextColor(color); - } - - /** - * Get current text size of the horizontal velocity value text view - * - * @return text size of the text view - */ - @Dimension - public float getHorizontalVelocityValueTextSize() { - return horizontalVelocityValueTextView.getTextSize(); - } - - /** - * Set the text size of the horizontal velocity value text view - * - * @param textSize text size float value - */ - public void setHorizontalVelocityValueTextSize(@Dimension float textSize) { - horizontalVelocityValueTextView.setTextSize(textSize); - } - - /** - * Get current background of the horizontal velocity value text view - * - * @return Drawable resource of the background - */ - @Nullable - public Drawable getHorizontalVelocityValueTextBackground() { - return horizontalVelocityValueTextView.getBackground(); - } - - /** - * Set the background for the horizontal velocity value text view - * - * @param drawable Drawable resource for the background - */ - public void setHorizontalVelocityValueTextBackground(@Nullable Drawable drawable) { - horizontalVelocityValueTextView.setBackground(drawable); - } - - /** - * Set the resource ID for the background of the horizontal velocity value text view - * - * @param resourceId Integer ID of the text view's background resource - */ - public void setHorizontalVelocityValueTextBackground(@DrawableRes int resourceId) { - horizontalVelocityValueTextView.setBackgroundResource(resourceId); - } - - /** - * Set text appearance of the horizontal velocity unit text view - * - * @param textAppearance Style resource for text appearance - */ - public void setHorizontalVelocityUnitTextAppearance(@StyleRes int textAppearance) { - horizontalVelocityUnitTextView.setTextAppearance(getContext(), textAppearance); - } - - /** - * Get current text color state list of the horizontal velocity unit text view - * - * @return ColorStateList resource - */ - @Nullable - public ColorStateList getHorizontalVelocityUnitTextColors() { - return horizontalVelocityUnitTextView.getTextColors(); - } - - /** - * Get current text color of the horizontal velocity unit text view - * - * @return color integer resource - */ - @ColorInt - public int getHorizontalVelocityUnitTextColor() { - return horizontalVelocityUnitTextView.getCurrentTextColor(); - } - - /** - * Set text color state list for the horizontal velocity unit text view - * - * @param colorStateList ColorStateList resource - */ - public void setHorizontalVelocityUnitTextColor(@NonNull ColorStateList colorStateList) { - horizontalVelocityUnitTextView.setTextColor(colorStateList); - } - - /** - * Set the text color for the horizontal velocity unit text view - * - * @param color color integer resource - */ - public void setHorizontalVelocityUnitTextColor(@ColorInt int color) { - horizontalVelocityUnitTextView.setTextColor(color); - } - - /** - * Get current text size of the horizontal velocity unit text view - * - * @return text size of the text view - */ - @Dimension - public float getHorizontalVelocityUnitTextSize() { - return horizontalVelocityUnitTextView.getTextSize(); - } - - /** - * Set the text size of the horizontal velocity unit text view - * - * @param textSize text size float value - */ - public void setHorizontalVelocityUnitTextSize(@Dimension float textSize) { - horizontalVelocityUnitTextView.setTextSize(textSize); - } - - /** - * Get current background of the horizontal velocity unit text view - * - * @return Drawable resource of the background - */ - @Nullable - public Drawable getHorizontalVelocityUnitTextBackground() { - return horizontalVelocityUnitTextView.getBackground(); - } - - /** - * Set the background for the horizontal velocity unit text view - * - * @param drawable Drawable resource for the background - */ - public void setHorizontalVelocityUnitTextBackground(@Nullable Drawable drawable) { - horizontalVelocityUnitTextView.setBackground(drawable); - } - - /** - * Set the resource ID for the background of the horizontal velocity unit text view - * - * @param resourceId Integer ID of the text view's background resource - */ - public void setHorizontalVelocityUnitTextBackground(@DrawableRes int resourceId) { - horizontalVelocityUnitTextView.setBackgroundResource(resourceId); - } - - //Initialize all customizable attributes - private void initAttributes(@NonNull Context context, @NonNull AttributeSet attrs) { - TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.HorizontalVelocityWidget); - speedMetricUnitType = - UnitConversionUtil.SpeedMetricUnitType.find(typedArray.getInteger(R.styleable.HorizontalVelocityWidget_uxsdk_speedMetricUnitType, - UnitConversionUtil.SpeedMetricUnitType.METERS_PER_SECOND - .value())); - - int horizontalVelocityTitleTextAppearanceId = - typedArray.getResourceId(R.styleable.HorizontalVelocityWidget_uxsdk_horizontalVelocityTitleTextAppearance, - INVALID_RESOURCE); - if (horizontalVelocityTitleTextAppearanceId != INVALID_RESOURCE) { - setHorizontalVelocityTitleTextAppearance(horizontalVelocityTitleTextAppearanceId); - } - - float horizontalVelocityTitleTextSize = - typedArray.getDimension(R.styleable.HorizontalVelocityWidget_uxsdk_horizontalVelocityTitleTextSize, - INVALID_RESOURCE); - if (horizontalVelocityTitleTextSize != INVALID_RESOURCE) { - setHorizontalVelocityTitleTextSize(DisplayUtil.pxToSp(context, horizontalVelocityTitleTextSize)); - } - - int horizontalVelocityTitleTextColor = - typedArray.getColor(R.styleable.HorizontalVelocityWidget_uxsdk_horizontalVelocityTitleTextColor, - INVALID_COLOR); - if (horizontalVelocityTitleTextColor != INVALID_COLOR) { - setHorizontalVelocityTitleTextColor(horizontalVelocityTitleTextColor); - } - - Drawable horizontalVelocityTitleTextBackgroundDrawable = - typedArray.getDrawable(R.styleable.HorizontalVelocityWidget_uxsdk_horizontalVelocityTitleBackgroundDrawable); - if (horizontalVelocityTitleTextBackgroundDrawable != null) { - setHorizontalVelocityTitleTextBackground(horizontalVelocityTitleTextBackgroundDrawable); - } - - int horizontalVelocityValueTextAppearanceId = - typedArray.getResourceId(R.styleable.HorizontalVelocityWidget_uxsdk_horizontalVelocityValueTextAppearance, - INVALID_RESOURCE); - if (horizontalVelocityValueTextAppearanceId != INVALID_RESOURCE) { - setHorizontalVelocityValueTextAppearance(horizontalVelocityValueTextAppearanceId); - } - - float horizontalVelocityValueTextSize = - typedArray.getDimension(R.styleable.HorizontalVelocityWidget_uxsdk_horizontalVelocityValueTextSize, - INVALID_RESOURCE); - if (horizontalVelocityValueTextSize != INVALID_RESOURCE) { - setHorizontalVelocityValueTextSize(DisplayUtil.pxToSp(context, horizontalVelocityValueTextSize)); - } - - int horizontalVelocityValueTextColor = - typedArray.getColor(R.styleable.HorizontalVelocityWidget_uxsdk_horizontalVelocityValueTextColor, - INVALID_COLOR); - if (horizontalVelocityValueTextColor != INVALID_COLOR) { - setHorizontalVelocityValueTextColor(horizontalVelocityValueTextColor); - } - - Drawable horizontalVelocityValueTextBackgroundDrawable = - typedArray.getDrawable(R.styleable.HorizontalVelocityWidget_uxsdk_horizontalVelocityValueBackgroundDrawable); - if (horizontalVelocityValueTextBackgroundDrawable != null) { - setHorizontalVelocityValueTextBackground(horizontalVelocityValueTextBackgroundDrawable); - } - - int horizontalVelocityUnitTextAppearanceId = - typedArray.getResourceId(R.styleable.HorizontalVelocityWidget_uxsdk_horizontalVelocityUnitTextAppearance, - INVALID_RESOURCE); - if (horizontalVelocityUnitTextAppearanceId != INVALID_RESOURCE) { - setHorizontalVelocityUnitTextAppearance(horizontalVelocityUnitTextAppearanceId); - } - - float horizontalVelocityUnitTextSize = - typedArray.getDimension(R.styleable.HorizontalVelocityWidget_uxsdk_horizontalVelocityUnitTextSize, - INVALID_RESOURCE); - if (horizontalVelocityUnitTextSize != INVALID_RESOURCE) { - setHorizontalVelocityUnitTextSize(DisplayUtil.pxToSp(context, horizontalVelocityUnitTextSize)); - } - - int horizontalVelocityUnitTextColor = - typedArray.getColor(R.styleable.HorizontalVelocityWidget_uxsdk_horizontalVelocityUnitTextColor, - INVALID_COLOR); - if (horizontalVelocityUnitTextColor != INVALID_COLOR) { - setHorizontalVelocityUnitTextColor(horizontalVelocityUnitTextColor); - } - - Drawable horizontalVelocityUnitTextBackgroundDrawable = - typedArray.getDrawable(R.styleable.HorizontalVelocityWidget_uxsdk_horizontalVelocityUnitBackgroundDrawable); - if (horizontalVelocityUnitTextBackgroundDrawable != null) { - setHorizontalVelocityUnitTextBackground(horizontalVelocityUnitTextBackgroundDrawable); - } - typedArray.recycle(); - } - //endregion -} diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/horizontalvelocity/HorizontalVelocityWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/horizontalvelocity/HorizontalVelocityWidget.kt new file mode 100644 index 00000000..14bd069c --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/horizontalvelocity/HorizontalVelocityWidget.kt @@ -0,0 +1,160 @@ +/* + * 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.ux.beta.core.widget.horizontalvelocity + +import android.content.Context +import android.util.AttributeSet +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.R +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.SchedulerProvider +import dji.ux.beta.core.base.WidgetSizeDescription +import dji.ux.beta.core.base.widget.BaseTelemetryWidget +import dji.ux.beta.core.communication.GlobalPreferencesManager +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.extension.getString +import dji.ux.beta.core.extension.getVelocityString +import dji.ux.beta.core.widget.horizontalvelocity.HorizontalVelocityWidget.ModelState +import dji.ux.beta.core.widget.horizontalvelocity.HorizontalVelocityWidget.ModelState.HorizontalVelocityStateUpdated +import dji.ux.beta.core.widget.horizontalvelocity.HorizontalVelocityWidget.ModelState.ProductConnected +import dji.ux.beta.core.widget.horizontalvelocity.HorizontalVelocityWidgetModel.HorizontalVelocityState +import java.text.DecimalFormat + +/** + * Widget displays the horizontal velocity of the aircraft. + * + */ +open class HorizontalVelocityWidget @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + widgetTheme: Int = 0 +) : BaseTelemetryWidget( + context, + attrs, + defStyleAttr, + WidgetType.TEXT, + widgetTheme, + R.style.UXSDKHorizontalVelocityWidget +) { + + //region Fields + override val metricDecimalFormat: DecimalFormat = DecimalFormat("###0.0") + + override val imperialDecimalFormat: DecimalFormat = DecimalFormat("###0.0") + + private val widgetModel: HorizontalVelocityWidgetModel by lazy { + HorizontalVelocityWidgetModel( + DJISDKModel.getInstance(), + ObservableInMemoryKeyedStore.getInstance(), + GlobalPreferencesManager.getInstance()) + } + //endregion + + //region Constructor + init { + setValueTextViewMinWidthByText("88.8") + } + //endregion + + //region Lifecycle + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + if (!isInEditMode) { + widgetModel.setup() + } + } + + override fun onDetachedFromWindow() { + if (!isInEditMode) { + widgetModel.cleanup() + } + super.onDetachedFromWindow() + } + + override fun reactToModelChanges() { + addReaction(widgetModel.productConnection + .observeOn(SchedulerProvider.ui()) + .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) + addReaction(widgetModel.horizontalVelocityState + .observeOn(SchedulerProvider.ui()) + .subscribe { updateUI(it) }) + } + + //endregion + + //region Reactions to model + private fun updateUI(horizontalVelocityState: HorizontalVelocityState) { + widgetStateDataProcessor.onNext(HorizontalVelocityStateUpdated(horizontalVelocityState)) + if (horizontalVelocityState is HorizontalVelocityState.CurrentVelocity) { + valueString = getDecimalFormat(horizontalVelocityState.unitType) + .format(horizontalVelocityState.velocity).toString() + unitString = getVelocityString(horizontalVelocityState.unitType) + } else { + valueString = getString(R.string.uxsdk_string_default_value) + unitString = null + } + } + + //endregion + + //region customizations + override fun getIdealDimensionRatioString(): String? = null + + + override val widgetSizeDescription: WidgetSizeDescription = + WidgetSizeDescription(WidgetSizeDescription.SizeType.OTHER, + widthDimension = WidgetSizeDescription.Dimension.EXPAND, + heightDimension = WidgetSizeDescription.Dimension.WRAP) + + //endregion + + //region Hooks + + /** + * Get the [ModelState] updates + */ + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { + return super.getWidgetStateUpdate() + } + + /** + * Class defines widget state updates + */ + sealed class ModelState { + /** + * Product connection update + */ + data class ProductConnected(val boolean: Boolean) : ModelState() + + /** + * Horizontal velocity state update + */ + data class HorizontalVelocityStateUpdated(val horizontalVelocityState: HorizontalVelocityState) : ModelState() + } + + //endregion +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/horizontalvelocity/HorizontalVelocityWidgetModel.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/horizontalvelocity/HorizontalVelocityWidgetModel.java deleted file mode 100644 index d4ccb546..00000000 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/horizontalvelocity/HorizontalVelocityWidgetModel.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * 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.ux.beta.core.widget.horizontalvelocity; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import dji.keysdk.FlightControllerKey; -import dji.thirdparty.io.reactivex.Flowable; -import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.GlobalPreferencesInterface; -import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.GlobalPreferenceKeys; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; -import dji.ux.beta.core.base.uxsdkkeys.UXKey; -import dji.ux.beta.core.base.uxsdkkeys.UXKeys; -import dji.ux.beta.core.util.DataProcessor; -import dji.ux.beta.core.util.UnitConversionUtil; - -/** - * Widget Model for the {@link HorizontalVelocityWidget} used to define - * the underlying logic and communication - */ -public class HorizontalVelocityWidgetModel extends WidgetModel { - //region Fields - private final GlobalPreferencesInterface preferencesManager; - private final DataProcessor aircraftVelocityXProcessor; - private final DataProcessor aircraftVelocityYProcessor; - private final DataProcessor horizontalVelocityProcessor; - private final DataProcessor unitTypeProcessor; - //endregion - - //region Constructor - public HorizontalVelocityWidgetModel(@NonNull DJISDKModel djiSdkModel, - @NonNull ObservableInMemoryKeyedStore keyedStore, - @Nullable GlobalPreferencesInterface preferencesManager) { - super(djiSdkModel, keyedStore); - this.preferencesManager = preferencesManager; - aircraftVelocityXProcessor = DataProcessor.create(0.0f); - aircraftVelocityYProcessor = DataProcessor.create(0.0f); - horizontalVelocityProcessor = DataProcessor.create(0.0f); - unitTypeProcessor = DataProcessor.create(UnitConversionUtil.UnitType.METRIC); - if (preferencesManager != null) { - unitTypeProcessor.onNext(preferencesManager.getUnitType()); - } - } - //endregion - - //region Data - - /** - * Get the value of the horizontal velocity of the aircraft. - * - * @return Flowable for the DataProcessor that user should subscribe to. - */ - public Flowable getHorizontalVelocity() { - return horizontalVelocityProcessor.toFlowable(); - } - - /** - * Get the unit type of the horizontal velocity value received. - * - * @return Flowable for the DataProcessor that user should subscribe to. - */ - public Flowable getUnitType() { - return unitTypeProcessor.toFlowable(); - } - //endregion - - //region Lifecycle - @Override - protected void inSetup() { - FlightControllerKey aircraftVelocityXKey = FlightControllerKey.create(FlightControllerKey.VELOCITY_X); - FlightControllerKey aircraftVelocityYKey = FlightControllerKey.create(FlightControllerKey.VELOCITY_Y); - - bindDataProcessor(aircraftVelocityXKey, aircraftVelocityXProcessor); - bindDataProcessor(aircraftVelocityYKey, aircraftVelocityYProcessor); - - UXKey unitKey = UXKeys.create(GlobalPreferenceKeys.UNIT_TYPE); - bindDataProcessor(unitKey, unitTypeProcessor); - - if (preferencesManager != null) { - preferencesManager.setUpListener(); - } - } - - @Override - protected void inCleanup() { - if (preferencesManager != null) { - preferencesManager.cleanup(); - } - } - - @Override - protected void updateStates() { - float speedX = aircraftVelocityXProcessor.getValue(); - float speedY = aircraftVelocityYProcessor.getValue(); - //Calculate final velocity using the x and y velocity components - convertValueByUnit((float) Math.sqrt(speedX * speedX + speedY * speedY)); - } - //endregion - - //region Helpers - private void convertValueByUnit(float horizontalVelocity) { - if (unitTypeProcessor.getValue() == UnitConversionUtil.UnitType.IMPERIAL) { - horizontalVelocityProcessor.onNext(UnitConversionUtil.convertMetersPerSecToMilesPerHr(horizontalVelocity)); - } else { - horizontalVelocityProcessor.onNext(horizontalVelocity); - } - } - //endregion -} diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/horizontalvelocity/HorizontalVelocityWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/horizontalvelocity/HorizontalVelocityWidgetModel.kt new file mode 100644 index 00000000..3aaf6478 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/horizontalvelocity/HorizontalVelocityWidgetModel.kt @@ -0,0 +1,110 @@ +/* + * 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.ux.beta.core.widget.horizontalvelocity + +import dji.keysdk.FlightControllerKey +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.WidgetModel +import dji.ux.beta.core.communication.GlobalPreferenceKeys +import dji.ux.beta.core.communication.GlobalPreferencesInterface +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.extension.toVelocity +import dji.ux.beta.core.util.DataProcessor +import dji.ux.beta.core.util.UnitConversionUtil.UnitType +import dji.ux.beta.core.widget.horizontalvelocity.HorizontalVelocityWidgetModel.HorizontalVelocityState.CurrentVelocity +import dji.ux.beta.core.widget.horizontalvelocity.HorizontalVelocityWidgetModel.HorizontalVelocityState.ProductDisconnected +import kotlin.math.sqrt + + +/** + * Widget Model for the [HorizontalVelocityWidget] used to define + * the underlying logic and communication + */ +class HorizontalVelocityWidgetModel(djiSdkModel: DJISDKModel, + keyedStore: ObservableInMemoryKeyedStore, + private val preferencesManager: GlobalPreferencesInterface? +) : WidgetModel(djiSdkModel, keyedStore) { + + + private val velocityXProcessor: DataProcessor = DataProcessor.create(0.0f) + private val velocityYProcessor: DataProcessor = DataProcessor.create(0.0f) + private val unitTypeDataProcessor: DataProcessor = DataProcessor.create(UnitType.METRIC) + private val horizontalVelocityStateProcessor: DataProcessor = DataProcessor.create(ProductDisconnected) + + /** + * Get the value of the horizontal velocity state of the aircraft + */ + val horizontalVelocityState: Flowable + get() = horizontalVelocityStateProcessor.toFlowable() + + override fun inSetup() { + val velocityXKey = FlightControllerKey.create(FlightControllerKey.VELOCITY_X) + bindDataProcessor(velocityXKey, velocityXProcessor) + val velocityYKey = FlightControllerKey.create(FlightControllerKey.VELOCITY_Y) + bindDataProcessor(velocityYKey, velocityYProcessor) + + val unitTypeKey = GlobalPreferenceKeys.create(GlobalPreferenceKeys.UNIT_TYPE) + bindDataProcessor(unitTypeKey, unitTypeDataProcessor) + preferencesManager?.setUpListener() + preferencesManager?.let { unitTypeDataProcessor.onNext(it.unitType) } + } + + override fun updateStates() { + if (productConnectionProcessor.value) { + horizontalVelocityStateProcessor.onNext( + CurrentVelocity(calculateHorizontalVelocity(), unitTypeDataProcessor.value)) + } else { + horizontalVelocityStateProcessor.onNext(ProductDisconnected) + } + + } + + override fun inCleanup() { + preferencesManager?.cleanup() + } + + private fun calculateHorizontalVelocity(): Float { + val xVelocitySquare = velocityXProcessor.value * velocityXProcessor.value + val yVelocitySquare = velocityYProcessor.value * velocityYProcessor.value + return sqrt((xVelocitySquare + yVelocitySquare)).toVelocity(unitTypeDataProcessor.value) + } + + + /** + * Class to represent states of horizontal velocity + */ + sealed class HorizontalVelocityState { + /** + * When product is disconnected + */ + object ProductDisconnected : HorizontalVelocityState() + + /** + * When aircraft is moving horizontally + */ + data class CurrentVelocity(val velocity: Float, val unitType: UnitType) : HorizontalVelocityState() + + } +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/location/LocationWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/location/LocationWidget.kt new file mode 100644 index 00000000..cf765f8e --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/location/LocationWidget.kt @@ -0,0 +1,145 @@ +/* + * 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.ux.beta.core.widget.location + +import android.content.Context +import android.util.AttributeSet +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.R +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.SchedulerProvider +import dji.ux.beta.core.base.WidgetSizeDescription +import dji.ux.beta.core.base.widget.BaseTelemetryWidget +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.extension.getString +import dji.ux.beta.core.widget.location.LocationWidget.ModelState +import dji.ux.beta.core.widget.location.LocationWidget.ModelState.LocationStateUpdated +import dji.ux.beta.core.widget.location.LocationWidgetModel.LocationState +import java.text.DecimalFormat + +/** + * Widget shows location coordinates of the aircraft + */ +open class LocationWidget @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + widgetTheme: Int = 0 +) : BaseTelemetryWidget( + context, + attrs, + defStyleAttr, + WidgetType.TEXT_IMAGE_LEFT, + widgetTheme, + R.style.UXSDKLocationWidget +) { + + //region Fields + override val metricDecimalFormat: DecimalFormat = DecimalFormat("+#00.000000;-#00.000000") + + override val imperialDecimalFormat: DecimalFormat = metricDecimalFormat + + private val widgetModel: LocationWidgetModel by lazy { + LocationWidgetModel( + DJISDKModel.getInstance(), + ObservableInMemoryKeyedStore.getInstance()) + } + //endregion + + //region Lifecycle + override fun onAttachedToWindow() { + super.onAttachedToWindow() + if (!isInEditMode) { + widgetModel.setup() + } + } + + override fun onDetachedFromWindow() { + if (!isInEditMode) { + widgetModel.cleanup() + } + super.onDetachedFromWindow() + } + + override fun reactToModelChanges() { + addReaction(widgetModel.productConnection + .observeOn(SchedulerProvider.ui()) + .subscribe { widgetStateDataProcessor.onNext(ModelState.ProductConnected(it)) }) + addReaction(widgetModel.locationState + .observeOn(SchedulerProvider.ui()) + .subscribe { updateUI(it) }) + } + + //endregion + + //region Reactions to model + private fun updateUI(locationState: LocationState) { + widgetStateDataProcessor.onNext(LocationStateUpdated(locationState)) + valueString = if (locationState is LocationState.CurrentLocation) { + String.format(getString(R.string.uxsdk_location_coordinates), + metricDecimalFormat.format(locationState.latitude).toString(), + metricDecimalFormat.format(locationState.longitude).toString()) + } else { + getString(R.string.uxsdk_location_default) + } + } + //endregion + + //region Customization + override fun getIdealDimensionRatioString(): String? = null + + + override val widgetSizeDescription: WidgetSizeDescription = + WidgetSizeDescription(WidgetSizeDescription.SizeType.OTHER, + widthDimension = WidgetSizeDescription.Dimension.EXPAND, + heightDimension = WidgetSizeDescription.Dimension.WRAP) + //endregion + + //region Hooks + /** + * Get the [ModelState] updates + */ + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { + return super.getWidgetStateUpdate() + } + + /** + * Class defines widget state updates + */ + + sealed class ModelState { + + /** + * Product connection update + */ + data class ProductConnected(val boolean: Boolean) : ModelState() + + /** + * Location model state + */ + data class LocationStateUpdated(val locationState: LocationState) : ModelState() + } + //endregion +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/location/LocationWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/location/LocationWidgetModel.kt new file mode 100644 index 00000000..3d488b5c --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/location/LocationWidgetModel.kt @@ -0,0 +1,100 @@ +/* + * 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.ux.beta.core.widget.location + +import dji.keysdk.FlightControllerKey +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.WidgetModel +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.util.DataProcessor +import dji.ux.beta.core.util.LocationUtil +import dji.ux.beta.core.widget.location.LocationWidgetModel.LocationState.ProductDisconnected + +/** + * Widget Model for the [LocationWidget] used to define + * the underlying logic and communication + */ +class LocationWidgetModel(djiSdkModel: DJISDKModel, + keyedStore: ObservableInMemoryKeyedStore +) : WidgetModel(djiSdkModel, keyedStore) { + + private val aircraftLatitudeProcessor: DataProcessor = DataProcessor.create(0.0) + private val aircraftLongitudeProcessor: DataProcessor = DataProcessor.create(0.0) + private val locationStateProcessor: DataProcessor = DataProcessor.create(ProductDisconnected) + + /** + * Value of the location state of aircraft + */ + val locationState: Flowable + get() = locationStateProcessor.toFlowable() + + override fun inSetup() { + val aircraftLatitudeKey = FlightControllerKey.create(FlightControllerKey.AIRCRAFT_LOCATION_LATITUDE) + val aircraftLongitudeKey = FlightControllerKey.create(FlightControllerKey.AIRCRAFT_LOCATION_LONGITUDE) + bindDataProcessor(aircraftLatitudeKey, aircraftLatitudeProcessor) + bindDataProcessor(aircraftLongitudeKey, aircraftLongitudeProcessor) + } + + override fun updateStates() { + if (productConnectionProcessor.value) { + if (LocationUtil.checkLatitude(aircraftLatitudeProcessor.value) + && LocationUtil.checkLongitude(aircraftLongitudeProcessor.value)) { + locationStateProcessor.onNext(LocationState.CurrentLocation( + aircraftLatitudeProcessor.value, + aircraftLongitudeProcessor.value)) + } else { + locationStateProcessor.onNext(LocationState.LocationUnavailable) + } + } else { + locationStateProcessor.onNext(ProductDisconnected) + } + } + + + override fun inCleanup() { + // No code required + } + + /** + * Class to represent states of location widget + */ + sealed class LocationState { + /** + * Product is disconnected + */ + object ProductDisconnected : LocationState() + + /** + * Product is connected but GPS location fix is unavailable + */ + object LocationUnavailable : LocationState() + + /** + * Reflecting the current location + */ + data class CurrentLocation(val latitude: Double, val longitude: Double) : LocationState() + + } +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/radar/MultiAngleRadarSectionViewHolder.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/radar/MultiAngleRadarSectionViewHolder.kt new file mode 100644 index 00000000..c3267953 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/radar/MultiAngleRadarSectionViewHolder.kt @@ -0,0 +1,105 @@ +/* + * 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.ux.beta.core.widget.radar + +import android.graphics.drawable.Drawable +import android.view.View +import android.widget.ImageView +import android.widget.TextView + +import androidx.annotation.IdRes + +import dji.common.flightcontroller.ObstacleDetectionSector +import dji.common.flightcontroller.ObstacleDetectionSectorWarning + +/** + * Represents a section of the obstacle detection radar that has multiple segments. + */ +class MultiAngleRadarSectionViewHolder( + @IdRes imageIds: IntArray, + @IdRes distanceId: Int, + @IdRes arrowId: Int, + parent: View +) : RadarSectionViewHolder() { + + override val distance: TextView = parent.findViewById(distanceId) + override val arrow: ImageView = parent.findViewById(arrowId) + private val sectorImages: Array = arrayOfNulls(imageIds.size) + + init { + imageIds.forEachIndexed { i, imageId -> + sectorImages[i] = parent.findViewById(imageId) + } + hide() + } + + override fun hide() { + distance.visibility = View.GONE + arrow.visibility = View.GONE + sectorImages.forEach { it?.visibility = View.GONE } + } + + private fun show() { + distance.visibility = View.VISIBLE + arrow.visibility = View.VISIBLE + sectorImages.forEach { it?.visibility = View.VISIBLE } + } + + override fun setImages(images: Array) { + sectorImages.forEachIndexed { index: Int, image: ImageView? -> + if (index < images.size) { + image?.setImageDrawable(images[index]) + } + } + } + + override fun setSectors(sectors: Array?, unitStr: String?) { + if (sectors != null && sectors.isNotEmpty()) { + var distanceValue: Float = Integer.MAX_VALUE.toFloat() + sectors.forEachIndexed { i, sector -> + if (sector.obstacleDistanceInMeters < distanceValue) { + distanceValue = sector.obstacleDistanceInMeters + } + val level = sector.warningLevel.value() + if (level in 0..5) { + sectorImages[i]?.setImageLevel(level) + } + } + + val isInvalidOrZero = sectors.all { sector -> + sector.warningLevel == ObstacleDetectionSectorWarning.INVALID + } || sectors.all { sector -> + sector.warningLevel == ObstacleDetectionSectorWarning.LEVEL_1 + } + if (isInvalidOrZero) { + hide() + } else { + show() + super.setDistance(distanceValue.toDouble(), unitStr) + } + } else { + hide() + } + } +} diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/radar/RadarSectionViewHolder.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/radar/RadarSectionViewHolder.kt new file mode 100644 index 00000000..2f5ea6af --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/radar/RadarSectionViewHolder.kt @@ -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.ux.beta.core.widget.radar + +import android.content.Context +import android.content.res.ColorStateList +import android.graphics.drawable.Drawable +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.ColorInt +import androidx.annotation.DrawableRes +import androidx.annotation.StyleRes +import dji.common.flightcontroller.ObstacleDetectionSector +import java.util.* + +/** + * Represents a section of the obstacle detection radar : forward, backward, left, or right. + */ +abstract class RadarSectionViewHolder { + + /** + * The [TextView] representing the distance between the obstacle and the drone + */ + abstract val distance: TextView + + /** + * The [ImageView] representing the direction of the obstacle + */ + abstract val arrow: ImageView + + /** + * The text color of the distance text view + */ + @get:ColorInt + var distanceTextColor: Int + get() = distance.currentTextColor + set(color) { + distance.setTextColor(color) + } + + /** + * The text color state list of the distance text view + */ + var distanceTextColors: ColorStateList? + get() = distance.textColors + set(colorStateList) { + distance.setTextColor(colorStateList) + } + + /** + * The background of the distance text view + */ + var distanceTextBackground: Drawable? + get() = distance.background + set(icon) { + distance.background = icon + } + + /** + * The text size of the distance text view + */ + var distanceTextSize: Float + get() = distance.textSize + set(textSize) { + distance.textSize = textSize + } + + /** + * The drawable resource for the arrow icon. The given icon should be pointed up, and each + * radar direction's icon will be rotated to point the corresponding direction. + */ + var distanceArrowIcon: Drawable? + get() = arrow.drawable + set(icon) { + arrow.setImageDrawable(icon) + } + + /** + * The drawable resource for the arrow icon's background + */ + var distanceArrowIconBackground: Drawable? + get() = arrow.background + set(icon) { + arrow.background = icon + } + + /** + * Set the distance of the detected obstacle. + * + * @param distance The distance of the detected obstacle. + * @param unitStr A string representing the units for the distance measurement. + */ + open fun setDistance(distance: Double, unitStr: String?) { + val distanceValue = distance.toFloat() + this.distance.text = String.format(Locale.getDefault(), "%.1f %s", distanceValue, unitStr) + } + + /** + * Set the sectors of a multi-angle radar section. For single-angle radar sections, this + * method has no effect. + * + * @param sectors An array of [ObstacleDetectionSector] representing the sections of a + * multi-angle radar. + * @param unitStr A string representing the units for the distance measurement. + */ + abstract fun setSectors(sectors: Array?, unitStr: String?) + + /** + * Set the images for this radar section. For single-angle radar sections, only the first + * image in the array is used. For multi-angle radar sections, the images will be + * overlapped to form the sections of the radar. + * + * @param images An array of level-list [Drawable] objects with levels from 0-5 for multi-angle + * radar sections or 0-1 for single-angle radar sections. + */ + abstract fun setImages(images: Array) + + /** + * Hide this radar section. May be shown again if [setSectors] or [setDistance] is called + * afterwards. + */ + abstract fun hide() + + //region Customizations + /** + * Set text appearance of the distance text view + * + * @param context A context object + * @param textAppearanceResId Style resource for text appearance + */ + fun setDistanceTextAppearance(context: Context?, @StyleRes textAppearanceResId: Int) { + distance.setTextAppearance(context, textAppearanceResId) + } + + /** + * Set the resource ID for the background of the distance text view + * + * @param resourceId Integer ID of the text view's background resource + */ + fun setDistanceTextBackground(@DrawableRes resourceId: Int) { + distance.setBackgroundResource(resourceId) + } + + /** + * Set the resource ID for the arrow icon. The given icon should be pointed up, and each + * radar direction's icon will be rotated to point the corresponding direction. + * + * @param resourceId Integer ID of the drawable resource + */ + fun setDistanceArrowIcon(@DrawableRes resourceId: Int) { + arrow.setImageResource(resourceId) + } + + /** + * Set the resource ID for the arrow icon's background + * + * @param resourceId Integer ID of the icon's background resource + */ + fun setDistanceArrowIconBackground(@DrawableRes resourceId: Int) { + arrow.setBackgroundResource(resourceId) + } + //endregion +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/radar/RadarWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/radar/RadarWidget.kt new file mode 100644 index 00000000..d81ae634 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/radar/RadarWidget.kt @@ -0,0 +1,583 @@ +/* + * 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.ux.beta.core.widget.radar + +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.ImageView +import androidx.annotation.* +import androidx.core.content.res.use +import dji.common.flightcontroller.ObstacleDetectionSector +import dji.common.flightcontroller.ObstacleDetectionSectorWarning +import dji.common.flightcontroller.VisionDetectionState +import dji.common.flightcontroller.VisionSensorPosition +import dji.common.product.Model +import dji.thirdparty.io.reactivex.Flowable +import dji.thirdparty.io.reactivex.Observable +import dji.thirdparty.io.reactivex.disposables.Disposable +import dji.thirdparty.io.reactivex.functions.BiFunction +import dji.thirdparty.io.reactivex.functions.Consumer +import dji.ux.beta.core.R +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.SchedulerProvider +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget +import dji.ux.beta.core.communication.GlobalPreferenceKeys +import dji.ux.beta.core.communication.GlobalPreferencesInterface +import dji.ux.beta.core.communication.GlobalPreferencesManager +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.extension.* +import dji.ux.beta.core.util.AudioUtil +import dji.ux.beta.core.util.DisplayUtil +import dji.ux.beta.core.util.UnitConversionUtil +import dji.ux.beta.core.widget.radar.RadarWidget.ModelState +import dji.ux.beta.core.widget.radar.RadarWidget.ModelState.* +import dji.ux.beta.core.widget.radar.RadarWidgetModel.ObstacleAvoidanceLevel + +private const val TAG = "RadarWidget" +private const val ASPECT_RATIO = 17f / 8 + +/** + * Shows the current state of the radar sensors. The forward and backward sensors each have four + * sectors represented by [ObstacleDetectionSector], and the left and right sensors each have + * a single sector. Each sector displays an icon according to its + * [ObstacleDetectionSectorWarning] level. + * + * Each radar section has its own distance indicator which uses the unit set in the UNIT_MODE + * global preferences [GlobalPreferencesInterface.unitType] and the + * [GlobalPreferenceKeys.UNIT_TYPE] UX Key and defaults to m if + * nothing is set. + * + * Additionally, an icon appears in the center when the upwards sensor detects an obstacle. + * + * This widget has a fixed aspect ratio of 17:8. + */ +open class RadarWidget @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ConstraintLayoutWidget(context, attrs, defStyleAttr) { + + //region Fields + private var radarSections: Array = arrayOfNulls(4) + private var upwardObstacle: ImageView = findViewById(R.id.imageview_upward_obstacle) + private var soundDisposable: Disposable? = null + + private val widgetModel: RadarWidgetModel by lazy { + RadarWidgetModel(DJISDKModel.getInstance(), + ObservableInMemoryKeyedStore.getInstance(), + GlobalPreferencesManager.getInstance()) + } + + /** + * The images for the forward radar section. The images will be overlapped to form the + * sections of the radar. An array of level-list [Drawable] objects with levels from 0-5 is + * recommended. + */ + var forwardRadarImages: Array = arrayOf( + getDrawable(R.drawable.uxsdk_ic_radar_forward_0), + getDrawable(R.drawable.uxsdk_ic_radar_forward_1), + getDrawable(R.drawable.uxsdk_ic_radar_forward_2), + getDrawable(R.drawable.uxsdk_ic_radar_forward_3)) + set(value) { + field = value + radarSections[VisionSensorPosition.NOSE.value()]?.setImages(value) + } + + /** + * The images for the backward radar section. The images will be overlapped to form the + * sections of the radar. An array of level-list [Drawable] objects with levels from 0-5 is + * recommended. + */ + var backwardRadarImages: Array = arrayOf( + getDrawable(R.drawable.uxsdk_ic_radar_backward_0), + getDrawable(R.drawable.uxsdk_ic_radar_backward_1), + getDrawable(R.drawable.uxsdk_ic_radar_backward_2), + getDrawable(R.drawable.uxsdk_ic_radar_backward_3)) + set(value) { + field = value + radarSections[VisionSensorPosition.TAIL.value()]?.setImages(value) + } + + /** + * The image for the left radar section. A level-list [Drawable] objects with levels from 0-1 + * is recommended. + */ + var leftRadarImage: Drawable? = getDrawable(R.drawable.uxsdk_ic_radar_left) + set(value) { + field = value + radarSections[VisionSensorPosition.LEFT.value()]?.setImages(arrayOf(value)) + } + + /** + * The image for the right radar section. A level-list [Drawable] objects with levels from 0-1 + * is recommended. + */ + var rightRadarImage: Drawable? = getDrawable(R.drawable.uxsdk_ic_radar_right) + set(value) { + field = value + radarSections[VisionSensorPosition.RIGHT.value()]?.setImages(arrayOf(value)) + } + + /** + * The text color for the distance text views + */ + @get:ColorInt + @setparam:ColorInt + var distanceTextColor: Int + get() = radarSections[0]?.distanceTextColor ?: getColor(R.color.uxsdk_white) + set(color) { + radarSections.forEach { it?.distanceTextColor = color } + } + + /** + * The text color state list of the distance text views + */ + var distanceTextColors: ColorStateList? + get() = radarSections[0]?.distanceTextColors + set(colors) { + radarSections.forEach { it?.distanceTextColors = colors } + } + + /** + * The background of the distance text views + */ + var distanceTextBackground: Drawable? + get() = radarSections[0]?.distanceTextBackground + set(background) { + radarSections.forEach { it?.distanceTextBackground = background } + } + + /** + * The text size of the distance text views + */ + @get:Dimension + @setparam:Dimension + var distanceTextSize: Float + get() = radarSections[0]?.distanceTextSize ?: 13f + set(textSize) { + radarSections.forEach { it?.distanceTextSize = textSize } + } + + /** + * The drawable resource for the arrow icons. The given icon should be pointed up, and each + * radar direction's icon will be rotated to point the corresponding direction. + */ + var distanceArrowIcon: Drawable? + get() = radarSections[0]?.distanceArrowIcon + set(icon) { + radarSections.forEach { it?.distanceArrowIcon = icon } + } + + /** + * The drawable resource for the arrow icon's background + */ + var distanceArrowIconBackground: Drawable? + get() = radarSections[0]?.distanceArrowIconBackground + set(background) { + radarSections.forEach { it?.distanceArrowIconBackground = background } + } + + /** + * The drawable resource for the upward obstacle icon + */ + var upwardObstacleIcon: Drawable? + get() = upwardObstacle.drawable + set(icon) { + upwardObstacle.setImageDrawable(icon) + } + + /** + * The drawable resource for the upward obstacle icon's background + */ + var upwardObstacleIconBackground: Drawable? + get() = upwardObstacle.background + set(background) { + upwardObstacle.background = background + } + //endregion + + //region Constructor + override fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { + View.inflate(context, R.layout.uxsdk_widget_radar, this) + } + + init { + val forwardIds = intArrayOf(R.id.imageview_radar_forward_0, + R.id.imageview_radar_forward_1, + R.id.imageview_radar_forward_2, + R.id.imageview_radar_forward_3) + + radarSections[VisionSensorPosition.NOSE.value()] = + MultiAngleRadarSectionViewHolder(forwardIds, + R.id.textview_forward_distance, + R.id.imageview_forward_arrow, this) + + val backwardIds = intArrayOf(R.id.imageview_radar_backward_0, + R.id.imageview_radar_backward_1, + R.id.imageview_radar_backward_2, + R.id.imageview_radar_backward_3) + + radarSections[VisionSensorPosition.TAIL.value()] = + MultiAngleRadarSectionViewHolder(backwardIds, + R.id.textview_backward_distance, + R.id.imageview_backward_arrow, this) + + radarSections[VisionSensorPosition.LEFT.value()] = + SingleAngleRadarSectionViewHolder(R.id.imageview_radar_left, + R.id.textview_left_distance, + R.id.imageview_left_arrow, this) + + radarSections[VisionSensorPosition.RIGHT.value()] = + SingleAngleRadarSectionViewHolder(R.id.imageview_radar_right, + R.id.textview_right_distance, + R.id.imageview_right_arrow, 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() + } + soundDisposable?.dispose() + soundDisposable = null + super.onDetachedFromWindow() + } + + override fun reactToModelChanges() { + addReaction(reactToUpdateRadarSections()) + addReaction(widgetModel.ascentLimitedByObstacle + .observeOn(SchedulerProvider.ui()) + .subscribe { updateUpwardRadar(it) }) + addReaction(widgetModel.obstacleAvoidanceLevel + .observeOn(SchedulerProvider.ui()) + .subscribe { playAlertSound(it) }) + addReaction(widgetModel.isRadarEnabled + .observeOn(SchedulerProvider.ui()) + .subscribe { updateRadarEnabled(it) }) + addReaction(widgetModel.productConnection + .observeOn(SchedulerProvider.ui()) + .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + var width = MeasureSpec.getSize(widthMeasureSpec) + var height = MeasureSpec.getSize(heightMeasureSpec) + if (height * ASPECT_RATIO < width) { + width = (height * ASPECT_RATIO).toInt() + } else { + height = (width / ASPECT_RATIO).toInt() + } + setMeasuredDimension(View.resolveSize(width, widthMeasureSpec), View.resolveSize(height, heightMeasureSpec)) + } + //endregion + + //region Reactions + private fun reactToUpdateRadarSections(): Disposable { + return Flowable.combineLatest(widgetModel.visionDetectionState, widgetModel.unitType, + BiFunction { first: VisionDetectionState, second: UnitConversionUtil.UnitType -> Pair(first, second) }) + .observeOn(SchedulerProvider.ui()) + .subscribe(Consumer { values: Pair -> updateRadarSections(values.first, values.second) }, + logErrorConsumer(TAG, "reactToUpdateRadarSections: ")) + } + + private fun updateRadarSections(state: VisionDetectionState, unitType: UnitConversionUtil.UnitType) { + val sectors = state.detectionSectors + if (state.position.value() < radarSections.size) { + val radarSection = radarSections[state.position.value()] + val unit: String = if (unitType == UnitConversionUtil.UnitType.IMPERIAL) { + getString(R.string.uxsdk_unit_feet) + } else { + getString(R.string.uxsdk_unit_meters) + } + radarSection?.setDistance(state.obstacleDistanceInMeters, unit) + radarSection?.setSectors(sectors, unit) + when (state.position) { + VisionSensorPosition.NOSE -> widgetStateDataProcessor.onNext(NoseStateUpdated(state)) + VisionSensorPosition.TAIL -> widgetStateDataProcessor.onNext(TailStateUpdated(state)) + VisionSensorPosition.LEFT -> widgetStateDataProcessor.onNext(LeftStateUpdated(state)) + VisionSensorPosition.RIGHT -> widgetStateDataProcessor.onNext(RightStateUpdated(state)) + else -> { + // Do nothing + } + } + } + } + + private fun updateUpwardRadar(isAscentLimitedByObstacle: Boolean) { + upwardObstacle.visibility = if (isAscentLimitedByObstacle) View.VISIBLE else View.GONE + widgetStateDataProcessor.onNext(AscentLimitedUpdated(isAscentLimitedByObstacle)) + } + + private fun updateRadarEnabled(isRadarEnabled: Boolean) { + if (!isRadarEnabled) { + radarSections.forEach { + it?.hide() + } + } + widgetStateDataProcessor.onNext(RadarEnabled(isRadarEnabled)) + } + + private fun playAlertSound(avoidanceLevel: ObstacleAvoidanceLevel) { + if (avoidanceLevel != ObstacleAvoidanceLevel.NONE) { + if (soundDisposable == null) { + soundDisposable = Observable.interval(1, java.util.concurrent.TimeUnit.SECONDS) + .subscribeOn(SchedulerProvider.io()) + .observeOn(SchedulerProvider.ui()) + .subscribe { AudioUtil.playSound(context, getSoundId(avoidanceLevel), true) } + } + } else { + soundDisposable?.dispose() + soundDisposable = null + } + } + + @RawRes + private fun getSoundId(avoidAlertLevel: ObstacleAvoidanceLevel): Int { + return when (avoidAlertLevel) { + ObstacleAvoidanceLevel.LEVEL_1 -> R.raw.uxsdk_radar_beep_1000 + ObstacleAvoidanceLevel.LEVEL_2 -> R.raw.uxsdk_radar_beep_500 + ObstacleAvoidanceLevel.LEVEL_3 -> R.raw.uxsdk_radar_beep_250 + else -> 0 + } + } + //endregion + + //region Customization + override fun getIdealDimensionRatioString(): String { + return getString(R.string.uxsdk_widget_radar_ratio) + } + + /** + * Set the warning level ranges for the specified product models. + * + * @param models The product models for which these level ranges apply. + * @param levelRanges An array where each number represents the maximum distance in meters for + * the corresponding warning level. For example [70, 4, 2] would indicate + * that warning level LEVEL_1 has the range (4-70], warning level LEVEL_2 + * has the range (2,4], and warning level LEVEL_3 has the range [0,2]. + * A distance with a value above the largest number in the array will have + * the warning level INVALID. + */ + fun setWarningLevelRanges(models: Array, levelRanges: FloatArray) { + widgetModel.setWarningLevelRanges(models, levelRanges) + } + + /** + * Set the resource IDs for the forward radar section. The images will be overlapped to form the + * sections of the radar. + * + * @param resourceIds An array of level-list resource IDs with levels from 0-5. + */ + fun setForwardRadarImages(resourceIds: IntArray) { + forwardRadarImages = resourceIds.map { resourceId: Int -> + getDrawable(resourceId) + }.toTypedArray() + } + + /** + * Set the resource IDs for the backward radar section. The images will be overlapped to form the + * sections of the radar. + * + * @param resourceIds An array of level-list resource IDs with levels from 0-5. + */ + fun setBackwardRadarImages(resourceIds: IntArray) { + backwardRadarImages = resourceIds.map { resourceId: Int -> + getDrawable(resourceId) + }.toTypedArray() + } + + /** + * Set the resource ID for the left radar section. + * + * @param resourceId A level-list resource ID with levels from 0-1. + */ + fun setLeftRadarImage(@DrawableRes resourceId: Int) { + leftRadarImage = getDrawable(resourceId) + } + + /** + * Set the resource ID for the right radar section. + * + * @param resourceId A level-list resource ID with levels from 0-1. + */ + fun setRightRadarImage(@DrawableRes resourceId: Int) { + leftRadarImage = getDrawable(resourceId) + } + + /** + * Set text appearance of the distance text views + * + * @param textAppearanceResId Style resource for text appearance + */ + fun setDistanceTextAppearance(@StyleRes textAppearanceResId: Int) { + radarSections.forEach { it?.setDistanceTextAppearance(context, textAppearanceResId) } + } + + /** + * Set the resource ID for the background of the distance text views + * + * @param resourceId Integer ID of the text view's background resource + */ + fun setDistanceTextBackground(@DrawableRes resourceId: Int) { + radarSections.forEach { it?.setDistanceTextBackground(resourceId) } + } + + /** + * Set the resource ID for the arrow icons. The given icon should be pointed up, and each + * radar direction's icon will be rotated to point the corresponding direction. + * + * @param resourceId Integer ID of the drawable resource + */ + fun setDistanceArrowIcon(@DrawableRes resourceId: Int) { + radarSections.forEach { it?.setDistanceArrowIcon(resourceId) } + } + + /** + * Set the resource ID for the arrow icon's background + * + * @param resourceId Integer ID of the icon's background resource + */ + fun setDistanceArrowIconBackground(@DrawableRes resourceId: Int) { + radarSections.forEach { it?.setDistanceArrowIconBackground(resourceId) } + } + + /** + * Set the resource ID for the upward obstacle icon + * + * @param resourceId Integer ID of the drawable resource + */ + fun setUpwardObstacleIcon(@DrawableRes resourceId: Int) { + upwardObstacleIcon = getDrawable(resourceId) + } + + /** + * Set the resource ID for the upward obstacle icon's background + * + * @param resourceId Integer ID of the icon's background resource + */ + fun setUpwardObstacleIconBackground(@DrawableRes resourceId: Int) { + upwardObstacle.setBackgroundResource(resourceId) + } + + @SuppressLint("Recycle") + private fun initAttributes(context: Context, attrs: AttributeSet) { + context.obtainStyledAttributes(attrs, R.styleable.RadarWidget).use { typedArray -> + typedArray.getDrawableArrayAndUse(R.styleable.RadarWidget_uxsdk_forwardImages) { + forwardRadarImages = it + } + typedArray.getDrawableArrayAndUse(R.styleable.RadarWidget_uxsdk_backwardImages) { + backwardRadarImages = it + } + typedArray.getDrawableAndUse(R.styleable.RadarWidget_uxsdk_leftImage) { + leftRadarImage = it + } + typedArray.getDrawableAndUse(R.styleable.RadarWidget_uxsdk_rightImage) { + rightRadarImage = it + } + typedArray.getDrawableAndUse(R.styleable.RadarWidget_uxsdk_distanceArrowIcon) { + distanceArrowIcon = it + } + typedArray.getDrawableAndUse(R.styleable.RadarWidget_uxsdk_distanceArrowIconBackground) { + distanceArrowIconBackground = it + } + typedArray.getDrawableAndUse(R.styleable.RadarWidget_uxsdk_upwardObstacleIcon) { + upwardObstacleIcon = it + } + typedArray.getDrawableAndUse(R.styleable.RadarWidget_uxsdk_upwardObstacleIconBackground) { + upwardObstacleIconBackground = it + } + typedArray.getDrawableAndUse(R.styleable.RadarWidget_uxsdk_distanceTextBackground) { + distanceTextBackground = it + } + typedArray.getResourceIdAndUse(R.styleable.RadarWidget_uxsdk_distanceTextAppearance) { + setDistanceTextAppearance(it) + } + typedArray.getColorAndUse(R.styleable.RadarWidget_uxsdk_distanceTextColor) { + distanceTextColor = it + } + typedArray.getDimensionAndUse(R.styleable.RadarWidget_uxsdk_distanceTextSize) { + distanceTextSize = DisplayUtil.pxToSp(context, it) + } + } + } + //endregion + + //region Hooks + /** + * Class defines the widget state updates + */ + sealed class ModelState { + /** + * Product connection update + */ + data class ProductConnected(val isConnected: Boolean) : ModelState() + + /** + * Ascent limited update + */ + data class AscentLimitedUpdated(val isAscentLimited: Boolean) : ModelState() + + /** + * Radar Enabled + */ + data class RadarEnabled(val isRadarEnabled: Boolean) : ModelState() + + /** + * Nose state update + */ + data class NoseStateUpdated(val noseState: VisionDetectionState) : ModelState() + + /** + * Tail state update + */ + data class TailStateUpdated(val tailState: VisionDetectionState) : ModelState() + + /** + * Right state update + */ + data class RightStateUpdated(val rightState: VisionDetectionState) : ModelState() + + /** + * Left state update + */ + data class LeftStateUpdated(val leftState: VisionDetectionState) : ModelState() + + } + //endregion + +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/radar/RadarWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/radar/RadarWidgetModel.kt new file mode 100644 index 00000000..7f2d7611 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/radar/RadarWidgetModel.kt @@ -0,0 +1,408 @@ +/* + * 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.ux.beta.core.widget.radar + +import dji.common.flightcontroller.* +import dji.common.product.Model +import dji.keysdk.DJIKey +import dji.keysdk.FlightControllerKey +import dji.keysdk.ProductKey +import dji.thirdparty.io.reactivex.Flowable +import dji.thirdparty.io.reactivex.functions.BiFunction +import dji.thirdparty.io.reactivex.functions.Function3 +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.WidgetModel +import dji.ux.beta.core.communication.GlobalPreferenceKeys +import dji.ux.beta.core.communication.GlobalPreferencesInterface +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.UXKeys +import dji.ux.beta.core.extension.toDistance +import dji.ux.beta.core.util.DataProcessor +import dji.ux.beta.core.util.ProductUtil +import dji.ux.beta.core.util.UnitConversionUtil +import java.util.concurrent.ConcurrentHashMap + +private const val MM_IN_METER = 1000 +private const val MAX_PERCEPTION_DISTANCE = 45 +private const val DEFAULT_RADAR_DISTANCE = 10f +private const val DEFAULT_AVOIDANCE_DISTANCE = 3f +private const val DEGREES_IN_CIRCLE = 360 +private const val SIDE_RADAR_DANGER_DISTANCE = 3 +private const val SIDE_RADAR_WARNING_DISTANCE = 6 + +/** + * Widget Model for the [RadarWidget] used to define + * the underlying logic and communication + */ +class RadarWidgetModel(djiSdkModel: DJISDKModel, + keyedStore: ObservableInMemoryKeyedStore, + private val preferencesManager: GlobalPreferencesInterface? +) : WidgetModel(djiSdkModel, keyedStore) { + + //region Fields + private val visionDetectionStateProcessor: DataProcessor = DataProcessor.create( + VisionDetectionState.createInstance( + false, 0.0, + VisionSystemWarning.UNKNOWN, + null, + VisionSensorPosition.UNKNOWN, + false, + 0)) + private val isAscentLimitedByObstacleProcessor: DataProcessor = DataProcessor.create(false) + private val modelProcessor: DataProcessor = DataProcessor.create(Model.UNKNOWN_AIRCRAFT) + private val isMotorOnProcessor: DataProcessor = DataProcessor.create(false) + private val flightModeProcessor: DataProcessor = DataProcessor.create(FlightMode.GPS_ATTI) + private val radarDistancesProcessor: DataProcessor = DataProcessor.create(intArrayOf()) + private val horizontalRadarDistanceProcessor: DataProcessor = DataProcessor.create(DEFAULT_RADAR_DISTANCE) + private val horizontalAvoidanceDistanceProcessor: DataProcessor = DataProcessor.create(DEFAULT_AVOIDANCE_DISTANCE) + private val unitTypeProcessor: DataProcessor = DataProcessor.create(UnitConversionUtil.UnitType.METRIC) + private val obstacleAvoidanceLevelProcessor: DataProcessor = DataProcessor.create(ObstacleAvoidanceLevel.NONE) + private val warningLevelRanges: MutableMap = ConcurrentHashMap() + //endregion + + //region Data + /** + * The vision detection state. + */ + val visionDetectionState: Flowable + get() = Flowable.combineLatest(visionDetectionStateProcessor.toFlowable(), + unitTypeProcessor.toFlowable(), + isRadarEnabled, + Function3 { state: VisionDetectionState, + unitType: UnitConversionUtil.UnitType, + isRadarEnabled: Boolean -> + var sectors: Array? = null + var obstacleDistanceInMeters = 0.0 + + if (isRadarEnabled) { + sectors = state.detectionSectors?.map { sector -> + ObstacleDetectionSector(sector.warningLevel, + sector.obstacleDistanceInMeters.toDistance(unitType)) + }?.toTypedArray() + obstacleDistanceInMeters = state.obstacleDistanceInMeters.toDistance(unitType) + + if (state.position == VisionSensorPosition.TAIL) { + sectors?.reverse() + } else if (state.position == VisionSensorPosition.LEFT + || state.position == VisionSensorPosition.RIGHT) { + val warningLevel = getSideRadarWarningLevel(state) + sectors = arrayOf(ObstacleDetectionSector(warningLevel, + obstacleDistanceInMeters.toFloat())) + } + adjustWarningLevels(sectors) + } + VisionDetectionState.createInstance(state.isSensorBeingUsed, + obstacleDistanceInMeters, + state.systemWarning, + sectors, + state.position, + state.isDisabled, + state.avoidAlertLevel) + }) + + /** + * Whether the radar is enabled. The radar is disabled if the product is in sport mode or no + * product is connected. + */ + val isRadarEnabled: Flowable + get() = Flowable.combineLatest(flightModeProcessor.toFlowable(), + productConnectionProcessor.toFlowable(), + BiFunction { flightMode: FlightMode, + isConnected: Boolean -> + flightMode != FlightMode.GPS_SPORT && isConnected + }) + + /** + * The obstacle avoidance level. Will only emit updates if the Model is M200 V2 series or M300 + * series and the motors are on. + */ + val obstacleAvoidanceLevel: Flowable + get() = Flowable.combineLatest(obstacleAvoidanceLevelProcessor.toFlowable(), + modelProcessor.toFlowable(), + isMotorOnProcessor.toFlowable(), + Function3 { level: ObstacleAvoidanceLevel, + model: Model, + isMotorOn: Boolean -> + if (ProductUtil.isM200V2OrM300(model) && isMotorOn) { + level + } else { + ObstacleAvoidanceLevel.NONE + } + }).distinctUntilChanged() + + /** + * Whether the drone's upward sensor has detected an obstacle. + */ + val ascentLimitedByObstacle: Flowable + get() = isAscentLimitedByObstacleProcessor.toFlowable() + + /** + * The unit type of the distance value. + */ + val unitType: Flowable + get() = unitTypeProcessor.toFlowable() + //endregion + + //region Constructor + init { + if (preferencesManager != null) { + unitTypeProcessor.onNext(preferencesManager.unitType) + } + } + //endregion + + //region Lifecycle + override fun inSetup() { + val visionDetectionStateKey: DJIKey = FlightControllerKey.createFlightAssistantKey(FlightControllerKey.VISION_DETECTION_STATE) + val isAscentLimitedByObstacleKey: DJIKey = FlightControllerKey.createFlightAssistantKey(FlightControllerKey.IS_ASCENT_LIMITED_BY_OBSTACLE) + val modelKey: DJIKey = ProductKey.create(ProductKey.MODEL_NAME) + val isMotorOnKey: DJIKey = FlightControllerKey.create(FlightControllerKey.ARE_MOTOR_ON) + val flightModeKey: DJIKey = FlightControllerKey.create(FlightControllerKey.FLIGHT_MODE) + val radarDistancesKey: DJIKey = FlightControllerKey.createFlightAssistantKey(FlightControllerKey.OMNI_PERCEPTION_RADAR_BIRD_VIEW_DISTANCE) + val horizontalRadarDistanceKey: DJIKey = FlightControllerKey.createFlightAssistantKey(FlightControllerKey.OMNI_HORIZONTAL_RADAR_DISTANCE) + val horizontalAvoidanceDistanceKey: DJIKey = FlightControllerKey.createFlightAssistantKey(FlightControllerKey.OMNI_HORIZONTAL_AVOIDANCE_DISTANCE) + bindDataProcessor(visionDetectionStateKey, visionDetectionStateProcessor) { visionDetectionState: Any? -> + obstacleAvoidanceLevelProcessor.onNext(getObstacleAvoidanceLevel(visionDetectionState as VisionDetectionState)) + } + bindDataProcessor(isAscentLimitedByObstacleKey, isAscentLimitedByObstacleProcessor) + bindDataProcessor(modelKey, modelProcessor) + bindDataProcessor(isMotorOnKey, isMotorOnProcessor) + bindDataProcessor(flightModeKey, flightModeProcessor) + bindDataProcessor(radarDistancesKey, radarDistancesProcessor) { radarDistances: Any? -> + parsePerceptionInformation(radarDistances as IntArray).forEach { + visionDetectionStateProcessor.onNext(it) + } + val distanceInMeters = getMinDistance(radarDistances).toDouble() / MM_IN_METER + obstacleAvoidanceLevelProcessor.onNext(getObstacleAvoidanceLevel(distanceInMeters.toFloat())) + } + bindDataProcessor(horizontalRadarDistanceKey, horizontalRadarDistanceProcessor) + bindDataProcessor(horizontalAvoidanceDistanceKey, horizontalAvoidanceDistanceProcessor) + val unitKey = UXKeys.create(GlobalPreferenceKeys.UNIT_TYPE) + bindDataProcessor(unitKey, unitTypeProcessor) + preferencesManager?.setUpListener() + } + + override fun inCleanup() { + preferencesManager?.cleanup() + } + + override fun updateStates() { + // Nothing to update + } + //endregion + + //region Customization + /** + * Sets the warning level ranges for the specified product models. + * + * @param models The product models for which these level ranges apply. + * @param levelRanges An array where each number represents the maximum distance in meters for + * the corresponding warning level. For example [70, 4, 2] would indicate + * that warning level LEVEL_1 has the range (4-70], warning level LEVEL_2 + * has the range (2,4], and warning level LEVEL_3 has the range [0,2]. + * A distance with a value above the largest number in the array will have + * the warning level INVALID. + */ + fun setWarningLevelRanges(models: Array, levelRanges: FloatArray) { + if (models.isEmpty() || levelRanges.isEmpty()) { + return + } + val ranges = levelRanges.copyOf(levelRanges.size) + ranges.sortDescending() + models.forEach { warningLevelRanges[it] = ranges } + } + //endregion + + //region Helpers + private fun adjustWarningLevels(sectors: Array?) { + val currentModel = modelProcessor.value + if (!warningLevelRanges.containsKey(currentModel) || sectors == null) { + return + } + val currentModelLevelRanges = warningLevelRanges[currentModel] + if (currentModelLevelRanges != null) { + sectors.forEachIndexed { i, sector -> + val distanceInMeters = sector.obstacleDistanceInMeters + val j = currentModelLevelRanges.indexOfLast { distanceInMeters < it } + if (j >= 0) { + sectors[i] = ObstacleDetectionSector(ObstacleDetectionSectorWarning.find(j), distanceInMeters) + } + } + } + } + + private fun getSideRadarWarningLevel(state: VisionDetectionState) : ObstacleDetectionSectorWarning { + return when { + state.obstacleDistanceInMeters <= 0 || state.obstacleDistanceInMeters >= SIDE_RADAR_WARNING_DISTANCE -> { + ObstacleDetectionSectorWarning.UNKNOWN + } + state.obstacleDistanceInMeters < SIDE_RADAR_DANGER_DISTANCE -> { + ObstacleDetectionSectorWarning.LEVEL_2 + } + else -> ObstacleDetectionSectorWarning.LEVEL_1 + } + } + + private fun parsePerceptionInformation(info: IntArray): List { + val numDegreesPerValue = DEGREES_IN_CIRCLE / info.size + + val forwardSlice1 = (323 / numDegreesPerValue)..(359 / numDegreesPerValue) + val forwardSlice2 = 0..(36 / numDegreesPerValue) + val rightSlice = (53 / numDegreesPerValue)..(126 / numDegreesPerValue) + val backwardSlice = (143 / numDegreesPerValue)..(216 / numDegreesPerValue) + val leftSlice = (233 / numDegreesPerValue)..(306 / numDegreesPerValue) + return arrayListOf( + getVisionDetectionState(info.sliceArray(forwardSlice1) + info.sliceArray(forwardSlice2), + VisionSensorPosition.NOSE), + getVisionDetectionState(info.sliceArray(rightSlice), + VisionSensorPosition.RIGHT), + getVisionDetectionState(info.sliceArray(backwardSlice), + VisionSensorPosition.TAIL), + getVisionDetectionState(info.sliceArray(leftSlice), + VisionSensorPosition.LEFT)) + } + + private fun getVisionDetectionState(distances: IntArray, position: VisionSensorPosition): VisionDetectionState { + val distanceInMeters = getMinDistance(distances).toDouble() / MM_IN_METER + val visionSectorState = when (position) { + VisionSensorPosition.NOSE, VisionSensorPosition.TAIL -> getVisionSectorState(distances) + else -> null + } + + return VisionDetectionState.createInstance(true, + distanceInMeters, + VisionSystemWarning.UNKNOWN, + visionSectorState, + position, + false, + 0) + } + + private fun getVisionSectorState(distances: IntArray?): Array? { + if (distances == null || distances.isEmpty()) { + return null + } + + val slice1 = 0 until (distances.size / 4) + val slice2 = (distances.size / 4) until (distances.size / 2) + val slice3 = (distances.size / 2) until (distances.size * 3 / 4) + val slice4 = (distances.size * 3 / 4) until (distances.size) + return arrayOf( + getSector(getMinDistance(distances.sliceArray(slice1)) / MM_IN_METER.toFloat()), + getSector(getMinDistance(distances.sliceArray(slice2)) / MM_IN_METER.toFloat()), + getSector(getMinDistance(distances.sliceArray(slice3)) / MM_IN_METER.toFloat()), + getSector(getMinDistance(distances.sliceArray(slice4)) / MM_IN_METER.toFloat())) + } + + private fun getSector(distanceInMeters: Float): ObstacleDetectionSector { + val sectorWarningLevel = if (modelProcessor.value == Model.MATRICE_300_RTK) { + when { + distanceInMeters < 0 -> ObstacleDetectionSectorWarning.UNKNOWN + distanceInMeters >= MAX_PERCEPTION_DISTANCE -> ObstacleDetectionSectorWarning.INVALID + distanceInMeters > horizontalRadarDistanceProcessor.value -> ObstacleDetectionSectorWarning.LEVEL_1 + distanceInMeters > horizontalAvoidanceDistanceProcessor.value + 2 -> ObstacleDetectionSectorWarning.LEVEL_4 + else -> ObstacleDetectionSectorWarning.LEVEL_6 + } + } else { + when { + distanceInMeters < 3 -> ObstacleDetectionSectorWarning.LEVEL_6 + distanceInMeters < 6 -> ObstacleDetectionSectorWarning.LEVEL_5 + distanceInMeters < 10 -> ObstacleDetectionSectorWarning.LEVEL_4 + distanceInMeters < 15 -> ObstacleDetectionSectorWarning.LEVEL_3 + distanceInMeters < 20 -> ObstacleDetectionSectorWarning.LEVEL_2 + else -> ObstacleDetectionSectorWarning.LEVEL_1 + } + } + return ObstacleDetectionSector(sectorWarningLevel, distanceInMeters) + } + + private fun getObstacleAvoidanceLevel(distanceInMeters: Float): ObstacleAvoidanceLevel { + return when { + distanceInMeters > horizontalRadarDistanceProcessor.value -> ObstacleAvoidanceLevel.NONE + distanceInMeters >= horizontalRadarDistanceProcessor.value / 3 + horizontalAvoidanceDistanceProcessor.value * 2 / 3 -> ObstacleAvoidanceLevel.LEVEL_1 + distanceInMeters >= horizontalRadarDistanceProcessor.value / 6 + horizontalAvoidanceDistanceProcessor.value * 5 / 6 -> ObstacleAvoidanceLevel.LEVEL_2 + else -> ObstacleAvoidanceLevel.LEVEL_3 + } + } + + private fun getObstacleAvoidanceLevel(visionDetectionState: VisionDetectionState): ObstacleAvoidanceLevel { + val distanceInMeters = if (visionDetectionState.position == VisionSensorPosition.NOSE + || visionDetectionState.position == VisionSensorPosition.TAIL) { + visionDetectionState.detectionSectors?.map { sector: ObstacleDetectionSector -> + sector.obstacleDistanceInMeters + }?.toTypedArray()?.min() ?: 0f + } else { + visionDetectionState.obstacleDistanceInMeters.toFloat() + } + + return when { + distanceInMeters < 2.5 -> ObstacleAvoidanceLevel.LEVEL_3 + distanceInMeters < 5 -> ObstacleAvoidanceLevel.LEVEL_2 + distanceInMeters < 10 -> ObstacleAvoidanceLevel.LEVEL_1 + else -> ObstacleAvoidanceLevel.NONE + } + } + + private fun getMinDistance(distances: IntArray): Int { + return distances.filter { it in 1..60000 }.min() ?: Int.MAX_VALUE + } + //endregion + + //region Classes + /** + * The proximity of obstacles + */ + enum class ObstacleAvoidanceLevel(@get:JvmName("value") val value: Int) { + + /** + * No obstacles are detected + */ + NONE(0), + + /** + * Obstacle is detected far away + */ + LEVEL_1(1), + + /** + * Obstacle is detected close by + */ + LEVEL_2(2), + + /** + * Obstacle is detected very close by + */ + LEVEL_3(3); + + companion object { + @JvmStatic + val values = values() + + @JvmStatic + fun find(value: Int): ObstacleAvoidanceLevel { + return values.find { it.value == value } ?: NONE + } + } + } + //endregion +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/radar/SingleAngleRadarSectionViewHolder.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/radar/SingleAngleRadarSectionViewHolder.kt new file mode 100644 index 00000000..cfcf7953 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/radar/SingleAngleRadarSectionViewHolder.kt @@ -0,0 +1,80 @@ +/* + * 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.ux.beta.core.widget.radar + +import android.graphics.drawable.Drawable +import android.view.View +import android.widget.ImageView +import android.widget.TextView + +import androidx.annotation.IdRes + +import dji.common.flightcontroller.ObstacleDetectionSector +import dji.common.flightcontroller.ObstacleDetectionSectorWarning + +/** + * Represents a section of the obstacle detection radar that has a single segment. + */ +class SingleAngleRadarSectionViewHolder( + @IdRes imageId: Int, + @IdRes distanceId: Int, + @IdRes arrowId: Int, + parent: View +) : RadarSectionViewHolder() { + + override val distance: TextView = parent.findViewById(distanceId) + override val arrow: ImageView = parent.findViewById(arrowId) + private val radarImage: ImageView = parent.findViewById(imageId) + + init { + hide() + } + + override fun hide() { + radarImage.visibility = View.GONE + distance.visibility = View.GONE + arrow.visibility = View.GONE + } + + private fun show() { + radarImage.visibility = View.VISIBLE + distance.visibility = View.VISIBLE + arrow.visibility = View.VISIBLE + } + + override fun setImages(images: Array) { + radarImage.setImageDrawable(images[0]) + } + + override fun setSectors(sectors: Array?, unitStr: String?) { + val warningLevel = sectors?.get(0)?.warningLevel + if (warningLevel == ObstacleDetectionSectorWarning.LEVEL_1 + || warningLevel == ObstacleDetectionSectorWarning.LEVEL_2) { + show() + radarImage.setImageLevel(warningLevel.value()) + } else { + hide() + } + } +} diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/remainingflighttime/RemainingFlightTimeWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/remainingflighttime/RemainingFlightTimeWidget.kt index 811a258a..680ee127 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/remainingflighttime/RemainingFlightTimeWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/remainingflighttime/RemainingFlightTimeWidget.kt @@ -33,15 +33,15 @@ import dji.thirdparty.io.reactivex.Flowable import dji.thirdparty.io.reactivex.disposables.Disposable import dji.thirdparty.io.reactivex.functions.BiFunction import dji.thirdparty.io.reactivex.functions.Consumer -import dji.ux.beta.R +import dji.ux.beta.core.R import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.FrameLayoutWidget import dji.ux.beta.core.base.SchedulerProvider -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.base.widget.FrameLayoutWidget +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.getColorAndUse import dji.ux.beta.core.extension.getString -import dji.ux.beta.core.widget.remainingflighttime.RemainingFlightTimeWidget.RemainingFlightTimeWidgetState -import dji.ux.beta.core.widget.remainingflighttime.RemainingFlightTimeWidget.RemainingFlightTimeWidgetState.* +import dji.ux.beta.core.widget.remainingflighttime.RemainingFlightTimeWidget.ModelState +import dji.ux.beta.core.widget.remainingflighttime.RemainingFlightTimeWidget.ModelState.* import dji.ux.beta.core.widget.remainingflighttime.RemainingFlightTimeWidgetModel.RemainingFlightTimeData /** @@ -60,7 +60,7 @@ open class RemainingFlightTimeWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : FrameLayoutWidget( +) : FrameLayoutWidget( context, attrs, defStyleAttr @@ -71,7 +71,6 @@ open class RemainingFlightTimeWidget @JvmOverloads constructor( DJISDKModel.getInstance(), ObservableInMemoryKeyedStore.getInstance()) } - private val schedulerProvider: SchedulerProvider = SchedulerProvider.getInstance() private val batteryRequiredToLandPaint: Paint = Paint() private val batteryChargeRemainingPaint: Paint = Paint() private val batteryRequiredToGoHomePaint: Paint = Paint() @@ -189,7 +188,7 @@ open class RemainingFlightTimeWidget @JvmOverloads constructor( //endregion - //region lifecycle + //region Lifecycle init { setWillNotDraw(false) @@ -308,16 +307,16 @@ open class RemainingFlightTimeWidget @JvmOverloads constructor( override fun reactToModelChanges() { addReaction(widgetModel.productConnection - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { isProductConnected: Boolean -> updateVisibility(isProductConnected) }) addReaction(reactToRemainingFlightTimeChange()) addReaction(widgetModel.isAircraftFlying - .observeOn(schedulerProvider.io()) + .observeOn(SchedulerProvider.io()) .subscribe { - widgetStateDataProcessor.onNext(AircraftFlying(it)) + widgetStateDataProcessor.onNext(AircraftFlyingUpdated(it)) }) addReaction(widgetModel.remainingFlightTimeData - .observeOn(schedulerProvider.io()) + .observeOn(SchedulerProvider.io()) .subscribe { widgetStateDataProcessor.onNext( FlightTimeDataUpdated(it)) @@ -358,7 +357,7 @@ open class RemainingFlightTimeWidget @JvmOverloads constructor( return Flowable.combineLatest(widgetModel.isAircraftFlying, widgetModel.remainingFlightTimeData, BiFunction { first: Boolean, second: RemainingFlightTimeData -> Pair(first, second) }) - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { values: Pair -> onRemainingFlightTimeChange(values.first, values.second) }, logErrorConsumer(TAG, "react to flight time update: ")) } @@ -464,32 +463,33 @@ open class RemainingFlightTimeWidget @JvmOverloads constructor( //endregion - //region hooks + //region Hooks /** - * Get the [RemainingFlightTimeWidgetState] updates + * Get the [ModelState] updates */ - override fun getWidgetStateUpdate(): Flowable { + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { return super.getWidgetStateUpdate() } /** * Class defines the widget state updates */ - sealed class RemainingFlightTimeWidgetState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : RemainingFlightTimeWidgetState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * Aircraft flying status change update */ - data class AircraftFlying(val isAircraftFlying: Boolean) : RemainingFlightTimeWidgetState() + data class AircraftFlyingUpdated(val isAircraftFlying: Boolean) : ModelState() /** * Remaining flight time data update */ - data class FlightTimeDataUpdated(val remainingFlightTimeData: RemainingFlightTimeData) : RemainingFlightTimeWidgetState() + data class FlightTimeDataUpdated(val remainingFlightTimeData: RemainingFlightTimeData) : ModelState() } //endregion diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/remainingflighttime/RemainingFlightTimeWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/remainingflighttime/RemainingFlightTimeWidgetModel.kt index 4c080fbb..8edfba90 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/remainingflighttime/RemainingFlightTimeWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/remainingflighttime/RemainingFlightTimeWidgetModel.kt @@ -29,7 +29,7 @@ import dji.keysdk.FlightControllerKey import dji.thirdparty.io.reactivex.Flowable import dji.ux.beta.core.base.DJISDKModel import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.util.DataProcessor /** @@ -39,11 +39,10 @@ import dji.ux.beta.core.util.DataProcessor * Widget Model for the [RemainingFlightTimeWidget] used to define the * underlying logic and communication */ -class RemainingFlightTimeWidgetModel( - djiSdkModel: DJISDKModel, - keyedStore: ObservableInMemoryKeyedStore +class RemainingFlightTimeWidgetModel(djiSdkModel: DJISDKModel, + keyedStore: ObservableInMemoryKeyedStore ) : WidgetModel(djiSdkModel, keyedStore) { - //region fields + //region Fields private val chargeRemainingProcessor: DataProcessor = DataProcessor.create(0) private val batteryNeededToLandProcessor: DataProcessor = DataProcessor.create(0) private val batteryNeededToGoHomeProcessor: DataProcessor = DataProcessor.create(0) diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/remotecontrollersignal/RemoteControllerSignalWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/remotecontrollersignal/RemoteControllerSignalWidget.kt index aa7f31b9..79bd1529 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/remotecontrollersignal/RemoteControllerSignalWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/remotecontrollersignal/RemoteControllerSignalWidget.kt @@ -34,16 +34,16 @@ import androidx.annotation.DrawableRes import androidx.annotation.IntRange import androidx.core.content.res.use import dji.thirdparty.io.reactivex.Flowable -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers +import dji.ux.beta.core.base.SchedulerProvider import dji.thirdparty.io.reactivex.functions.Consumer -import dji.ux.beta.R -import dji.ux.beta.core.base.ConstraintLayoutWidget +import dji.ux.beta.core.R import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.* -import dji.ux.beta.core.widget.remotecontrollersignal.RemoteControllerSignalWidget.RemoteControllerSignalWidgetState -import dji.ux.beta.core.widget.remotecontrollersignal.RemoteControllerSignalWidget.RemoteControllerSignalWidgetState.ProductConnected -import dji.ux.beta.core.widget.remotecontrollersignal.RemoteControllerSignalWidget.RemoteControllerSignalWidgetState.SignalQualityUpdated +import dji.ux.beta.core.widget.remotecontrollersignal.RemoteControllerSignalWidget.ModelState +import dji.ux.beta.core.widget.remotecontrollersignal.RemoteControllerSignalWidget.ModelState.ProductConnected +import dji.ux.beta.core.widget.remotecontrollersignal.RemoteControllerSignalWidget.ModelState.SignalQualityUpdated /** * This widget shows the strength of the signal between the RC and the aircraft. @@ -52,7 +52,7 @@ open class RemoteControllerSignalWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ConstraintLayoutWidget(context, attrs, defStyleAttr) { +) : ConstraintLayoutWidget(context, attrs, defStyleAttr) { //region Fields private val rcIconImageView: ImageView = findViewById(R.id.imageview_rc_icon) @@ -128,7 +128,7 @@ open class RemoteControllerSignalWidget @JvmOverloads constructor( } //endregion - //region Constructors + //region Constructor override fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { inflate(context, R.layout.uxsdk_widget_remote_controller_signal, this) } @@ -155,10 +155,10 @@ open class RemoteControllerSignalWidget @JvmOverloads constructor( override fun reactToModelChanges() { addReaction(widgetModel.rcSignalQuality - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { this.updateRCSignal(it) }) addReaction(widgetModel.productConnection - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { this.updateIconColor(it) }) } //endregion @@ -183,7 +183,7 @@ open class RemoteControllerSignalWidget @JvmOverloads constructor( private fun checkAndUpdateIconColor() { if (!isInEditMode) { addDisposable(widgetModel.productConnection.firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { this.updateIconColor(it) }, logErrorConsumer(TAG, "Update Icon Color "))) } } @@ -248,27 +248,28 @@ open class RemoteControllerSignalWidget @JvmOverloads constructor( } //endregion - //region hooks + //region Hooks /** - * Get the [RemoteControllerSignalWidgetState] updates + * Get the [ModelState] updates */ - override fun getWidgetStateUpdate(): Flowable { + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { return super.getWidgetStateUpdate() } /** * Class defines the widget state updates */ - sealed class RemoteControllerSignalWidgetState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : RemoteControllerSignalWidgetState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * Signal quality update */ - data class SignalQualityUpdated(val signalValue: Int) : RemoteControllerSignalWidgetState() + data class SignalQualityUpdated(val signalValue: Int) : ModelState() } //endregion diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/remotecontrollersignal/RemoteControllerSignalWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/remotecontrollersignal/RemoteControllerSignalWidgetModel.kt index f62ea152..9b2b27ea 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/remotecontrollersignal/RemoteControllerSignalWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/remotecontrollersignal/RemoteControllerSignalWidgetModel.kt @@ -18,7 +18,7 @@ * 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.ux.beta.core.widget.remotecontrollersignal @@ -28,7 +28,7 @@ import dji.keysdk.AirLinkKey.UPLINK_SIGNAL_QUALITY import dji.thirdparty.io.reactivex.Flowable import dji.ux.beta.core.base.DJISDKModel import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.util.DataProcessor /** diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/simulator/SimulatorIndicatorWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/simulator/SimulatorIndicatorWidget.kt index 77168a9e..2615e4e5 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/simulator/SimulatorIndicatorWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/simulator/SimulatorIndicatorWidget.kt @@ -36,16 +36,16 @@ import dji.thirdparty.io.reactivex.Flowable import dji.thirdparty.io.reactivex.disposables.Disposable import dji.thirdparty.io.reactivex.functions.BiFunction import dji.thirdparty.io.reactivex.functions.Consumer -import dji.ux.beta.R +import dji.ux.beta.core.R import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.OnStateChangeCallback import dji.ux.beta.core.base.SchedulerProvider -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore import dji.ux.beta.core.base.widget.IconButtonWidget +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.OnStateChangeCallback import dji.ux.beta.core.extension.* -import dji.ux.beta.core.widget.simulator.SimulatorIndicatorWidget.SimulatorIndicatorWidgetState -import dji.ux.beta.core.widget.simulator.SimulatorIndicatorWidget.SimulatorIndicatorWidgetState.ProductConnected -import dji.ux.beta.core.widget.simulator.SimulatorIndicatorWidget.SimulatorIndicatorWidgetState.SimulatorStateUpdated +import dji.ux.beta.core.widget.simulator.SimulatorIndicatorWidget.ModelState +import dji.ux.beta.core.widget.simulator.SimulatorIndicatorWidget.ModelState.ProductConnected +import dji.ux.beta.core.widget.simulator.SimulatorIndicatorWidget.ModelState.SimulatorStateUpdated /** * Simulator Indicator Widget will display the current state of the simulator @@ -58,15 +58,14 @@ open class SimulatorIndicatorWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : IconButtonWidget( +) : IconButtonWidget( context, attrs, defStyleAttr ), View.OnClickListener { - //region fields + //region Fields private var stateChangeResourceId: Int = INVALID_RESOURCE - private val schedulerProvider: SchedulerProvider = SchedulerProvider.getInstance() private val widgetModel by lazy { SimulatorIndicatorWidgetModel( DJISDKModel.getInstance(), @@ -99,7 +98,7 @@ open class SimulatorIndicatorWidget @JvmOverloads constructor( //endregion - //region lifecycle + //region Lifecycle init { attrs?.let { initAttributes(context, it) } connectedStateIconColor = getColor(R.color.uxsdk_white) @@ -108,10 +107,10 @@ open class SimulatorIndicatorWidget @JvmOverloads constructor( override fun reactToModelChanges() { addReaction(reactToSimulatorStateChange()) addReaction(widgetModel.isSimulatorActive - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { widgetStateDataProcessor.onNext(SimulatorStateUpdated(it)) }) addReaction(widgetModel.productConnection - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) } @@ -154,7 +153,7 @@ open class SimulatorIndicatorWidget @JvmOverloads constructor( private fun reactToSimulatorStateChange(): Disposable { return Flowable.combineLatest(widgetModel.productConnection, widgetModel.isSimulatorActive, BiFunction> { first: Boolean, second: Boolean -> Pair(first, second) }) - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { values: Pair -> updateUI(values.first, values.second) }, logErrorConsumer(TAG, "react to Focus Mode Change: ")) } @@ -232,27 +231,29 @@ open class SimulatorIndicatorWidget @JvmOverloads constructor( //endregion - //region hooks + //region Hooks + /** - * Get the [SimulatorIndicatorWidgetState] updates + * Get the [ModelState] updates */ - override fun getWidgetStateUpdate(): Flowable { + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { return super.getWidgetStateUpdate() } /** * Class defines the widget state updates */ - sealed class SimulatorIndicatorWidgetState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : SimulatorIndicatorWidgetState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * Simulator State update */ - data class SimulatorStateUpdated(val isActive: Boolean) : SimulatorIndicatorWidgetState() + data class SimulatorStateUpdated(val isActive: Boolean) : ModelState() } //endregion diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/simulator/SimulatorIndicatorWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/simulator/SimulatorIndicatorWidgetModel.kt index 2b2cfa8c..95da022f 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/simulator/SimulatorIndicatorWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/simulator/SimulatorIndicatorWidgetModel.kt @@ -27,7 +27,7 @@ import dji.keysdk.FlightControllerKey import dji.thirdparty.io.reactivex.Flowable import dji.ux.beta.core.base.DJISDKModel import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.util.DataProcessor /** @@ -42,7 +42,7 @@ class SimulatorIndicatorWidgetModel( keyedStore: ObservableInMemoryKeyedStore ) : WidgetModel(djiSdkModel, keyedStore) { - //region fields + //region Fields private val simulatorActiveDataProcessor: DataProcessor = DataProcessor.create(false) //endregion diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/systemstatus/SystemStatusWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/systemstatus/SystemStatusWidget.kt index de4d21ab..d2cb3265 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/systemstatus/SystemStatusWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/systemstatus/SystemStatusWidget.kt @@ -40,19 +40,24 @@ import androidx.core.content.res.use import dji.common.logics.warningstatuslogic.WarningStatusItem import dji.common.logics.warningstatuslogic.WarningStatusItem.WarningLevel import dji.thirdparty.io.reactivex.Flowable -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers import dji.thirdparty.io.reactivex.disposables.Disposable import dji.thirdparty.io.reactivex.functions.BiFunction import dji.thirdparty.io.reactivex.functions.Consumer -import dji.ux.beta.R -import dji.ux.beta.core.base.* -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.thirdparty.io.reactivex.processors.PublishProcessor +import dji.ux.beta.core.R +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.SchedulerProvider +import dji.ux.beta.core.base.WidgetSizeDescription +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget +import dji.ux.beta.core.communication.GlobalPreferencesManager +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.OnStateChangeCallback import dji.ux.beta.core.extension.* import dji.ux.beta.core.util.DisplayUtil import dji.ux.beta.core.util.UnitConversionUtil -import dji.ux.beta.core.widget.systemstatus.SystemStatusWidget.SystemStatusWidgetState -import dji.ux.beta.core.widget.systemstatus.SystemStatusWidget.SystemStatusWidgetState.ProductConnected -import dji.ux.beta.core.widget.systemstatus.SystemStatusWidget.SystemStatusWidgetState.SystemStatusUpdated +import dji.ux.beta.core.widget.systemstatus.SystemStatusWidget.ModelState +import dji.ux.beta.core.widget.systemstatus.SystemStatusWidget.ModelState.ProductConnected +import dji.ux.beta.core.widget.systemstatus.SystemStatusWidget.ModelState.SystemStatusUpdated import java.util.* private const val TAG = "SystemStatusWidget" @@ -71,16 +76,16 @@ open class SystemStatusWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ConstraintLayoutWidget(context, attrs, defStyleAttr), View.OnClickListener { +) : ConstraintLayoutWidget(context, attrs, defStyleAttr), View.OnClickListener { //region Fields private val systemStatusTextView: TextView = findViewById(R.id.textview_system_status) private val systemStatusBackgroundImageView: ImageView = findViewById(R.id.imageview_system_status_background) private val blinkAnimation: Animation = AnimationUtils.loadAnimation(context, R.anim.uxsdk_anim_blink) + protected val uiUpdateStateProcessor: PublishProcessor = PublishProcessor.create() private val widgetModel by lazy { SystemStatusWidgetModel(DJISDKModel.getInstance(), ObservableInMemoryKeyedStore.getInstance(), - SchedulerProvider.getInstance(), GlobalPreferencesManager.getInstance()) } @@ -105,7 +110,7 @@ open class SystemStatusWidget @JvmOverloads constructor( /** * Call back for when the widget is tapped. - * This can be used to link the widget to [dji.ux.beta.core.panelwidget.systemstatus.SystemStatusListPanelWidget] + * This can be used to link the widget to [dji.ux.beta.core.panel.systemstatus.SystemStatusListPanelWidget] */ var stateChangeCallback: OnStateChangeCallback? = null @@ -136,7 +141,7 @@ open class SystemStatusWidget @JvmOverloads constructor( //endregion - //region Constructors + //region Constructor override fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { inflate(context, R.layout.uxsdk_widget_system_status, this) } @@ -167,18 +172,19 @@ open class SystemStatusWidget @JvmOverloads constructor( override fun reactToModelChanges() { addReaction(widgetModel.systemStatus - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { updateUI(it) }) addReaction(reactToCompassError()) addReaction(widgetModel.warningStatusMessageData - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { updateMessage(it) }) addReaction(widgetModel.productConnection - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) } override fun onClick(v: View?) { + uiUpdateStateProcessor.onNext(UIState.WidgetClicked) stateChangeCallback?.onStateChange(null) } //endregion @@ -227,7 +233,7 @@ open class SystemStatusWidget @JvmOverloads constructor( private fun reactToCompassError(): Disposable { return Flowable.combineLatest(widgetModel.systemStatus, widgetModel.isMotorOn, BiFunction> { first: WarningStatusItem, second: Boolean -> Pair(first, second) }) - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { values: Pair -> updateVoiceNotification(values.first, values.second) }, logErrorConsumer(TAG, "react to Compass Error: ")) } @@ -241,7 +247,7 @@ open class SystemStatusWidget @JvmOverloads constructor( private fun checkAndUpdateUI() { if (!isInEditMode) { addDisposable(widgetModel.systemStatus.firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { this.updateUI(it) }, logErrorConsumer(TAG, "Update UI "))) } } @@ -395,27 +401,47 @@ open class SystemStatusWidget @JvmOverloads constructor( //endregion - //region hooks + //region Hooks + /** - * Get the [SystemStatusWidgetState] updates + * Get the [ModelState] updates */ - override fun getWidgetStateUpdate(): Flowable { + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { return super.getWidgetStateUpdate() } + /** + * Get the [UIState] updates + */ + fun getUIStateUpdates(): Flowable { + return uiUpdateStateProcessor.onBackpressureBuffer() + } + + /** + * Class defines widget UI states + */ + sealed class UIState { + + /** + * Widget click update + */ + object WidgetClicked : UIState() + } + /** * Class defines the widget state updates */ - sealed class SystemStatusWidgetState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : SystemStatusWidgetState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * System status update */ - data class SystemStatusUpdated(val status: WarningStatusItem) : SystemStatusWidgetState() + data class SystemStatusUpdated(val status: WarningStatusItem) : ModelState() } /** @@ -434,16 +460,12 @@ open class SystemStatusWidget @JvmOverloads constructor( GRADIENT(1); companion object { + @JvmStatic + val values = values() + @JvmStatic fun find(value: Int): DefaultMode { - var result = COLOR - for (i in values().indices) { - if (values()[i].value == value) { - result = values()[i] - break - } - } - return result + return values.find { it.value == value } ?: COLOR } } } diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/systemstatus/SystemStatusWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/systemstatus/SystemStatusWidgetModel.kt index 6fa67872..b3f5087e 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/systemstatus/SystemStatusWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/systemstatus/SystemStatusWidgetModel.kt @@ -30,10 +30,8 @@ import dji.keysdk.FlightControllerKey import dji.thirdparty.io.reactivex.Completable import dji.thirdparty.io.reactivex.Flowable import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.GlobalPreferencesInterface -import dji.ux.beta.core.base.SchedulerProviderInterface import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.* +import dji.ux.beta.core.communication.* import dji.ux.beta.core.model.VoiceNotificationType import dji.ux.beta.core.util.DataProcessor import dji.ux.beta.core.util.UnitConversionUtil @@ -44,8 +42,8 @@ import dji.ux.beta.core.util.UnitConversionUtil */ class SystemStatusWidgetModel(djiSdkModel: DJISDKModel, private val keyedStore: ObservableInMemoryKeyedStore, - private val schedulerProvider: SchedulerProviderInterface, - private val preferencesManager: GlobalPreferencesInterface?) : WidgetModel(djiSdkModel, keyedStore) { + private val preferencesManager: GlobalPreferencesInterface? +) : WidgetModel(djiSdkModel, keyedStore) { //region Fields private val systemStatusProcessor: DataProcessor = DataProcessor.create(WarningStatusItem.getDefaultItem()) @@ -86,7 +84,6 @@ class SystemStatusWidgetModel(djiSdkModel: DJISDKModel, fun sendVoiceNotification(): Completable { val notificationType = VoiceNotificationType.ATTI return keyedStore.setValue(sendVoiceNotificationKey, notificationType) - .subscribeOn(schedulerProvider.io()) } //endregion diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/useraccount/UserAccountLoginWidget.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/useraccount/UserAccountLoginWidget.java index 687bf161..0aed5e99 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/useraccount/UserAccountLoginWidget.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/useraccount/UserAccountLoginWidget.java @@ -48,14 +48,14 @@ import dji.sdk.useraccount.UserAccountInformation; import dji.sdk.useraccount.UserAccountManager; import dji.thirdparty.io.reactivex.Flowable; -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers; import dji.thirdparty.io.reactivex.disposables.Disposable; -import dji.ux.beta.R; -import dji.ux.beta.core.base.ConstraintLayoutWidget; +import dji.ux.beta.core.R; import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.OnStateChangeCallback; +import dji.ux.beta.core.base.SchedulerProvider; import dji.ux.beta.core.base.UXSDKError; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.communication.OnStateChangeCallback; /** * User Account Login Widget @@ -65,7 +65,7 @@ */ public class UserAccountLoginWidget extends ConstraintLayoutWidget implements OnClickListener { - //region fields + //region Fields private static final String TAG = "LoginWidget"; private TextView widgetStateTextView; private ImageView widgetActionImageView; @@ -81,7 +81,7 @@ public class UserAccountLoginWidget extends ConstraintLayoutWidget implements On //endregion - //region lifecycle + //region Lifecycle public UserAccountLoginWidget(Context context) { super(context); } @@ -146,7 +146,7 @@ protected void onDetachedFromWindow() { public void onClick(View v) { addDisposable(widgetModel.getUserAccountState() .firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(userAccountState -> { if (userAccountState == UserAccountState.AUTHORIZED) { logoutUser(); @@ -177,7 +177,7 @@ private Flowable> getAccountState private Disposable reactToAccountStateChange() { return getAccountState() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(values -> updateUI(values.first, values.second), logErrorConsumer(TAG, "react to User Account ")); } @@ -185,7 +185,7 @@ private Disposable reactToAccountStateChange() { private void checkAndUpdateUI() { if (!isInEditMode()) { addDisposable(getAccountState().firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(values -> updateUI(values.first, values.second), logErrorConsumer(TAG, "react to User Account "))); } @@ -193,7 +193,7 @@ private void checkAndUpdateUI() { private void loginUser() { addDisposable(widgetModel.loginUser(getContext()) - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(() -> { }, error -> { if (error instanceof UXSDKError) { @@ -203,7 +203,7 @@ private void loginUser() { } private void logoutUser() { - addDisposable(widgetModel.logoutUser().observeOn(AndroidSchedulers.mainThread()).subscribe(() -> { + addDisposable(widgetModel.logoutUser().observeOn(SchedulerProvider.ui()).subscribe(() -> { }, error -> { if (error instanceof UXSDKError) { diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/useraccount/UserAccountLoginWidgetModel.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/useraccount/UserAccountLoginWidgetModel.java index b02f12a6..d6aadabb 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/useraccount/UserAccountLoginWidgetModel.java +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/useraccount/UserAccountLoginWidgetModel.java @@ -39,7 +39,7 @@ import dji.ux.beta.core.base.UXSDKError; import dji.ux.beta.core.base.UXSDKErrorDescription; import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; import dji.ux.beta.core.util.DataProcessor; /** @@ -51,7 +51,7 @@ public class UserAccountLoginWidgetModel extends WidgetModel implements UserAccountManager.UserAccountStateChangeListener { - //region fields + //region Fields private static final String TAG = "LoginWidgetModel"; private final DataProcessor userAccountStateDataProcessor; private final DataProcessor userAccountInformationDataProcessor; diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/verticalvelocity/VerticalVelocityWidget.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/verticalvelocity/VerticalVelocityWidget.java deleted file mode 100644 index 7eb49b00..00000000 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/verticalvelocity/VerticalVelocityWidget.java +++ /dev/null @@ -1,648 +0,0 @@ -/* - * 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.ux.beta.core.widget.verticalvelocity; - -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.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.ColorInt; -import androidx.annotation.Dimension; -import androidx.annotation.DrawableRes; -import androidx.annotation.FloatRange; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StyleRes; - -import java.text.DecimalFormat; - -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers; -import dji.ux.beta.R; -import dji.ux.beta.core.base.ConstraintLayoutWidget; -import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.GlobalPreferencesInterface; -import dji.ux.beta.core.base.GlobalPreferencesManager; -import dji.ux.beta.core.base.uxsdkkeys.GlobalPreferenceKeys; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; -import dji.ux.beta.core.util.DisplayUtil; -import dji.ux.beta.core.util.UnitConversionUtil; - -/** - * Shows the vertical velocity of the aircraft. The - * arrow indicates the direction of travel of the aircraft. - *

- * Uses the unit set in the UNIT_TYPE global preferences - * {@link GlobalPreferencesInterface#getUnitType()} and the - * {@link GlobalPreferenceKeys#UNIT_TYPE} UX Key - * and defaults to m/s if nothing is set. - */ -public class VerticalVelocityWidget extends ConstraintLayoutWidget { - //region Constants - private static final int EMS = 2; - private static final float MINIMUM_VELOCITY = -20.0f; - private static final float MAXIMUM_VELOCITY = 20.0f; - private static final String TAG = "VerticalVelocityWidget"; - //endregion - - //region Fields - private static DecimalFormat decimalFormat = new DecimalFormat("#0.0"); - private ImageView verticalVelocityImageView; - private TextView verticalVelocityTitleTextView; - private TextView verticalVelocityValueTextView; - private TextView verticalVelocityUnitTextView; - private Drawable upwardVelocityDrawable; - private Drawable downwardVelocityDrawable; - private VerticalVelocityWidgetModel widgetModel; - //endregion - - public VerticalVelocityWidget(Context context) { - super(context); - } - - public VerticalVelocityWidget(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public VerticalVelocityWidget(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_base_dashboard_image_and_text, this); - verticalVelocityImageView = findViewById(R.id.imageview_icon); - verticalVelocityTitleTextView = findViewById(R.id.textview_title); - verticalVelocityValueTextView = findViewById(R.id.textview_value); - verticalVelocityUnitTextView = findViewById(R.id.textview_unit); - - if (!isInEditMode()) { - widgetModel = new VerticalVelocityWidgetModel(DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - GlobalPreferencesManager.getInstance()); - verticalVelocityTitleTextView.setText(getResources().getString(R.string.uxsdk_vertical_velocity_title)); - verticalVelocityValueTextView.setMinEms(EMS); - } - - upwardVelocityDrawable = getResources().getDrawable(R.drawable.uxsdk_ic_arrow_up); - downwardVelocityDrawable = getResources().getDrawable(R.drawable.uxsdk_ic_arrow_down); - - if (attrs != null) { - initAttributes(context, attrs); - } - } - - //region Lifecycle - @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.getVerticalVelocity() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::updateValueText)); - addReaction(widgetModel.getUnitType() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::updateUnitText)); - } - //endregion - - //region reaction helpers - private void updateValueText(@FloatRange(from = MINIMUM_VELOCITY, to = MAXIMUM_VELOCITY) float verticalVelocity) { - verticalVelocityValueTextView.setText(decimalFormat.format(Math.abs(verticalVelocity))); - if (verticalVelocity == 0) { - verticalVelocityImageView.setVisibility(GONE); - } else if (verticalVelocity < 0) { - verticalVelocityImageView.setVisibility(VISIBLE); - verticalVelocityImageView.setImageDrawable(upwardVelocityDrawable); - } else { - verticalVelocityImageView.setVisibility(VISIBLE); - verticalVelocityImageView.setImageDrawable(downwardVelocityDrawable); - } - } - - private void updateUnitText(UnitConversionUtil.UnitType unitType) { - if (unitType == UnitConversionUtil.UnitType.IMPERIAL) { - verticalVelocityUnitTextView.setText(getResources().getString(R.string.uxsdk_unit_mile_per_hr)); - } else { - verticalVelocityUnitTextView.setText(getResources().getString(R.string.uxsdk_unit_meter_per_second)); - } - } - - private void checkAndUpdateValueText() { - if (!isInEditMode()) { - addDisposable(widgetModel.getVerticalVelocity().firstOrError() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::updateValueText, logErrorConsumer(TAG, "check and update value text"))); - } - } - //endregion - - //region Customization - @NonNull - @Override - public String getIdealDimensionRatioString() { - return getResources().getString(R.string.uxsdk_widget_base_dashboard_distance_ratio); - } - //endregion - - //region Customization Helpers - - /** - * Set text appearance of the vertical velocity title text view - * - * @param textAppearance Style resource for text appearance - */ - public void setVerticalVelocityTitleTextAppearance(@StyleRes int textAppearance) { - verticalVelocityTitleTextView.setTextAppearance(getContext(), textAppearance); - } - - /** - * Get current text color state list of the vertical velocity title text view - * - * @return ColorStateList resource - */ - @Nullable - public ColorStateList getVerticalVelocityTitleTextColors() { - return verticalVelocityTitleTextView.getTextColors(); - } - - /** - * Get current text color of the vertical velocity title text view - * - * @return color integer resource - */ - @ColorInt - public int getVerticalVelocityTitleTextColor() { - return verticalVelocityTitleTextView.getCurrentTextColor(); - } - - /** - * Set text color state list for the vertical velocity title text view - * - * @param colorStateList ColorStateList resource - */ - public void setVerticalVelocityTitleTextColor(@NonNull ColorStateList colorStateList) { - verticalVelocityTitleTextView.setTextColor(colorStateList); - } - - /** - * Set the text color for the vertical velocity title text view - * - * @param color color integer resource - */ - public void setVerticalVelocityTitleTextColor(@ColorInt int color) { - verticalVelocityTitleTextView.setTextColor(color); - } - - /** - * Get current text size of the vertical velocity title text view - * - * @return text size of the text view - */ - @Dimension - public float getVerticalVelocityTitleTextSize() { - return verticalVelocityTitleTextView.getTextSize(); - } - - /** - * Set the text size of the vertical velocity title text view - * - * @param textSize text size float value - */ - public void setVerticalVelocityTitleTextSize(@Dimension float textSize) { - verticalVelocityTitleTextView.setTextSize(textSize); - } - - /** - * Get current background of the vertical velocity title text view - * - * @return Drawable resource of the background - */ - @Nullable - public Drawable getVerticalVelocityTitleTextBackground() { - return verticalVelocityTitleTextView.getBackground(); - } - - /** - * Set the background of the vertical velocity title text view - * - * @param drawable Drawable resource for the background - */ - public void setVerticalVelocityTitleTextBackground(@Nullable Drawable drawable) { - verticalVelocityTitleTextView.setBackground(drawable); - } - - /** - * Set the resource ID for the background of the vertical velocity title text view - * - * @param resourceId Integer ID of the text view's background resource - */ - public void setVerticalVelocityTitleTextBackground(@DrawableRes int resourceId) { - verticalVelocityTitleTextView.setBackgroundResource(resourceId); - } - - /** - * Get the drawable resource for the upward vertical velocity icon - * - * @return Drawable resource of the icon - */ - @Nullable - public Drawable getUpwardVerticalVelocityIcon() { - return upwardVelocityDrawable; - } - - /** - * Set the resource ID for the upward vertical velocity icon - * - * @param resourceId Integer ID of the drawable resource - */ - public void setUpwardVerticalVelocityIcon(@DrawableRes int resourceId) { - setUpwardVerticalVelocityIcon(getResources().getDrawable(resourceId)); - } - - /** - * Set the drawable resource for the upward vertical velocity icon - * - * @param icon Drawable resource for the image - */ - public void setUpwardVerticalVelocityIcon(@Nullable Drawable icon) { - upwardVelocityDrawable = icon; - checkAndUpdateValueText(); - } - - /** - * Get the drawable resource for the downward vertical velocity icon - * - * @return Drawable resource of the icon - */ - @Nullable - public Drawable getDownwardVerticalVelocityIcon() { - return downwardVelocityDrawable; - } - - /** - * Set the resource ID for the downward vertical velocity icon - * - * @param resourceId Integer ID of the drawable resource - */ - public void setDownwardVerticalVelocityIcon(@DrawableRes int resourceId) { - setDownwardVerticalVelocityIcon(getResources().getDrawable(resourceId)); - } - - /** - * Set the drawable resource for the downward vertical velocity icon - * - * @param icon Drawable resource for the image - */ - public void setDownwardVerticalVelocityIcon(@Nullable Drawable icon) { - downwardVelocityDrawable = icon; - checkAndUpdateValueText(); - } - - /** - * Get the drawable resource for the vertical velocity icon's background - * - * @return Drawable resource of the icon's background - */ - @Nullable - public Drawable getVerticalVelocityIconBackground() { - return verticalVelocityImageView.getBackground(); - } - - /** - * Set the resource ID for the vertical velocity icon's background - * - * @param resourceId Integer ID of the icon's background resource - */ - public void setVerticalVelocityIconBackground(@DrawableRes int resourceId) { - verticalVelocityImageView.setBackgroundResource(resourceId); - } - - /** - * Set the drawable resource for the vertical velocity icon's background - * - * @param background Drawable resource for the icon's background - */ - public void setVerticalVelocityIconBackground(@Nullable Drawable background) { - verticalVelocityImageView.setBackground(background); - } - - /** - * Set text appearance of the vertical velocity value text view - * - * @param textAppearance Style resource for text appearance - */ - public void setVerticalVelocityValueTextAppearance(@StyleRes int textAppearance) { - verticalVelocityValueTextView.setTextAppearance(getContext(), textAppearance); - } - - /** - * Get current text color state list of the vertical velocity value text view - * - * @return ColorStateList resource - */ - @Nullable - public ColorStateList getVerticalVelocityValueTextColors() { - return verticalVelocityValueTextView.getTextColors(); - } - - /** - * Get current text color of the vertical velocity value text view - * - * @return color integer resource - */ - @ColorInt - public int getVerticalVelocityValueTextColor() { - return verticalVelocityValueTextView.getCurrentTextColor(); - } - - /** - * Set text color state list for the vertical velocity value text view - * - * @param colorStateList ColorStateList resource - */ - public void setVerticalVelocityValueTextColor(@NonNull ColorStateList colorStateList) { - verticalVelocityValueTextView.setTextColor(colorStateList); - } - - /** - * Set the text color for the vertical velocity value text view - * - * @param color color integer resource - */ - public void setVerticalVelocityValueTextColor(@ColorInt int color) { - verticalVelocityValueTextView.setTextColor(color); - } - - /** - * Get current text size of the vertical velocity value text view - * - * @return text size of the text view - */ - @Dimension - public float getVerticalVelocityValueTextSize() { - return verticalVelocityValueTextView.getTextSize(); - } - - /** - * Set the text size of the vertical velocity value text view - * - * @param textSize text size float value - */ - public void setVerticalVelocityValueTextSize(@Dimension float textSize) { - verticalVelocityValueTextView.setTextSize(textSize); - } - - /** - * Get current background of the vertical velocity value text view - * - * @return Drawable resource of the background - */ - @Nullable - public Drawable getVerticalVelocityValueTextBackground() { - return verticalVelocityValueTextView.getBackground(); - } - - /** - * Set the background for the vertical velocity value text view - * - * @param drawable Drawable resource for the background - */ - public void setVerticalVelocityValueTextBackground(@Nullable Drawable drawable) { - verticalVelocityValueTextView.setBackground(drawable); - } - - /** - * Set the resource ID for the background of the vertical velocity value text view - * - * @param resourceId Integer ID of the text view's background resource - */ - public void setVerticalVelocityValueTextBackground(@DrawableRes int resourceId) { - verticalVelocityValueTextView.setBackgroundResource(resourceId); - } - - /** - * Set text appearance of the vertical velocity unit text view - * - * @param textAppearance Style resource for text appearance - */ - public void setVerticalVelocityUnitTextAppearance(@StyleRes int textAppearance) { - verticalVelocityUnitTextView.setTextAppearance(getContext(), textAppearance); - } - - /** - * Get current text color state list of the vertical velocity unit text view - * - * @return ColorStateList resource - */ - @Nullable - public ColorStateList getVerticalVelocityUnitTextColors() { - return verticalVelocityUnitTextView.getTextColors(); - } - - /** - * Get current text color of the vertical velocity unit text view - * - * @return color integer resource - */ - @ColorInt - public int getVerticalVelocityUnitTextColor() { - return verticalVelocityUnitTextView.getCurrentTextColor(); - } - - /** - * Set text color state list for the vertical velocity unit text view - * - * @param colorStateList ColorStateList resource - */ - public void setVerticalVelocityUnitTextColor(@NonNull ColorStateList colorStateList) { - verticalVelocityUnitTextView.setTextColor(colorStateList); - } - - /** - * Set the text color for the vertical velocity unit text view - * - * @param color color integer resource - */ - public void setVerticalVelocityUnitTextColor(@ColorInt int color) { - verticalVelocityUnitTextView.setTextColor(color); - } - - /** - * Get current text size of the vertical velocity unit text view - * - * @return text size of the text view - */ - @Dimension - public float getVerticalVelocityUnitTextSize() { - return verticalVelocityUnitTextView.getTextSize(); - } - - /** - * Set the text size of the vertical velocity unit text view - * - * @param textSize text size float value - */ - public void setVerticalVelocityUnitTextSize(@Dimension float textSize) { - verticalVelocityUnitTextView.setTextSize(textSize); - } - - /** - * Get current background of the vertical velocity unit text view - * - * @return Drawable resource of the background - */ - @Nullable - public Drawable getVerticalVelocityUnitTextBackground() { - return verticalVelocityUnitTextView.getBackground(); - } - - /** - * Set the background for the vertical velocity unit text view - * - * @param drawable Drawable resource for the background - */ - public void setVerticalVelocityUnitTextBackground(@Nullable Drawable drawable) { - verticalVelocityUnitTextView.setBackground(drawable); - } - - /** - * Set the resource ID for the background of the vertical velocity unit text view - * - * @param resourceId Integer ID of the text view's background resource - */ - public void setVerticalVelocityUnitTextBackground(@DrawableRes int resourceId) { - verticalVelocityUnitTextView.setBackgroundResource(resourceId); - } - - //Initialize all customizable attributes - private void initAttributes(@NonNull Context context, @NonNull AttributeSet attrs) { - TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.VerticalVelocityWidget); - int verticalVelocityTitleTextAppearanceId = - typedArray.getResourceId(R.styleable.VerticalVelocityWidget_uxsdk_verticalVelocityTitleTextAppearance, - INVALID_RESOURCE); - if (verticalVelocityTitleTextAppearanceId != INVALID_RESOURCE) { - setVerticalVelocityTitleTextAppearance(verticalVelocityTitleTextAppearanceId); - } - - float verticalVelocityTitleTextSize = - typedArray.getDimension(R.styleable.VerticalVelocityWidget_uxsdk_verticalVelocityTitleTextSize, INVALID_RESOURCE); - if (verticalVelocityTitleTextSize != INVALID_RESOURCE) { - setVerticalVelocityTitleTextSize(DisplayUtil.pxToSp(context, verticalVelocityTitleTextSize)); - } - - int verticalVelocityTitleTextColor = - typedArray.getColor(R.styleable.VerticalVelocityWidget_uxsdk_verticalVelocityTitleTextColor, INVALID_COLOR); - if (verticalVelocityTitleTextColor != INVALID_COLOR) { - setVerticalVelocityTitleTextColor(verticalVelocityTitleTextColor); - } - - Drawable verticalVelocityTitleTextBackgroundDrawable = - typedArray.getDrawable(R.styleable.VerticalVelocityWidget_uxsdk_verticalVelocityTitleBackgroundDrawable); - if (verticalVelocityTitleTextBackgroundDrawable != null) { - setVerticalVelocityTitleTextBackground(verticalVelocityTitleTextBackgroundDrawable); - } - - Drawable upwardVerticalVelocityIcon = - typedArray.getDrawable(R.styleable.VerticalVelocityWidget_uxsdk_upwardVerticalVelocityIcon); - if (upwardVerticalVelocityIcon != null) { - setUpwardVerticalVelocityIcon(upwardVerticalVelocityIcon); - } - - Drawable downwardVerticalVelocityIcon = - typedArray.getDrawable(R.styleable.VerticalVelocityWidget_uxsdk_downwardVerticalVelocityIcon); - if (downwardVerticalVelocityIcon != null) { - setDownwardVerticalVelocityIcon(downwardVerticalVelocityIcon); - } - - int verticalVelocityValueTextAppearanceId = - typedArray.getResourceId(R.styleable.VerticalVelocityWidget_uxsdk_verticalVelocityValueTextAppearance, - INVALID_RESOURCE); - if (verticalVelocityValueTextAppearanceId != INVALID_RESOURCE) { - setVerticalVelocityValueTextAppearance(verticalVelocityValueTextAppearanceId); - } - - float verticalVelocityValueTextSize = - typedArray.getDimension(R.styleable.VerticalVelocityWidget_uxsdk_verticalVelocityValueTextSize, INVALID_RESOURCE); - if (verticalVelocityValueTextSize != INVALID_RESOURCE) { - setVerticalVelocityValueTextSize(DisplayUtil.pxToSp(context, verticalVelocityValueTextSize)); - } - - int verticalVelocityValueTextColor = - typedArray.getColor(R.styleable.VerticalVelocityWidget_uxsdk_verticalVelocityValueTextColor, INVALID_COLOR); - if (verticalVelocityValueTextColor != INVALID_COLOR) { - setVerticalVelocityValueTextColor(verticalVelocityValueTextColor); - } - - Drawable verticalVelocityValueTextBackgroundDrawable = - typedArray.getDrawable(R.styleable.VerticalVelocityWidget_uxsdk_verticalVelocityValueBackgroundDrawable); - if (verticalVelocityValueTextBackgroundDrawable != null) { - setVerticalVelocityValueTextBackground(verticalVelocityValueTextBackgroundDrawable); - } - - int verticalVelocityUnitTextAppearanceId = - typedArray.getResourceId(R.styleable.VerticalVelocityWidget_uxsdk_verticalVelocityUnitTextAppearance, - INVALID_RESOURCE); - if (verticalVelocityUnitTextAppearanceId != INVALID_RESOURCE) { - setVerticalVelocityUnitTextAppearance(verticalVelocityUnitTextAppearanceId); - } - - float verticalVelocityUnitTextSize = - typedArray.getDimension(R.styleable.VerticalVelocityWidget_uxsdk_verticalVelocityUnitTextSize, INVALID_RESOURCE); - if (verticalVelocityUnitTextSize != INVALID_RESOURCE) { - setVerticalVelocityUnitTextSize(DisplayUtil.pxToSp(context, verticalVelocityUnitTextSize)); - } - - int verticalVelocityUnitTextColor = - typedArray.getColor(R.styleable.VerticalVelocityWidget_uxsdk_verticalVelocityUnitTextColor, INVALID_COLOR); - if (verticalVelocityUnitTextColor != INVALID_COLOR) { - setVerticalVelocityUnitTextColor(verticalVelocityUnitTextColor); - } - - Drawable verticalVelocityUnitTextBackgroundDrawable = - typedArray.getDrawable(R.styleable.VerticalVelocityWidget_uxsdk_verticalVelocityUnitBackgroundDrawable); - if (verticalVelocityUnitTextBackgroundDrawable != null) { - setVerticalVelocityUnitTextBackground(verticalVelocityUnitTextBackgroundDrawable); - } - typedArray.recycle(); - } - //endregion -} diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/verticalvelocity/VerticalVelocityWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/verticalvelocity/VerticalVelocityWidget.kt new file mode 100644 index 00000000..a4ed85d9 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/verticalvelocity/VerticalVelocityWidget.kt @@ -0,0 +1,227 @@ +/* + * 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.ux.beta.core.widget.verticalvelocity + +import android.annotation.SuppressLint +import android.content.Context +import android.content.res.TypedArray +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import androidx.annotation.DrawableRes +import androidx.core.content.res.use +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.R +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.SchedulerProvider +import dji.ux.beta.core.base.WidgetSizeDescription +import dji.ux.beta.core.base.widget.BaseTelemetryWidget +import dji.ux.beta.core.communication.GlobalPreferencesManager +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.extension.getDrawable +import dji.ux.beta.core.extension.getDrawableAndUse +import dji.ux.beta.core.extension.getString +import dji.ux.beta.core.extension.getVelocityString +import dji.ux.beta.core.util.UnitConversionUtil.UnitType +import dji.ux.beta.core.widget.verticalvelocity.VerticalVelocityWidget.ModelState +import dji.ux.beta.core.widget.verticalvelocity.VerticalVelocityWidgetModel.VerticalVelocityState +import java.text.DecimalFormat + +/** + * Widget displays the vertical velocity of the aircraft. + * + */ +open class VerticalVelocityWidget @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + widgetTheme: Int = 0 +) : BaseTelemetryWidget( + context, + attrs, + defStyleAttr, + WidgetType.TEXT_IMAGE_RIGHT, + widgetTheme, + R.style.UXSDKVerticalVelocityWidget +) { + + + //region Fields + /** + * Icon for upward velocity + */ + var upwardVelocityIcon: Drawable = getDrawable(R.drawable.uxsdk_ic_arrow_up) + + /** + * Icon for downward velocity + */ + var downwardVelocityIcon: Drawable = getDrawable(R.drawable.uxsdk_ic_arrow_down) + + private val widgetModel: VerticalVelocityWidgetModel by lazy { + VerticalVelocityWidgetModel( + DJISDKModel.getInstance(), + ObservableInMemoryKeyedStore.getInstance(), + GlobalPreferencesManager.getInstance()) + } + + override val metricDecimalFormat: DecimalFormat = DecimalFormat("###0.0") + + override val imperialDecimalFormat: DecimalFormat = DecimalFormat("###0.0") + //endregion + + //region Constructor + init { + initThemeAttributes(context, widgetTheme) + initAttributes(context, attrs) + setValueTextViewMinWidthByText("88.8") + } + //endregion + + //region Lifecycle + override fun onAttachedToWindow() { + super.onAttachedToWindow() + if (!isInEditMode) { + widgetModel.setup() + } + } + + override fun onDetachedFromWindow() { + if (!isInEditMode) { + widgetModel.cleanup() + } + super.onDetachedFromWindow() + } + + override fun reactToModelChanges() { + addReaction(widgetModel.productConnection + .observeOn(SchedulerProvider.ui()) + .subscribe { widgetStateDataProcessor.onNext(ModelState.ProductConnected(it)) }) + addReaction(widgetModel.verticalVelocityState + .observeOn(SchedulerProvider.ui()) + .subscribe { updateUI(it) }) + } + //endregion + + //region Customization + override fun getIdealDimensionRatioString(): String? = null + + + override val widgetSizeDescription: WidgetSizeDescription = + WidgetSizeDescription(WidgetSizeDescription.SizeType.OTHER, + widthDimension = WidgetSizeDescription.Dimension.EXPAND, + heightDimension = WidgetSizeDescription.Dimension.WRAP) + + /** + * Set the icon for upward velocity + * + * @param resourceId Integer ID of the icon + */ + fun setUpwardVelocityIcon(@DrawableRes resourceId: Int) { + upwardVelocityIcon = getDrawable(resourceId) + } + + /** + * Set the icon for downward velocity + * + * @param resourceId Integer ID of the icon + */ + fun setDownwardVelocityIcon(@DrawableRes resourceId: Int) { + downwardVelocityIcon = getDrawable(resourceId) + } + + @SuppressLint("Recycle") + private fun initThemeAttributes(context: Context, widgetTheme: Int) { + val verticalVelocityAttributeArray: IntArray = R.styleable.VerticalVelocityWidget + context.obtainStyledAttributes(widgetTheme, verticalVelocityAttributeArray).use { + initAttributesByTypedArray(it) + } + } + + @SuppressLint("Recycle") + private fun initAttributes(context: Context, attrs: AttributeSet?) { + context.obtainStyledAttributes(attrs, R.styleable.VerticalVelocityWidget, 0, defaultStyle).use { + initAttributesByTypedArray(it) + } + } + + private fun initAttributesByTypedArray(typedArray: TypedArray) { + typedArray.getDrawableAndUse(R.styleable.VerticalVelocityWidget_uxsdk_upward_velocity_icon) { + upwardVelocityIcon = it + } + typedArray.getDrawableAndUse(R.styleable.VerticalVelocityWidget_uxsdk_downward_velocity_icon) { + downwardVelocityIcon = it + } + } + //endregion + + //region Reactions to model + private fun updateUI(verticalVelocityState: VerticalVelocityState) { + widgetStateDataProcessor.onNext(ModelState.VerticalVelocityStateUpdated(verticalVelocityState)) + when (verticalVelocityState) { + VerticalVelocityState.ProductDisconnected -> updateToDisconnectedState() + is VerticalVelocityState.Idle -> updateVelocityState(0.0f, verticalVelocityState.unitType, null) + is VerticalVelocityState.UpwardVelocity -> updateVelocityState(verticalVelocityState.velocity, verticalVelocityState.unitType, upwardVelocityIcon) + is VerticalVelocityState.DownwardVelocity -> updateVelocityState(verticalVelocityState.velocity, verticalVelocityState.unitType, downwardVelocityIcon) + } + + } + + private fun updateVelocityState(velocity: Float, unitType: UnitType, icon: Drawable?) { + widgetIcon = icon + valueString = getDecimalFormat(unitType).format(velocity).toString() + unitString = getVelocityString(unitType) + } + + private fun updateToDisconnectedState() { + unitString = null + widgetIcon = null + valueString = getString(R.string.uxsdk_string_default_value) + } + //endregion + + //region Hooks + /** + * Get the [ModelState] updates + */ + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { + return super.getWidgetStateUpdate() + } + + /** + * Class defines widget state updates + */ + sealed class ModelState { + /** + * Product connection update + */ + data class ProductConnected(val boolean: Boolean) : ModelState() + + /** + * Vertical velocity state update + */ + data class VerticalVelocityStateUpdated(val verticalVelocityState: VerticalVelocityState) : ModelState() + } + //endregion + +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/verticalvelocity/VerticalVelocityWidgetModel.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/verticalvelocity/VerticalVelocityWidgetModel.java deleted file mode 100644 index da0b5b45..00000000 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/verticalvelocity/VerticalVelocityWidgetModel.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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.ux.beta.core.widget.verticalvelocity; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import dji.keysdk.FlightControllerKey; -import dji.thirdparty.io.reactivex.Flowable; -import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.GlobalPreferencesInterface; -import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.GlobalPreferenceKeys; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; -import dji.ux.beta.core.base.uxsdkkeys.UXKey; -import dji.ux.beta.core.base.uxsdkkeys.UXKeys; -import dji.ux.beta.core.util.DataProcessor; -import dji.ux.beta.core.util.UnitConversionUtil; - -/** - * Widget Model for the {@link VerticalVelocityWidget} used to define - * the underlying logic and communication - */ -public class VerticalVelocityWidgetModel extends WidgetModel { - //region Fields - private final GlobalPreferencesInterface preferencesManager; - private final DataProcessor rawVerticalVelocityProcessor; - private final DataProcessor verticalVelocityProcessor; - private final DataProcessor unitTypeProcessor; - //endregion - - //region Constructor - public VerticalVelocityWidgetModel(@NonNull DJISDKModel djiSdkModel, - @NonNull ObservableInMemoryKeyedStore keyedStore, - @Nullable GlobalPreferencesInterface preferencesManager) { - super(djiSdkModel, keyedStore); - this.preferencesManager = preferencesManager; - rawVerticalVelocityProcessor = DataProcessor.create(0.0f); - verticalVelocityProcessor = DataProcessor.create(0.0f); - unitTypeProcessor = DataProcessor.create(UnitConversionUtil.UnitType.METRIC); - if (preferencesManager != null) { - unitTypeProcessor.onNext(preferencesManager.getUnitType()); - } - } - //endregion - - //region Data - - /** - * Get the value of the vertical velocity of the aircraft - * - * @return Flowable for the DataProcessor that user should subscribe to. - */ - public Flowable getVerticalVelocity() { - return verticalVelocityProcessor.toFlowable(); - } - - /** - * Get the unit type of the vertical velocity value received. - * - * @return Flowable for the DataProcessor that user should subscribe to. - */ - public Flowable getUnitType() { - return unitTypeProcessor.toFlowable(); - } - //endregion - - //region Lifecycle - @Override - protected void inSetup() { - FlightControllerKey aircraftVelocityZKey = FlightControllerKey.create(FlightControllerKey.VELOCITY_Z); - bindDataProcessor(aircraftVelocityZKey, - rawVerticalVelocityProcessor, - verticalVelocity -> convertValueByUnit((float) verticalVelocity)); - - UXKey unitKey = UXKeys.create(GlobalPreferenceKeys.UNIT_TYPE); - bindDataProcessor(unitKey, unitTypeProcessor); - - if (preferencesManager != null) { - preferencesManager.setUpListener(); - } - } - - @Override - protected void inCleanup() { - if (preferencesManager != null) { - preferencesManager.cleanup(); - } - } - - @Override - protected void updateStates() { - //Nothing to update - } - //endregion - - //region Helpers - private void convertValueByUnit(float verticalVelocity) { - if (unitTypeProcessor.getValue() == UnitConversionUtil.UnitType.IMPERIAL) { - verticalVelocityProcessor.onNext(UnitConversionUtil.convertMetersPerSecToMilesPerHr(verticalVelocity)); - } else { - verticalVelocityProcessor.onNext(verticalVelocity); - } - } - //endregion -} diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/verticalvelocity/VerticalVelocityWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/verticalvelocity/VerticalVelocityWidgetModel.kt new file mode 100644 index 00000000..20e4f770 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/verticalvelocity/VerticalVelocityWidgetModel.kt @@ -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.ux.beta.core.widget.verticalvelocity + +import dji.keysdk.FlightControllerKey +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.WidgetModel +import dji.ux.beta.core.communication.GlobalPreferenceKeys +import dji.ux.beta.core.communication.GlobalPreferencesInterface +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.extension.toVelocity +import dji.ux.beta.core.util.DataProcessor +import dji.ux.beta.core.util.UnitConversionUtil.UnitType +import dji.ux.beta.core.widget.verticalvelocity.VerticalVelocityWidgetModel.VerticalVelocityState.* +import kotlin.math.abs + +/** + * Widget Model for the [VerticalVelocityWidget] used to define + * the underlying logic and communication + */ +class VerticalVelocityWidgetModel(djiSdkModel: DJISDKModel, + keyedStore: ObservableInMemoryKeyedStore, + private val preferencesManager: GlobalPreferencesInterface? +) : WidgetModel(djiSdkModel, keyedStore) { + + private val verticalVelocityProcessor: DataProcessor = DataProcessor.create(0.0f) + private val unitTypeDataProcessor: DataProcessor = DataProcessor.create(UnitType.METRIC) + private val verticalVelocityStateProcessor: DataProcessor = DataProcessor.create(ProductDisconnected) + + /** + * Value of the vertical velocity state of the aircraft + */ + val verticalVelocityState: Flowable + get() = verticalVelocityStateProcessor.toFlowable() + + override fun inSetup() { + val verticalVelocityKey = FlightControllerKey.create(FlightControllerKey.VELOCITY_Z) + bindDataProcessor(verticalVelocityKey, verticalVelocityProcessor) + + val unitTypeKey = GlobalPreferenceKeys.create(GlobalPreferenceKeys.UNIT_TYPE) + bindDataProcessor(unitTypeKey, unitTypeDataProcessor) + preferencesManager?.setUpListener() + preferencesManager?.let { unitTypeDataProcessor.onNext(it.unitType) } + } + + override fun updateStates() { + if (productConnectionProcessor.value) { + when { + verticalVelocityProcessor.value < 0 -> { + verticalVelocityStateProcessor.onNext(UpwardVelocity( + abs(verticalVelocityProcessor.value).toVelocity(unitTypeDataProcessor.value), + unitTypeDataProcessor.value)) + } + verticalVelocityProcessor.value > 0 -> { + verticalVelocityStateProcessor.onNext(DownwardVelocity( + abs(verticalVelocityProcessor.value).toVelocity(unitTypeDataProcessor.value), + unitTypeDataProcessor.value)) + } + else -> { + verticalVelocityStateProcessor.onNext(Idle(unitTypeDataProcessor.value)) + } + } + } else { + verticalVelocityStateProcessor.onNext(ProductDisconnected) + } + } + + override fun inCleanup() { + preferencesManager?.cleanup() + } + + /** + * Class to represent states of vertical velocity + */ + sealed class VerticalVelocityState { + /** + * When product is disconnected + */ + object ProductDisconnected : VerticalVelocityState() + + /** + * When aircraft is not moving vertically + */ + data class Idle(val unitType: UnitType) : VerticalVelocityState() + + /** + * When aircraft is moving in upward direction + */ + data class UpwardVelocity(val velocity: Float, val unitType: UnitType) : VerticalVelocityState() + + /** + * When aircraft is moving in downward direction + */ + data class DownwardVelocity(val velocity: Float, val unitType: UnitType) : VerticalVelocityState() + } + +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/videosignal/VideoSignalWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/videosignal/VideoSignalWidget.kt index 64eed2e0..72745a06 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/videosignal/VideoSignalWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/videosignal/VideoSignalWidget.kt @@ -44,15 +44,14 @@ import dji.thirdparty.io.reactivex.Flowable import dji.thirdparty.io.reactivex.disposables.Disposable import dji.thirdparty.io.reactivex.functions.BiFunction import dji.thirdparty.io.reactivex.functions.Consumer -import dji.ux.beta.R -import dji.ux.beta.core.base.ConstraintLayoutWidget +import dji.ux.beta.core.R import dji.ux.beta.core.base.DJISDKModel import dji.ux.beta.core.base.SchedulerProvider -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.* import dji.ux.beta.core.util.DisplayUtil -import dji.ux.beta.core.widget.videosignal.VideoSignalWidget.VideoSignalWidgetState -import dji.ux.beta.core.widget.videosignal.VideoSignalWidget.VideoSignalWidgetState.* +import dji.ux.beta.core.widget.videosignal.VideoSignalWidget.ModelState.* /** * This widget shows the strength of the video signal between the @@ -62,13 +61,12 @@ open class VideoSignalWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ConstraintLayoutWidget(context, attrs, defStyleAttr) { +) : ConstraintLayoutWidget(context, attrs, defStyleAttr) { //region Fields private val videoIconImageView: ImageView = findViewById(R.id.imageview_video_icon) private val videoSignalImageView: ImageView = findViewById(R.id.imageview_video_signal) private val frequencyBandTextView: TextView = findViewById(R.id.textview_frequency_band) - private val schedulerProvider: SchedulerProvider = SchedulerProvider.getInstance() private val widgetModel: VideoSignalWidgetModel by lazy { VideoSignalWidgetModel( DJISDKModel.getInstance(), @@ -170,7 +168,7 @@ open class VideoSignalWidget @JvmOverloads constructor( } //endregion - //region Constructors + //region Constructor override fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { View.inflate(context, R.layout.uxsdk_widget_video_signal, this) } @@ -197,16 +195,16 @@ open class VideoSignalWidget @JvmOverloads constructor( override fun reactToModelChanges() { addReaction(widgetModel.productConnection - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { this.updateIconColor(it) }) addReaction(widgetModel.videoSignalQuality - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { this.updateVideoSignal(it) }) addReaction(widgetModel.wifiFrequencyBand - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { this.updateWifiFrequencyBandText(it) }) addReaction(widgetModel.lightBridgeFrequencyBand - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { this.updateLightBridgeFrequencyBandText(it) }) addReaction(reactToOcuSyncFrequencyStateChange()) @@ -215,7 +213,7 @@ open class VideoSignalWidget @JvmOverloads constructor( //region Reactions to model private fun updateVideoSignal(@IntRange(from = 0, to = 100) videoSignalQuality: Int) { - widgetStateDataProcessor.onNext(VideoSignalQualityUpdate(videoSignalQuality)) + widgetStateDataProcessor.onNext(VideoSignalQualityUpdated(videoSignalQuality)) videoSignalImageView.setImageLevel(videoSignalQuality) } @@ -235,13 +233,13 @@ open class VideoSignalWidget @JvmOverloads constructor( private fun checkAndUpdateIconColor() { if (!isInEditMode) { addDisposable(widgetModel.productConnection.firstOrError() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { this.updateIconColor(it) }, logErrorConsumer(TAG, "Update Icon Color "))) } } private fun updateLightBridgeFrequencyBandText(frequencyBandType: LightbridgeFrequencyBand) { - widgetStateDataProcessor.onNext(LightbridgeFrequencyBandUpdate(frequencyBandType)) + widgetStateDataProcessor.onNext(LightbridgeFrequencyBandUpdated(frequencyBandType)) if (frequencyBandType != LightbridgeFrequencyBand.UNKNOWN) { frequencyBandTextView.text = when (frequencyBandType) { LightbridgeFrequencyBand.FREQUENCY_BAND_2_DOT_4_GHZ -> FREQUENCY_BAND_2_DOT_4_GHZ @@ -253,7 +251,7 @@ open class VideoSignalWidget @JvmOverloads constructor( } private fun updateWifiFrequencyBandText(frequencyBandType: WiFiFrequencyBand) { - widgetStateDataProcessor.onNext(WiFiFrequencyBandUpdate(frequencyBandType)) + widgetStateDataProcessor.onNext(WiFiFrequencyBandUpdated(frequencyBandType)) if (frequencyBandType != WiFiFrequencyBand.UNKNOWN) { frequencyBandTextView.text = when (frequencyBandType) { WiFiFrequencyBand.FREQUENCY_BAND_2_DOT_4_GHZ -> FREQUENCY_BAND_2_DOT_4_GHZ @@ -269,9 +267,9 @@ open class VideoSignalWidget @JvmOverloads constructor( widgetModel.ocuSyncFrequencyBand, widgetModel.ocuSyncFrequencyPointIndex, BiFunction> { ocuSyncFrequencyBand, signalQuality -> Pair.create(ocuSyncFrequencyBand, signalQuality) }) - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { values -> - widgetStateDataProcessor.onNext(OcuSyncFrequencyBandUpdate(values.first)) + widgetStateDataProcessor.onNext(OcuSyncFrequencyBandUpdated(values.first)) if (values.first != OcuSyncFrequencyBand.UNKNOWN) { frequencyBandTextView.text = when (values.first) { OcuSyncFrequencyBand.FREQUENCY_BAND_2_DOT_4_GHZ -> FREQUENCY_BAND_2_DOT_4_GHZ @@ -384,43 +382,48 @@ open class VideoSignalWidget @JvmOverloads constructor( } //endregion + //region Hooks + /** - * Get the [VideoSignalWidgetState] updates + * Get the [ModelState] updates */ - override fun getWidgetStateUpdate(): Flowable { + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { return super.getWidgetStateUpdate() } /** * Class defines the widget state updates */ - sealed class VideoSignalWidgetState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : VideoSignalWidgetState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * Video signal quality / strength update */ - data class VideoSignalQualityUpdate(val signalQuality: Int) : VideoSignalWidgetState() + data class VideoSignalQualityUpdated(val signalQuality: Int) : ModelState() /** * Lightbridge frequency band update */ - data class LightbridgeFrequencyBandUpdate(val frequencyBandType: LightbridgeFrequencyBand) : VideoSignalWidgetState() + data class LightbridgeFrequencyBandUpdated(val frequencyBandType: LightbridgeFrequencyBand) : ModelState() /** * WiFi frequency band update */ - data class WiFiFrequencyBandUpdate(val frequencyBandType: WiFiFrequencyBand) : VideoSignalWidgetState() + data class WiFiFrequencyBandUpdated(val frequencyBandType: WiFiFrequencyBand) : ModelState() /** * OcuSync frequency band update */ - data class OcuSyncFrequencyBandUpdate(val frequencyBandType: OcuSyncFrequencyBand) : VideoSignalWidgetState() + data class OcuSyncFrequencyBandUpdated(val frequencyBandType: OcuSyncFrequencyBand) : ModelState() } + //endregion + companion object { private const val TAG = "VideoSignalWidget" private const val FREQUENCY_BAND_2_DOT_4_GHZ = "2.4G" diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/videosignal/VideoSignalWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/videosignal/VideoSignalWidgetModel.kt index ac535588..85607fa4 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/videosignal/VideoSignalWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/videosignal/VideoSignalWidgetModel.kt @@ -18,7 +18,7 @@ * 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.ux.beta.core.widget.videosignal @@ -30,7 +30,7 @@ import dji.keysdk.AirLinkKey import dji.thirdparty.io.reactivex.Flowable import dji.ux.beta.core.base.DJISDKModel import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.util.DataProcessor /** diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/vision/VisionWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/vision/VisionWidget.kt index 65379815..135c4daf 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/vision/VisionWidget.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/vision/VisionWidget.kt @@ -34,20 +34,20 @@ import androidx.annotation.ColorInt import androidx.annotation.DrawableRes import androidx.core.content.res.use import dji.thirdparty.io.reactivex.Flowable -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers +import dji.ux.beta.core.base.SchedulerProvider import dji.thirdparty.io.reactivex.functions.Consumer import dji.thirdparty.io.reactivex.processors.PublishProcessor -import dji.ux.beta.R +import dji.ux.beta.core.R import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.FrameLayoutWidget -import dji.ux.beta.core.base.SchedulerProvider -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.base.widget.FrameLayoutWidget +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.getColor import dji.ux.beta.core.extension.getDrawable import dji.ux.beta.core.extension.getDrawableAndUse import dji.ux.beta.core.extension.getString -import dji.ux.beta.core.widget.vision.VisionWidget.VisionWidgetState -import dji.ux.beta.core.widget.vision.VisionWidget.VisionWidgetState.* +import dji.ux.beta.core.widget.vision.VisionWidget.ModelState +import dji.ux.beta.core.widget.vision.VisionWidget.ModelState.* +import dji.ux.beta.core.widget.vision.VisionWidget.UIState.VisibilityUpdated /** * Shows the current state of the vision system. There are two different vision systems that are @@ -60,31 +60,29 @@ open class VisionWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : FrameLayoutWidget( +) : FrameLayoutWidget( context, attrs, defStyleAttr ), View.OnClickListener { //region Fields - private val schedulerProvider: SchedulerProvider = SchedulerProvider.getInstance() private val visionIconImageView: ImageView = findViewById(R.id.imageview_vision_icon) - private val visionMap: MutableMap = + private val visionMap: MutableMap = mutableMapOf( - VisionWidgetModel.VisionSystemStatus.NORMAL to getDrawable(R.drawable.uxsdk_ic_topbar_visual_normal), - VisionWidgetModel.VisionSystemStatus.CLOSED to getDrawable(R.drawable.uxsdk_ic_topbar_visual_closed), - VisionWidgetModel.VisionSystemStatus.DISABLED to getDrawable(R.drawable.uxsdk_ic_topbar_visual_error), - VisionWidgetModel.VisionSystemStatus.OMNI_ALL to getDrawable(R.drawable.uxsdk_ic_avoid_normal_all), - VisionWidgetModel.VisionSystemStatus.OMNI_FRONT_BACK to getDrawable(R.drawable.uxsdk_ic_avoid_normal_front_back), - VisionWidgetModel.VisionSystemStatus.OMNI_HORIZONTAL to getDrawable(R.drawable.uxsdk_ic_omni_perception_horizontal), - VisionWidgetModel.VisionSystemStatus.OMNI_VERTICAL to getDrawable(R.drawable.uxsdk_ic_omni_perception_vertical), - VisionWidgetModel.VisionSystemStatus.OMNI_DISABLED to getDrawable(R.drawable.uxsdk_ic_avoid_disable_all), - VisionWidgetModel.VisionSystemStatus.OMNI_CLOSED to getDrawable(R.drawable.uxsdk_ic_avoid_disable_all)) - private val uiUpdateStateProcessor: PublishProcessor = PublishProcessor.create() + VisionWidgetModel.VisionSystemState.NORMAL to getDrawable(R.drawable.uxsdk_ic_topbar_visual_normal), + VisionWidgetModel.VisionSystemState.CLOSED to getDrawable(R.drawable.uxsdk_ic_topbar_visual_closed), + VisionWidgetModel.VisionSystemState.DISABLED to getDrawable(R.drawable.uxsdk_ic_topbar_visual_error), + VisionWidgetModel.VisionSystemState.OMNI_ALL to getDrawable(R.drawable.uxsdk_ic_avoid_normal_all), + VisionWidgetModel.VisionSystemState.OMNI_FRONT_BACK to getDrawable(R.drawable.uxsdk_ic_avoid_normal_front_back), + VisionWidgetModel.VisionSystemState.OMNI_HORIZONTAL to getDrawable(R.drawable.uxsdk_ic_omni_perception_horizontal), + VisionWidgetModel.VisionSystemState.OMNI_VERTICAL to getDrawable(R.drawable.uxsdk_ic_omni_perception_vertical), + VisionWidgetModel.VisionSystemState.OMNI_DISABLED to getDrawable(R.drawable.uxsdk_ic_avoid_disable_all), + VisionWidgetModel.VisionSystemState.OMNI_CLOSED to getDrawable(R.drawable.uxsdk_ic_avoid_disable_all)) + private val uiUpdateStateProcessor: PublishProcessor = PublishProcessor.create() private val widgetModel by lazy { VisionWidgetModel(DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - schedulerProvider) + ObservableInMemoryKeyedStore.getInstance()) } /** @@ -107,7 +105,7 @@ open class VisionWidget @JvmOverloads constructor( } //endregion - //region Constructors + //region Constructor override fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { View.inflate(context, R.layout.uxsdk_widget_vision, this) } @@ -134,41 +132,41 @@ open class VisionWidget @JvmOverloads constructor( } override fun onClick(v: View?) { - uiUpdateStateProcessor.onNext(VisionWidgetUIState.WidgetClick) + uiUpdateStateProcessor.onNext(UIState.WidgetClicked) } override fun reactToModelChanges() { - addReaction(widgetModel.visionSystemStatus - .observeOn(schedulerProvider.ui()) + addReaction(widgetModel.visionSystemState + .observeOn(SchedulerProvider.ui()) .subscribe { updateIcon(it) }) addReaction(widgetModel.isUserAvoidanceEnabled - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { sendWarningMessage(it) }) addReaction(widgetModel.isVisionSupportedByProduct - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { updateVisibility(it) }) addReaction(widgetModel.productConnection - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { updateIconColor(it) }) } //endregion //region Reactions - private fun updateIcon(visionSystemStatus: VisionWidgetModel.VisionSystemStatus) { - visionIconImageView.setImageDrawable(visionMap[visionSystemStatus]) - widgetStateDataProcessor.onNext(VisionSystemStatusUpdate(visionSystemStatus)) + private fun updateIcon(visionSystemState: VisionWidgetModel.VisionSystemState) { + visionIconImageView.setImageDrawable(visionMap[visionSystemState]) + widgetStateDataProcessor.onNext(VisionSystemStateUpdated(visionSystemState)) } private fun sendWarningMessage(isUserAvoidanceEnabled: Boolean) { addDisposable(widgetModel.sendWarningMessage(getString(R.string.uxsdk_visual_radar_avoidance_disabled_message_post), isUserAvoidanceEnabled) .subscribe()) - widgetStateDataProcessor.onNext(UserAvoidanceEnabledUpdate(isUserAvoidanceEnabled)) + widgetStateDataProcessor.onNext(UserAvoidanceEnabledUpdated(isUserAvoidanceEnabled)) } private fun updateVisibility(isVisionSupported: Boolean) { visibility = if (isVisionSupported) View.VISIBLE else View.GONE - widgetStateDataProcessor.onNext(VisibilityUpdate(isVisionSupported)) + uiUpdateStateProcessor.onNext(VisibilityUpdated(isVisionSupported)) } private fun updateIconColor(isConnected: Boolean) { @@ -182,8 +180,8 @@ open class VisionWidget @JvmOverloads constructor( private fun checkAndUpdateIcon() { if (!isInEditMode) { - addDisposable(widgetModel.visionSystemStatus.firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + addDisposable(widgetModel.visionSystemState.firstOrError() + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { this.updateIcon(it) }, logErrorConsumer(TAG, "Update Icon "))) } } @@ -191,7 +189,7 @@ open class VisionWidget @JvmOverloads constructor( private fun checkAndUpdateIconColor() { if (!isInEditMode) { addDisposable(widgetModel.productConnection.firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { this.updateIconColor(it) }, logErrorConsumer(TAG, "Update Icon Color "))) } } @@ -203,37 +201,37 @@ open class VisionWidget @JvmOverloads constructor( } /** - * Sets the icon to the given image when the [VisionWidgetModel.VisionSystemStatus] is the + * Sets the icon to the given image when the [VisionWidgetModel.VisionSystemState] is the * given value. * - * @param status The status at which the icon will change to the given image. + * @param state The state at which the icon will change to the given image. * @param resourceId The id of the image the icon will change to. */ - fun setVisionIcon(status: VisionWidgetModel.VisionSystemStatus, @DrawableRes resourceId: Int) { - setVisionIcon(status, getDrawable(resourceId)) + fun setVisionIcon(state: VisionWidgetModel.VisionSystemState, @DrawableRes resourceId: Int) { + setVisionIcon(state, getDrawable(resourceId)) } /** - * Sets the icon to the given image when the [VisionWidgetModel.VisionSystemStatus] is the + * Sets the icon to the given image when the [VisionWidgetModel.VisionSystemState] is the * given value. * - * @param status The status at which the icon will change to the given image. + * @param state The state at which the icon will change to the given image. * @param drawable The image the icon will change to. */ - fun setVisionIcon(status: VisionWidgetModel.VisionSystemStatus, drawable: Drawable?) { - visionMap[status] = drawable + fun setVisionIcon(state: VisionWidgetModel.VisionSystemState, drawable: Drawable?) { + visionMap[state] = drawable checkAndUpdateIcon() } /** - * Gets the image that the icon will change to when the [VisionWidgetModel.VisionSystemStatus] + * Gets the image that the icon will change to when the [VisionWidgetModel.VisionSystemState] * is the given value. * - * @param status The status at which the icon will change. - * @return The image the icon will change to for the given status. + * @param state The state at which the icon will change. + * @return The image the icon will change to for the given state. */ - fun getVisionIcon(status: VisionWidgetModel.VisionSystemStatus): Drawable? { - return visionMap[status] + fun getVisionIcon(state: VisionWidgetModel.VisionSystemState): Drawable? { + return visionMap[state] } /** @@ -250,25 +248,25 @@ open class VisionWidget @JvmOverloads constructor( private fun initAttributes(context: Context, attrs: AttributeSet) { context.obtainStyledAttributes(attrs, R.styleable.VisionWidget).use { typedArray -> typedArray.getDrawableAndUse(R.styleable.VisionWidget_uxsdk_normalVisionIcon) { - setVisionIcon(VisionWidgetModel.VisionSystemStatus.NORMAL, it) + setVisionIcon(VisionWidgetModel.VisionSystemState.NORMAL, it) } typedArray.getDrawableAndUse(R.styleable.VisionWidget_uxsdk_closedVisionIcon) { - setVisionIcon(VisionWidgetModel.VisionSystemStatus.CLOSED, it) + setVisionIcon(VisionWidgetModel.VisionSystemState.CLOSED, it) } typedArray.getDrawableAndUse(R.styleable.VisionWidget_uxsdk_disabledVisionIcon) { - setVisionIcon(VisionWidgetModel.VisionSystemStatus.DISABLED, it) + setVisionIcon(VisionWidgetModel.VisionSystemState.DISABLED, it) } typedArray.getDrawableAndUse(R.styleable.VisionWidget_uxsdk_omniAllVisionIcon) { - setVisionIcon(VisionWidgetModel.VisionSystemStatus.OMNI_ALL, it) + setVisionIcon(VisionWidgetModel.VisionSystemState.OMNI_ALL, it) } typedArray.getDrawableAndUse(R.styleable.VisionWidget_uxsdk_omniFrontBackVisionIcon) { - setVisionIcon(VisionWidgetModel.VisionSystemStatus.OMNI_FRONT_BACK, it) + setVisionIcon(VisionWidgetModel.VisionSystemState.OMNI_FRONT_BACK, it) } typedArray.getDrawableAndUse(R.styleable.VisionWidget_uxsdk_omniClosedVisionIcon) { - setVisionIcon(VisionWidgetModel.VisionSystemStatus.OMNI_CLOSED, it) + setVisionIcon(VisionWidgetModel.VisionSystemState.OMNI_CLOSED, it) } typedArray.getDrawableAndUse(R.styleable.VisionWidget_uxsdk_omniDisabledVisionIcon) { - setVisionIcon(VisionWidgetModel.VisionSystemStatus.OMNI_DISABLED, it) + setVisionIcon(VisionWidgetModel.VisionSystemState.OMNI_DISABLED, it) } typedArray.getDrawableAndUse(R.styleable.VisionWidget_uxsdk_visionIconBackground) { iconBackground = it @@ -281,52 +279,54 @@ open class VisionWidget @JvmOverloads constructor( //region Hooks /** - * Get the [VisionWidgetUIState] updates + * Get the [UIState] updates */ - fun getUIStateUpdates(): Flowable { - return uiUpdateStateProcessor + fun getUIStateUpdates(): Flowable { + return uiUpdateStateProcessor.onBackpressureBuffer() + } + + /** + * Get the [ModelState] updates + */ + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { + return super.getWidgetStateUpdate() } /** * Widget UI update State */ - sealed class VisionWidgetUIState { + sealed class UIState { /** * Widget click update */ - object WidgetClick : VisionWidgetUIState() - } + object WidgetClicked : UIState() - /** - * Get the [VisionWidgetState] updates - */ - override fun getWidgetStateUpdate(): Flowable { - return super.getWidgetStateUpdate() + /** + * Is vision supported by product update + */ + data class VisibilityUpdated(val isVisible: Boolean) : UIState() } /** * Class defines the widget state updates */ - sealed class VisionWidgetState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : VisionWidgetState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * Vision system status update */ - data class VisionSystemStatusUpdate(val visionSystemStatus: VisionWidgetModel.VisionSystemStatus) : VisionWidgetState() + data class VisionSystemStateUpdated(val visionSystemState: VisionWidgetModel.VisionSystemState) : ModelState() /** * Is user avoidance enabled update */ - data class UserAvoidanceEnabledUpdate(val isUserAvoidanceEnabled: Boolean) : VisionWidgetState() + data class UserAvoidanceEnabledUpdated(val isUserAvoidanceEnabled: Boolean) : ModelState() - /** - * Is vision supported by product update - */ - data class VisibilityUpdate(val isVisible: Boolean) : VisionWidgetState() } //endregion diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/vision/VisionWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/vision/VisionWidgetModel.kt index 9eff1ff5..00268f8b 100644 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/vision/VisionWidgetModel.kt +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/vision/VisionWidgetModel.kt @@ -34,12 +34,12 @@ import dji.keysdk.ProductKey import dji.thirdparty.io.reactivex.Completable import dji.thirdparty.io.reactivex.Flowable import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.SchedulerProviderInterface +import dji.ux.beta.core.base.SchedulerProvider import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.MessagingKeys -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.base.uxsdkkeys.UXKey -import dji.ux.beta.core.base.uxsdkkeys.UXKeys +import dji.ux.beta.core.communication.MessagingKeys +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.UXKey +import dji.ux.beta.core.communication.UXKeys import dji.ux.beta.core.model.WarningMessage import dji.ux.beta.core.model.WarningMessageError import dji.ux.beta.core.util.DataProcessor @@ -51,12 +51,11 @@ import java.util.* * the underlying logic and communication */ open class VisionWidgetModel(djiSdkModel: DJISDKModel, - private val keyedStore: ObservableInMemoryKeyedStore, - private val schedulerProvider: SchedulerProviderInterface + private val keyedStore: ObservableInMemoryKeyedStore ) : WidgetModel(djiSdkModel, keyedStore) { //region Fields - private val statusMap: MutableMap = HashMap() + private val stateMap: MutableMap = EnumMap(VisionSensorPosition::class.java) private val visionDetectionStateProcessor: DataProcessor = DataProcessor.create( VisionDetectionState.createInstance(false, 0.0, VisionSystemWarning.INVALID, null, VisionSensorPosition.UNKNOWN, false, 0)) @@ -74,16 +73,15 @@ open class VisionWidgetModel(djiSdkModel: DJISDKModel, private val omniHorizontalAvoidanceEnabledProcessor: DataProcessor = DataProcessor.create(false) private val omniVerticalAvoidanceEnabledProcessor: DataProcessor = DataProcessor.create(false) private val omniAvoidanceStateProcessor: DataProcessor = DataProcessor.create(ObstacleAvoidanceSensorState.Builder().build()) - private val visionSystemStatusProcessor: DataProcessor = DataProcessor.create(VisionSystemStatus.NORMAL) - private val sendWarningMessageKey: UXKey = UXKeys.create(MessagingKeys.SEND_WARNING_MESSAGE) + private val visionSystemStateProcessor: DataProcessor = DataProcessor.create(VisionSystemState.NORMAL) //endregion //region Data /** * Get the status of the vision system. */ - val visionSystemStatus: Flowable - get() = visionSystemStatusProcessor.toFlowable() + val visionSystemState: Flowable + get() = visionSystemStateProcessor.toFlowable() /** * Get whether user avoidance is enabled. @@ -108,6 +106,7 @@ open class VisionWidgetModel(djiSdkModel: DJISDKModel, * @return Completable representing the success/failure of the set action. */ fun sendWarningMessage(reason: String?, isUserAvoidanceEnabled: Boolean): Completable { + val sendWarningMessageKey: UXKey = UXKeys.create(MessagingKeys.SEND_WARNING_MESSAGE) val subCode = WarningMessageError.VISION_AVOID.value() val action = if (isUserAvoidanceEnabled) WarningMessage.Action.REMOVE else WarningMessage.Action.INSERT val builder = WarningMessage.Builder(WarningMessage.WarningType.VISION) @@ -117,7 +116,7 @@ open class VisionWidgetModel(djiSdkModel: DJISDKModel, .type(WarningMessage.Type.AUTO_DISAPPEAR).action(action) val warningMessage = builder.build() return keyedStore.setValue(sendWarningMessageKey, warningMessage) - .subscribeOn(schedulerProvider.io()) + .subscribeOn(SchedulerProvider.io()) } //endregion @@ -162,15 +161,16 @@ open class VisionWidgetModel(djiSdkModel: DJISDKModel, override fun updateStates() { if (!productConnectionProcessor.value) { - visionSystemStatusProcessor.onNext(VisionSystemStatus.NORMAL) + visionSystemStateProcessor.onNext(VisionSystemState.NORMAL) } else { addSingleVisionStatus() - if (Model.MATRICE_300_RTK == productModelProcessor.value) { - visionSystemStatusProcessor.onNext(omniHorizontalVerticalAvoidanceState) + if (Model.MATRICE_300_RTK == productModelProcessor.value + || Model.MAVIC_AIR_2 == productModelProcessor.value) { + visionSystemStateProcessor.onNext(omniHorizontalVerticalAvoidanceState) } else if (!ProductUtil.isMavic2SeriesProduct(productModelProcessor.value)) { - visionSystemStatusProcessor.onNext(overallVisionSystemStatus) + visionSystemStateProcessor.onNext(overallVisionSystemState) } else { - visionSystemStatusProcessor.onNext(omnidirectionalVisionSystemStatus) + visionSystemStateProcessor.onNext(omnidirectionalVisionSystemState) } } } @@ -179,7 +179,7 @@ open class VisionWidgetModel(djiSdkModel: DJISDKModel, //region Helpers private fun addSingleVisionStatus() { val state = visionDetectionStateProcessor.value - statusMap[state.position] = getSingleVisionSystemStatus(state) + stateMap[state.position] = getSingleVisionSystemStatus(state) } /** @@ -187,16 +187,17 @@ open class VisionWidgetModel(djiSdkModel: DJISDKModel, * * @return The overall status of all vision sensors on the aircraft. */ - private val overallVisionSystemStatus: VisionSystemStatus + private val overallVisionSystemState: VisionSystemState get() { - var status = VisionSystemStatus.CLOSED - for ((_, item) in statusMap) { - if (item == VisionSystemStatus.NORMAL) { - status = VisionSystemStatus.NORMAL - } else if (item == VisionSystemStatus.CLOSED) { - status = VisionSystemStatus.CLOSED + var status = VisionSystemState.CLOSED + for ((_, item) in stateMap) { + if (item == VisionSystemState.NORMAL) { + status = VisionSystemState.NORMAL + } else if (item == VisionSystemState.CLOSED) { + status = VisionSystemState.CLOSED + break } else { - status = VisionSystemStatus.DISABLED + status = VisionSystemState.DISABLED break } } @@ -209,51 +210,53 @@ open class VisionWidgetModel(djiSdkModel: DJISDKModel, * @param state The state of single vision sensor on the aircraft. * @return The status of the vision sensor. */ - private fun getSingleVisionSystemStatus(state: VisionDetectionState): VisionSystemStatus { + private fun getSingleVisionSystemStatus(state: VisionDetectionState): VisionSystemState { return if (isUserAvoidEnabledProcessor.value) { if (isVisionSystemEnabled && !state.isDisabled) { - VisionSystemStatus.NORMAL + VisionSystemState.NORMAL } else { - VisionSystemStatus.DISABLED + VisionSystemState.DISABLED } } else { - VisionSystemStatus.CLOSED + VisionSystemState.CLOSED } } - private val omniHorizontalVerticalAvoidanceState: VisionSystemStatus + private val omniHorizontalVerticalAvoidanceState: VisionSystemState get() { - val horizontalState: VisionSystemStatus = - if (omniHorizontalAvoidanceEnabledProcessor.value && omniAvoidanceStateProcessor.value.areObstacleAvoidanceSensorsInHorizontalDirectionEnabled()) { - if (omniAvoidanceStateProcessor.value.areObstacleAvoidanceSensorsInHorizontalDirectionWorking()) { - VisionSystemStatus.NORMAL + val horizontalState: VisionSystemState = + if (omniHorizontalAvoidanceEnabledProcessor.value + && omniAvoidanceStateProcessor.value.areVisualObstacleAvoidanceSensorsInHorizontalDirectionEnabled()) { + if (omniAvoidanceStateProcessor.value.areVisualObstacleAvoidanceSensorsInHorizontalDirectionWorking()) { + VisionSystemState.NORMAL } else { - VisionSystemStatus.DISABLED + VisionSystemState.DISABLED } } else { - VisionSystemStatus.CLOSED + VisionSystemState.CLOSED } - val verticalState: VisionSystemStatus = - if (omniVerticalAvoidanceEnabledProcessor.value && omniAvoidanceStateProcessor.value.areObstacleAvoidanceSensorsInVerticalDirectionEnabled()) { - if (omniAvoidanceStateProcessor.value.areObstacleAvoidanceSensorsInVerticalDirectionWorking()) { - VisionSystemStatus.NORMAL + val verticalState: VisionSystemState = + if (omniVerticalAvoidanceEnabledProcessor.value + && omniAvoidanceStateProcessor.value.areVisualObstacleAvoidanceSensorsInVerticalDirectionEnabled()) { + if (omniAvoidanceStateProcessor.value.areVisualObstacleAvoidanceSensorsInVerticalDirectionWorking()) { + VisionSystemState.NORMAL } else { - VisionSystemStatus.DISABLED + VisionSystemState.DISABLED } } else { - VisionSystemStatus.CLOSED + VisionSystemState.CLOSED } - return if (horizontalState == VisionSystemStatus.NORMAL) { - if (verticalState == VisionSystemStatus.NORMAL) { - VisionSystemStatus.OMNI_ALL + return if (horizontalState == VisionSystemState.NORMAL) { + if (verticalState == VisionSystemState.NORMAL) { + VisionSystemState.OMNI_ALL } else { - VisionSystemStatus.OMNI_HORIZONTAL + VisionSystemState.OMNI_HORIZONTAL } } else { - if (verticalState == VisionSystemStatus.NORMAL) { - VisionSystemStatus.OMNI_VERTICAL + if (verticalState == VisionSystemState.NORMAL) { + VisionSystemState.OMNI_VERTICAL } else { - VisionSystemStatus.OMNI_CLOSED + VisionSystemState.OMNI_CLOSED } } } @@ -263,26 +266,26 @@ open class VisionWidgetModel(djiSdkModel: DJISDKModel, * * @return The status of the omnidirectional vision system. */ - private val omnidirectionalVisionSystemStatus: VisionSystemStatus + private val omnidirectionalVisionSystemState: VisionSystemState get() { if (ProductUtil.isMavic2Enterprise(productModelProcessor.value)) { if (isAllOmnidirectionalDataOpen) { - return VisionSystemStatus.OMNI_ALL - } else if (overallVisionSystemStatus == VisionSystemStatus.DISABLED) { - VisionSystemStatus.OMNI_DISABLED + return VisionSystemState.OMNI_ALL + } else if (overallVisionSystemState == VisionSystemState.DISABLED) { + return VisionSystemState.OMNI_DISABLED } else if (isNoseTailVisionNormal || isNoseTailDataOpen) { - return VisionSystemStatus.OMNI_FRONT_BACK + return VisionSystemState.OMNI_FRONT_BACK } } else { - if (overallVisionSystemStatus == VisionSystemStatus.NORMAL && isAllOmnidirectionalDataOpen) { - return VisionSystemStatus.OMNI_ALL - } else if (overallVisionSystemStatus == VisionSystemStatus.DISABLED) { - VisionSystemStatus.OMNI_DISABLED + if (overallVisionSystemState == VisionSystemState.NORMAL && isAllOmnidirectionalDataOpen) { + return VisionSystemState.OMNI_ALL + } else if (overallVisionSystemState == VisionSystemState.DISABLED) { + return VisionSystemState.OMNI_DISABLED } else if (isNoseTailVisionNormal && isNoseTailDataOpen) { - return VisionSystemStatus.OMNI_FRONT_BACK + return VisionSystemState.OMNI_FRONT_BACK } } - return VisionSystemStatus.OMNI_CLOSED + return VisionSystemState.OMNI_CLOSED } private val isAllOmnidirectionalDataOpen: Boolean @@ -295,8 +298,8 @@ open class VisionWidgetModel(djiSdkModel: DJISDKModel, get() = isFrontRadarOpenProcessor.value && isBackRadarOpenProcessor.value private val isNoseTailVisionNormal: Boolean - get() = statusMap[VisionSensorPosition.NOSE] == VisionSystemStatus.NORMAL - && statusMap[VisionSensorPosition.TAIL] == VisionSystemStatus.NORMAL + get() = stateMap[VisionSensorPosition.NOSE] == VisionSystemState.NORMAL + && stateMap[VisionSensorPosition.TAIL] == VisionSystemState.NORMAL /** * Whether the vision system is enabled. It could be disabled due to the flight mode, @@ -352,7 +355,7 @@ open class VisionWidgetModel(djiSdkModel: DJISDKModel, /** * The status of the vision system. */ - enum class VisionSystemStatus { + enum class VisionSystemState { /** * Obstacle avoidance is disabled by the user. */ diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/vps/VPSWidget.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/vps/VPSWidget.java deleted file mode 100644 index 72f9a658..00000000 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/vps/VPSWidget.java +++ /dev/null @@ -1,680 +0,0 @@ -/* - * 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.ux.beta.core.widget.vps; - -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.util.Pair; -import android.widget.ImageView; -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 androidx.annotation.StyleRes; - -import java.text.DecimalFormat; - -import dji.thirdparty.io.reactivex.Flowable; -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers; -import dji.thirdparty.io.reactivex.disposables.Disposable; -import dji.ux.beta.R; -import dji.ux.beta.core.base.ConstraintLayoutWidget; -import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.GlobalPreferencesInterface; -import dji.ux.beta.core.base.GlobalPreferencesManager; -import dji.ux.beta.core.base.uxsdkkeys.GlobalPreferenceKeys; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; -import dji.ux.beta.core.util.DisplayUtil; -import dji.ux.beta.core.util.UnitConversionUtil; - -/** - * Shows the status of the vision positioning system - * as well as the height of the aircraft as received from the - * vision positioning system if available. - *

- * Uses the unit set in the UNIT_TYPE global preferences - * {@link GlobalPreferencesInterface#getUnitType()} and the - * {@link GlobalPreferenceKeys#UNIT_TYPE} UX Key - * and defaults to meters if nothing is set. - */ -public class VPSWidget extends ConstraintLayoutWidget { - //region Constants - private static final String TAG = "VPSWidget"; - private static final int EMS = 2; - private static final float MIN_VPS_HEIGHT = 1.2f; - //endregion - - //region Fields - private static DecimalFormat decimalFormat = new DecimalFormat("##0.0"); - private TextView vpsTitleTextView; - private ImageView vpsImageView; - private TextView vpsValueTextView; - private TextView vpsUnitTextView; - private Drawable vpsEnabledDrawable; - private Drawable vpsDisabledDrawable; - @ColorInt - private int enabledColor; - @ColorInt - private int disabledColor; - private VPSWidgetModel widgetModel; - //endregion - - //region Constructors - public VPSWidget(Context context) { - super(context); - } - - public VPSWidget(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public VPSWidget(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_base_dashboard_image_and_text, this); - vpsTitleTextView = findViewById(R.id.textview_title); - vpsImageView = findViewById(R.id.imageview_icon); - vpsValueTextView = findViewById(R.id.textview_value); - vpsUnitTextView = findViewById(R.id.textview_unit); - - if (!isInEditMode()) { - widgetModel = new VPSWidgetModel(DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - GlobalPreferencesManager.getInstance()); - vpsTitleTextView.setText(getResources().getString(R.string.uxsdk_vps_title)); - vpsValueTextView.setMinEms(EMS); - } - - vpsEnabledDrawable = getResources().getDrawable(R.drawable.uxsdk_ic_vps_enabled); - vpsDisabledDrawable = getResources().getDrawable(R.drawable.uxsdk_ic_vps_disabled); - enabledColor = getResources().getColor(R.color.uxsdk_white); - disabledColor = getResources().getColor(R.color.uxsdk_red); - - if (attrs != null) { - initAttributes(context, attrs); - } - } - //endregion - - //region Lifecycle - @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(reactToVPSChange()); - addReaction(widgetModel.getUnitType() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::updateUnitText)); - } - //endregion - - //region Reaction Helpers - private Flowable getVPSChange() { - return Flowable.combineLatest(widgetModel.getVisionPositioningEnabled(), - widgetModel.getUltrasonicBeingUsed(), - widgetModel.getUltrasonicHeight(), - (isVisionPositioningEnabled, isUltrasonicBeingUsed, ultrasonicHeight) -> Pair.create( - (isVisionPositioningEnabled && isUltrasonicBeingUsed), - ultrasonicHeight)); - } - - private Disposable reactToVPSChange() { - return getVPSChange() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(values -> updateUI((boolean) values.first, (float) values.second)); - } - - private void checkAndUpdateUI() { - if (!isInEditMode()) { - addDisposable(getVPSChange().firstOrError() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(values -> updateUI((boolean) values.first, (float) values.second), - logErrorConsumer(TAG, "update UI"))); - } - } - - private void updateUI(boolean isVPSEnabledAndUsed, float ultrasonicHeight) { - if (!isVPSEnabledAndUsed) { - vpsValueTextView.setText(getResources().getString(R.string.uxsdk_string_default_value)); - vpsValueTextView.setTextColor(disabledColor); - vpsUnitTextView.setVisibility(GONE); - vpsImageView.setImageDrawable(vpsDisabledDrawable); - } else { - vpsUnitTextView.setVisibility(VISIBLE); - vpsImageView.setImageDrawable(vpsEnabledDrawable); - vpsValueTextView.setText(decimalFormat.format(ultrasonicHeight)); - if (ultrasonicHeight <= MIN_VPS_HEIGHT) { - vpsValueTextView.setTextColor(disabledColor); - } else { - vpsValueTextView.setTextColor(enabledColor); - } - } - } - - private void updateUnitText(UnitConversionUtil.UnitType unitType) { - if (unitType == UnitConversionUtil.UnitType.IMPERIAL) { - vpsUnitTextView.setText(getResources().getString(R.string.uxsdk_unit_feet)); - } else { - vpsUnitTextView.setText(getResources().getString(R.string.uxsdk_unit_meters)); - } - } - //endregion - - //region Customization - @NonNull - @Override - public String getIdealDimensionRatioString() { - return getResources().getString(R.string.uxsdk_widget_base_dashboard_distance_ratio); - } - //endregion - - //region Customization Helpers - - /** - * Set text appearance of the vision positioning system status title text view - * - * @param textAppearance Style resource for text appearance - */ - public void setVPSTitleTextAppearance(@StyleRes int textAppearance) { - vpsTitleTextView.setTextAppearance(getContext(), textAppearance); - } - - /** - * Get current text color state list of the vision positioning system status title text view - * - * @return ColorStateList resource - */ - @Nullable - public ColorStateList getVPSTitleTextColors() { - return vpsTitleTextView.getTextColors(); - } - - /** - * Get current text color of the vision positioning system status title text view - * - * @return color integer resource - */ - @ColorInt - public int getVPSTitleTextColor() { - return vpsTitleTextView.getCurrentTextColor(); - } - - /** - * Set text color state list for the vision positioning system status title text view - * - * @param colorStateList ColorStateList resource - */ - public void setVPSTitleTextColor(@NonNull ColorStateList colorStateList) { - vpsTitleTextView.setTextColor(colorStateList); - } - - /** - * Set the text color for the vision positioning system status title text view - * - * @param color color integer resource - */ - public void setVPSTitleTextColor(@ColorInt int color) { - vpsTitleTextView.setTextColor(color); - } - - /** - * Get current text size of the vision positioning system status title text view - * - * @return text size of the text view - */ - @Dimension - public float getVPSTitleTextSize() { - return vpsTitleTextView.getTextSize(); - } - - /** - * Set the text size of the vision positioning system status title text view - * - * @param textSize text size float value - */ - public void setVPSTitleTextSize(@Dimension float textSize) { - vpsTitleTextView.setTextSize(textSize); - } - - /** - * Get current background of the vision positioning system status title text view - * - * @return Drawable resource of the background - */ - @Nullable - public Drawable getVPSTitleTextBackground() { - return vpsTitleTextView.getBackground(); - } - - /** - * Set the background of the vision positioning system status title text view - * - * @param drawable Drawable resource for the background - */ - public void setVPSTitleTextBackground(@Nullable Drawable drawable) { - vpsTitleTextView.setBackground(drawable); - } - - /** - * Set the resource ID for the background of the vision positioning system status title text - * view - * - * @param resourceId Integer ID of the text view's background resource - */ - public void setVPSTitleTextBackground(@DrawableRes int resourceId) { - vpsTitleTextView.setBackgroundResource(resourceId); - } - - /** - * Get the drawable resource for the vision positioning system enabled icon - * - * @return Drawable resource of the icon - */ - @Nullable - public Drawable getVPSEnabledIcon() { - return vpsEnabledDrawable; - } - - /** - * Set the resource ID for the vision positioning system enabled icon - * - * @param resourceId Integer ID of the drawable resource - */ - public void setVPSEnabledIcon(@DrawableRes int resourceId) { - setVPSEnabledIcon(getResources().getDrawable(resourceId)); - } - - /** - * Set the drawable resource for the vision positioning system enabled icon - * - * @param icon Drawable resource for the image - */ - public void setVPSEnabledIcon(@Nullable Drawable icon) { - vpsEnabledDrawable = icon; - checkAndUpdateUI(); - } - - /** - * Get the drawable resource for the vision positioning system disabled icon - * - * @return Drawable resource for the icon - */ - @Nullable - public Drawable getVPSDisabledIcon() { - return vpsDisabledDrawable; - } - - /** - * Set the resource ID for the vision positioning system disabled icon - * - * @param resourceId Integer ID of the drawable resource - */ - public void setVPSDisabledIcon(@DrawableRes int resourceId) { - setVPSEnabledIcon(getResources().getDrawable(resourceId)); - } - - /** - * Set the drawable resource for the vision positioning system disabled icon - * - * @param icon Drawable resource for the image - */ - public void setVPSDisabledIcon(@Nullable Drawable icon) { - vpsDisabledDrawable = icon; - checkAndUpdateUI(); - } - - /** - * Get the drawable resource for the vision positioning system icon's background - * - * @return Drawable resource of the icon's background - */ - @Nullable - public Drawable getVPSIconBackground() { - return vpsImageView.getBackground(); - } - - /** - * Set the resource ID for the vision positioning system icon's background - * - * @param resourceId Integer ID of the icon's background resource - */ - public void setVPSIconBackground(@DrawableRes int resourceId) { - vpsImageView.setBackgroundResource(resourceId); - } - - /** - * Set the drawable resource for the vision positioning system icon's background - * - * @param background Drawable resource for the icon's background - */ - public void setVPSIconBackground(@Nullable Drawable background) { - vpsImageView.setBackground(background); - } - - /** - * Set text appearance of the vision positioning system status value text view - * - * @param textAppearance Style resource for text appearance - */ - public void setVPSValueTextAppearance(@StyleRes int textAppearance) { - vpsValueTextView.setTextAppearance(getContext(), textAppearance); - } - - /** - * Get current text color of the vision positioning system status enabled value text view - * - * @return color integer resource - */ - @ColorInt - public int getVPSValueEnabledTextColor() { - return enabledColor; - } - - /** - * Set the text color for the vision positioning system status enabled value text view - * - * @param color color integer resource - */ - public void setVPSValueEnabledTextColor(@ColorInt int color) { - enabledColor = color; - checkAndUpdateUI(); - } - - /** - * Get current text color of the vision positioning system status disabled value text view - * - * @return color integer resource - */ - @ColorInt - public int getVPSValueDisabledTextColor() { - return disabledColor; - } - - /** - * Set the text color for the vision positioning system status disabled value text view - * - * @param color color integer resource - */ - public void setVPSValueDisabledTextColor(@ColorInt int color) { - disabledColor = color; - checkAndUpdateUI(); - } - - /** - * Get current text size of the vision positioning system status value text view - * - * @return text size of the text view - */ - @Dimension - public float getVPSValueTextSize() { - return vpsValueTextView.getTextSize(); - } - - /** - * Set the text size of the vision positioning system status value text view - * - * @param textSize text size float value - */ - public void setVPSValueTextSize(@Dimension float textSize) { - vpsValueTextView.setTextSize(textSize); - } - - /** - * Get current background of the vision positioning system status value text view - * - * @return Drawable resource of the background - */ - @Nullable - public Drawable getVPSValueTextBackground() { - return vpsValueTextView.getBackground(); - } - - /** - * Set the background for the vision positioning system status value text view - * - * @param drawable Drawable resource for the background - */ - public void setVPSValueTextBackground(@Nullable Drawable drawable) { - vpsValueTextView.setBackground(drawable); - } - - /** - * Set the resource ID for the background of the vision positioning system status value text - * view - * - * @param resourceId Integer ID of the text view's background resource - */ - public void setVPSValueTextBackground(@DrawableRes int resourceId) { - vpsValueTextView.setBackgroundResource(resourceId); - } - - /** - * Set text appearance of the vision positioning system status unit text view - * - * @param textAppearance Style resource for text appearance - */ - public void setVPSUnitTextAppearance(@StyleRes int textAppearance) { - vpsUnitTextView.setTextAppearance(getContext(), textAppearance); - } - - /** - * Get current text color state list of the vision positioning system status unit text view - * - * @return ColorStateList resource - */ - @Nullable - public ColorStateList getVPSUnitTextColors() { - return vpsUnitTextView.getTextColors(); - } - - /** - * Get current text color of the vision positioning system status unit text view - * - * @return color integer resource - */ - @ColorInt - public int getVPSUnitTextColor() { - return vpsUnitTextView.getCurrentTextColor(); - } - - /** - * Set text color state list for the vision positioning system status unit text view - * - * @param colorStateList ColorStateList resource - */ - public void setVPSUnitTextColor(@NonNull ColorStateList colorStateList) { - vpsUnitTextView.setTextColor(colorStateList); - } - - /** - * Set the text color for the vision positioning system status unit text view - * - * @param color color integer resource - */ - public void setVPSUnitTextColor(@ColorInt int color) { - vpsUnitTextView.setTextColor(color); - } - - /** - * Get current text size of the vision positioning system status unit text view - * - * @return text size of the text view - */ - @Dimension - public float getVPSUnitTextSize() { - return vpsUnitTextView.getTextSize(); - } - - /** - * Set the text size of the vision positioning system status unit text view - * - * @param textSize text size float value - */ - public void setVPSUnitTextSize(@Dimension float textSize) { - vpsUnitTextView.setTextSize(textSize); - } - - /** - * Get current background of the vision positioning system status unit text view - * - * @return Drawable resource of the background - */ - @Nullable - public Drawable getVPSUnitTextBackground() { - return vpsUnitTextView.getBackground(); - } - - /** - * Set the background for the vision positioning system status unit text view - * - * @param drawable Drawable resource for the background - */ - public void setVPSUnitTextBackground(@Nullable Drawable drawable) { - vpsUnitTextView.setBackground(drawable); - } - - /** - * Set the resource ID for the background of the vision positioning system status unit text - * view - * - * @param resourceId Integer ID of the text view's background resource - */ - public void setVPSUnitTextBackground(@DrawableRes int resourceId) { - vpsUnitTextView.setBackgroundResource(resourceId); - } - - //Initialize all customizable attributes - private void initAttributes(@NonNull Context context, @NonNull AttributeSet attrs) { - TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.VPSWidget); - int vpsTitleTextAppearanceId = - typedArray.getResourceId(R.styleable.VPSWidget_uxsdk_vpsTitleTextAppearance, INVALID_RESOURCE); - if (vpsTitleTextAppearanceId != INVALID_RESOURCE) { - setVPSTitleTextAppearance(vpsTitleTextAppearanceId); - } - - float vpsTitleTextSize = - typedArray.getDimension(R.styleable.VPSWidget_uxsdk_vpsTitleTextSize, INVALID_RESOURCE); - if (vpsTitleTextSize != INVALID_RESOURCE) { - setVPSTitleTextSize(DisplayUtil.pxToSp(context, vpsTitleTextSize)); - } - - int vpsTitleTextColor = typedArray.getColor(R.styleable.VPSWidget_uxsdk_vpsTitleTextColor, INVALID_COLOR); - if (vpsTitleTextColor != INVALID_COLOR) { - setVPSTitleTextColor(vpsTitleTextColor); - } - - Drawable vpsTitleTextBackgroundDrawable = - typedArray.getDrawable(R.styleable.VPSWidget_uxsdk_vpsTitleBackgroundDrawable); - if (vpsTitleTextBackgroundDrawable != null) { - setVPSTitleTextBackground(vpsTitleTextBackgroundDrawable); - } - - Drawable vpsEnabledIcon = typedArray.getDrawable(R.styleable.VPSWidget_uxsdk_vpsEnabledIcon); - if (vpsEnabledIcon != null) { - setVPSEnabledIcon(vpsEnabledIcon); - } - - Drawable vpsDisabledIcon = typedArray.getDrawable(R.styleable.VPSWidget_uxsdk_vpsDisabledIcon); - if (vpsDisabledIcon != null) { - setVPSDisabledIcon(vpsDisabledIcon); - } - - int vpsValueTextAppearanceId = - typedArray.getResourceId(R.styleable.VPSWidget_uxsdk_vpsValueTextAppearance, INVALID_RESOURCE); - if (vpsValueTextAppearanceId != INVALID_RESOURCE) { - setVPSValueTextAppearance(vpsValueTextAppearanceId); - } - - float vpsValueTextSize = - typedArray.getDimension(R.styleable.VPSWidget_uxsdk_vpsValueTextSize, INVALID_RESOURCE); - if (vpsValueTextSize != INVALID_RESOURCE) { - setVPSValueTextSize(DisplayUtil.pxToSp(context, vpsValueTextSize)); - } - - int vpsValueEnabledTextColor = - typedArray.getColor(R.styleable.VPSWidget_uxsdk_vpsValueEnabledTextColor, INVALID_COLOR); - if (vpsValueEnabledTextColor != INVALID_COLOR) { - setVPSValueEnabledTextColor(vpsValueEnabledTextColor); - } - - int vpsValueDisabledTextColor = - typedArray.getColor(R.styleable.VPSWidget_uxsdk_vpsValueDisabledTextColor, INVALID_COLOR); - if (vpsValueDisabledTextColor != INVALID_COLOR) { - setVPSValueDisabledTextColor(vpsValueDisabledTextColor); - } - - Drawable vpsValueTextBackgroundDrawable = - typedArray.getDrawable(R.styleable.VPSWidget_uxsdk_vpsValueBackgroundDrawable); - if (vpsValueTextBackgroundDrawable != null) { - setVPSValueTextBackground(vpsValueTextBackgroundDrawable); - } - - int vpsUnitTextAppearanceId = - typedArray.getResourceId(R.styleable.VPSWidget_uxsdk_vpsUnitTextAppearance, INVALID_RESOURCE); - if (vpsUnitTextAppearanceId != INVALID_RESOURCE) { - setVPSUnitTextAppearance(vpsUnitTextAppearanceId); - } - - float vpsUnitTextSize = typedArray.getDimension(R.styleable.VPSWidget_uxsdk_vpsUnitTextSize, INVALID_RESOURCE); - if (vpsUnitTextSize != INVALID_RESOURCE) { - setVPSUnitTextSize(DisplayUtil.pxToSp(context, vpsUnitTextSize)); - } - - int vpsUnitTextColor = typedArray.getColor(R.styleable.VPSWidget_uxsdk_vpsUnitTextColor, INVALID_COLOR); - if (vpsUnitTextColor != INVALID_COLOR) { - setVPSUnitTextColor(vpsUnitTextColor); - } - - Drawable vpsUnitTextBackgroundDrawable = - typedArray.getDrawable(R.styleable.VPSWidget_uxsdk_vpsUnitBackgroundDrawable); - if (vpsUnitTextBackgroundDrawable != null) { - setVPSUnitTextBackground(vpsUnitTextBackgroundDrawable); - } - typedArray.recycle(); - } - //endregion -} diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/vps/VPSWidget.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/vps/VPSWidget.kt new file mode 100644 index 00000000..23d42a04 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/vps/VPSWidget.kt @@ -0,0 +1,231 @@ +/* + * 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.ux.beta.core.widget.vps + +import android.annotation.SuppressLint +import android.content.Context +import android.content.res.TypedArray +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import androidx.annotation.DrawableRes +import androidx.core.content.res.use +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.R +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.SchedulerProvider +import dji.ux.beta.core.base.WidgetSizeDescription +import dji.ux.beta.core.base.widget.BaseTelemetryWidget +import dji.ux.beta.core.communication.GlobalPreferencesManager +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.extension.* +import dji.ux.beta.core.widget.vps.VPSWidget.ModelState +import dji.ux.beta.core.widget.vps.VPSWidgetModel.VPSState +import java.text.DecimalFormat + +private const val MIN_VPS_HEIGHT = 1.2f + +/** + * Shows the status of the vision positioning system + * as well as the height of the aircraft as received from the + * vision positioning system if available. + */ +open class VPSWidget @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + widgetTheme: Int = 0 +) : BaseTelemetryWidget( + context, + attrs, + defStyleAttr, + WidgetType.TEXT_IMAGE_RIGHT, + widgetTheme, + R.style.UXSDKVPSWidget +) { + + //region Fields + override val metricDecimalFormat: DecimalFormat = DecimalFormat("###0.0") + + override val imperialDecimalFormat: DecimalFormat = DecimalFormat("###0") + + /** + * Icon for VPS enabled + */ + var vpsEnabledIcon: Drawable = getDrawable(R.drawable.uxsdk_ic_vps_enabled) + + /** + * Icon for VPS disabled + */ + var vpsDisabledIcon: Drawable = getDrawable(R.drawable.uxsdk_ic_vps_disabled) + + private val widgetModel: VPSWidgetModel by lazy { + VPSWidgetModel( + DJISDKModel.getInstance(), + ObservableInMemoryKeyedStore.getInstance(), + GlobalPreferencesManager.getInstance()) + } + //endregion + + //region Constructor + init { + initThemeAttributes(context, widgetTheme) + initAttributes(context, attrs) + setValueTextViewMinWidthByText("88.8") + } + //endregion + + //region Lifecycle + override fun onAttachedToWindow() { + super.onAttachedToWindow() + if (!isInEditMode) { + widgetModel.setup() + } + } + + override fun onDetachedFromWindow() { + if (!isInEditMode) { + widgetModel.cleanup() + } + super.onDetachedFromWindow() + } + + override fun reactToModelChanges() { + addReaction(widgetModel.productConnection + .observeOn(SchedulerProvider.ui()) + .subscribe { widgetStateDataProcessor.onNext(ModelState.ProductConnected(it)) }) + addReaction(widgetModel.vpsState + .observeOn(SchedulerProvider.ui()) + .subscribe { updateUI(it) }) + } + //endregion + + //region Reaction to model + private fun updateUI(vpsState: VPSState) { + widgetStateDataProcessor.onNext(ModelState.VPSStateUpdated(vpsState)) + when (vpsState) { + VPSState.ProductDisconnected, + VPSState.Disabled -> updateToDisconnectedState() + is VPSState.Enabled -> updateVPSState(vpsState) + } + } + + private fun updateVPSState(vpsState: VPSState.Enabled) { + widgetIcon = vpsEnabledIcon + unitString = getDistanceString(vpsState.unitType) + valueString = getDecimalFormat(vpsState.unitType).format(vpsState.height) + valueTextColor = if (vpsState.height > MIN_VPS_HEIGHT.toDistance(vpsState.unitType)) { + normalValueColor + } else { + errorValueColor + } + } + + private fun updateToDisconnectedState() { + unitString = null + widgetIcon = vpsDisabledIcon + valueString = getString(R.string.uxsdk_string_default_value) + valueTextColor = normalValueColor + } + //endregion + + //region Customization + override fun getIdealDimensionRatioString(): String? = null + + + override val widgetSizeDescription: WidgetSizeDescription = + WidgetSizeDescription(WidgetSizeDescription.SizeType.OTHER, + widthDimension = WidgetSizeDescription.Dimension.EXPAND, + heightDimension = WidgetSizeDescription.Dimension.WRAP) + + /** + * Set the icon for VPS enabled + * + * @param resourceId Integer ID of the icon + */ + fun setVPSEnabledIcon(@DrawableRes resourceId: Int) { + vpsEnabledIcon = getDrawable(resourceId) + } + + /** + * Set the icon for VPS disabled + * + * @param resourceId Integer ID of the icon + */ + fun setVPSDisabledIcon(@DrawableRes resourceId: Int) { + vpsDisabledIcon = getDrawable(resourceId) + } + + @SuppressLint("Recycle") + private fun initThemeAttributes(context: Context, widgetTheme: Int) { + val vpsAttributeArray: IntArray = R.styleable.VPSWidget + context.obtainStyledAttributes(widgetTheme, vpsAttributeArray).use { + initAttributesByTypedArray(it) + } + } + + @SuppressLint("Recycle") + private fun initAttributes(context: Context, attrs: AttributeSet?) { + context.obtainStyledAttributes(attrs, R.styleable.VPSWidget, 0, defaultStyle).use { + initAttributesByTypedArray(it) + } + } + + private fun initAttributesByTypedArray(typedArray: TypedArray) { + typedArray.getDrawableAndUse(R.styleable.VPSWidget_uxsdk_vpsEnabledIcon) { + vpsEnabledIcon = it + } + typedArray.getDrawableAndUse(R.styleable.VPSWidget_uxsdk_vpsDisabledIcon) { + vpsDisabledIcon = it + } + } + + //endregion + + //region Hooks + /** + * Get the [ModelState] updates + */ + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { + return super.getWidgetStateUpdate() + } + + /** + * Class defines widget state updates + */ + sealed class ModelState { + + /** + * Product connection update + */ + data class ProductConnected(val boolean: Boolean) : ModelState() + + /** + * VPS model state + */ + data class VPSStateUpdated(val vpsState: VPSState) : ModelState() + } + //endregion + +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/vps/VPSWidgetModel.java b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/vps/VPSWidgetModel.java deleted file mode 100644 index cc4fb614..00000000 --- a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/vps/VPSWidgetModel.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * 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.ux.beta.core.widget.vps; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import dji.keysdk.FlightControllerKey; -import dji.thirdparty.io.reactivex.Flowable; -import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.GlobalPreferencesInterface; -import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.GlobalPreferenceKeys; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; -import dji.ux.beta.core.base.uxsdkkeys.UXKey; -import dji.ux.beta.core.base.uxsdkkeys.UXKeys; -import dji.ux.beta.core.util.DataProcessor; -import dji.ux.beta.core.util.UnitConversionUtil; - -/** - * Widget Model for the Vision Positioning System widget {@link VPSWidget} - * used to define the underlying logic and communication - */ -public class VPSWidgetModel extends WidgetModel { - //region Fields - private final GlobalPreferencesInterface preferencesManager; - private final DataProcessor visionPositioningEnabledProcessor; - private final DataProcessor ultrasonicBeingUsedProcessor; - private final DataProcessor rawUltrasonicHeightProcessor; - private final DataProcessor ultrasonicHeightProcessor; - private final DataProcessor unitTypeProcessor; - //endregion - - //region Constructor - public VPSWidgetModel(@NonNull DJISDKModel djiSdkModel, - @NonNull ObservableInMemoryKeyedStore keyedStore, - @Nullable GlobalPreferencesInterface preferencesManager) { - super(djiSdkModel, keyedStore); - this.preferencesManager = preferencesManager; - visionPositioningEnabledProcessor = DataProcessor.create(false); - ultrasonicBeingUsedProcessor = DataProcessor.create(false); - ultrasonicHeightProcessor = DataProcessor.create(0.0f); - rawUltrasonicHeightProcessor = DataProcessor.create(0.0f); - unitTypeProcessor = DataProcessor.create(UnitConversionUtil.UnitType.METRIC); - if (preferencesManager != null) { - unitTypeProcessor.onNext(preferencesManager.getUnitType()); - } - } - //endregion - - //region Data - - /** - * Get the height of the aircraft as returned by the ultrasonic sensor. - * - * @return Flowable for the DataProcessor that user should subscribe to. - */ - public Flowable getUltrasonicHeight() { - return ultrasonicHeightProcessor.toFlowable(); - } - - /** - * Get if the vision positioning is enabled or disabled. - * - * @return Flowable for the DataProcessor that user should subscribe to. - */ - public Flowable getVisionPositioningEnabled() { - return visionPositioningEnabledProcessor.toFlowable(); - } - - public Flowable getUltrasonicBeingUsed() { - return ultrasonicBeingUsedProcessor.toFlowable(); - } - - /** - * Get the unit type of the ultrasonic height value received. - * - * @return Flowable for the DataProcessor that user should subscribe to. - */ - public Flowable getUnitType() { - return unitTypeProcessor.toFlowable(); - } - //endregion - - //region Lifecycle - @Override - protected void inSetup() { - FlightControllerKey visionPositioningEnabledKey = - FlightControllerKey.createFlightAssistantKey(FlightControllerKey.VISION_ASSISTED_POSITIONING_ENABLED); - FlightControllerKey isUltrasonicBeingUsedKey = - FlightControllerKey.create(FlightControllerKey.IS_ULTRASONIC_BEING_USED); - FlightControllerKey ultrasonicHeightKey = - FlightControllerKey.create(FlightControllerKey.ULTRASONIC_HEIGHT_IN_METERS); - - bindDataProcessor(visionPositioningEnabledKey, visionPositioningEnabledProcessor); - bindDataProcessor(isUltrasonicBeingUsedKey, ultrasonicBeingUsedProcessor); - bindDataProcessor(ultrasonicHeightKey, - rawUltrasonicHeightProcessor, - ultrasonicHeight -> convertValueByUnit((float) ultrasonicHeight)); - - UXKey unitKey = UXKeys.create(GlobalPreferenceKeys.UNIT_TYPE); - bindDataProcessor(unitKey, unitTypeProcessor); - - if (preferencesManager != null) { - preferencesManager.setUpListener(); - } - } - - @Override - protected void inCleanup() { - if (preferencesManager != null) { - preferencesManager.cleanup(); - } - } - - @Override - protected void updateStates() { - //Nothing to update - } - //endregion - - //region Helpers - private void convertValueByUnit(float ultrasonicHeight) { - if (unitTypeProcessor.getValue() == UnitConversionUtil.UnitType.IMPERIAL) { - ultrasonicHeightProcessor.onNext(UnitConversionUtil.convertMetersToFeet(ultrasonicHeight)); - } else { - ultrasonicHeightProcessor.onNext(ultrasonicHeight); - } - } - //endregion -} diff --git a/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/vps/VPSWidgetModel.kt b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/vps/VPSWidgetModel.kt new file mode 100644 index 00000000..63e686a3 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/java/dji/ux/beta/core/widget/vps/VPSWidgetModel.kt @@ -0,0 +1,113 @@ +/* + * 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.ux.beta.core.widget.vps + +import dji.keysdk.FlightControllerKey +import dji.thirdparty.io.reactivex.Flowable +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.WidgetModel +import dji.ux.beta.core.communication.GlobalPreferenceKeys +import dji.ux.beta.core.communication.GlobalPreferencesInterface +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.extension.toDistance +import dji.ux.beta.core.util.DataProcessor +import dji.ux.beta.core.util.UnitConversionUtil + +/** + * Widget Model for the [VPSWidget] used to define + * the underlying logic and communication + */ +class VPSWidgetModel(djiSdkModel: DJISDKModel, + keyedStore: ObservableInMemoryKeyedStore, + private val preferencesManager: GlobalPreferencesInterface? +) : WidgetModel(djiSdkModel, keyedStore) { + + private val unitTypeDataProcessor: DataProcessor = DataProcessor.create(UnitConversionUtil.UnitType.METRIC) + private val visionPositioningEnabledProcessor: DataProcessor = DataProcessor.create(false) + private val ultrasonicBeingUsedProcessor: DataProcessor = DataProcessor.create(false) + private val rawUltrasonicHeightProcessor: DataProcessor = DataProcessor.create(0.0f) + private val vpsStateProcessor: DataProcessor = DataProcessor.create(VPSState.ProductDisconnected) + + /** + * Get the current VPS state + */ + val vpsState: Flowable + get() = vpsStateProcessor.toFlowable() + + override fun inSetup() { + val visionPositioningEnabledKey = FlightControllerKey.createFlightAssistantKey(FlightControllerKey.VISION_ASSISTED_POSITIONING_ENABLED) + val isUltrasonicBeingUsedKey = FlightControllerKey.create(FlightControllerKey.IS_ULTRASONIC_BEING_USED) + val ultrasonicHeightKey = FlightControllerKey.create(FlightControllerKey.ULTRASONIC_HEIGHT_IN_METERS) + + bindDataProcessor(visionPositioningEnabledKey, visionPositioningEnabledProcessor) + bindDataProcessor(isUltrasonicBeingUsedKey, ultrasonicBeingUsedProcessor) + bindDataProcessor(ultrasonicHeightKey, rawUltrasonicHeightProcessor) + + val unitTypeKey = GlobalPreferenceKeys.create(GlobalPreferenceKeys.UNIT_TYPE) + bindDataProcessor(unitTypeKey, unitTypeDataProcessor) + preferencesManager?.setUpListener() + preferencesManager?.let { unitTypeDataProcessor.onNext(it.unitType) } + } + + override fun updateStates() { + if (productConnectionProcessor.value) { + if (ultrasonicBeingUsedProcessor.value + && visionPositioningEnabledProcessor.value) { + vpsStateProcessor.onNext(VPSState.Enabled( + rawUltrasonicHeightProcessor.value.toDistance(unitTypeDataProcessor.value), + unitTypeDataProcessor.value)) + } else { + vpsStateProcessor.onNext(VPSState.Disabled) + } + } else { + vpsStateProcessor.onNext(VPSState.ProductDisconnected) + } + } + + override fun inCleanup() { + preferencesManager?.cleanup() + } + + /** + * Class to represent states of VPS + */ + sealed class VPSState { + + /** + * When product is disconnected + */ + object ProductDisconnected : VPSState() + + /** + * Vision disabled or ultrasonic is not being used + */ + object Disabled : VPSState() + + /** + * Current VPS height + */ + data class Enabled(val height: Float, val unitType: UnitConversionUtil.UnitType) : VPSState() + + } +} \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_distance_bg.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_distance_bg.xml new file mode 100644 index 00000000..2875c18d --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_distance_bg.xml @@ -0,0 +1,34 @@ + + + + + + + + + + \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_gradient_error.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_gradient_error.xml index ec7f36e3..2dfd8d9a 100644 --- a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_gradient_error.xml +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_gradient_error.xml @@ -1,4 +1,5 @@ - + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_avoid_left_warning.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_avoid_left_warning.xml new file mode 100644 index 00000000..985a4e51 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_avoid_left_warning.xml @@ -0,0 +1,42 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_avoid_right_danger.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_avoid_right_danger.xml new file mode 100644 index 00000000..1c71402e --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_avoid_right_danger.xml @@ -0,0 +1,42 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_avoid_right_warning.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_avoid_right_warning.xml new file mode 100644 index 00000000..2dee8817 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_avoid_right_warning.xml @@ -0,0 +1,42 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_dashboard_background.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_arrow.xml similarity index 85% rename from android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_dashboard_background.xml rename to android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_arrow.xml index 698cd82a..e8751182 100644 --- a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_dashboard_background.xml +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_arrow.xml @@ -22,15 +22,14 @@ --> + android:width="12dp" + android:height="12dp" + android:viewportWidth="12" + android:viewportHeight="12"> diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_0.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_0.xml new file mode 100644 index 00000000..d617848f --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_0.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_minus.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_0_0.xml similarity index 69% rename from android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_minus.xml rename to android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_0_0.xml index 0bfbbdeb..b2e98fa8 100644 --- a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_minus.xml +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_0_0.xml @@ -22,14 +22,17 @@ --> - - + xmlns:tools="http://schemas.android.com/tools" + android:width="447dp" + android:height="62dp" + android:viewportWidth="447" + android:viewportHeight="62" + tools:ignore="VectorRaster"> + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_0_1.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_0_1.xml new file mode 100644 index 00000000..dab5790f --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_0_1.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_0_2.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_0_2.xml new file mode 100644 index 00000000..27b152ef --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_0_2.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_0_3.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_0_3.xml new file mode 100644 index 00000000..15545139 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_0_3.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_0_4.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_0_4.xml new file mode 100644 index 00000000..4abf5619 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_0_4.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_0_5.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_0_5.xml new file mode 100644 index 00000000..9b964a5b --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_0_5.xml @@ -0,0 +1,52 @@ + + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_1.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_1.xml new file mode 100644 index 00000000..8fed6406 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_1.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_plus.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_1_0.xml similarity index 67% rename from android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_plus.xml rename to android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_1_0.xml index b3604e6e..cc08b449 100644 --- a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_plus.xml +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_1_0.xml @@ -22,14 +22,17 @@ --> - - + xmlns:tools="http://schemas.android.com/tools" + android:width="447dp" + android:height="62dp" + android:viewportWidth="447" + android:viewportHeight="62" + tools:ignore="VectorRaster"> + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_1_1.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_1_1.xml new file mode 100644 index 00000000..fcca320d --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_1_1.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_1_2.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_1_2.xml new file mode 100644 index 00000000..73f81fd9 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_1_2.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_1_3.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_1_3.xml new file mode 100644 index 00000000..45c290ad --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_1_3.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_1_4.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_1_4.xml new file mode 100644 index 00000000..6e0bfa1f --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_1_4.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_1_5.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_1_5.xml new file mode 100644 index 00000000..f3c9ddcd --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_1_5.xml @@ -0,0 +1,52 @@ + + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_2.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_2.xml new file mode 100644 index 00000000..3fcb3490 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_2.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_2_0.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_2_0.xml new file mode 100644 index 00000000..6c9f3adb --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_2_0.xml @@ -0,0 +1,38 @@ + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_2_1.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_2_1.xml new file mode 100644 index 00000000..1efb3358 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_2_1.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_2_2.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_2_2.xml new file mode 100644 index 00000000..43fab01c --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_2_2.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_2_3.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_2_3.xml new file mode 100644 index 00000000..85ac7364 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_2_3.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_2_4.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_2_4.xml new file mode 100644 index 00000000..933487d3 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_2_4.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_2_5.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_2_5.xml new file mode 100644 index 00000000..7a31aeeb --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_2_5.xml @@ -0,0 +1,52 @@ + + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_3.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_3.xml new file mode 100644 index 00000000..d52836b8 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_3.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_3_0.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_3_0.xml new file mode 100644 index 00000000..ba1bca79 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_3_0.xml @@ -0,0 +1,38 @@ + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_3_1.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_3_1.xml new file mode 100644 index 00000000..8924d2a2 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_3_1.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_3_2.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_3_2.xml new file mode 100644 index 00000000..decbbdbf --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_3_2.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_3_3.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_3_3.xml new file mode 100644 index 00000000..e37b9957 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_3_3.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_3_4.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_3_4.xml new file mode 100644 index 00000000..c0197222 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_3_4.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_3_5.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_3_5.xml new file mode 100644 index 00000000..ffa2c25b --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_backward_3_5.xml @@ -0,0 +1,52 @@ + + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_0.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_0.xml new file mode 100644 index 00000000..d3e6323b --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_0.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_0_0.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_0_0.xml new file mode 100644 index 00000000..7ffa6381 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_0_0.xml @@ -0,0 +1,38 @@ + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_0_1.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_0_1.xml new file mode 100644 index 00000000..db8fb93d --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_0_1.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_0_2.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_0_2.xml new file mode 100644 index 00000000..24705880 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_0_2.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_0_3.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_0_3.xml new file mode 100644 index 00000000..ac5b54fe --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_0_3.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_0_4.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_0_4.xml new file mode 100644 index 00000000..a8661322 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_0_4.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_0_5.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_0_5.xml new file mode 100644 index 00000000..3d279f19 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_0_5.xml @@ -0,0 +1,52 @@ + + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_1.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_1.xml new file mode 100644 index 00000000..d507bdd1 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_1.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_1_0.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_1_0.xml new file mode 100644 index 00000000..3bcb223d --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_1_0.xml @@ -0,0 +1,38 @@ + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_1_1.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_1_1.xml new file mode 100644 index 00000000..10df194b --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_1_1.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_1_2.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_1_2.xml new file mode 100644 index 00000000..95f0754c --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_1_2.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_1_3.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_1_3.xml new file mode 100644 index 00000000..8e97daeb --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_1_3.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_1_4.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_1_4.xml new file mode 100644 index 00000000..e4d5706a --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_1_4.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_1_5.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_1_5.xml new file mode 100644 index 00000000..f038792f --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_1_5.xml @@ -0,0 +1,52 @@ + + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_2.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_2.xml new file mode 100644 index 00000000..04f1f34a --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_2.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_2_0.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_2_0.xml new file mode 100644 index 00000000..068612a4 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_2_0.xml @@ -0,0 +1,38 @@ + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_2_1.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_2_1.xml new file mode 100644 index 00000000..6a63bba7 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_2_1.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_2_2.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_2_2.xml new file mode 100644 index 00000000..27e89fef --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_2_2.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_2_3.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_2_3.xml new file mode 100644 index 00000000..0db12cdc --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_2_3.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_2_4.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_2_4.xml new file mode 100644 index 00000000..36c52c5f --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_2_4.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_2_5.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_2_5.xml new file mode 100644 index 00000000..317ecbab --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_2_5.xml @@ -0,0 +1,52 @@ + + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_3.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_3.xml new file mode 100644 index 00000000..395cd729 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_3.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_3_0.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_3_0.xml new file mode 100644 index 00000000..9658f52a --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_3_0.xml @@ -0,0 +1,38 @@ + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_3_1.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_3_1.xml new file mode 100644 index 00000000..b9b509a7 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_3_1.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_3_2.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_3_2.xml new file mode 100644 index 00000000..9d8f9d37 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_3_2.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_3_3.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_3_3.xml new file mode 100644 index 00000000..f9b005e4 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_3_3.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_3_4.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_3_4.xml new file mode 100644 index 00000000..7045ea5b --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_3_4.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_3_5.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_3_5.xml new file mode 100644 index 00000000..79d1bf6f --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_forward_3_5.xml @@ -0,0 +1,52 @@ + + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_left.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_left.xml new file mode 100644 index 00000000..494258a7 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_left.xml @@ -0,0 +1,28 @@ + + + + + + + \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_right.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_right.xml new file mode 100644 index 00000000..8156e0d1 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_radar_right.xml @@ -0,0 +1,28 @@ + + + + + + + \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_system_status_list_novice_mode.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_system_status_list_novice_mode.xml index 71cb5a43..d432146f 100644 --- a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_system_status_list_novice_mode.xml +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_system_status_list_novice_mode.xml @@ -1,3 +1,26 @@ + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_upward_obstacle.xml b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_upward_obstacle.xml new file mode 100644 index 00000000..afea025c --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/drawable/uxsdk_ic_upward_obstacle.xml @@ -0,0 +1,49 @@ + + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_dialog_sliding_action.xml b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_dialog_sliding_action.xml index 2ee40462..3b44c34f 100644 --- a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_dialog_sliding_action.xml +++ b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_dialog_sliding_action.xml @@ -88,7 +88,6 @@ android:layout_marginBottom="5dp" android:background="@drawable/uxsdk_background_sliding_seekbar_text" android:drawableEnd="@drawable/uxsdk_ic_double_arrow" - android:drawableRight="@drawable/uxsdk_ic_double_arrow" android:drawablePadding="6dp" android:gravity="end|center_vertical" android:paddingStart="6dp" diff --git a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_layout_terms_view.xml b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_layout_dialog_checkbox.xml similarity index 93% rename from android-uxsdk-beta-core/src/main/res/layout/uxsdk_layout_terms_view.xml rename to android-uxsdk-beta-core/src/main/res/layout/uxsdk_layout_dialog_checkbox.xml index 462092da..5d958443 100644 --- a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_layout_terms_view.xml +++ b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_layout_dialog_checkbox.xml @@ -30,11 +30,10 @@ android:paddingRight="12dp"> + app:layout_constraintTop_toBottomOf="@+id/textview_dialog_content" /> \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_layout_title_bar.xml b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_layout_title_bar.xml index 726e7c80..c3d407e6 100644 --- a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_layout_title_bar.xml +++ b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_layout_title_bar.xml @@ -54,7 +54,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" - android:layout_marginLeft="16dp" android:clickable="true" android:focusable="true" android:padding="@dimen/uxsdk_spacing_normal" @@ -97,7 +96,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="16dp" - android:layout_marginRight="16dp" android:clickable="true" android:focusable="true" android:padding="@dimen/uxsdk_spacing_normal" diff --git a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_list_item_title_widget.xml b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_list_item_title_widget.xml index 92371521..2938f174 100644 --- a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_list_item_title_widget.xml +++ b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_list_item_title_widget.xml @@ -83,8 +83,8 @@ android:id="@+id/text_view_list_item_title" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginLeft="@dimen/uxsdk_list_item_button_padding_horizontal" - android:gravity="center_vertical|left" + android:layout_marginStart="@dimen/uxsdk_list_item_button_padding_horizontal" + android:gravity="center_vertical|start" android:textColor="@color/uxsdk_white" android:textSize="@dimen/uxsdk_text_size_medium" app:layout_constraintBottom_toBottomOf="@+id/guideline_bottom" diff --git a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_view_seek_bar.xml b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_view_seek_bar.xml index 1d6ff14c..97f6c3dc 100644 --- a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_view_seek_bar.xml +++ b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_view_seek_bar.xml @@ -88,6 +88,7 @@ app:srcCompat="@drawable/uxsdk_ic_camera_decrement_minus" tools:ignore="ContentDescription" /> + + + android:layout_height="match_parent" + tools:parentTag="dji.ux.beta.core.widget.airsense.AirSenseWidget"> - - - - - - - - - - - - diff --git a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_base_dashboard_text_only.xml b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_base_dashboard_text_only.xml deleted file mode 100644 index ce86e31d..00000000 --- a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_base_dashboard_text_only.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - diff --git a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_base_telemetry.xml b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_base_telemetry.xml new file mode 100644 index 00000000..d494dabe --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_base_telemetry.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_compass.xml b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_compass.xml index 79f10581..7ddda86d 100644 --- a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_compass.xml +++ b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_compass.xml @@ -23,12 +23,12 @@ --> + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:clipChildren="false" + tools:parentTag="dji.ux.beta.core.widget.compass.CompassWidget"> + app:srcCompat="@drawable/uxsdk_ic_compass_background" + tools:ignore="ContentDescription" /> + app:srcCompat="@drawable/uxsdk_ic_compass_inner_circles" + tools:ignore="ContentDescription" /> + app:layout_constraintLeft_toLeftOf="@id/imageview_compass_background" + app:layout_constraintRight_toRightOf="@id/imageview_compass_background" + app:layout_constraintTop_toTopOf="@id/imageview_compass_background" /> + app:layout_constraintTop_toTopOf="@id/imageview_compass_background" /> + app:layout_constraintTop_toTopOf="@id/imageview_compass_background" /> + app:srcCompat="@drawable/uxsdk_compass_rc" + tools:ignore="ContentDescription" /> + app:srcCompat="@drawable/uxsdk_ic_compass_home" + tools:ignore="ContentDescription" /> + app:srcCompat="@drawable/uxsdk_ic_compass_gimbal_yaw" + tools:ignore="ContentDescription" /> + app:srcCompat="@drawable/uxsdk_ic_compass_aircraft" + tools:ignore="ContentDescription" /> + app:srcCompat="@drawable/uxsdk_ic_compass_north" + tools:ignore="ContentDescription" /> \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_dashboard.xml b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_dashboard.xml deleted file mode 100644 index aa09fd71..00000000 --- a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_dashboard.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_flight_mode.xml b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_flight_mode.xml index cafe138b..0a8cc663 100644 --- a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_flight_mode.xml +++ b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_flight_mode.xml @@ -44,7 +44,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="2dp" - android:layout_marginLeft="2dp" android:text="@string/uxsdk_string_default_value" android:textColor="@color/uxsdk_white" android:textSize="@dimen/uxsdk_text_size_medium" diff --git a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_gps_signal.xml b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_gps_signal.xml index b85b0b43..d8b158b6 100644 --- a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_gps_signal.xml +++ b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_gps_signal.xml @@ -55,7 +55,6 @@ android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="2dp" - android:layout_marginLeft="2dp" android:src="@drawable/uxsdk_gps_signal_level_list" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/textview_rtk_enabled" @@ -68,7 +67,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="2dp" - android:layout_marginLeft="2dp" android:paddingBottom="4dp" android:textColor="@color/uxsdk_white" android:textSize="@dimen/uxsdk_text_size_tiny" diff --git a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_radar.xml b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_radar.xml new file mode 100644 index 00000000..b5c09e58 --- /dev/null +++ b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_radar.xml @@ -0,0 +1,254 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_remote_controller_signal.xml b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_remote_controller_signal.xml index 3322902e..7e5f0f0f 100644 --- a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_remote_controller_signal.xml +++ b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_remote_controller_signal.xml @@ -45,7 +45,6 @@ android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginStart="2dp" - android:layout_marginLeft="2dp" android:src="@drawable/uxsdk_signal_level_list" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_video_signal.xml b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_video_signal.xml index 198f2f4a..b4bf2a74 100644 --- a/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_video_signal.xml +++ b/android-uxsdk-beta-core/src/main/res/layout/uxsdk_widget_video_signal.xml @@ -55,7 +55,6 @@ android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="2dp" - android:layout_marginLeft="2dp" android:src="@drawable/uxsdk_signal_level_list" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/android-uxsdk-beta-core/src/main/res/raw-ja/uxsdk_tips_voice_atti_mode.mp3 b/android-uxsdk-beta-core/src/main/res/raw-ja/uxsdk_tips_voice_atti_mode.mp3 new file mode 100644 index 00000000..3531a5d7 Binary files /dev/null and b/android-uxsdk-beta-core/src/main/res/raw-ja/uxsdk_tips_voice_atti_mode.mp3 differ diff --git a/android-uxsdk-beta-core/src/main/res/raw-zh/uxsdk_tips_voice_atti_mode.mp3 b/android-uxsdk-beta-core/src/main/res/raw-zh/uxsdk_tips_voice_atti_mode.mp3 new file mode 100644 index 00000000..21ef88e7 Binary files /dev/null and b/android-uxsdk-beta-core/src/main/res/raw-zh/uxsdk_tips_voice_atti_mode.mp3 differ diff --git a/android-uxsdk-beta-core/src/main/res/raw/uxsdk_radar_beep_1000.wav b/android-uxsdk-beta-core/src/main/res/raw/uxsdk_radar_beep_1000.wav new file mode 100644 index 00000000..ad7463f2 Binary files /dev/null and b/android-uxsdk-beta-core/src/main/res/raw/uxsdk_radar_beep_1000.wav differ diff --git a/android-uxsdk-beta-core/src/main/res/raw/uxsdk_radar_beep_250.wav b/android-uxsdk-beta-core/src/main/res/raw/uxsdk_radar_beep_250.wav new file mode 100644 index 00000000..ad613854 Binary files /dev/null and b/android-uxsdk-beta-core/src/main/res/raw/uxsdk_radar_beep_250.wav differ diff --git a/android-uxsdk-beta-core/src/main/res/raw/uxsdk_radar_beep_500.wav b/android-uxsdk-beta-core/src/main/res/raw/uxsdk_radar_beep_500.wav new file mode 100644 index 00000000..9d6db971 Binary files /dev/null and b/android-uxsdk-beta-core/src/main/res/raw/uxsdk_radar_beep_500.wav differ diff --git a/android-uxsdk-beta-core/src/main/res/raw/uxsdk_tips_voice_atti_mode.mp3 b/android-uxsdk-beta-core/src/main/res/raw/uxsdk_tips_voice_atti_mode.mp3 new file mode 100644 index 00000000..d61c6b35 Binary files /dev/null and b/android-uxsdk-beta-core/src/main/res/raw/uxsdk_tips_voice_atti_mode.mp3 differ diff --git a/android-uxsdk-beta-core/src/main/res/values-1440x2560/dimens.xml b/android-uxsdk-beta-core/src/main/res/values-1440x2560/dimens.xml index aaa2700e..43ca7005 100644 --- a/android-uxsdk-beta-core/src/main/res/values-1440x2560/dimens.xml +++ b/android-uxsdk-beta-core/src/main/res/values-1440x2560/dimens.xml @@ -1,4 +1,27 @@ + + 4sp 4sp diff --git a/android-uxsdk-beta-core/src/main/res/values/attrs.xml b/android-uxsdk-beta-core/src/main/res/values/attrs.xml index 7e822dc9..ba084612 100644 --- a/android-uxsdk-beta-core/src/main/res/values/attrs.xml +++ b/android-uxsdk-beta-core/src/main/res/values/attrs.xml @@ -44,9 +44,16 @@ + + + + + + + @@ -108,6 +115,14 @@ + + + + + + + + @@ -230,7 +245,7 @@ - + @@ -268,11 +283,12 @@ + and {@link dji.ux.beta.accessory.widget.rtk.RTKWidget}. --> + @@ -291,102 +307,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -408,18 +328,6 @@ - - - - - - - - - - - - @@ -465,6 +373,21 @@ + + + + + + + + + + + + + + + @@ -480,10 +403,10 @@ - - - - + + + + @@ -694,6 +617,14 @@ + + + + + + + + @@ -714,17 +645,18 @@ - + + - + @@ -732,21 +664,93 @@ + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android-uxsdk-beta-core/src/main/res/values/colors.xml b/android-uxsdk-beta-core/src/main/res/values/colors.xml index 83c64d1a..b602510b 100644 --- a/android-uxsdk-beta-core/src/main/res/values/colors.xml +++ b/android-uxsdk-beta-core/src/main/res/values/colors.xml @@ -103,7 +103,6 @@ #1FA3F6 #801FA3F6 - @color/uxsdk_green @color/uxsdk_yellow_500 diff --git a/android-uxsdk-beta-core/src/main/res/values/dimens.xml b/android-uxsdk-beta-core/src/main/res/values/dimens.xml index 08be09aa..5eca7a44 100644 --- a/android-uxsdk-beta-core/src/main/res/values/dimens.xml +++ b/android-uxsdk-beta-core/src/main/res/values/dimens.xml @@ -47,12 +47,24 @@ 21dp 42dp + 45dp + 10dp + 5dp + 15sp + 14sp + 1dp + 2dp 4sp 3sp + 10sp + + 100dp + 32dp + 16dp 13sp @@ -69,4 +81,9 @@ 5sp 5sp 8sp + 55dp + + 5dp + 2dp + 6dp \ No newline at end of file diff --git a/android-uxsdk-beta-core/src/main/res/values/integers.xml b/android-uxsdk-beta-core/src/main/res/values/integers.xml index 16fa8878..b5c6ad62 100755 --- a/android-uxsdk-beta-core/src/main/res/values/integers.xml +++ b/android-uxsdk-beta-core/src/main/res/values/integers.xml @@ -23,5 +23,6 @@ --> + 200 333 diff --git a/android-uxsdk-beta-core/src/main/res/values/strings.xml b/android-uxsdk-beta-core/src/main/res/values/strings.xml index 5b5ab0fa..2f604427 100644 --- a/android-uxsdk-beta-core/src/main/res/values/strings.xml +++ b/android-uxsdk-beta-core/src/main/res/values/strings.xml @@ -30,6 +30,7 @@ Cancel Error Alert + Tips Success Enable Disable @@ -51,7 +52,8 @@ H - + + m ft %1$s m @@ -59,12 +61,14 @@ m/s km/h mph - H: - D: - DRC: - VPS: - H.S: - V.S: + H + H AGL + H AMSL + D + DRC + VPS + H.S. + V.S. %1$02d:%2$02d:%3$02d %1$02d:%2$02d @@ -87,10 +91,17 @@ Obstacle Avoidance Disabled. Fly with caution. + EI + Max Flight Altitude: %1$s - + Flight Mode + Aircraft Battery Temperature + Radio Channel Quality + %1$.1f°C + %1$.1f°F + %1$.1fK Travel Mode The aircraft will automatically retract the landing gear and enter Travel Mode. Failed to enter Travel Mode. %1$s @@ -99,13 +110,14 @@ Exit The aircraft will enter Travel Mode for easier transportation. Place the aircraft on a flat, hard surface and remove the gimbal. If the aircraft does not enter Travel Mode, adjust the landing gear manually. - Remote Controller Battery + Remote Controller Battery %1$d%% Control Stick Mode Mode 1 Mode 2 Mode 3 + Custom SD Card Remaining Capacity Format @@ -157,7 +169,11 @@ 30m 100ft (%1$d~%2$dm) - (%1$d~%2$dft) + ≈ (%1$d~%2$dft) + + Disabled + Normal + System Status List @@ -170,9 +186,11 @@ Beginner mode enabled. Altitude and distance will both be limited to 100ft / 30m. - Unit Type - Imperial - Metric + Unit Mode + Imperial + Metric + Imperial unit mode set successfully. Measurement values will now follow imperial unit system. Since the aircraft supports metric unit system, the values seen are approximate conversions. + Example: 10m ≈ 32ft - 34ft SSD Remaining Capacity Not Inserted @@ -189,4 +207,7 @@ Error formatting SSD. %1$s Are you sure you want to format the SSD? SSD formatting completed. + + N/A, N/A + %1$s, %2$s diff --git a/android-uxsdk-beta-core/src/main/res/values/strings_dimension_ratios.xml b/android-uxsdk-beta-core/src/main/res/values/strings_dimension_ratios.xml index c9311b39..7981e627 100644 --- a/android-uxsdk-beta-core/src/main/res/values/strings_dimension_ratios.xml +++ b/android-uxsdk-beta-core/src/main/res/values/strings_dimension_ratios.xml @@ -1,4 +1,5 @@ - - + diff --git a/android-uxsdk-beta-intelligentflight/src/main/java/dji/ux/beta/intelligentflight/widget/returnhome/ReturnHomeWidget.kt b/android-uxsdk-beta-flight/src/main/java/dji/ux/beta/flight/widget/returnhome/ReturnHomeWidget.kt similarity index 91% rename from android-uxsdk-beta-intelligentflight/src/main/java/dji/ux/beta/intelligentflight/widget/returnhome/ReturnHomeWidget.kt rename to android-uxsdk-beta-flight/src/main/java/dji/ux/beta/flight/widget/returnhome/ReturnHomeWidget.kt index f22d965a..b0fa607a 100644 --- a/android-uxsdk-beta-intelligentflight/src/main/java/dji/ux/beta/intelligentflight/widget/returnhome/ReturnHomeWidget.kt +++ b/android-uxsdk-beta-flight/src/main/java/dji/ux/beta/flight/widget/returnhome/ReturnHomeWidget.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.intelligentflight.widget.returnhome +package dji.ux.beta.flight.widget.returnhome import android.annotation.SuppressLint import android.content.Context @@ -38,26 +38,25 @@ import androidx.core.content.res.use import dji.common.flightcontroller.flyzone.FlyZoneReturnToHomeState import dji.log.DJILog import dji.thirdparty.io.reactivex.Flowable -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers +import dji.ux.beta.core.base.SchedulerProvider import dji.thirdparty.io.reactivex.disposables.Disposable import dji.thirdparty.io.reactivex.functions.Consumer import dji.thirdparty.io.reactivex.schedulers.Schedulers import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.GlobalPreferencesManager -import dji.ux.beta.core.base.SchedulerProvider import dji.ux.beta.core.base.UXSDKError -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore import dji.ux.beta.core.base.widget.IconButtonWidget -import dji.ux.beta.core.base.widget.IconButtonWidget.WidgetUIState.* +import dji.ux.beta.core.base.widget.IconButtonWidget.UIState.* +import dji.ux.beta.core.communication.GlobalPreferencesManager +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.* import dji.ux.beta.core.ui.SlidingDialog import dji.ux.beta.core.util.DisplayUtil import dji.ux.beta.core.util.ProductUtil import dji.ux.beta.core.util.UnitConversionUtil -import dji.ux.beta.intelligentflight.R -import dji.ux.beta.intelligentflight.widget.returnhome.ReturnHomeWidget.ReturnHomeWidgetState -import dji.ux.beta.intelligentflight.widget.returnhome.ReturnHomeWidget.ReturnHomeWidgetState.* -import dji.ux.beta.intelligentflight.widget.returnhome.ReturnHomeWidgetModel.ReturnHomeState +import dji.ux.beta.flight.R +import dji.ux.beta.flight.widget.returnhome.ReturnHomeWidget.ModelState +import dji.ux.beta.flight.widget.returnhome.ReturnHomeWidget.ModelState.* +import dji.ux.beta.flight.widget.returnhome.ReturnHomeWidgetModel.ReturnHomeState import java.text.DecimalFormat private const val TAG = "ReturnHomeWidget" @@ -73,7 +72,7 @@ open class ReturnHomeWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : IconButtonWidget(context, attrs, defStyleAttr), View.OnClickListener { +) : IconButtonWidget(context, attrs, defStyleAttr), View.OnClickListener { //region Fields private var slidingDialog: SlidingDialog? = null private val decimalFormat = DecimalFormat("#.#") @@ -82,7 +81,6 @@ open class ReturnHomeWidget @JvmOverloads constructor( ReturnHomeWidgetModel( DJISDKModel.getInstance(), ObservableInMemoryKeyedStore.getInstance(), - SchedulerProvider.getInstance(), GlobalPreferencesManager.getInstance()) } @@ -108,6 +106,16 @@ open class ReturnHomeWidget @JvmOverloads constructor( } } + /** + * The alpha of the image when the widget is disabled or pressed + */ + var disabledAlpha = 0.38f + + /** + * The alpha of the image when the widget is enabled + */ + var enabledAlpha = 1.0f + /** * The theme of the dialog */ @@ -320,7 +328,7 @@ open class ReturnHomeWidget @JvmOverloads constructor( //endregion - //region lifecycle + //region Lifecycle init { setOnClickListener(this) initDialog() @@ -343,10 +351,10 @@ open class ReturnHomeWidget @JvmOverloads constructor( override fun reactToModelChanges() { addReaction(widgetModel.returnHomeState - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { returnHomeState: ReturnHomeState -> updateReturnHomeState(returnHomeState) }) addReaction(widgetModel.productConnection - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) } @@ -371,14 +379,14 @@ open class ReturnHomeWidget @JvmOverloads constructor( slidingDialog?.setOnEventListener(object : SlidingDialog.OnEventListener { override fun onCancelClick(dialog: DialogInterface?) { slidingDialog?.dismiss() - uiUpdateStateProcessor.onNext(DialogActionDismiss(null)) + uiUpdateStateProcessor.onNext(DialogActionCancelled(null)) } override fun onSlideChecked(dialog: DialogInterface?, checked: Boolean) { if (checked) { addDisposable(returnToHome()) slidingDialog?.dismiss() - uiUpdateStateProcessor.onNext(DialogActionConfirm(null)) + uiUpdateStateProcessor.onNext(DialogActionConfirmed(null)) } } @@ -386,6 +394,8 @@ open class ReturnHomeWidget @JvmOverloads constructor( // no checkbox used } }) + + slidingDialog?.setOnDismissListener { uiUpdateStateProcessor.onNext(DialogDismissed(null)) } } } @@ -401,26 +411,26 @@ open class ReturnHomeWidget @JvmOverloads constructor( private fun returnToHome(): Disposable { return widgetModel.performReturnHomeAction() - .subscribeOn(Schedulers.io()) + .subscribeOn(SchedulerProvider.io()) .subscribe({ - widgetStateDataProcessor.onNext(ReturnHomeStartedSuccess) + widgetStateDataProcessor.onNext(ReturnHomeStartSucceeded) }) { error: Throwable -> if (error is UXSDKError) { DJILog.e(TAG, error.toString()) - widgetStateDataProcessor.onNext(ReturnHomeStartedError(error)) + widgetStateDataProcessor.onNext(ReturnHomeStartFailed(error)) } } } private fun cancelReturnToHome(): Disposable { return widgetModel.performCancelReturnHomeAction() - .subscribeOn(Schedulers.io()) + .subscribeOn(SchedulerProvider.io()) .subscribe({ - widgetStateDataProcessor.onNext(ReturnHomeCanceledSuccess) + widgetStateDataProcessor.onNext(ReturnHomeCancelSucceeded) }) { error: Throwable -> if (error is UXSDKError) { DJILog.e(TAG, error.toString()) - widgetStateDataProcessor.onNext(ReturnHomeCanceledError(error)) + widgetStateDataProcessor.onNext(ReturnHomeCancelFailed(error)) } } } @@ -461,24 +471,24 @@ open class ReturnHomeWidget @JvmOverloads constructor( if (foregroundImageView.imageDrawable == cancelReturnHomeActionIcon) { foregroundImageView.isEnabled = isEnabled if (isPressed) { - foregroundImageView.alpha = DISABLE_ALPHA + foregroundImageView.alpha = disabledAlpha } else { - foregroundImageView.alpha = ENABLE_ALPHA + foregroundImageView.alpha = enabledAlpha } return } if ((isPressed || isFocused) || !isEnabled) { - foregroundImageView.alpha = DISABLE_ALPHA + foregroundImageView.alpha = disabledAlpha } else { - foregroundImageView.alpha = ENABLE_ALPHA + foregroundImageView.alpha = enabledAlpha } } private fun checkAndUpdateReturnHomeState() { if (!isInEditMode) { addDisposable(widgetModel.returnHomeState.firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { this.updateReturnHomeState(it) }, logErrorConsumer(TAG, "Update Return Home State "))) } @@ -491,6 +501,12 @@ open class ReturnHomeWidget @JvmOverloads constructor( @SuppressLint("Recycle") private fun initAttributes(context: Context, attrs: AttributeSet) { context.obtainStyledAttributes(attrs, R.styleable.ReturnHomeWidget).use { typedArray -> + typedArray.getFloatAndUse(R.styleable.ReturnHomeWidget_uxsdk_disabledAlpha) { + disabledAlpha = it + } + typedArray.getFloatAndUse(R.styleable.ReturnHomeWidget_uxsdk_enabledAlpha) { + enabledAlpha = it + } typedArray.getResourceIdAndUse(R.styleable.ReturnHomeWidget_uxsdk_dialogTheme) { dialogTheme = it } @@ -635,47 +651,48 @@ open class ReturnHomeWidget @JvmOverloads constructor( } //endregion - //region hooks + //region Hooks /** - * Get the [ReturnHomeWidgetState] updates + * Get the [ModelState] updates */ - override fun getWidgetStateUpdate(): Flowable { + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { return super.getWidgetStateUpdate() } /** * Class defines the widget state updates */ - sealed class ReturnHomeWidgetState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : ReturnHomeWidgetState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * Return Home State update */ - data class ReturnHomeStateUpdated(val state: ReturnHomeState) : ReturnHomeWidgetState() + data class ReturnHomeStateUpdated(val state: ReturnHomeState) : ModelState() /** * Return to Home started successfully */ - object ReturnHomeStartedSuccess : ReturnHomeWidgetState() + object ReturnHomeStartSucceeded : ModelState() /** * Return to Home not started due to error */ - data class ReturnHomeStartedError(val error: UXSDKError) : ReturnHomeWidgetState() + data class ReturnHomeStartFailed(val error: UXSDKError) : ModelState() /** * Return to Home canceled successfully */ - object ReturnHomeCanceledSuccess : ReturnHomeWidgetState() + object ReturnHomeCancelSucceeded : ModelState() /** * Return to Home not canceled due to error */ - data class ReturnHomeCanceledError(val error: UXSDKError) : ReturnHomeWidgetState() + data class ReturnHomeCancelFailed(val error: UXSDKError) : ModelState() } //endregion diff --git a/android-uxsdk-beta-intelligentflight/src/main/java/dji/ux/beta/intelligentflight/widget/returnhome/ReturnHomeWidgetModel.kt b/android-uxsdk-beta-flight/src/main/java/dji/ux/beta/flight/widget/returnhome/ReturnHomeWidgetModel.kt similarity index 95% rename from android-uxsdk-beta-intelligentflight/src/main/java/dji/ux/beta/intelligentflight/widget/returnhome/ReturnHomeWidgetModel.kt rename to android-uxsdk-beta-flight/src/main/java/dji/ux/beta/flight/widget/returnhome/ReturnHomeWidgetModel.kt index 3bed3c15..6b3e57f1 100644 --- a/android-uxsdk-beta-intelligentflight/src/main/java/dji/ux/beta/intelligentflight/widget/returnhome/ReturnHomeWidgetModel.kt +++ b/android-uxsdk-beta-flight/src/main/java/dji/ux/beta/flight/widget/returnhome/ReturnHomeWidgetModel.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.intelligentflight.widget.returnhome +package dji.ux.beta.flight.widget.returnhome import dji.common.flightcontroller.flyzone.FlyZoneReturnToHomeState import dji.common.remotecontroller.RCMode @@ -31,12 +31,12 @@ import dji.keysdk.RemoteControllerKey import dji.thirdparty.io.reactivex.Completable import dji.thirdparty.io.reactivex.Flowable import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.GlobalPreferencesInterface import dji.ux.beta.core.base.SchedulerProviderInterface import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.GlobalPreferenceKeys -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.base.uxsdkkeys.UXKeys +import dji.ux.beta.core.communication.GlobalPreferenceKeys +import dji.ux.beta.core.communication.GlobalPreferencesInterface +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.UXKeys import dji.ux.beta.core.util.DataProcessor import dji.ux.beta.core.util.LocationUtil import dji.ux.beta.core.util.UnitConversionUtil @@ -47,7 +47,6 @@ import dji.ux.beta.core.util.UnitConversionUtil */ class ReturnHomeWidgetModel(djiSdkModel: DJISDKModel, keyedStore: ObservableInMemoryKeyedStore, - private val schedulerProvider: SchedulerProviderInterface, private val preferencesManager: GlobalPreferencesInterface? ) : WidgetModel(djiSdkModel, keyedStore) { @@ -154,7 +153,7 @@ class ReturnHomeWidgetModel(djiSdkModel: DJISDKModel, */ fun performReturnHomeAction(): Completable { val goHome: DJIKey = FlightControllerKey.create(FlightControllerKey.START_GO_HOME) - return djiSdkModel.performAction(goHome).subscribeOn(schedulerProvider.io()) + return djiSdkModel.performAction(goHome) } /** @@ -162,7 +161,7 @@ class ReturnHomeWidgetModel(djiSdkModel: DJISDKModel, */ fun performCancelReturnHomeAction(): Completable { val cancelGoHomeAction: DJIKey = FlightControllerKey.create(FlightControllerKey.CANCEL_GO_HOME) - return djiSdkModel.performAction(cancelGoHomeAction).subscribeOn(schedulerProvider.io()) + return djiSdkModel.performAction(cancelGoHomeAction) } //endregion diff --git a/android-uxsdk-beta-intelligentflight/src/main/java/dji/ux/beta/intelligentflight/widget/takeoff/TakeOffWidget.kt b/android-uxsdk-beta-flight/src/main/java/dji/ux/beta/flight/widget/takeoff/TakeOffWidget.kt similarity index 91% rename from android-uxsdk-beta-intelligentflight/src/main/java/dji/ux/beta/intelligentflight/widget/takeoff/TakeOffWidget.kt rename to android-uxsdk-beta-flight/src/main/java/dji/ux/beta/flight/widget/takeoff/TakeOffWidget.kt index 7b376f52..65cb838a 100644 --- a/android-uxsdk-beta-intelligentflight/src/main/java/dji/ux/beta/intelligentflight/widget/takeoff/TakeOffWidget.kt +++ b/android-uxsdk-beta-flight/src/main/java/dji/ux/beta/flight/widget/takeoff/TakeOffWidget.kt @@ -21,7 +21,7 @@ * */ -package dji.ux.beta.intelligentflight.widget.takeoff +package dji.ux.beta.flight.widget.takeoff import android.annotation.SuppressLint import android.content.Context @@ -37,25 +37,24 @@ import androidx.annotation.StyleRes import androidx.core.content.res.use import dji.log.DJILog import dji.thirdparty.io.reactivex.Flowable -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers +import dji.ux.beta.core.base.SchedulerProvider import dji.thirdparty.io.reactivex.disposables.Disposable import dji.thirdparty.io.reactivex.functions.Consumer import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.GlobalPreferencesManager -import dji.ux.beta.core.base.SchedulerProvider import dji.ux.beta.core.base.UXSDKError -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore import dji.ux.beta.core.base.widget.IconButtonWidget -import dji.ux.beta.core.base.widget.IconButtonWidget.WidgetUIState.* +import dji.ux.beta.core.base.widget.IconButtonWidget.UIState.* +import dji.ux.beta.core.communication.GlobalPreferencesManager +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.extension.* import dji.ux.beta.core.ui.SlidingDialog import dji.ux.beta.core.util.DisplayUtil import dji.ux.beta.core.util.UnitConversionUtil -import dji.ux.beta.intelligentflight.R -import dji.ux.beta.intelligentflight.widget.takeoff.TakeOffWidget.TakeOffWidgetDialogType.* -import dji.ux.beta.intelligentflight.widget.takeoff.TakeOffWidget.TakeOffWidgetState -import dji.ux.beta.intelligentflight.widget.takeoff.TakeOffWidget.TakeOffWidgetState.* -import dji.ux.beta.intelligentflight.widget.takeoff.TakeOffWidgetModel.TakeOffLandingState +import dji.ux.beta.flight.R +import dji.ux.beta.flight.widget.takeoff.TakeOffWidget.ModelState +import dji.ux.beta.flight.widget.takeoff.TakeOffWidget.ModelState.* +import dji.ux.beta.flight.widget.takeoff.TakeOffWidget.DialogType.* +import dji.ux.beta.flight.widget.takeoff.TakeOffWidgetModel.TakeOffLandingState import java.text.DecimalFormat private const val TAG = "TakeOffWidget" @@ -73,20 +72,29 @@ open class TakeOffWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : IconButtonWidget(context, attrs, defStyleAttr) { +) : IconButtonWidget(context, attrs, defStyleAttr) { //region Fields private var slidingDialog: SlidingDialog? = null private val decimalFormat = DecimalFormat("#.#") - private var takeOffWidgetDialogType: TakeOffWidgetDialogType? = null + private var dialogType: DialogType? = null private val widgetModel by lazy { TakeOffWidgetModel( DJISDKModel.getInstance(), ObservableInMemoryKeyedStore.getInstance(), - SchedulerProvider.getInstance(), GlobalPreferencesManager.getInstance()) } + /** + * The alpha of the image when the widget is disabled or pressed + */ + var disabledAlpha = 0.38f + + /** + * The alpha of the image when the widget is enabled + */ + var enabledAlpha = 1.0f + /** * The theme of the dialogs */ @@ -376,10 +384,10 @@ open class TakeOffWidget @JvmOverloads constructor( override fun reactToModelChanges() { addReaction(widgetModel.takeOffLandingState - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { updateTakeOffStatus(it) }) addReaction(widgetModel.productConnection - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe { widgetStateDataProcessor.onNext(ProductConnected(it)) }) } @@ -408,12 +416,12 @@ open class TakeOffWidget @JvmOverloads constructor( override fun onCancelClick(dialog: DialogInterface?) { slidingDialog?.dismiss() addDisposable(widgetModel.takeOffLandingState.firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { takeOffLandingState: TakeOffLandingState -> if (takeOffLandingState == TakeOffLandingState.WAITING_FOR_LANDING_CONFIRMATION) { performCancelLandAction() } - uiUpdateStateProcessor.onNext(DialogActionDismiss(takeOffWidgetDialogType)) + uiUpdateStateProcessor.onNext(DialogActionCancelled(dialogType)) }, logErrorConsumer(TAG, "Update takeoff Landing State"))) } @@ -432,27 +440,30 @@ open class TakeOffWidget @JvmOverloads constructor( } } slidingDialog?.dismiss() - uiUpdateStateProcessor.onNext(DialogActionConfirm(takeOffWidgetDialogType)) + uiUpdateStateProcessor.onNext(DialogActionConfirmed(dialogType)) } } override fun onCheckBoxChecked(dialog: DialogInterface?, checked: Boolean) { - uiUpdateStateProcessor.onNext(DialogCheckboxCheckChanged(takeOffWidgetDialogType)) + uiUpdateStateProcessor.onNext(DialogCheckboxCheckChanged(dialogType)) checkBoxChecked = checked updateTakeOffDialogMessage() } }) + + slidingDialog?.setOnDismissListener { uiUpdateStateProcessor.onNext(DialogDismissed(dialogType)) } + } } private fun performTakeOffAction(): Disposable { return widgetModel.performTakeOffAction() .subscribe({ - widgetStateDataProcessor.onNext(TakeOffStartedSuccess) + widgetStateDataProcessor.onNext(TakeOffStartSucceeded) }) { error: Throwable -> if (error is UXSDKError) { DJILog.e(TAG, error.toString()) - widgetStateDataProcessor.onNext(TakeOffStartedError(error)) + widgetStateDataProcessor.onNext(TakeOffStartFailed(error)) } } } @@ -460,11 +471,11 @@ open class TakeOffWidget @JvmOverloads constructor( private fun performPrecisionTakeOffAction(): Disposable { return widgetModel.performPrecisionTakeOffAction() .subscribe({ - widgetStateDataProcessor.onNext(PrecisionTakeOffStartedSuccess) + widgetStateDataProcessor.onNext(PrecisionTakeOffStartSucceeded) }) { error: Throwable -> if (error is UXSDKError) { DJILog.e(TAG, error.toString()) - widgetStateDataProcessor.onNext(PrecisionTakeOffStartedError(error)) + widgetStateDataProcessor.onNext(PrecisionTakeOffStartFailed(error)) } } } @@ -472,11 +483,11 @@ open class TakeOffWidget @JvmOverloads constructor( private fun performLandingAction(): Disposable { return widgetModel.performLandingAction() .subscribe({ - widgetStateDataProcessor.onNext(LandingStartedSuccess) + widgetStateDataProcessor.onNext(LandingStartSucceeded) }) { error: Throwable -> if (error is UXSDKError) { DJILog.e(TAG, error.toString()) - widgetStateDataProcessor.onNext(LandingStartedError(error)) + widgetStateDataProcessor.onNext(LandingStartFailed(error)) } } } @@ -484,23 +495,25 @@ open class TakeOffWidget @JvmOverloads constructor( private fun performLandingConfirmationAction(): Disposable { return widgetModel.performLandingConfirmationAction() .subscribe({ - widgetStateDataProcessor.onNext(LandingConfirmedSuccess) + widgetStateDataProcessor.onNext(LandingConfirmSucceeded) }) { error: Throwable -> if (error is UXSDKError) { DJILog.e(TAG, error.toString()) - widgetStateDataProcessor.onNext(LandingConfirmedError(error)) + widgetStateDataProcessor.onNext(LandingConfirmFailed(error)) } } } private fun performCancelLandAction(): Disposable { return widgetModel.performCancelLandingAction() + .observeOn(SchedulerProvider.ui()) .subscribe({ - widgetStateDataProcessor.onNext(LandingCanceledSuccess) + slidingDialog?.dismiss() + widgetStateDataProcessor.onNext(LandingCancelSucceeded) }) { error: Throwable -> if (error is UXSDKError) { DJILog.e(TAG, error.toString()) - widgetStateDataProcessor.onNext(LandingCanceledError(error)) + widgetStateDataProcessor.onNext(LandingCancelFailed(error)) } } } @@ -524,8 +537,8 @@ open class TakeOffWidget @JvmOverloads constructor( ?.setDialogIcon(takeOffDialogIcon) ?.setCheckBoxMessageRes(R.string.uxsdk_precision_takeoff) showDialog() - uiUpdateStateProcessor.onNext(DialogDisplayed(TakeOffDialog)) - takeOffWidgetDialogType = TakeOffDialog + uiUpdateStateProcessor.onNext(DialogDisplayed(TakeOff)) + dialogType = TakeOff updateCheckBoxVisibility() updateTakeOffDialogMessage() @@ -533,7 +546,7 @@ open class TakeOffWidget @JvmOverloads constructor( private fun updateCheckBoxVisibility() { addDisposable(widgetModel.isPrecisionTakeoffSupported - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { slidingDialog?.setCheckBoxVisibility(it) if (!it) { @@ -550,7 +563,7 @@ open class TakeOffWidget @JvmOverloads constructor( ?.setDialogMessageTextSize(dialogPrecisionMessageTextSize) } else { addDisposable(widgetModel.isInAttiMode.firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { isInAttiMode: Boolean -> val takeOffHeightString = getHeightString(widgetModel.takeOffHeight) slidingDialog?.setDialogMessage( @@ -582,8 +595,8 @@ open class TakeOffWidget @JvmOverloads constructor( ?.setCheckBoxVisibility(false) ?.setCheckBoxChecked(false) showDialog() - uiUpdateStateProcessor.onNext(DialogDisplayed(LandingDialog)) - takeOffWidgetDialogType = LandingDialog + uiUpdateStateProcessor.onNext(DialogDisplayed(Landing)) + dialogType = Landing } private fun showLandingConfirmationDialog() { @@ -593,15 +606,15 @@ open class TakeOffWidget @JvmOverloads constructor( ?.setCheckBoxVisibility(false) ?.setCheckBoxChecked(false) showDialog() - uiUpdateStateProcessor.onNext(DialogDisplayed(LandingConfirmationDialog)) - takeOffWidgetDialogType = LandingConfirmationDialog + uiUpdateStateProcessor.onNext(DialogDisplayed(LandingConfirmation)) + dialogType = LandingConfirmation updateLandingConfirmationDialogMessage() } private fun updateLandingConfirmationDialogMessage() { addDisposable(widgetModel.isInspire2OrMatrice200Series.firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { isInspire2OrMatrice200Series: Boolean -> val landHeightString = getHeightString(widgetModel.landHeight) slidingDialog?.setDialogMessage( @@ -621,8 +634,8 @@ open class TakeOffWidget @JvmOverloads constructor( ?.setCheckBoxVisibility(false) ?.setCheckBoxChecked(false) showDialog() - uiUpdateStateProcessor.onNext(DialogDisplayed(UnsafeToLandDialog)) - takeOffWidgetDialogType = UnsafeToLandDialog + uiUpdateStateProcessor.onNext(DialogDisplayed(UnsafeToLand)) + dialogType = UnsafeToLand } private fun updateTakeOffStatus(takeOffLandingState: TakeOffLandingState) { @@ -665,24 +678,24 @@ open class TakeOffWidget @JvmOverloads constructor( if (foregroundImageView.imageDrawable == cancelLandActionIcon) { foregroundImageView.isEnabled = isEnabled if (isPressed) { - foregroundImageView.alpha = DISABLE_ALPHA + foregroundImageView.alpha = disabledAlpha } else { - foregroundImageView.alpha = ENABLE_ALPHA + foregroundImageView.alpha = enabledAlpha } return } if ((isPressed || isFocused) || !isEnabled) { - foregroundImageView.alpha = DISABLE_ALPHA + foregroundImageView.alpha = disabledAlpha } else { - foregroundImageView.alpha = ENABLE_ALPHA + foregroundImageView.alpha = enabledAlpha } } private fun checkAndUpdateTakeOffLandingState() { if (!isInEditMode) { addDisposable(widgetModel.takeOffLandingState.firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { this.updateTakeOffStatus(it) }, logErrorConsumer(TAG, "Update Take Off Landing State "))) } @@ -695,6 +708,12 @@ open class TakeOffWidget @JvmOverloads constructor( @SuppressLint("Recycle") private fun initAttributes(context: Context, attrs: AttributeSet) { context.obtainStyledAttributes(attrs, R.styleable.TakeOffWidget).use { typedArray -> + typedArray.getFloatAndUse(R.styleable.TakeOffWidget_uxsdk_disabledAlpha) { + disabledAlpha = it + } + typedArray.getFloatAndUse(R.styleable.TakeOffWidget_uxsdk_enabledAlpha) { + enabledAlpha = it + } typedArray.getResourceIdAndUse(R.styleable.TakeOffWidget_uxsdk_dialogTheme) { dialogTheme = it } @@ -911,107 +930,108 @@ open class TakeOffWidget @JvmOverloads constructor( } //endregion - //region hooks + //region Hooks /** - * Get the [TakeOffWidgetState] updates - */ - override fun getWidgetStateUpdate(): Flowable { + * Get the [ModelState] updates + */ + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { return super.getWidgetStateUpdate() } /** * Class defines the widget state updates */ - sealed class TakeOffWidgetState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : TakeOffWidgetState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * Takeoff Landing State update */ - data class TakeOffLandingStateUpdated(val state: TakeOffLandingState) : TakeOffWidgetState() + data class TakeOffLandingStateUpdated(val state: TakeOffLandingState) : ModelState() /** * Takeoff started successfully */ - object TakeOffStartedSuccess : TakeOffWidgetState() + object TakeOffStartSucceeded : ModelState() /** * Takeoff not started due to error */ - data class TakeOffStartedError(val error: UXSDKError) : TakeOffWidgetState() + data class TakeOffStartFailed(val error: UXSDKError) : ModelState() /** * Precision Takeoff started successfully */ - object PrecisionTakeOffStartedSuccess : TakeOffWidgetState() + object PrecisionTakeOffStartSucceeded : ModelState() /** * Precision Takeoff not started due to error */ - data class PrecisionTakeOffStartedError(val error: UXSDKError) : TakeOffWidgetState() + data class PrecisionTakeOffStartFailed(val error: UXSDKError) : ModelState() /** * Landing started successfully */ - object LandingStartedSuccess : TakeOffWidgetState() + object LandingStartSucceeded : ModelState() /** * Landing not started due to error */ - data class LandingStartedError(val error: UXSDKError) : TakeOffWidgetState() + data class LandingStartFailed(val error: UXSDKError) : ModelState() /** * Landing confirmed successfully */ - object LandingConfirmedSuccess : TakeOffWidgetState() + object LandingConfirmSucceeded : ModelState() /** * Landing not confirmed due to error */ - data class LandingConfirmedError(val error: UXSDKError) : TakeOffWidgetState() + data class LandingConfirmFailed(val error: UXSDKError) : ModelState() /** * Landing canceled successfully */ - object LandingCanceledSuccess : TakeOffWidgetState() + object LandingCancelSucceeded : ModelState() /** * Landing not canceled due to error */ - data class LandingCanceledError(val error: UXSDKError) : TakeOffWidgetState() + data class LandingCancelFailed(val error: UXSDKError) : ModelState() } /** * The type of dialog shown */ - sealed class TakeOffWidgetDialogType { + sealed class DialogType { /** * The takeoff dialog, which is shown when the widget is clicked and the aircraft is ready * to take off. */ - object TakeOffDialog : TakeOffWidgetDialogType() + object TakeOff : DialogType() /** * The landing dialog, which is shown when the widget is clicked and the aircraft is ready * to land. */ - object LandingDialog : TakeOffWidgetDialogType() + object Landing : DialogType() /** * The landing confirmation dialog, which is shown when the aircraft has paused * auto-landing and is waiting for confirmation before continuing. */ - object LandingConfirmationDialog : TakeOffWidgetDialogType() + object LandingConfirmation : DialogType() /** * The unsafe to land dialog, which is shown when the aircraft is auto-landing and has * determined it is unsafe to land. */ - object UnsafeToLandDialog : TakeOffWidgetDialogType() + object UnsafeToLand : DialogType() } //endregion diff --git a/android-uxsdk-beta-intelligentflight/src/main/java/dji/ux/beta/intelligentflight/widget/takeoff/TakeOffWidgetModel.kt b/android-uxsdk-beta-flight/src/main/java/dji/ux/beta/flight/widget/takeoff/TakeOffWidgetModel.kt similarity index 94% rename from android-uxsdk-beta-intelligentflight/src/main/java/dji/ux/beta/intelligentflight/widget/takeoff/TakeOffWidgetModel.kt rename to android-uxsdk-beta-flight/src/main/java/dji/ux/beta/flight/widget/takeoff/TakeOffWidgetModel.kt index 59a50e96..57c100c1 100644 --- a/android-uxsdk-beta-intelligentflight/src/main/java/dji/ux/beta/intelligentflight/widget/takeoff/TakeOffWidgetModel.kt +++ b/android-uxsdk-beta-flight/src/main/java/dji/ux/beta/flight/widget/takeoff/TakeOffWidgetModel.kt @@ -18,10 +18,10 @@ * 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.ux.beta.intelligentflight.widget.takeoff +package dji.ux.beta.flight.widget.takeoff import dji.common.flightcontroller.VisionLandingProtectionState import dji.common.product.Model @@ -34,12 +34,11 @@ import dji.thirdparty.io.reactivex.Completable import dji.thirdparty.io.reactivex.Flowable import dji.thirdparty.io.reactivex.Single import dji.ux.beta.core.base.DJISDKModel -import dji.ux.beta.core.base.GlobalPreferencesInterface -import dji.ux.beta.core.base.SchedulerProviderInterface import dji.ux.beta.core.base.WidgetModel -import dji.ux.beta.core.base.uxsdkkeys.GlobalPreferenceKeys -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore -import dji.ux.beta.core.base.uxsdkkeys.UXKeys +import dji.ux.beta.core.communication.GlobalPreferenceKeys +import dji.ux.beta.core.communication.GlobalPreferencesInterface +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.UXKeys import dji.ux.beta.core.util.DataProcessor import dji.ux.beta.core.util.ProductUtil import dji.ux.beta.core.util.UnitConversionUtil @@ -54,7 +53,6 @@ private const val LAND_HEIGHT: Float = 0.3f */ class TakeOffWidgetModel(djiSdkModel: DJISDKModel, keyedStore: ObservableInMemoryKeyedStore, - private val schedulerProvider: SchedulerProviderInterface, private val preferencesManager: GlobalPreferencesInterface? ) : WidgetModel(djiSdkModel, keyedStore) { @@ -90,7 +88,7 @@ class TakeOffWidgetModel(djiSdkModel: DJISDKModel, */ val isPrecisionTakeoffSupported: Single get() = djiSdkModel.getValue(FlightControllerKey.create(FlightControllerKey.IS_PRECISION_TAKE_OFF_SUPPORTED)) - .map { it as Boolean } + .map { it as Boolean && djiSdkModel.isKeySupported(FlightControllerKey.create(FlightControllerKey.PRECISION_TAKE_OFF)) } /** * Get whether the product is in ATTI mode @@ -147,7 +145,6 @@ class TakeOffWidgetModel(djiSdkModel: DJISDKModel, return@onErrorResumeNext Completable.error(error) } } - .subscribeOn(schedulerProvider.io()) } /** @@ -163,7 +160,6 @@ class TakeOffWidgetModel(djiSdkModel: DJISDKModel, return@onErrorResumeNext Completable.error(error) } } - .subscribeOn(schedulerProvider.io()) } /** @@ -171,7 +167,7 @@ class TakeOffWidgetModel(djiSdkModel: DJISDKModel, */ fun performLandingAction(): Completable { val landAction: DJIKey = FlightControllerKey.create(FlightControllerKey.START_LANDING) - return djiSdkModel.performAction(landAction).subscribeOn(schedulerProvider.io()) + return djiSdkModel.performAction(landAction) } /** @@ -179,7 +175,7 @@ class TakeOffWidgetModel(djiSdkModel: DJISDKModel, */ fun performCancelLandingAction(): Completable { val cancelLanding: DJIKey = FlightControllerKey.create(FlightControllerKey.CANCEL_LANDING) - return djiSdkModel.performAction(cancelLanding).subscribeOn(schedulerProvider.io()) + return djiSdkModel.performAction(cancelLanding) } /** @@ -188,7 +184,7 @@ class TakeOffWidgetModel(djiSdkModel: DJISDKModel, */ fun performLandingConfirmationAction(): Completable { val forceAction: DJIKey = FlightControllerKey.create(FlightControllerKey.CONFIRM_LANDING) - return djiSdkModel.performAction(forceAction).subscribeOn(schedulerProvider.io()) + return djiSdkModel.performAction(forceAction) } //endregion diff --git a/android-uxsdk-beta-intelligentflight/src/main/res/drawable-hdpi/uxsdk_ic_land.png b/android-uxsdk-beta-flight/src/main/res/drawable-hdpi/uxsdk_ic_land.png similarity index 100% rename from android-uxsdk-beta-intelligentflight/src/main/res/drawable-hdpi/uxsdk_ic_land.png rename to android-uxsdk-beta-flight/src/main/res/drawable-hdpi/uxsdk_ic_land.png diff --git a/android-uxsdk-beta-intelligentflight/src/main/res/drawable-hdpi/uxsdk_ic_return_home.png b/android-uxsdk-beta-flight/src/main/res/drawable-hdpi/uxsdk_ic_return_home.png similarity index 100% rename from android-uxsdk-beta-intelligentflight/src/main/res/drawable-hdpi/uxsdk_ic_return_home.png rename to android-uxsdk-beta-flight/src/main/res/drawable-hdpi/uxsdk_ic_return_home.png diff --git a/android-uxsdk-beta-intelligentflight/src/main/res/drawable-hdpi/uxsdk_ic_takeoff.png b/android-uxsdk-beta-flight/src/main/res/drawable-hdpi/uxsdk_ic_takeoff.png similarity index 100% rename from android-uxsdk-beta-intelligentflight/src/main/res/drawable-hdpi/uxsdk_ic_takeoff.png rename to android-uxsdk-beta-flight/src/main/res/drawable-hdpi/uxsdk_ic_takeoff.png diff --git a/android-uxsdk-beta-intelligentflight/src/main/res/drawable-mdpi/uxsdk_ic_land.png b/android-uxsdk-beta-flight/src/main/res/drawable-mdpi/uxsdk_ic_land.png similarity index 100% rename from android-uxsdk-beta-intelligentflight/src/main/res/drawable-mdpi/uxsdk_ic_land.png rename to android-uxsdk-beta-flight/src/main/res/drawable-mdpi/uxsdk_ic_land.png diff --git a/android-uxsdk-beta-intelligentflight/src/main/res/drawable-mdpi/uxsdk_ic_return_home.png b/android-uxsdk-beta-flight/src/main/res/drawable-mdpi/uxsdk_ic_return_home.png similarity index 100% rename from android-uxsdk-beta-intelligentflight/src/main/res/drawable-mdpi/uxsdk_ic_return_home.png rename to android-uxsdk-beta-flight/src/main/res/drawable-mdpi/uxsdk_ic_return_home.png diff --git a/android-uxsdk-beta-intelligentflight/src/main/res/drawable-mdpi/uxsdk_ic_takeoff.png b/android-uxsdk-beta-flight/src/main/res/drawable-mdpi/uxsdk_ic_takeoff.png similarity index 100% rename from android-uxsdk-beta-intelligentflight/src/main/res/drawable-mdpi/uxsdk_ic_takeoff.png rename to android-uxsdk-beta-flight/src/main/res/drawable-mdpi/uxsdk_ic_takeoff.png diff --git a/android-uxsdk-beta-intelligentflight/src/main/res/drawable-xhdpi/uxsdk_ic_land.png b/android-uxsdk-beta-flight/src/main/res/drawable-xhdpi/uxsdk_ic_land.png similarity index 100% rename from android-uxsdk-beta-intelligentflight/src/main/res/drawable-xhdpi/uxsdk_ic_land.png rename to android-uxsdk-beta-flight/src/main/res/drawable-xhdpi/uxsdk_ic_land.png diff --git a/android-uxsdk-beta-intelligentflight/src/main/res/drawable-xhdpi/uxsdk_ic_return_home.png b/android-uxsdk-beta-flight/src/main/res/drawable-xhdpi/uxsdk_ic_return_home.png similarity index 100% rename from android-uxsdk-beta-intelligentflight/src/main/res/drawable-xhdpi/uxsdk_ic_return_home.png rename to android-uxsdk-beta-flight/src/main/res/drawable-xhdpi/uxsdk_ic_return_home.png diff --git a/android-uxsdk-beta-intelligentflight/src/main/res/drawable-xhdpi/uxsdk_ic_takeoff.png b/android-uxsdk-beta-flight/src/main/res/drawable-xhdpi/uxsdk_ic_takeoff.png similarity index 100% rename from android-uxsdk-beta-intelligentflight/src/main/res/drawable-xhdpi/uxsdk_ic_takeoff.png rename to android-uxsdk-beta-flight/src/main/res/drawable-xhdpi/uxsdk_ic_takeoff.png diff --git a/android-uxsdk-beta-intelligentflight/src/main/res/drawable-xxhdpi/uxsdk_ic_land.png b/android-uxsdk-beta-flight/src/main/res/drawable-xxhdpi/uxsdk_ic_land.png similarity index 100% rename from android-uxsdk-beta-intelligentflight/src/main/res/drawable-xxhdpi/uxsdk_ic_land.png rename to android-uxsdk-beta-flight/src/main/res/drawable-xxhdpi/uxsdk_ic_land.png diff --git a/android-uxsdk-beta-intelligentflight/src/main/res/drawable-xxhdpi/uxsdk_ic_return_home.png b/android-uxsdk-beta-flight/src/main/res/drawable-xxhdpi/uxsdk_ic_return_home.png similarity index 100% rename from android-uxsdk-beta-intelligentflight/src/main/res/drawable-xxhdpi/uxsdk_ic_return_home.png rename to android-uxsdk-beta-flight/src/main/res/drawable-xxhdpi/uxsdk_ic_return_home.png diff --git a/android-uxsdk-beta-intelligentflight/src/main/res/drawable-xxhdpi/uxsdk_ic_takeoff.png b/android-uxsdk-beta-flight/src/main/res/drawable-xxhdpi/uxsdk_ic_takeoff.png similarity index 100% rename from android-uxsdk-beta-intelligentflight/src/main/res/drawable-xxhdpi/uxsdk_ic_takeoff.png rename to android-uxsdk-beta-flight/src/main/res/drawable-xxhdpi/uxsdk_ic_takeoff.png diff --git a/android-uxsdk-beta-intelligentflight/src/main/res/drawable-xxxhdpi/uxsdk_ic_land.png b/android-uxsdk-beta-flight/src/main/res/drawable-xxxhdpi/uxsdk_ic_land.png similarity index 100% rename from android-uxsdk-beta-intelligentflight/src/main/res/drawable-xxxhdpi/uxsdk_ic_land.png rename to android-uxsdk-beta-flight/src/main/res/drawable-xxxhdpi/uxsdk_ic_land.png diff --git a/android-uxsdk-beta-intelligentflight/src/main/res/drawable-xxxhdpi/uxsdk_ic_return_home.png b/android-uxsdk-beta-flight/src/main/res/drawable-xxxhdpi/uxsdk_ic_return_home.png similarity index 100% rename from android-uxsdk-beta-intelligentflight/src/main/res/drawable-xxxhdpi/uxsdk_ic_return_home.png rename to android-uxsdk-beta-flight/src/main/res/drawable-xxxhdpi/uxsdk_ic_return_home.png diff --git a/android-uxsdk-beta-intelligentflight/src/main/res/drawable-xxxhdpi/uxsdk_ic_takeoff.png b/android-uxsdk-beta-flight/src/main/res/drawable-xxxhdpi/uxsdk_ic_takeoff.png similarity index 100% rename from android-uxsdk-beta-intelligentflight/src/main/res/drawable-xxxhdpi/uxsdk_ic_takeoff.png rename to android-uxsdk-beta-flight/src/main/res/drawable-xxxhdpi/uxsdk_ic_takeoff.png diff --git a/android-uxsdk-beta-intelligentflight/src/main/res/drawable/uxsdk_cancel_landing_selector.xml b/android-uxsdk-beta-flight/src/main/res/drawable/uxsdk_cancel_landing_selector.xml similarity index 100% rename from android-uxsdk-beta-intelligentflight/src/main/res/drawable/uxsdk_cancel_landing_selector.xml rename to android-uxsdk-beta-flight/src/main/res/drawable/uxsdk_cancel_landing_selector.xml diff --git a/android-uxsdk-beta-intelligentflight/src/main/res/drawable/uxsdk_ic_cancel_landing.xml b/android-uxsdk-beta-flight/src/main/res/drawable/uxsdk_ic_cancel_landing.xml similarity index 100% rename from android-uxsdk-beta-intelligentflight/src/main/res/drawable/uxsdk_ic_cancel_landing.xml rename to android-uxsdk-beta-flight/src/main/res/drawable/uxsdk_ic_cancel_landing.xml diff --git a/android-uxsdk-beta-intelligentflight/src/main/res/drawable/uxsdk_ic_cancel_landing_disabled.xml b/android-uxsdk-beta-flight/src/main/res/drawable/uxsdk_ic_cancel_landing_disabled.xml similarity index 100% rename from android-uxsdk-beta-intelligentflight/src/main/res/drawable/uxsdk_ic_cancel_landing_disabled.xml rename to android-uxsdk-beta-flight/src/main/res/drawable/uxsdk_ic_cancel_landing_disabled.xml diff --git a/android-uxsdk-beta-intelligentflight/src/main/res/drawable/uxsdk_ic_land_yellow.xml b/android-uxsdk-beta-flight/src/main/res/drawable/uxsdk_ic_land_yellow.xml similarity index 100% rename from android-uxsdk-beta-intelligentflight/src/main/res/drawable/uxsdk_ic_land_yellow.xml rename to android-uxsdk-beta-flight/src/main/res/drawable/uxsdk_ic_land_yellow.xml diff --git a/android-uxsdk-beta-intelligentflight/src/main/res/drawable/uxsdk_ic_return_home_yellow.xml b/android-uxsdk-beta-flight/src/main/res/drawable/uxsdk_ic_return_home_yellow.xml similarity index 100% rename from android-uxsdk-beta-intelligentflight/src/main/res/drawable/uxsdk_ic_return_home_yellow.xml rename to android-uxsdk-beta-flight/src/main/res/drawable/uxsdk_ic_return_home_yellow.xml diff --git a/android-uxsdk-beta-intelligentflight/src/main/res/values/attrs.xml b/android-uxsdk-beta-flight/src/main/res/values/attrs.xml similarity index 96% rename from android-uxsdk-beta-intelligentflight/src/main/res/values/attrs.xml rename to android-uxsdk-beta-flight/src/main/res/values/attrs.xml index a4271fc9..d7bc2da3 100644 --- a/android-uxsdk-beta-intelligentflight/src/main/res/values/attrs.xml +++ b/android-uxsdk-beta-flight/src/main/res/values/attrs.xml @@ -61,6 +61,8 @@ + + @@ -89,6 +91,8 @@ + + \ No newline at end of file diff --git a/android-uxsdk-beta-intelligentflight/src/main/res/values/strings.xml b/android-uxsdk-beta-flight/src/main/res/values/strings.xml similarity index 99% rename from android-uxsdk-beta-intelligentflight/src/main/res/values/strings.xml rename to android-uxsdk-beta-flight/src/main/res/values/strings.xml index d9b0575e..4217e7eb 100644 --- a/android-uxsdk-beta-intelligentflight/src/main/res/values/strings.xml +++ b/android-uxsdk-beta-flight/src/main/res/values/strings.xml @@ -23,7 +23,6 @@ - Take Off? diff --git a/android-uxsdk-beta-intelligentflight/src/main/res/values/styles.xml b/android-uxsdk-beta-flight/src/main/res/values/styles.xml similarity index 100% rename from android-uxsdk-beta-intelligentflight/src/main/res/values/styles.xml rename to android-uxsdk-beta-flight/src/main/res/values/styles.xml diff --git a/android-uxsdk-beta-map/build.gradle b/android-uxsdk-beta-map/build.gradle index 39e9bdd4..296adc3c 100644 --- a/android-uxsdk-beta-map/build.gradle +++ b/android-uxsdk-beta-map/build.gradle @@ -68,7 +68,7 @@ android { dependencies { api project(path: ':android-uxsdk-beta-core') - implementation ('com.dji:dji-sdk:4.13.1', { + implementation ('com.dji:dji-sdk:4.14-trial1', { /** * Comment the "library-anti-distortion" if your app needs Anti Distortion for Mavic 2 Pro * and Mavic 2 Zoom. @@ -80,7 +80,7 @@ dependencies { exclude module: 'library-anti-distortion' exclude module: 'fly-safe-database' }) - compileOnly ('com.dji:dji-sdk-provided:4.13.1') + compileOnly ('com.dji:dji-sdk-provided:4.14-trial1') implementation 'androidx.multidex:multidex:2.0.0' implementation 'androidx.appcompat:appcompat:1.0.0' diff --git a/android-uxsdk-beta-map/src/main/java/dji/ux/beta/map/widget/map/FlyZoneHelper.java b/android-uxsdk-beta-map/src/main/java/dji/ux/beta/map/widget/map/FlyZoneHelper.java index 1e0df1b7..d0aa4787 100644 --- a/android-uxsdk-beta-map/src/main/java/dji/ux/beta/map/widget/map/FlyZoneHelper.java +++ b/android-uxsdk-beta-map/src/main/java/dji/ux/beta/map/widget/map/FlyZoneHelper.java @@ -76,7 +76,7 @@ public class FlyZoneHelper { protected static final float DEFAULT_BORDER_WIDTH = 5; - //region fields + //region Fields private static final int DEFAULT_ALPHA = 26; private static final float DEFAULT_ANCHOR = 0.5f; private static final String TAG = "FlyZoneHelper"; diff --git a/android-uxsdk-beta-map/src/main/java/dji/ux/beta/map/widget/map/MapWidget.java b/android-uxsdk-beta-map/src/main/java/dji/ux/beta/map/widget/map/MapWidget.java index ea15a53c..8684b80d 100644 --- a/android-uxsdk-beta-map/src/main/java/dji/ux/beta/map/widget/map/MapWidget.java +++ b/android-uxsdk-beta-map/src/main/java/dji/ux/beta/map/widget/map/MapWidget.java @@ -74,19 +74,23 @@ import dji.sdk.util.LocationUtil; import dji.thirdparty.io.reactivex.Flowable; import dji.thirdparty.io.reactivex.Single; -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers; import dji.thirdparty.io.reactivex.disposables.Disposable; -import dji.ux.beta.core.base.ConstraintLayoutWidget; import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.OnStateChangeCallback; import dji.ux.beta.core.base.SchedulerProvider; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.communication.OnStateChangeCallback; import dji.ux.beta.core.util.MathUtil; import dji.ux.beta.core.util.SettingDefinitions; import dji.ux.beta.core.util.ViewUtil; import dji.ux.beta.core.widget.useraccount.UserAccountLoginWidget; import dji.ux.beta.map.R; +/** + * MapWidget displays the aircraft's state and information on the map. This + * includes aircraft location, home location, aircraft trail path, aircraft + * heading, and No Fly Zones. It also provides the user with options to unlock some Fly Zones. + */ public class MapWidget extends ConstraintLayoutWidget implements View.OnTouchListener, OnStateChangeCallback, FlyZoneActionListener { //region Constants @@ -119,7 +123,6 @@ public class MapWidget extends ConstraintLayoutWidget implements View.OnTouchLis private boolean isAutoFrameMapBounds = false; private DJIMap.MapType mapType; private UserAccountLoginWidget userAccountLoginWidget; - private SchedulerProvider schedulerProvider; private DJIMap.OnMarkerClickListener onMarkerClickListener; //endregion @@ -171,7 +174,7 @@ public class MapWidget extends ConstraintLayoutWidget implements View.OnTouchLis //endregion - //region lifecycle + //region Lifecycle public MapWidget(@NonNull Context context) { super(context); } @@ -190,12 +193,10 @@ protected void initView(@NonNull Context context, @Nullable AttributeSet attrs, legendGroup = findViewById(R.id.constraint_group_legend); userAccountLoginWidget = findViewById(R.id.widget_login); userAccountLoginWidget.setOnStateChangeCallback(this); - schedulerProvider = SchedulerProvider.getInstance(); if (!isInEditMode()) { widgetModel = new MapWidgetModel(DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - schedulerProvider); + ObservableInMemoryKeyedStore.getInstance()); flyZoneHelper = new FlyZoneHelper(context, this, this); } @@ -207,14 +208,14 @@ protected void initView(@NonNull Context context, @Nullable AttributeSet attrs, @Override protected void reactToModelChanges() { - addReaction(widgetModel.getProductConnection().observeOn(schedulerProvider.ui()).subscribe(connected -> { + addReaction(widgetModel.getProductConnection().observeOn(SchedulerProvider.ui()).subscribe(connected -> { if (connected) { addReaction(reactToHeadingChanges()); addReaction(widgetModel.getHomeLocation() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(MapWidget.this::updateHomeLocation)); addReaction(widgetModel.getAircraftLocation() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(MapWidget.this::updateAircraftLocation)); } })); @@ -328,7 +329,7 @@ public void onStateChange(@Nullable Object state) { @Override public void requestSelfUnlock(@NonNull ArrayList arrayList) { addDisposable(widgetModel.unlockFlyZone(arrayList) - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::getFlyZoneList, error -> { Toast.makeText(getContext(), getResources().getString(R.string.uxsdk_fly_zone_unlock_failed, error.getMessage()), @@ -345,7 +346,7 @@ public void requestFlyZoneList() { @Override public void requestEnableFlyZone(@NonNull CustomUnlockZone customUnlockZone) { addDisposable(widgetModel.enableCustomUnlockZoneOnAircraft(customUnlockZone) - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::requestCustomUnlockZonesFromServer, logErrorConsumer(TAG, "request enable fly zone "))); } @@ -353,7 +354,7 @@ public void requestEnableFlyZone(@NonNull CustomUnlockZone customUnlockZone) { @Override public void requestDisableFlyZone() { addDisposable(widgetModel.disableCustomUnlockZoneOnAircraft() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::requestCustomUnlockZonesFromServer, logErrorConsumer(TAG, "request disable fly zone "))); } @@ -542,7 +543,7 @@ private void setMapType(DJIMap.MapType mapType) { private Disposable reactToHeadingChanges() { return Flowable.combineLatest(widgetModel.getAircraftHeading(), widgetModel.getGimbalHeading(), Pair::create) - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(values -> { updateAircraftHeading(values.first); setGimbalHeading(values.first, values.second); @@ -643,7 +644,7 @@ private void initAircraftOnMap(DJILatLng aircraftPosition) { private void getFlyZoneList() { addDisposable(widgetModel.getFlyZoneList() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::onFlyZoneListUpdate, logErrorConsumer(TAG, "get fly zone list "))); } @@ -1023,12 +1024,22 @@ public void initMapboxMap(@NonNull String mapboxAccessToken, @Nullable final OnM /** * Perform common initialization steps after the specific provider's map has finished initializing */ + @SuppressWarnings("SameReturnValue") private void postInit(OnMapReadyListener listener) { updateHomeCountry(); - if (listener != null) { - listener.onMapReady(map); - } - map.setMapType(mapType); + map.setMapType(mapType, () -> { + Single aircraftLocation = widgetModel.getAircraftLocation().firstOrError(); + Single homeLocation = widgetModel.getHomeLocation().firstOrError(); + addDisposable(Single.zip(aircraftLocation, homeLocation, Pair::new) + .observeOn(SchedulerProvider.ui()) + .subscribe(pair -> { + updateAircraftLocation(pair.first); + updateHomeLocation(pair.second); + if (listener != null) { + listener.onMapReady(map); + } + }, logErrorConsumer(TAG, "updateAircraftAndHomeLocation"))); + }); map.setOnMarkerClickListener(marker -> { String title = marker.getTitle(); if (title != null && title.length() > 0 @@ -1045,11 +1056,11 @@ private void postInit(OnMapReadyListener listener) { }); addDisposable(widgetModel.getAircraftLocation() .firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::updateAircraftLocation, logErrorConsumer(TAG, "updateAircraftLocation"))); addDisposable(widgetModel.getHomeLocation() .firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::updateHomeLocation, logErrorConsumer(TAG, "updateHomeLocation"))); } @@ -1496,7 +1507,7 @@ public void requestCustomUnlockZonesFromServer() { if (flyZoneHelper.isUserAuthorized()) { addDisposable(Single.zip(widgetModel.getCustomUnlockZonesFromServer(), widgetModel.getCustomUnlockZonesFromAircraft(), Pair::new) - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(result -> flyZoneHelper.onCustomUnlockZoneUpdate(result.second, result.first), logErrorConsumer(TAG, "get custom unlock zones "))); } @@ -1508,7 +1519,7 @@ public void requestCustomUnlockZonesFromServer() { public void syncCustomUnlockZonesToAircraft() { if (flyZoneHelper.isUserAuthorized()) { addDisposable(widgetModel.syncZonesToAircraft() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::requestCustomUnlockZonesFromServer, logErrorConsumer(TAG, "sync custom unlock zones "))); } @@ -1580,9 +1591,18 @@ public enum MapCenterLock { this.index = index; } + private static MapCenterLock[] values; + + public static MapCenterLock[] getValues() { + if (values == null) { + values = values(); + } + return values; + } + @NonNull public static MapCenterLock find(@IntRange(from = -1, to = 2) int index) { - for (MapCenterLock mapCenterLock : MapCenterLock.values()) { + for (MapCenterLock mapCenterLock : MapCenterLock.getValues()) { if (mapCenterLock.getIndex() == index) { return mapCenterLock; } diff --git a/android-uxsdk-beta-map/src/main/java/dji/ux/beta/map/widget/map/MapWidgetModel.java b/android-uxsdk-beta-map/src/main/java/dji/ux/beta/map/widget/map/MapWidgetModel.java index d15c604a..024f9cc6 100644 --- a/android-uxsdk-beta-map/src/main/java/dji/ux/beta/map/widget/map/MapWidgetModel.java +++ b/android-uxsdk-beta-map/src/main/java/dji/ux/beta/map/widget/map/MapWidgetModel.java @@ -52,11 +52,11 @@ import dji.thirdparty.io.reactivex.Single; import dji.thirdparty.io.reactivex.SingleOnSubscribe; import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.SchedulerProviderInterface; +import dji.ux.beta.core.base.SchedulerProvider; import dji.ux.beta.core.base.UXSDKError; import dji.ux.beta.core.base.UXSDKErrorDescription; import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; import dji.ux.beta.core.util.DataProcessor; /** @@ -68,7 +68,7 @@ public class MapWidgetModel extends WidgetModel { public static final double INVALID_COORDINATE = 181; //valid longitude range is -180 to 180. - //region fields + //region Fields private static final String TAG = "MapWidgetModel"; private static final int FIRST_TIME_DELAY = 3; private final DataProcessor aircraftLocationDataProcessor; @@ -81,14 +81,12 @@ public class MapWidgetModel extends WidgetModel { private List flyZoneList; private List customFlyZoneList; private Map customUnlockZoneMap; - private SchedulerProviderInterface schedulerProvider; private boolean isFirstFlyZoneListRequest = true; //endregion //region life-cycle public MapWidgetModel(@NonNull DJISDKModel djiSdkModel, - @NonNull ObservableInMemoryKeyedStore keyedStore, - @NonNull SchedulerProviderInterface schedulerProvider) { + @NonNull ObservableInMemoryKeyedStore keyedStore) { super(djiSdkModel, keyedStore); aircraftLocationDataProcessor = DataProcessor.create(new LocationCoordinate3D(INVALID_COORDINATE, INVALID_COORDINATE, -1f)); @@ -102,7 +100,6 @@ public MapWidgetModel(@NonNull DJISDKModel djiSdkModel, flyZoneList = new ArrayList<>(); customUnlockZoneMap = new HashMap<>(); customFlyZoneList = new ArrayList<>(); - this.schedulerProvider = schedulerProvider; } @Override @@ -202,10 +199,10 @@ public Single> getFlyZoneList() { }); if (isFirstFlyZoneListRequest) { isFirstFlyZoneListRequest = false; - return Single.timer(FIRST_TIME_DELAY, TimeUnit.SECONDS, schedulerProvider.computation()) + return Single.timer(FIRST_TIME_DELAY, TimeUnit.SECONDS, SchedulerProvider.computation()) .flatMap(aLong -> flyZoneListSingle); } else { - return flyZoneListSingle.subscribeOn(schedulerProvider.computation()); + return flyZoneListSingle.subscribeOn(SchedulerProvider.computation()); } } @@ -249,7 +246,7 @@ public void onFailure(DJIError error) { } } }); - }).subscribeOn(schedulerProvider.computation()); + }).subscribeOn(SchedulerProvider.computation()); } /** @@ -295,7 +292,7 @@ public void onFailure(DJIError error) { } } }); - }).subscribeOn(schedulerProvider.computation()); + }).subscribeOn(SchedulerProvider.computation()); } /** @@ -331,7 +328,7 @@ public Completable unlockFlyZone(final ArrayList flyZoneIdList) { } } }); - }).subscribeOn(schedulerProvider.computation()); + }).subscribeOn(SchedulerProvider.computation()); } /** @@ -427,7 +424,7 @@ public void onFailure(DJIError error) { } } }); - }).subscribeOn(schedulerProvider.computation()); + }).subscribeOn(SchedulerProvider.computation()); } private Single> getSelfUnlockedFlyZones() { @@ -457,7 +454,7 @@ public void onFailure(DJIError error) { } } }); - }).subscribeOn(schedulerProvider.computation()); + }).subscribeOn(SchedulerProvider.computation()); } private FlyZoneManager getFlyZoneManager() { diff --git a/android-uxsdk-beta-map/src/main/res/layout/uxsdk_widget_map.xml b/android-uxsdk-beta-map/src/main/res/layout/uxsdk_widget_map.xml index 2015892e..fe89db8e 100644 --- a/android-uxsdk-beta-map/src/main/res/layout/uxsdk_widget_map.xml +++ b/android-uxsdk-beta-map/src/main/res/layout/uxsdk_widget_map.xml @@ -56,7 +56,7 @@ android:layout_height="25dp" android:layout_margin="@dimen/uxsdk_map_widget_margin" android:gravity="center_vertical" - android:paddingLeft="@dimen/uxsdk_map_widget_margin" + android:paddingStart="@dimen/uxsdk_map_widget_margin" android:text="@string/uxsdk_fly_zone_warning" app:layout_constraintEnd_toStartOf="@+id/imageview_legend_self_unlock_zone" app:layout_constraintStart_toEndOf="@+id/imageview_legend_warning_zone" @@ -79,7 +79,7 @@ android:layout_height="25dp" android:layout_margin="@dimen/uxsdk_map_widget_margin" android:gravity="center_vertical" - android:paddingLeft="@dimen/uxsdk_map_widget_margin" + android:paddingStart="@dimen/uxsdk_map_widget_margin" android:text="@string/uxsdk_fly_zone_self_unlock" app:layout_constraintEnd_toEndOf="@+id/view_legend_background" app:layout_constraintStart_toEndOf="@+id/imageview_legend_self_unlock_zone" @@ -102,7 +102,7 @@ android:layout_height="25dp" android:layout_margin="@dimen/uxsdk_map_widget_margin" android:gravity="center_vertical" - android:paddingLeft="@dimen/uxsdk_map_widget_margin" + android:paddingStart="@dimen/uxsdk_map_widget_margin" android:text="@string/uxsdk_fly_zone_enhanced_warning" app:layout_constraintEnd_toStartOf="@+id/imageview_legend_self_unlock_zone" app:layout_constraintStart_toEndOf="@+id/imageview_legend_enhanced_warning_zone" @@ -126,7 +126,7 @@ android:layout_height="25dp" android:layout_margin="@dimen/uxsdk_map_widget_margin" android:gravity="center_vertical" - android:paddingLeft="@dimen/uxsdk_map_widget_margin" + android:paddingStart="@dimen/uxsdk_map_widget_margin" android:text="@string/uxsdk_fly_zone_custom_unlock" app:layout_constraintEnd_toEndOf="@+id/view_legend_background" app:layout_constraintStart_toEndOf="@+id/imageview_legend_custom_unlock_zone" @@ -149,7 +149,7 @@ android:layout_height="25dp" android:layout_margin="@dimen/uxsdk_map_widget_margin" android:gravity="center_vertical" - android:paddingLeft="@dimen/uxsdk_map_widget_margin" + android:paddingStart="@dimen/uxsdk_map_widget_margin" android:text="@string/uxsdk_fly_zone_authorized" app:layout_constraintEnd_toStartOf="@+id/imageview_legend_self_unlock_zone" app:layout_constraintStart_toEndOf="@+id/imageview_legend_authorized_zone" @@ -172,7 +172,7 @@ android:layout_height="35dp" android:layout_margin="@dimen/uxsdk_map_widget_margin" android:gravity="center_vertical" - android:paddingLeft="@dimen/uxsdk_map_widget_margin" + android:paddingStart="@dimen/uxsdk_map_widget_margin" android:text="@string/uxsdk_fly_zone_custom_unlock_aircraft" app:layout_constraintEnd_toEndOf="@+id/view_legend_background" app:layout_constraintStart_toEndOf="@+id/imageview_legend_custom_unlock_aircraft_zone" @@ -195,7 +195,7 @@ android:layout_height="25dp" android:layout_margin="@dimen/uxsdk_map_widget_margin" android:gravity="center_vertical" - android:paddingLeft="@dimen/uxsdk_map_widget_margin" + android:paddingStart="@dimen/uxsdk_map_widget_margin" android:text="@string/uxsdk_fly_zone_restricted" app:layout_constraintEnd_toStartOf="@+id/imageview_legend_self_unlock_zone" app:layout_constraintStart_toEndOf="@+id/imageview_legend_restricted_zone" @@ -218,12 +218,13 @@ android:layout_width="0dp" android:layout_height="35dp" android:gravity="center_vertical" - android:paddingLeft="@dimen/uxsdk_map_widget_margin" + android:paddingStart="@dimen/uxsdk_map_widget_margin" android:text="@string/uxsdk_fly_zone_custom_unlock_enabled" app:layout_constraintEnd_toEndOf="@+id/view_legend_background" app:layout_constraintStart_toEndOf="@+id/imageview_legend_custom_unlock_enabled_zone" app:layout_constraintTop_toTopOf="@+id/imageview_legend_custom_unlock_enabled_zone" /> + Currently unlocked and expires at %1$s Failed to unlock. You are not authorized to unlock this zone. Failed to unlock. %1$s + Failed to login. You are not authorized to perform this action. + Something went wrong. Failed to login. Please try again later. Warning Enhanced warning Authorized diff --git a/android-uxsdk-beta-sample/build.gradle b/android-uxsdk-beta-sample/build.gradle index e785fb40..1c1927b1 100644 --- a/android-uxsdk-beta-sample/build.gradle +++ b/android-uxsdk-beta-sample/build.gradle @@ -75,6 +75,7 @@ android { doNotStrip "*/*/libDJIUpgradeJNI.so" doNotStrip "*/*/libDJIWaypointV2Core.so" doNotStrip "*/*/libDJIMOP.so" + doNotStrip "*/*/libDJISDKLOGJNI.so" pickFirst 'lib/*/libstlport_shared.so' pickFirst 'lib/*/libRoadLineRebuildAPI.so' @@ -83,9 +84,6 @@ android { pickFirst 'lib/*/libGNaviData.so' pickFirst 'lib/*/libGNaviMap.so' pickFirst 'lib/*/libGNaviSearch.so' - pickFirst 'lib/*/libDJIFlySafeCore.so' - pickFirst 'lib/*/libdjifs_jni.so' - pickFirst 'lib/*/libsfjni.so' exclude 'META-INF/proguard/okhttp3.pro' exclude 'META-INF/rxjava.properties' } @@ -97,7 +95,7 @@ android { dependencies { - implementation ('com.dji:dji-sdk:4.13.1', { + implementation ('com.dji:dji-sdk:4.14-trial1', { /** * Comment the "library-anti-distortion" if your app needs Anti Distortion for Mavic 2 Pro * and Mavic 2 Zoom. @@ -108,53 +106,54 @@ dependencies { */ exclude module: 'library-anti-distortion' exclude module: 'fly-safe-database' + + /** + * Uncomment the following line to exclude Amap from the app. + * Note that Google Play Store does not allow APKs that include this library. + */ + // exclude group: 'com.amap.api' }) - compileOnly ('com.dji:dji-sdk-provided:4.13.1') + compileOnly ('com.dji:dji-sdk-provided:4.14-trial1') + // UXSDK dependencies + implementation 'androidx.annotation:annotation:1.0.0' + implementation 'androidx.appcompat:appcompat:1.0.0' + implementation 'androidx.core:core:1.0.0' + implementation "androidx.core:core-ktx:1.1.0" + + // UXSDK map dependencies: Only include dependencies for map providers that are used in your app + // Amap: Do not include if publishing to Google Play Store implementation 'com.amap.api:3dmap:6.9.2' implementation 'com.amap.api:search:6.9.2' implementation 'com.amap.api:location:4.7.0' - //HERE maps + // HERE maps implementation files('libs/HERE-sdk-3.15.0.aar') - implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:5.5.1' - - implementation 'com.google.code.gson:gson:2.8.2' - implementation 'com.squareup.wire:wire-runtime:2.2.0' - implementation 'org.bouncycastle:bcprov-jdk15on:1.57' - implementation 'org.bouncycastle:bcpkix-jdk15on:1.57' - implementation 'com.squareup.okio:okio:1.15.0' - implementation 'com.vividsolutions:jts:1.8' - implementation 'org.reactivestreams:reactive-streams:1.0.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.2' - implementation 'com.lmax:disruptor:3.3.9' - implementation 'androidx.appcompat:appcompat:1.0.0' - implementation 'androidx.recyclerview:recyclerview:1.0.0' + // Mapbox + implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:9.2.0' + // Google maps implementation 'com.google.android.gms:play-services-base:16.0.0' implementation 'com.google.android.gms:play-services-location:16.0.0' implementation 'com.google.android.gms:play-services-maps:16.0.0' implementation 'com.google.android.gms:play-services-places:16.0.0' - implementation 'com.google.android.gms:play-services-places:16.0.0' - implementation 'org.greenrobot:eventbus:3.0.0' - implementation 'com.google.protobuf:protobuf-java:3.8.0' - implementation 'androidx.multidex:multidex:2.0.0' - implementation 'com.dji:library-anti-distortion:4.7' + + // SDK dependencies + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0-rc01' - implementation 'androidx.core:core:1.0.0' - implementation 'androidx.annotation:annotation:1.0.0' - implementation 'androidx.media:media:1.0.0' - implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation "androidx.core:core-ktx:1.1.0" + implementation 'androidx.multidex:multidex:2.0.1' + implementation 'androidx.recyclerview:recyclerview:1.0.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + + // Sample app dependencies implementation 'com.jakewharton:butterknife:10.0.0' annotationProcessor 'com.jakewharton:butterknife-compiler:10.0.0' // UXSDK Modules implementation project(path: ':android-uxsdk-beta-cameracore') - implementation project(path: ':android-uxsdk-beta-hardwareaccessory') + implementation project(path: ':android-uxsdk-beta-accessory') implementation project(path: ':android-uxsdk-beta-map') implementation project(path: ':android-uxsdk-beta-training') implementation project(path: ':android-uxsdk-beta-visualcamera') - implementation project(path: ':android-uxsdk-beta-intelligentflight') + implementation project(path: ':android-uxsdk-beta-flight') } repositories { mavenCentral() diff --git a/android-uxsdk-beta-sample/gradle/wrapper/gradle-wrapper.properties b/android-uxsdk-beta-sample/gradle/wrapper/gradle-wrapper.properties index fcdad622..9636cdcc 100644 --- a/android-uxsdk-beta-sample/gradle/wrapper/gradle-wrapper.properties +++ b/android-uxsdk-beta-sample/gradle/wrapper/gradle-wrapper.properties @@ -1,3 +1,26 @@ +# +# 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. +# +# + #Mon May 11 14:17:57 PDT 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists diff --git a/android-uxsdk-beta-sample/local.properties b/android-uxsdk-beta-sample/local.properties index fb916192..cf59ac52 100644 --- a/android-uxsdk-beta-sample/local.properties +++ b/android-uxsdk-beta-sample/local.properties @@ -1,3 +1,26 @@ +# +# 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. +# +# + ## This file must *NOT* be checked into Version Control Systems, # as it contains information specific to your local configuration. # diff --git a/android-uxsdk-beta-sample/src/main/AndroidManifest.xml b/android-uxsdk-beta-sample/src/main/AndroidManifest.xml index c756ae03..436fdcb4 100644 --- a/android-uxsdk-beta-sample/src/main/AndroidManifest.xml +++ b/android-uxsdk-beta-sample/src/main/AndroidManifest.xml @@ -64,7 +64,6 @@ - + + \ No newline at end of file diff --git a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/MainActivity.java b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/MainActivity.java index f4488e32..ef2733e5 100644 --- a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/MainActivity.java +++ b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/MainActivity.java @@ -47,8 +47,10 @@ import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; +import com.dji.ux.beta.sample.showcase.defaultlayout.DefaultLayoutActivity; +import com.dji.ux.beta.sample.showcase.map.MapWidgetActivity; +import com.dji.ux.beta.sample.showcase.widgetlist.WidgetsActivity; import com.dji.ux.beta.sample.util.MapUtil; -import com.dji.ux.beta.sample.widgetlist.WidgetsActivity; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -71,7 +73,7 @@ /** * Handles the connection to the product and provides links to the different test activities. Also - * shows the current connection status and displays logs for the different steps of the SDK + * shows the current connection state and displays logs for the different steps of the SDK * registration process. */ public class MainActivity extends AppCompatActivity implements PopupMenu.OnMenuItemClickListener { @@ -368,6 +370,7 @@ public void onWidgetListClick() { startActivity(intent); } + /** * Displays a menu of map providers before launching the {@link MapWidgetActivity}. Disables * providers that are not supported by this device. @@ -386,7 +389,6 @@ public void onMapClick(View view) { popup.show(); } - /** * When one of the map providers is clicked, the {@link MapWidgetActivity} is launched with * the {@link dji.ux.beta.map.widget.map.MapWidget} initialized with the given provider. diff --git a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/SampleApplication.java b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/SampleApplication.java index ccb7ab18..0d51fffd 100644 --- a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/SampleApplication.java +++ b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/SampleApplication.java @@ -30,11 +30,10 @@ import androidx.multidex.MultiDex; - import com.secneo.sdk.Helper; -import dji.ux.beta.core.base.DefaultGlobalPreferences; -import dji.ux.beta.core.base.GlobalPreferencesManager; +import dji.ux.beta.core.communication.DefaultGlobalPreferences; +import dji.ux.beta.core.communication.GlobalPreferencesManager; import static com.dji.ux.beta.sample.DJIConnectionControlActivity.ACCESSORY_ATTACHED; diff --git a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/DefaultLayoutActivity.java b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/defaultlayout/DefaultLayoutActivity.java similarity index 84% rename from android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/DefaultLayoutActivity.java rename to android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/defaultlayout/DefaultLayoutActivity.java index 080213d0..ae1ae6f4 100644 --- a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/DefaultLayoutActivity.java +++ b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/defaultlayout/DefaultLayoutActivity.java @@ -21,7 +21,7 @@ * */ -package com.dji.ux.beta.sample; +package com.dji.ux.beta.sample.showcase.defaultlayout; import android.os.Bundle; import android.os.PersistableBundle; @@ -34,22 +34,28 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.constraintlayout.widget.ConstraintLayout; +import com.dji.ux.beta.sample.R; + import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; import dji.common.airlink.PhysicalSource; +import dji.common.product.Model; import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers; import dji.thirdparty.io.reactivex.disposables.CompositeDisposable; +import dji.ux.beta.accessory.widget.rtk.RTKWidget; import dji.ux.beta.cameracore.widget.fpvinteraction.FPVInteractionWidget; import dji.ux.beta.core.extension.ViewExtensions; -import dji.ux.beta.core.panelwidget.systemstatus.SystemStatusListPanelWidget; -import dji.ux.beta.core.panelwidget.topbar.TopBarPanelWidget; +import dji.ux.beta.core.panel.systemstatus.SystemStatusListPanelWidget; +import dji.ux.beta.core.panel.topbar.TopBarPanelWidget; +import dji.ux.beta.core.util.DisplayUtil; import dji.ux.beta.core.util.SettingDefinitions; import dji.ux.beta.core.widget.fpv.FPVWidget; import dji.ux.beta.core.widget.gpssignal.GPSSignalWidget; +import dji.ux.beta.core.widget.radar.RadarWidget; import dji.ux.beta.core.widget.simulator.SimulatorIndicatorWidget; import dji.ux.beta.core.widget.systemstatus.SystemStatusWidget; -import dji.ux.beta.hardwareaccessory.widget.rtk.RTKWidget; +import dji.ux.beta.core.widget.useraccount.UserAccountLoginWidget; import dji.ux.beta.map.widget.map.MapWidget; import dji.ux.beta.training.widget.simulatorcontrol.SimulatorControlWidget; @@ -61,6 +67,8 @@ public class DefaultLayoutActivity extends AppCompatActivity { //region Fields private final static String TAG = "DefaultLayoutActivity"; + @BindView(R.id.widget_radar) + protected RadarWidget radarWidget; @BindView(R.id.widget_fpv) protected FPVWidget fpvWidget; @BindView(R.id.widget_fpv_interaction) @@ -86,6 +94,7 @@ public class DefaultLayoutActivity extends AppCompatActivity { private int deviceWidth; private int deviceHeight; private CompositeDisposable compositeDisposable; + private UserAccountLoginWidget userAccountLoginWidget; //endregion //region Lifecycle @@ -103,7 +112,11 @@ protected void onCreate(Bundle savedInstanceState) { deviceWidth = displayMetrics.widthPixels; ButterKnife.bind(this); - mapWidget.initAMap(map -> map.setOnMapClickListener(latLng -> onViewClick(mapWidget))); + setM200SeriesWarningLevelRanges(); + mapWidget.initAMap(map -> { + map.setOnMapClickListener(latLng -> onViewClick(mapWidget)); + map.getUiSettings().setZoomControlsEnabled(false); + }); mapWidget.getUserAccountLoginWidget().setVisibility(View.GONE); mapWidget.onCreate(savedInstanceState); @@ -123,6 +136,11 @@ protected void onCreate(Bundle savedInstanceState) { if (gpsSignalWidget != null) { gpsSignalWidget.setStateChangeCallback(findViewById(R.id.widget_rtk)); } + + userAccountLoginWidget = mapWidget.getUserAccountLoginWidget(); + ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) userAccountLoginWidget.getLayoutParams(); + params.topMargin = (deviceHeight / 10) + (int) DisplayUtil.dipToPx(this, 10); + userAccountLoginWidget.setLayoutParams(params); } @Override @@ -161,11 +179,11 @@ protected void onResume() { } })); - compositeDisposable.add(rtkWidget.getWidgetStateUpdate() + compositeDisposable.add(rtkWidget.getUIStateUpdates() .observeOn(AndroidSchedulers.mainThread()) - .subscribe(rtkWidgetState -> { - if (rtkWidgetState instanceof RTKWidget.RTKWidgetState.VisibilityUpdate) { - if (((RTKWidget.RTKWidgetState.VisibilityUpdate) rtkWidgetState).isVisible()) { + .subscribe(uiState -> { + if (uiState instanceof RTKWidget.UIState.VisibilityUpdated) { + if (((RTKWidget.UIState.VisibilityUpdated) uiState).isVisible()) { hideOtherPanels(rtkWidget); } } @@ -173,8 +191,8 @@ protected void onResume() { compositeDisposable.add(simulatorControlWidget.getUIStateUpdates() .observeOn(AndroidSchedulers.mainThread()) .subscribe(simulatorControlWidgetState -> { - if (simulatorControlWidgetState instanceof SimulatorControlWidget.SimulatorControlWidgetUIUpdate.VisibilityToggled) { - if (((SimulatorControlWidget.SimulatorControlWidgetUIUpdate.VisibilityToggled) simulatorControlWidgetState).isVisible()) { + if (simulatorControlWidgetState instanceof SimulatorControlWidget.UIState.VisibilityUpdated) { + if (((SimulatorControlWidget.UIState.VisibilityUpdated) simulatorControlWidgetState).isVisible()) { hideOtherPanels(simulatorControlWidget); } } @@ -207,6 +225,19 @@ private void hideOtherPanels(@Nullable View widget) { } } + private void setM200SeriesWarningLevelRanges() { + Model[] m200SeriesModels = { + Model.MATRICE_200, + Model.MATRICE_210, + Model.MATRICE_210_RTK, + Model.MATRICE_200_V2, + Model.MATRICE_210_V2, + Model.MATRICE_210_RTK_V2 + }; + float[] ranges = {70, 30, 20, 12, 6, 3}; + radarWidget.setWarningLevelRanges(m200SeriesModels, ranges); + } + /** * Handles a click event on the FPV widget */ @@ -239,7 +270,7 @@ private void onViewClick(View view) { //enable interaction on FPV fpvInteractionWidget.setInteractionEnabled(true); //disable user login widget on map - mapWidget.getUserAccountLoginWidget().setVisibility(View.GONE); + userAccountLoginWidget.setVisibility(View.GONE); isMapMini = true; } else if (view == mapWidget && isMapMini) { //reorder widgets @@ -250,7 +281,7 @@ private void onViewClick(View view) { //disable interaction on FPV fpvInteractionWidget.setInteractionEnabled(false); //enable user login widget on map - mapWidget.getUserAccountLoginWidget().setVisibility(View.VISIBLE); + userAccountLoginWidget.setVisibility(View.VISIBLE); isMapMini = false; } } diff --git a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/FlyZoneDialogView.java b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/map/FlyZoneDialogView.java similarity index 99% rename from android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/FlyZoneDialogView.java rename to android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/map/FlyZoneDialogView.java index d71bf813..6325e095 100644 --- a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/FlyZoneDialogView.java +++ b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/map/FlyZoneDialogView.java @@ -21,7 +21,7 @@ * */ -package com.dji.ux.beta.sample; +package com.dji.ux.beta.sample.showcase.map; import android.content.Context; import android.graphics.Color; @@ -36,6 +36,8 @@ import androidx.annotation.ColorInt; import androidx.core.graphics.ColorUtils; +import com.dji.ux.beta.sample.R; + import java.util.Random; import dji.common.flightcontroller.flyzone.FlyZoneCategory; @@ -56,7 +58,7 @@ public class FlyZoneDialogView extends ScrollView { private Button btnCustomUnlockSync; //endregion - //region lifecycle + //region Lifecycle public FlyZoneDialogView(Context context) { super(context); inflate(context, R.layout.dialog_fly_zone, this); diff --git a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/MapWidgetActivity.java b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/map/MapWidgetActivity.java similarity index 98% rename from android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/MapWidgetActivity.java rename to android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/map/MapWidgetActivity.java index 14733964..901f0ad9 100644 --- a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/MapWidgetActivity.java +++ b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/map/MapWidgetActivity.java @@ -21,7 +21,7 @@ * */ -package com.dji.ux.beta.sample; +package com.dji.ux.beta.sample.showcase.map; import android.app.AlertDialog; import android.content.DialogInterface; @@ -50,6 +50,7 @@ import com.dji.mapkit.core.maps.DJIMap; import com.dji.mapkit.core.models.annotations.DJIMarker; import com.dji.mapkit.core.models.annotations.DJIMarkerOptions; +import com.dji.ux.beta.sample.R; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.BitmapDescriptor; import com.google.android.gms.maps.model.BitmapDescriptorFactory; @@ -88,14 +89,15 @@ */ public class MapWidgetActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener { + //region constants + private static final String TAG = "MapWidgetActivity"; /** * The key for passing a map provider through the intent. */ public static final String MAP_PROVIDER_KEY = "MapProvider"; - //region constants - private static final String TAG = "MapWidgetActivity"; //endregion - //region fields + + //region Fields @BindView(R.id.map_widget) protected MapWidget mapWidget; @BindView(R.id.icon_spinner) @@ -125,7 +127,7 @@ public class MapWidgetActivity extends AppCompatActivity implements SeekBar.OnSe private List markerList; //endregion - //region lifecycle + //region Lifecycle @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -543,7 +545,11 @@ private void addOverlay() { default: Random rnd = new Random(); mapboxMap = (MapboxMap) mapWidget.getMap().getMap(); - mapboxMap.getLayer("water").setProperties(PropertyFactory.fillColor(Color.argb(255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256)))); + mapboxMap.getStyle(style -> style.getLayer("water") + .setProperties(PropertyFactory.fillColor(Color.argb(255, + rnd.nextInt(256), + rnd.nextInt(256), + rnd.nextInt(256))))); break; } } diff --git a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/widgetlist/WidgetFragment.java b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/widgetlist/WidgetFragment.java similarity index 98% rename from android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/widgetlist/WidgetFragment.java rename to android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/widgetlist/WidgetFragment.java index 5a8db9c9..3d3fc156 100644 --- a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/widgetlist/WidgetFragment.java +++ b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/widgetlist/WidgetFragment.java @@ -21,7 +21,7 @@ * */ -package com.dji.ux.beta.sample.widgetlist; +package com.dji.ux.beta.sample.showcase.widgetlist; import android.os.Bundle; import android.view.LayoutInflater; @@ -38,7 +38,6 @@ import dji.ux.beta.core.widget.simulator.SimulatorIndicatorWidget; import dji.ux.beta.training.widget.simulatorcontrol.SimulatorControlWidget; - /** * A fragment that shows a single widget or a set of coupled widgets. */ diff --git a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/widgetlist/WidgetListFragment.java b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/widgetlist/WidgetListFragment.java similarity index 98% rename from android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/widgetlist/WidgetListFragment.java rename to android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/widgetlist/WidgetListFragment.java index 49a2084b..640c6a44 100644 --- a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/widgetlist/WidgetListFragment.java +++ b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/widgetlist/WidgetListFragment.java @@ -21,7 +21,7 @@ * */ -package com.dji.ux.beta.sample.widgetlist; +package com.dji.ux.beta.sample.showcase.widgetlist; import android.content.Context; import android.os.Bundle; diff --git a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/widgetlist/WidgetListItem.java b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/widgetlist/WidgetListItem.java similarity index 97% rename from android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/widgetlist/WidgetListItem.java rename to android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/widgetlist/WidgetListItem.java index 1bbe044b..414a9092 100644 --- a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/widgetlist/WidgetListItem.java +++ b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/widgetlist/WidgetListItem.java @@ -21,7 +21,7 @@ * */ -package com.dji.ux.beta.sample.widgetlist; +package com.dji.ux.beta.sample.showcase.widgetlist; import androidx.annotation.StringRes; @@ -30,7 +30,7 @@ */ public class WidgetListItem { - //region fields + //region Fields @StringRes private final int titleId; private final WidgetViewHolder[] widgetViewHolders; diff --git a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/widgetlist/WidgetListItemAdapter.java b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/widgetlist/WidgetListItemAdapter.java similarity index 96% rename from android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/widgetlist/WidgetListItemAdapter.java rename to android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/widgetlist/WidgetListItemAdapter.java index e7de51e1..24209010 100644 --- a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/widgetlist/WidgetListItemAdapter.java +++ b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/widgetlist/WidgetListItemAdapter.java @@ -21,7 +21,7 @@ * */ -package com.dji.ux.beta.sample.widgetlist; +package com.dji.ux.beta.sample.showcase.widgetlist; import android.content.res.Resources; import android.view.LayoutInflater; @@ -41,12 +41,12 @@ */ public class WidgetListItemAdapter extends RecyclerView.Adapter { - //region fields + //region Fields private ArrayList widgetListItems; private WidgetListFragment.OnWidgetItemSelectedListener onWidgetItemSelectedListener; //endregion - //region lifecycle + //region Lifecycle public WidgetListItemAdapter(ArrayList widgetListItems, WidgetListFragment.OnWidgetItemSelectedListener onWidgetItemSelectedListener) { this.widgetListItems = widgetListItems; this.onWidgetItemSelectedListener = onWidgetItemSelectedListener; @@ -81,12 +81,12 @@ public void onBindViewHolder(WidgetListItemViewHolder holder, int position, List */ public class WidgetListItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { - //region fields + //region Fields private TextView titleTextView; private WidgetListFragment.OnWidgetItemSelectedListener onWidgetItemSelectedListener; //endregion - //region lifecycle + //region Lifecycle private WidgetListItemViewHolder(TextView itemView, WidgetListFragment.OnWidgetItemSelectedListener onWidgetItemSelectedListener) { super(itemView); titleTextView = itemView; diff --git a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/widgetlist/WidgetView.java b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/widgetlist/WidgetView.java similarity index 98% rename from android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/widgetlist/WidgetView.java rename to android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/widgetlist/WidgetView.java index d104dc66..d7922359 100644 --- a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/widgetlist/WidgetView.java +++ b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/widgetlist/WidgetView.java @@ -21,7 +21,7 @@ * */ -package com.dji.ux.beta.sample.widgetlist; +package com.dji.ux.beta.sample.showcase.widgetlist; import android.content.Context; import android.util.AttributeSet; @@ -41,8 +41,6 @@ import butterknife.OnTouch; import dji.ux.beta.cameracore.widget.fpvinteraction.FPVInteractionWidget; - - /** * A view with a single widget and an indicator of the current size of the widget. This widget * can be resized using pinch-to-zoom and the size indicator updates to match the current @@ -56,7 +54,7 @@ public class WidgetView extends ConstraintLayout { protected TextView aspectRatioTextView; @BindView(R.id.textview_current_size) protected TextView currentSizeTextView; - //region fields + //region Fields private WidgetViewHolder widgetViewHolder; private ScaleGestureDetector scaleGestureDetector; //endregion @@ -67,7 +65,7 @@ public class WidgetView extends ConstraintLayout { private ViewGroup widget; //endregion - //region lifecycle + //region Lifecycle public WidgetView(Context context) { super(context); } diff --git a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/widgetlist/WidgetViewHolder.java b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/widgetlist/WidgetViewHolder.java similarity index 95% rename from android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/widgetlist/WidgetViewHolder.java rename to android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/widgetlist/WidgetViewHolder.java index bac8ecc3..f636ba24 100644 --- a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/widgetlist/WidgetViewHolder.java +++ b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/widgetlist/WidgetViewHolder.java @@ -21,7 +21,7 @@ * */ -package com.dji.ux.beta.sample.widgetlist; +package com.dji.ux.beta.sample.showcase.widgetlist; import android.content.Context; import android.view.ViewGroup; @@ -34,17 +34,16 @@ import java.lang.reflect.InvocationTargetException; import dji.log.DJILog; -import dji.ux.beta.core.base.ConstraintLayoutWidget; -import dji.ux.beta.core.base.FrameLayoutWidget; +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget; +import dji.ux.beta.core.base.widget.FrameLayoutWidget; import dji.ux.beta.core.util.DisplayUtil; - /** * A view holder for a single widget. */ public class WidgetViewHolder { - //region fields + //region Fields private static final String TAG = "WidgetViewHolder"; private ViewGroup widget; private Class clazz; @@ -52,7 +51,7 @@ public class WidgetViewHolder { private int layoutHeightDp; //endregion - //region lifecycle + //region Lifecycle public WidgetViewHolder(Class clazz) { this(clazz, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); } diff --git a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/widgetlist/WidgetsActivity.java b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/widgetlist/WidgetsActivity.java similarity index 94% rename from android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/widgetlist/WidgetsActivity.java rename to android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/widgetlist/WidgetsActivity.java index 2989e00e..233190f8 100644 --- a/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/widgetlist/WidgetsActivity.java +++ b/android-uxsdk-beta-sample/src/main/java/com/dji/ux/beta/sample/showcase/widgetlist/WidgetsActivity.java @@ -19,10 +19,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * - * */ -package com.dji.ux.beta.sample.widgetlist; +package com.dji.ux.beta.sample.showcase.widgetlist; import android.os.Bundle; import android.view.ViewGroup; @@ -34,6 +33,9 @@ import java.util.ArrayList; +import dji.ux.beta.accessory.widget.rtk.RTKEnabledWidget; +import dji.ux.beta.accessory.widget.rtk.RTKSatelliteStatusWidget; +import dji.ux.beta.accessory.widget.rtk.RTKWidget; import dji.ux.beta.cameracore.widget.autoexposurelock.AutoExposureLockWidget; import dji.ux.beta.cameracore.widget.cameracapture.CameraCaptureWidget; import dji.ux.beta.cameracore.widget.cameracapture.recordvideo.RecordVideoWidget; @@ -45,14 +47,14 @@ import dji.ux.beta.cameracore.widget.focusexposureswitch.FocusExposureSwitchWidget; import dji.ux.beta.cameracore.widget.focusmode.FocusModeWidget; import dji.ux.beta.cameracore.widget.fpvinteraction.FPVInteractionWidget; -import dji.ux.beta.core.panelwidget.systemstatus.SystemStatusListPanelWidget; -import dji.ux.beta.core.panelwidget.topbar.TopBarPanelWidget; +import dji.ux.beta.core.panel.systemstatus.SystemStatusListPanelWidget; +import dji.ux.beta.core.panel.telemetry.TelemetryPanelWidget; +import dji.ux.beta.core.panel.topbar.TopBarPanelWidget; import dji.ux.beta.core.widget.airsense.AirSenseWidget; -import dji.ux.beta.core.widget.altitude.AltitudeWidget; +import dji.ux.beta.core.widget.altitude.AGLAltitudeWidget; import dji.ux.beta.core.widget.battery.BatteryWidget; import dji.ux.beta.core.widget.compass.CompassWidget; import dji.ux.beta.core.widget.connection.ConnectionWidget; -import dji.ux.beta.core.widget.dashboard.DashboardWidget; import dji.ux.beta.core.widget.distancehome.DistanceHomeWidget; import dji.ux.beta.core.widget.distancerc.DistanceRCWidget; import dji.ux.beta.core.widget.flightmode.FlightModeWidget; @@ -68,9 +70,6 @@ import dji.ux.beta.core.widget.videosignal.VideoSignalWidget; import dji.ux.beta.core.widget.vision.VisionWidget; import dji.ux.beta.core.widget.vps.VPSWidget; -import dji.ux.beta.hardwareaccessory.widget.rtk.RTKEnabledWidget; -import dji.ux.beta.hardwareaccessory.widget.rtk.RTKSatelliteStatusWidget; -import dji.ux.beta.hardwareaccessory.widget.rtk.RTKWidget; import dji.ux.beta.training.widget.simulatorcontrol.SimulatorControlWidget; import dji.ux.beta.visualcamera.widget.cameraconfig.aperture.CameraConfigApertureWidget; import dji.ux.beta.visualcamera.widget.cameraconfig.ev.CameraConfigEVWidget; @@ -80,7 +79,6 @@ import dji.ux.beta.visualcamera.widget.cameraconfig.storage.CameraConfigStorageWidget; import dji.ux.beta.visualcamera.widget.cameraconfig.wb.CameraConfigWBWidget; - /** * Displays a list of widget names. Clicking on a widget name will show that widget in a separate * panel on large devices, or in a new activity on smaller devices. @@ -125,7 +123,7 @@ private void populateList() { widgetListItems.add(new WidgetListItem(R.string.air_sense_widget_title, new WidgetViewHolder(AirSenseWidget.class, 58, 50))); widgetListItems.add(new WidgetListItem(R.string.altitude_widget_title, - new WidgetViewHolder(AltitudeWidget.class))); + new WidgetViewHolder(AGLAltitudeWidget.class))); widgetListItems.add(new WidgetListItem(R.string.auto_exposure_lock_widget_title, new WidgetViewHolder(AutoExposureLockWidget.class, 35, 35))); widgetListItems.add(new WidgetListItem(R.string.battery_widget_title, @@ -154,8 +152,6 @@ private void populateList() { new WidgetViewHolder(CompassWidget.class, ViewGroup.LayoutParams.WRAP_CONTENT, 91))); widgetListItems.add(new WidgetListItem(R.string.connection_widget_title, new WidgetViewHolder(ConnectionWidget.class, ViewGroup.LayoutParams.WRAP_CONTENT, 50))); - widgetListItems.add(new WidgetListItem(R.string.dashboard_widget_title, - new WidgetViewHolder(DashboardWidget.class, ViewGroup.LayoutParams.WRAP_CONTENT, 91))); widgetListItems.add(new WidgetListItem(R.string.distance_home_widget_title, new WidgetViewHolder(DistanceHomeWidget.class))); widgetListItems.add(new WidgetListItem(R.string.distance_rc_widget_title, @@ -199,6 +195,8 @@ private void populateList() { new WidgetViewHolder(SimulatorControlWidget.class, 300, ViewGroup.LayoutParams.MATCH_PARENT))); widgetListItems.add(new WidgetListItem(R.string.system_status_panel_title, new WidgetViewHolder(SystemStatusListPanelWidget.class, 550, 200))); + widgetListItems.add(new WidgetListItem(R.string.telemetry_widget_title, + new WidgetViewHolder(TelemetryPanelWidget.class, 350, 91))); widgetListItems.add(new WidgetListItem(R.string.top_bar_panel_title, new WidgetViewHolder(TopBarPanelWidget.class, ViewGroup.LayoutParams.MATCH_PARENT, 25))); widgetListItems.add(new WidgetListItem(R.string.user_account_login_widget_title, diff --git a/android-uxsdk-beta-sample/src/main/res/drawable/fpv_gradient_bottom.xml b/android-uxsdk-beta-sample/src/main/res/drawable/fpv_gradient_bottom.xml new file mode 100644 index 00000000..5eb89b2b --- /dev/null +++ b/android-uxsdk-beta-sample/src/main/res/drawable/fpv_gradient_bottom.xml @@ -0,0 +1,33 @@ + + + + + + + + \ No newline at end of file diff --git a/android-uxsdk-beta-sample/src/main/res/drawable/fpv_gradient_left.xml b/android-uxsdk-beta-sample/src/main/res/drawable/fpv_gradient_left.xml new file mode 100644 index 00000000..9b689308 --- /dev/null +++ b/android-uxsdk-beta-sample/src/main/res/drawable/fpv_gradient_left.xml @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file diff --git a/android-uxsdk-beta-sample/src/main/res/drawable/fpv_gradient_right.xml b/android-uxsdk-beta-sample/src/main/res/drawable/fpv_gradient_right.xml new file mode 100644 index 00000000..f20220f1 --- /dev/null +++ b/android-uxsdk-beta-sample/src/main/res/drawable/fpv_gradient_right.xml @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file diff --git a/android-uxsdk-beta-sample/src/main/res/drawable/ic_dog.xml b/android-uxsdk-beta-sample/src/main/res/drawable/ic_dog.xml deleted file mode 100644 index c15afafd..00000000 --- a/android-uxsdk-beta-sample/src/main/res/drawable/ic_dog.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - diff --git a/android-uxsdk-beta-sample/src/main/res/layout-large/activity_widgets.xml b/android-uxsdk-beta-sample/src/main/res/layout-large/activity_widgets.xml index 6045f226..32126d04 100644 --- a/android-uxsdk-beta-sample/src/main/res/layout-large/activity_widgets.xml +++ b/android-uxsdk-beta-sample/src/main/res/layout-large/activity_widgets.xml @@ -20,7 +20,6 @@ ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE ~ SOFTWARE. ~ - ~ --> - @@ -226,55 +260,62 @@ android:id="@+id/widget_remaining_flight_time" android:layout_width="match_parent" android:layout_height="0dp" + app:layout_constraintBottom_toBottomOf="@+id/panel_top_bar" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHeight_percent="0.04" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintBottom_toBottomOf="@+id/panel_top_bar" app:layout_constraintTop_toBottomOf="@+id/panel_top_bar" /> - + + + + + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/widget_remaining_flight_time" /> - - - + app:layout_constraintTop_toBottomOf="@+id/widget_take_off" /> - - diff --git a/android-uxsdk-beta-sample/src/main/res/layout/activity_main.xml b/android-uxsdk-beta-sample/src/main/res/layout/activity_main.xml index 0fcb8855..f609c172 100644 --- a/android-uxsdk-beta-sample/src/main/res/layout/activity_main.xml +++ b/android-uxsdk-beta-sample/src/main/res/layout/activity_main.xml @@ -108,7 +108,6 @@ android:paddingBottom="8dp"> + + 0.115 \ No newline at end of file diff --git a/android-uxsdk-beta-sample/src/main/res/values-sw600dp/dimens.xml b/android-uxsdk-beta-sample/src/main/res/values-sw600dp/dimens.xml index ec06e041..a339e059 100644 --- a/android-uxsdk-beta-sample/src/main/res/values-sw600dp/dimens.xml +++ b/android-uxsdk-beta-sample/src/main/res/values-sw600dp/dimens.xml @@ -27,11 +27,8 @@ 225dp 5dp - 24sp 40dp 50dp - 20sp - 12sp 0.09 \ No newline at end of file diff --git a/android-uxsdk-beta-sample/src/main/res/values/colors.xml b/android-uxsdk-beta-sample/src/main/res/values/colors.xml index ab1692df..aa3d11ca 100644 --- a/android-uxsdk-beta-sample/src/main/res/values/colors.xml +++ b/android-uxsdk-beta-sample/src/main/res/values/colors.xml @@ -26,24 +26,14 @@ #242d34 #000000 #FF4081 - #2fc918 - #f4425f - #a2a9b5 #FFFFFF - #000000 - #424242 - #FFFFFF #242d34 - #ff5e5e5e #000000 #FFFFFF - #E0E0E0 - #ff3fc14d #444444 #303030 #B3FFFFFF #80000000 - #20A3F6 #00000000 #727272 #F7F9FA diff --git a/android-uxsdk-beta-sample/src/main/res/values/dimens.xml b/android-uxsdk-beta-sample/src/main/res/values/dimens.xml index 852e8852..b63e9639 100644 --- a/android-uxsdk-beta-sample/src/main/res/values/dimens.xml +++ b/android-uxsdk-beta-sample/src/main/res/values/dimens.xml @@ -23,7 +23,6 @@ --> - 30dp 15dp 5dp 100dp @@ -31,12 +30,8 @@ 12dp 3dp - 16sp 35dp 35dp 6dp - 14sp - 8sp - 56dp 0.1 \ No newline at end of file diff --git a/android-uxsdk-beta-sample/src/main/res/values/strings.xml b/android-uxsdk-beta-sample/src/main/res/values/strings.xml index fc99cb64..585c1240 100644 --- a/android-uxsdk-beta-sample/src/main/res/values/strings.xml +++ b/android-uxsdk-beta-sample/src/main/res/values/strings.xml @@ -32,25 +32,9 @@ Registered Logs Showcase - Internal Widget List Default Layout Map - - None - Parallel - Parallel/Diagonal - - - Auto - Primary - Secondary - - - Landscape - Portrait - - Google Maps @@ -127,7 +111,7 @@ Camera Controls Widget Camera Settings Menu Indicator Widget Compass Widget - Dashboard Widget + Telemetry Panel Distance Home Widget Distance RC Widget Exposure Settings Indicator Widget @@ -157,4 +141,5 @@ RTK Enabled Widget RTK Satellite Status Widget Remaining Flight Time Widget + diff --git a/android-uxsdk-beta-training/build.gradle b/android-uxsdk-beta-training/build.gradle index 133ad926..b5876748 100644 --- a/android-uxsdk-beta-training/build.gradle +++ b/android-uxsdk-beta-training/build.gradle @@ -69,7 +69,7 @@ android { dependencies { api project(path: ':android-uxsdk-beta-core') - implementation ('com.dji:dji-sdk:4.13.1', { + implementation ('com.dji:dji-sdk:4.14-trial1', { /** * Comment the "library-anti-distortion" if your app needs Anti Distortion for Mavic 2 Pro * and Mavic 2 Zoom. @@ -81,7 +81,7 @@ dependencies { exclude module: 'library-anti-distortion' exclude module: 'fly-safe-database' }) - compileOnly ('com.dji:dji-sdk-provided:4.13.1') + compileOnly ('com.dji:dji-sdk-provided:4.14-trial1') implementation 'androidx.multidex:multidex:2.0.0' implementation 'androidx.appcompat:appcompat:1.0.0' diff --git a/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/util/SimulatorPresetUtils.kt b/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/util/SimulatorPresetUtils.kt index 0e388ba4..62e4d3ed 100644 --- a/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/util/SimulatorPresetUtils.kt +++ b/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/util/SimulatorPresetUtils.kt @@ -18,15 +18,16 @@ * 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.ux.beta.training.util -import android.annotation.SuppressLint import android.content.Context import android.content.SharedPreferences +import androidx.core.content.edit import dji.sdk.sdkmanager.DJISDKManager +import dji.ux.beta.training.widget.simulatorcontrol.preset.SimulatorPresetData /** * Simulator Widget Preferences @@ -41,47 +42,105 @@ import dji.sdk.sdkmanager.DJISDKManager object SimulatorPresetUtils { private const val SIMULATOR_SHARED_PREFERENCES = "simulatorsharedpreferences" private const val SIMULATOR_FREQUENCY = "simulatorfrequency" - private val sharedPreferences: SharedPreferences - private val editor: SharedPreferences.Editor - - init { - sharedPreferences = DJISDKManager.getInstance().context.getSharedPreferences(SIMULATOR_SHARED_PREFERENCES, Context.MODE_PRIVATE) - @SuppressLint("CommitPrefErrors") - editor = sharedPreferences.edit() - } - - - fun savePreset(key: String, lat: Double, lng: Double, satelliteCount: Int, frequency: Int) { - editor.putString(key, "$lat $lng $satelliteCount $frequency").commit() - } - - fun savePreset(key: String, simulatorPresetData: dji.ux.beta.training.widget.simulatorcontrol.preset.SimulatorPresetData) { - editor.putString(key, simulatorPresetData.latitude - .toString() + " " + simulatorPresetData.longitude - + " " + simulatorPresetData.satelliteCount - + " " + simulatorPresetData.updateFrequency).commit() - } + private const val SIMULATOR_LAT = "simulatorLatitude" + private const val SIMULATOR_LNG = "simulatorLongitude" + private val sharedPreferences: SharedPreferences = DJISDKManager.getInstance() + .context + .getSharedPreferences(SIMULATOR_SHARED_PREFERENCES, Context.MODE_PRIVATE) + /** + * List representing saved presets in simulator control + */ val presetList: Map get() { val resultList = sharedPreferences.all resultList.remove(SIMULATOR_FREQUENCY) + resultList.remove(SIMULATOR_LAT) + resultList.remove(SIMULATOR_LNG) return resultList } - fun deletePreset(key: String?) { - editor.remove(key).commit() + /** + * Cached location latitude value for simulator start + */ + var currentSimulatorStartLat: String + get() = sharedPreferences.getString(SIMULATOR_LAT, "") ?: "" + set(value) { + sharedPreferences.edit { putString(SIMULATOR_LAT, value) } + } + + /** + * Cached location longitude value for simulator start + */ + var currentSimulatorStartLng: String + get() = sharedPreferences.getString(SIMULATOR_LNG, "") ?: "" + set(value) { + sharedPreferences.edit { putString(SIMULATOR_LNG, value) } + } + + /** + * Cached frequency value for simulator start + */ + var currentSimulatorFrequency: Int + get() = sharedPreferences.getInt(SIMULATOR_FREQUENCY, -1) + set(value) { + sharedPreferences.edit { putInt(SIMULATOR_FREQUENCY, value) } + } + + /** + * Save preset to be used later. + * + * @param key - String value to be used as key and display name. + * @param lat - Double value representing latitude. + * @param lng - Double value representing longitude. + * @param satelliteCount - Integer value representing satellite count. + * @param frequency - Integer value representing data frequency. + */ + fun savePreset(key: String, lat: Double, lng: Double, satelliteCount: Int, frequency: Int) { + sharedPreferences.edit { putString(key, "$lat $lng $satelliteCount $frequency") } } - fun saveCurrentSimulationFrequency(frequency: Int) { - editor.putInt(SIMULATOR_FREQUENCY, frequency).commit() + /** + * Save preset to be used later. + * + * @param key - String value to be used as key and display name. + * @param simulatorPresetData - instance of [SimulatorPresetData]. + */ + fun savePreset(key: String, simulatorPresetData: SimulatorPresetData) { + savePreset(key, simulatorPresetData.latitude, + simulatorPresetData.longitude, + simulatorPresetData.satelliteCount, + simulatorPresetData.updateFrequency) } - val currentSimulatorFrequency: Int - get() = sharedPreferences.getInt(SIMULATOR_FREQUENCY, -1) + /** + * Delete a preset. + * + * @param key - String key of the preset to be deleted. + */ + fun deletePreset(key: String?) { + sharedPreferences.edit { remove(key) } + } + /** + * Clear cached frequency value. + */ fun clearSimulatorFrequency() { - editor.remove(SIMULATOR_FREQUENCY).commit() + deletePreset(SIMULATOR_FREQUENCY) + } + + /** + * Clear cached latitude value. + */ + fun clearSimulatorStartLat() { + deletePreset(SIMULATOR_LAT) + } + + /** + * Clear cached longitude value. + */ + fun clearSimulatorStartLng() { + deletePreset(SIMULATOR_LNG) } } \ No newline at end of file diff --git a/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/SimulatorControlWidget.kt b/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/SimulatorControlWidget.kt index 73a14d6c..ebb0ed5d 100644 --- a/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/SimulatorControlWidget.kt +++ b/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/SimulatorControlWidget.kt @@ -18,7 +18,7 @@ * 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.ux.beta.training.widget.simulatorcontrol @@ -45,17 +45,21 @@ import dji.log.DJILog import dji.thirdparty.io.reactivex.Flowable import dji.thirdparty.io.reactivex.functions.Consumer import dji.thirdparty.io.reactivex.processors.PublishProcessor -import dji.ux.beta.core.base.* -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.SchedulerProvider +import dji.ux.beta.core.base.UXSDKError +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore +import dji.ux.beta.core.communication.OnStateChangeCallback import dji.ux.beta.core.extension.* import dji.ux.beta.core.ui.SeekBarView import dji.ux.beta.core.util.DisplayUtil import dji.ux.beta.core.util.EditTextNumberInputFilter import dji.ux.beta.training.R import dji.ux.beta.training.util.SimulatorPresetUtils -import dji.ux.beta.training.widget.simulatorcontrol.SimulatorControlWidget.SimulatorControlWidgetState -import dji.ux.beta.training.widget.simulatorcontrol.SimulatorControlWidget.SimulatorControlWidgetState.* -import dji.ux.beta.training.widget.simulatorcontrol.SimulatorControlWidget.SimulatorControlWidgetUIUpdate.* +import dji.ux.beta.training.widget.simulatorcontrol.SimulatorControlWidget.ModelState +import dji.ux.beta.training.widget.simulatorcontrol.SimulatorControlWidget.ModelState.* +import dji.ux.beta.training.widget.simulatorcontrol.SimulatorControlWidget.UIState.* import dji.ux.beta.training.widget.simulatorcontrol.preset.OnLoadPresetListener import dji.ux.beta.training.widget.simulatorcontrol.preset.PresetListDialog import dji.ux.beta.training.widget.simulatorcontrol.preset.SavePresetDialog @@ -63,6 +67,7 @@ import dji.ux.beta.training.widget.simulatorcontrol.preset.SimulatorPresetData import java.text.DecimalFormat import java.text.DecimalFormatSymbols import java.util.* +import java.util.concurrent.TimeUnit import kotlin.math.max /** @@ -78,19 +83,17 @@ open class SimulatorControlWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ConstraintLayoutWidget( +) : ConstraintLayoutWidget( context, attrs, defStyleAttr), View.OnClickListener, OnStateChangeCallback, OnLoadPresetListener { - //region fields - private val schedulerProvider: SchedulerProvider = SchedulerProvider.getInstance() + //region Fields private val widgetModel by lazy { SimulatorControlWidgetModel( DJISDKModel.getInstance(), - ObservableInMemoryKeyedStore.getInstance(), - schedulerProvider) + ObservableInMemoryKeyedStore.getInstance()) } private val latitudeEditText: EditText = findViewById(R.id.edit_text_simulator_lat) private val longitudeEditText: EditText = findViewById(R.id.edit_text_simulator_lng) @@ -112,9 +115,6 @@ open class SimulatorControlWidget @JvmOverloads constructor( private val frequencyTextView: TextView = findViewById(R.id.textview_simulator_frequency_value) private val loadPresetTextView: TextView = findViewById(R.id.textview_load_preset) private val savePresetTextView: TextView = findViewById(R.id.textview_save_preset) - private val windXTextView: TextView = findViewById(R.id.textview_wind_x) - private val windYTextView: TextView = findViewById(R.id.textview_wind_y) - private val windZTextView: TextView = findViewById(R.id.textview_wind_z) private val latitudeLabelTextView: TextView = findViewById(R.id.textview_simulator_latitude_label) private val longitudeLabelTextView: TextView = findViewById(R.id.textview_simulator_longitude_label) private val satelliteLabelTextView: TextView = findViewById(R.id.textview_simulator_satellite_label) @@ -130,6 +130,9 @@ open class SimulatorControlWidget @JvmOverloads constructor( private val windXLabelTextView: TextView = findViewById(R.id.textview_wind_speed_x_label) private val windYLabelTextView: TextView = findViewById(R.id.textview_wind_speed_y_label) private val windZLabelTextView: TextView = findViewById(R.id.textview_wind_speed_z_label) + private val windSpeedXSeekBar: SeekBarView = findViewById(R.id.seek_bar_wind_speed_x) + private val windSpeedYSeekBar: SeekBarView = findViewById(R.id.seek_bar_wind_speed_y) + private val windSpeedZSeekBar: SeekBarView = findViewById(R.id.seek_bar_wind_speed_z) private val positionSectionHeaderTextView: TextView = findViewById(R.id.textview_location_section_header) private val windSectionHeaderTextView: TextView = findViewById(R.id.textview_wind_section_header) private val attitudeSectionHeaderTextView: TextView = findViewById(R.id.textview_attitude_section_header) @@ -142,11 +145,33 @@ open class SimulatorControlWidget @JvmOverloads constructor( private lateinit var df: DecimalFormat private var shouldReactToCheckChange = false private val seekBarChangeListener = object : SeekBarView.OnSeekBarChangeListener { - override fun onProgressChanged(seekBarView: SeekBarView, progress: Int) { - if (seekBarView == satelliteCountSeekBar) { - satelliteCountSeekBar.setText(satelliteCountSeekBar.progress.toString()) - } else if (seekBarView == frequencySeekBar) { - frequencySeekBar.setText(max(MIN_FREQUENCY, frequencySeekBar.progress).toString()) + override fun onProgressChanged(seekBarView: SeekBarView, progress: Int, isFromUI: Boolean) { + when (seekBarView) { + satelliteCountSeekBar -> { + satelliteCountSeekBar.text = satelliteCountSeekBar.progress.toString() + } + frequencySeekBar -> { + frequencySeekBar.text = max(MIN_FREQUENCY, frequencySeekBar.progress).toString() + } + windSpeedXSeekBar -> { + seekBarView.text = normalizeWindValue(seekBarView.progress).toString() + if (isFromUI) { + setWindSpeedUI(WIND_DIRECTION_X, seekBarView.progress > 0) + } + + } + windSpeedYSeekBar -> { + seekBarView.text = normalizeWindValue(seekBarView.progress).toString() + if (isFromUI) { + setWindSpeedUI(WIND_DIRECTION_Y, seekBarView.progress > 0) + } + } + windSpeedZSeekBar -> { + seekBarView.text = normalizeWindValue(seekBarView.progress).toString() + if (isFromUI) { + setWindSpeedUI(WIND_DIRECTION_Z, seekBarView.progress > 0) + } + } } } @@ -168,7 +193,7 @@ open class SimulatorControlWidget @JvmOverloads constructor( } - private val uiUpdateStateProcessor: PublishProcessor = PublishProcessor.create() + private val uiUpdateStateProcessor: PublishProcessor = PublishProcessor.create() /** * The drawable resource for the simulator active icon @@ -280,9 +305,6 @@ open class SimulatorControlWidget @JvmOverloads constructor( yawTextView.textColorStateList = value rollTextView.textColorStateList = value frequencyTextView.textColorStateList = value - windXTextView.textColorStateList = value - windYTextView.textColorStateList = value - windZTextView.textColorStateList = value } /** @@ -303,16 +325,13 @@ open class SimulatorControlWidget @JvmOverloads constructor( yawTextView.textColor = value rollTextView.textColor = value frequencyTextView.textColor = value - windXTextView.textColor = value - windYTextView.textColor = value - windZTextView.textColor = value } /** * Text size of the value fields */ var valueTextSize: Float - get() = windZTextView.textSize + get() = latitudeTextView.textSize set(textSize) { latitudeTextView.textSize = textSize longitudeTextView.textSize = textSize @@ -326,9 +345,6 @@ open class SimulatorControlWidget @JvmOverloads constructor( yawTextView.textSize = textSize rollTextView.textSize = textSize frequencyTextView.textSize = textSize - windXTextView.textSize = textSize - windYTextView.textSize = textSize - windZTextView.textSize = textSize } /** @@ -349,9 +365,6 @@ open class SimulatorControlWidget @JvmOverloads constructor( yawTextView.background = value rollTextView.background = value frequencyTextView.background = value - windXTextView.background = value - windYTextView.background = value - windZTextView.background = value } /** @@ -574,9 +587,48 @@ open class SimulatorControlWidget @JvmOverloads constructor( windSectionHeaderTextView.textColor = value } + /** + * Text color of the seek bar value + */ + var seekBarTextColor: Int + get() = frequencySeekBar.valueTextColor + set(value) { + frequencySeekBar.valueTextColor = value + satelliteCountSeekBar.valueTextColor = value + windSpeedXSeekBar.valueTextColor = value + windSpeedYSeekBar.valueTextColor = value + windSpeedZSeekBar.valueTextColor = value + } + + /** + * Drawable for the seek bar track + */ + var seekBarTrackIconBackground: Drawable? + get() = frequencySeekBar.trackIconBackground + set(value) { + frequencySeekBar.trackIconBackground = value + satelliteCountSeekBar.trackIconBackground = value + windSpeedXSeekBar.trackIconBackground = value + windSpeedYSeekBar.trackIconBackground = value + windSpeedZSeekBar.trackIconBackground = value + } + + /** + * Drawable for the seek bar thumb + */ + var seekBarThumbIcon: Drawable? + get() = frequencySeekBar.thumbIcon + set(value) { + frequencySeekBar.thumbIcon = value + satelliteCountSeekBar.thumbIcon = value + windSpeedXSeekBar.thumbIcon = value + windSpeedYSeekBar.thumbIcon = value + windSpeedZSeekBar.thumbIcon = value + } + //endregion - //region lifecycle + //region Lifecycle override fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { View.inflate(context, R.layout.uxsdk_widget_simulator_control, this) } @@ -589,19 +641,20 @@ open class SimulatorControlWidget @JvmOverloads constructor( override fun reactToModelChanges() { addReaction(widgetModel.productConnection - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { onProductChanged(it) }) addReaction(widgetModel.satelliteCount - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { this.updateSatelliteCount(it) }) addReaction(widgetModel.simulatorWindData - .observeOn(schedulerProvider.ui()) + .debounce(500, TimeUnit.MILLISECONDS) + .observeOn(SchedulerProvider.ui()) .subscribe { this.updateWindValues(it) }) addReaction(widgetModel.simulatorState - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { this.updateWidgetValues(it) }) addReaction(widgetModel.isSimulatorActive - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe { this.updateUI(it) }) } @@ -640,19 +693,13 @@ open class SimulatorControlWidget @JvmOverloads constructor( override fun onClick(v: View) { when (v.id) { R.id.textview_load_preset -> { - uiUpdateStateProcessor.onNext(LoadPresetTap) + uiUpdateStateProcessor.onNext(LoadPresetClicked) showPresetListDialog() } R.id.textview_save_preset -> { - uiUpdateStateProcessor.onNext(SavePresetTap) + uiUpdateStateProcessor.onNext(SavePresetClicked) showSavePresetDialog() } - R.id.imageview_btn_plus_x -> setWindSpeedUI(WIND_DIRECTION_X, true) - R.id.imageview_btn_plus_y -> setWindSpeedUI(WIND_DIRECTION_Y, true) - R.id.imageview_btn_plus_z -> setWindSpeedUI(WIND_DIRECTION_Z, true) - R.id.imageview_btn_minus_x -> setWindSpeedUI(WIND_DIRECTION_X, false) - R.id.imageview_btn_minus_y -> setWindSpeedUI(WIND_DIRECTION_Y, false) - R.id.imageview_btn_minus_z -> setWindSpeedUI(WIND_DIRECTION_Z, false) } } @@ -669,7 +716,7 @@ open class SimulatorControlWidget @JvmOverloads constructor( } else { View.VISIBLE } - uiUpdateStateProcessor.onNext(VisibilityToggled(visibility == View.VISIBLE)) + uiUpdateStateProcessor.onNext(VisibilityUpdated(visibility == View.VISIBLE)) } @SuppressLint("Recycle") @@ -776,30 +823,14 @@ open class SimulatorControlWidget @JvmOverloads constructor( } private fun setWindSpeedUI(windDirection: Int, isPositive: Boolean) { - uiUpdateStateProcessor.onNext(SimulatorWindChangeTap(windDirection, isPositive)) - - val change = if (isPositive) 1 else -1 - var textView = windXTextView - when (windDirection) { - WIND_DIRECTION_X -> textView = windXTextView - WIND_DIRECTION_Y -> textView = windYTextView - WIND_DIRECTION_Z -> textView = windZTextView - } - var currentValue = textView.text.toString().toInt() - currentValue += change - if (currentValue > SIMULATION_MAX_WIND_SPEED) { - currentValue = SIMULATION_MAX_WIND_SPEED - } else if (currentValue < SIMULATION_MIN_WIND_SPEED) { - currentValue = SIMULATION_MIN_WIND_SPEED - } - textView.text = currentValue.toString() + uiUpdateStateProcessor.onNext(SimulatorWindChangeClicked(windDirection, isPositive)) addDisposable(widgetModel.setSimulatorWindData(SimulatorWindData.Builder() - .windSpeedX(windXTextView.text.toString().toInt()) - .windSpeedY(windYTextView.text.toString().toInt()) - .windSpeedZ(windZTextView.text.toString().toInt()) - .build()) - .subscribeOn(schedulerProvider.io()) - .observeOn(schedulerProvider.ui()) + .windSpeedX(normalizeWindValue(windSpeedXSeekBar.progress)) + .windSpeedY(normalizeWindValue(windSpeedYSeekBar.progress)) + .windSpeedZ(normalizeWindValue(windSpeedZSeekBar.progress)) + .build()) + .subscribeOn(SchedulerProvider.io()) + .observeOn(SchedulerProvider.ui()) .subscribe({}) { error: Throwable -> if (error is UXSDKError) { DJILog.e(TAG, error.toString()) @@ -812,7 +843,7 @@ open class SimulatorControlWidget @JvmOverloads constructor( if (!isInEditMode) { addDisposable(widgetModel.simulatorWindData .lastOrError() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { simulatorWindData -> updateWindValues(simulatorWindData) }, logErrorConsumer(TAG, "Update wind"))) } @@ -836,7 +867,7 @@ open class SimulatorControlWidget @JvmOverloads constructor( private fun checkAndUpdateState() { if (!isInEditMode) { addDisposable(widgetModel.isSimulatorActive.firstOrError() - .observeOn(schedulerProvider.ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(Consumer { this.updateUI(it) }, logErrorConsumer(TAG, "Update Icon "))) } @@ -851,12 +882,6 @@ open class SimulatorControlWidget @JvmOverloads constructor( longitudeEditText.filters = arrayOf(EditTextNumberInputFilter("-180", "180")) loadPresetTextView.setOnClickListener(this) savePresetTextView.setOnClickListener(this) - findViewById(R.id.imageview_btn_plus_x).setOnClickListener(this) - findViewById(R.id.imageview_btn_plus_y).setOnClickListener(this) - findViewById(R.id.imageview_btn_plus_z).setOnClickListener(this) - findViewById(R.id.imageview_btn_minus_x).setOnClickListener(this) - findViewById(R.id.imageview_btn_minus_y).setOnClickListener(this) - findViewById(R.id.imageview_btn_minus_z).setOnClickListener(this) simulatorSwitch.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean -> handleSwitchChange(isChecked) } valueTextColor = getColor(R.color.uxsdk_blue) titleTextColor = getColor(R.color.uxsdk_white) @@ -876,7 +901,29 @@ open class SimulatorControlWidget @JvmOverloads constructor( frequencySeekBar.enable(true) frequencySeekBar.progress = DEFAULT_FREQUENCY + windSpeedXSeekBar.max = WIND_SEEK_BAR_MAX + windSpeedXSeekBar.addOnSeekBarChangeListener(seekBarChangeListener) + windSpeedXSeekBar.enable(true) + windSpeedXSeekBar.progress = WIND_SEEK_BAR_MAX / 2 + windSpeedYSeekBar.max = WIND_SEEK_BAR_MAX + windSpeedYSeekBar.addOnSeekBarChangeListener(seekBarChangeListener) + windSpeedYSeekBar.enable(true) + windSpeedYSeekBar.progress = WIND_SEEK_BAR_MAX / 2 + + windSpeedZSeekBar.max = WIND_SEEK_BAR_MAX + windSpeedZSeekBar.addOnSeekBarChangeListener(seekBarChangeListener) + windSpeedZSeekBar.enable(true) + windSpeedZSeekBar.progress = WIND_SEEK_BAR_MAX / 2 + + } + + private fun normalizeWindValue(progress: Int): Int { + return progress - SIMULATION_MAX_WIND_SPEED + } + + private fun deNormalizeWindValue(progress: Int): Int { + return progress + SIMULATION_MAX_WIND_SPEED } private fun handleSwitchChange(isChecked: Boolean) { @@ -903,13 +950,15 @@ open class SimulatorControlWidget @JvmOverloads constructor( if (locationCoordinate2D != null) { setSimulatorStatus(true) - SimulatorPresetUtils.saveCurrentSimulationFrequency(frequencySeekBar.progress) + SimulatorPresetUtils.currentSimulatorFrequency = frequencySeekBar.progress + SimulatorPresetUtils.currentSimulatorStartLat = latitudeEditText.text.toString() + SimulatorPresetUtils.currentSimulatorStartLng = longitudeEditText.text.toString() val initializationData = InitializationData.createInstance(locationCoordinate2D, max(MIN_FREQUENCY, frequencySeekBar.progress), satelliteCountSeekBar.progress) addDisposable(widgetModel.startSimulator(initializationData) - .subscribeOn(schedulerProvider.io()) - .observeOn(schedulerProvider.ui()) + .subscribeOn(SchedulerProvider.io()) + .observeOn(SchedulerProvider.ui()) .subscribe({}) { error: Throwable -> if (error is UXSDKError) { DJILog.e(TAG, error.toString()) @@ -930,6 +979,8 @@ open class SimulatorControlWidget @JvmOverloads constructor( updateWidgetToStartedState() } else { SimulatorPresetUtils.clearSimulatorFrequency() + SimulatorPresetUtils.clearSimulatorStartLat() + SimulatorPresetUtils.clearSimulatorStartLng() updateWidgetToStoppedState() } shouldReactToCheckChange = true @@ -937,15 +988,27 @@ open class SimulatorControlWidget @JvmOverloads constructor( private fun updateWidgetToStartedState() { simulatorSwitch.isChecked = true - latitudeTextView.text = if (latitudeEditText.text.toString().isEmpty()) { - getString(R.string.uxsdk_simulator_null_string) - } else { - df.format(latitudeEditText.text.toString().toDouble()) + latitudeTextView.text = when { + latitudeEditText.text.toString().isNotEmpty() -> { + df.format(latitudeEditText.text.toString().toDouble()) + } + SimulatorPresetUtils.currentSimulatorStartLat.isNotEmpty() -> { + SimulatorPresetUtils.currentSimulatorStartLat + } + else -> { + getString(R.string.uxsdk_simulator_null_string) + } } - longitudeTextView.text = if (longitudeEditText.text.toString().isEmpty()) { - getString(R.string.uxsdk_simulator_null_string) - } else { - df.format(longitudeEditText.text.toString().toDouble()) + longitudeTextView.text = when { + longitudeEditText.text.toString().isNotEmpty() -> { + df.format(longitudeEditText.text.toString().toDouble()) + } + SimulatorPresetUtils.currentSimulatorStartLng.isNotEmpty() -> { + SimulatorPresetUtils.currentSimulatorStartLng + } + else -> { + getString(R.string.uxsdk_simulator_null_string) + } } val simulatorFrequency = SimulatorPresetUtils.currentSimulatorFrequency frequencyTextView.text = if (simulatorFrequency > 0) { @@ -987,9 +1050,9 @@ open class SimulatorControlWidget @JvmOverloads constructor( rollTextView.text = getString(R.string.uxsdk_simulator_null_string) motorsStartedTextView.text = getString(R.string.uxsdk_simulator_null_string) aircraftFlyingTextView.text = getString(R.string.uxsdk_simulator_null_string) - windXTextView.text = getString(R.string.uxsdk_simulator_zero_string) - windYTextView.text = getString(R.string.uxsdk_simulator_zero_string) - windZTextView.text = getString(R.string.uxsdk_simulator_zero_string) + windSpeedXSeekBar.progress = 20 + windSpeedYSeekBar.progress = 20 + windSpeedZSeekBar.progress = 20 simulatorTitleTextView.setCompoundDrawablesWithIntrinsicBounds(simulatorInactiveIcon, null, null, null) realWorldPositionGroup.visibility = View.GONE windSimulationGroup.visibility = View.GONE @@ -1014,9 +1077,24 @@ open class SimulatorControlWidget @JvmOverloads constructor( private fun updateWindValues(simulatorWindData: SimulatorWindData) { widgetStateDataProcessor.onNext(SimulatorWindDataUpdated(simulatorWindData)) - windXTextView.text = simulatorWindData.windSpeedX.toString() - windYTextView.text = simulatorWindData.windSpeedY.toString() - windZTextView.text = simulatorWindData.windSpeedZ.toString() + if (simulatorWindData.windSpeedX.toString() != windSpeedXSeekBar.text) { + windSpeedXSeekBar.text = simulatorWindData.windSpeedX.toString() + } + if (deNormalizeWindValue(simulatorWindData.windSpeedX) != windSpeedXSeekBar.progress) { + windSpeedXSeekBar.progress = deNormalizeWindValue(simulatorWindData.windSpeedX) + } + if (simulatorWindData.windSpeedY.toString() != windSpeedYSeekBar.text) { + windSpeedYSeekBar.text = simulatorWindData.windSpeedY.toString() + } + if (deNormalizeWindValue(simulatorWindData.windSpeedY) != windSpeedYSeekBar.progress) { + windSpeedYSeekBar.progress = deNormalizeWindValue(simulatorWindData.windSpeedY) + } + if (simulatorWindData.windSpeedZ.toString() != windSpeedZSeekBar.text) { + windSpeedZSeekBar.text = simulatorWindData.windSpeedZ.toString() + } + if (deNormalizeWindValue(simulatorWindData.windSpeedZ) != windSpeedZSeekBar.progress) { + windSpeedZSeekBar.progress = deNormalizeWindValue(simulatorWindData.windSpeedZ) + } } private val simulatedLocation: LocationCoordinate2D? @@ -1107,9 +1185,6 @@ open class SimulatorControlWidget @JvmOverloads constructor( yawTextView.setTextAppearance(context, textAppearance) rollTextView.setTextAppearance(context, textAppearance) frequencyTextView.setTextAppearance(context, textAppearance) - windXTextView.setTextAppearance(context, textAppearance) - windYTextView.setTextAppearance(context, textAppearance) - windZTextView.setTextAppearance(context, textAppearance) } /** @@ -1211,74 +1286,75 @@ open class SimulatorControlWidget @JvmOverloads constructor( fun setHeaderBackground(@DrawableRes resourceId: Int) { headerBackground = getDrawable(resourceId) } + //endregion + //region Hooks /** - * Get the [SimulatorControlWidgetUIUpdate] updates + * Get the [UIState] updates * */ - fun getUIStateUpdates(): Flowable { - return uiUpdateStateProcessor + fun getUIStateUpdates(): Flowable { + return uiUpdateStateProcessor.onBackpressureBuffer() } /** - * Get the [SimulatorControlWidgetState] updates + * Get the [ModelState] updates */ - override fun getWidgetStateUpdate(): Flowable { + @SuppressWarnings + override fun getWidgetStateUpdate(): Flowable { return super.getWidgetStateUpdate() } - //endregion - /** * * Class defines widget state updates */ - sealed class SimulatorControlWidgetState { + sealed class ModelState { /** * Product connection update */ - data class ProductConnected(val isConnected: Boolean) : SimulatorControlWidgetState() + data class ProductConnected(val isConnected: Boolean) : ModelState() /** * Simulator state update */ - data class SimulatorStateUpdated(val simulatorState: SimulatorState) : SimulatorControlWidgetState() + data class SimulatorStateUpdated(val simulatorState: SimulatorState) : ModelState() /** * Simulator active/inactive update */ - data class SimulatorActiveUpdated(val isActive: Boolean) : SimulatorControlWidgetState() + data class SimulatorActiveUpdated(val isActive: Boolean) : ModelState() /** * Simulator wind data update */ - data class SimulatorWindDataUpdated(val windData: SimulatorWindData) : SimulatorControlWidgetState() + data class SimulatorWindDataUpdated(val windData: SimulatorWindData) : ModelState() } /** * Class defines the widget UI updates */ - sealed class SimulatorControlWidgetUIUpdate { + sealed class UIState { /** * Update when widget visibility is toggled */ - data class VisibilityToggled(val isVisible: Boolean) : SimulatorControlWidgetUIUpdate() + data class VisibilityUpdated(val isVisible: Boolean) : UIState() /** * Update when load preset button is tapped */ - object LoadPresetTap : SimulatorControlWidgetUIUpdate() + object LoadPresetClicked : UIState() /** * Update when save preset button is tapped */ - object SavePresetTap : SimulatorControlWidgetUIUpdate() + object SavePresetClicked : UIState() /** * Update when start/stop simulator switch is tapped */ - data class SimulatorSwitchTap(val isChecked: Boolean) : SimulatorControlWidgetUIUpdate() + data class SimulatorSwitchTap(val isChecked: Boolean) : UIState() /** * Update when simulator wind variation button tapped @@ -1287,10 +1363,11 @@ open class SimulatorControlWidget @JvmOverloads constructor( * 1 - Y * 2 - Z */ - data class SimulatorWindChangeTap(@IntRange(from = 0, to = 2) val windDirection: Int, - val isPositive: Boolean) : SimulatorControlWidgetUIUpdate() + data class SimulatorWindChangeClicked(@IntRange(from = 0, to = 2) val windDirection: Int, + val isPositive: Boolean) : UIState() } + //endregion companion object { private const val TAG = "SimulatorCtlWidget" @@ -1299,8 +1376,9 @@ open class SimulatorControlWidget @JvmOverloads constructor( private const val WIND_DIRECTION_Z = 2 private const val MIN_FREQUENCY = 2 private const val DEFAULT_FREQUENCY = 20 - private const val SIMULATION_MIN_WIND_SPEED = 0 + private const val SIMULATION_MIN_WIND_SPEED = -20 private const val SIMULATION_MAX_WIND_SPEED = 20 + private const val WIND_SEEK_BAR_MAX = 40 } } \ No newline at end of file diff --git a/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/SimulatorControlWidgetModel.kt b/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/SimulatorControlWidgetModel.kt index 42966f44..8c0b954e 100644 --- a/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/SimulatorControlWidgetModel.kt +++ b/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/SimulatorControlWidgetModel.kt @@ -18,7 +18,7 @@ * 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.ux.beta.training.widget.simulatorcontrol @@ -30,8 +30,11 @@ import dji.keysdk.DJIKey import dji.keysdk.FlightControllerKey import dji.thirdparty.io.reactivex.Completable import dji.thirdparty.io.reactivex.Flowable -import dji.ux.beta.core.base.* -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore +import dji.ux.beta.core.base.DJISDKModel +import dji.ux.beta.core.base.UXSDKError +import dji.ux.beta.core.base.UXSDKErrorDescription +import dji.ux.beta.core.base.WidgetModel +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore import dji.ux.beta.core.util.DataProcessor /** @@ -42,8 +45,8 @@ import dji.ux.beta.core.util.DataProcessor * underlying logic and communication */ class SimulatorControlWidgetModel(djiSdkModel: DJISDKModel, - keyedStore: ObservableInMemoryKeyedStore, - private val schedulerProvider: SchedulerProviderInterface) : WidgetModel(djiSdkModel, keyedStore) { + keyedStore: ObservableInMemoryKeyedStore +) : WidgetModel(djiSdkModel, keyedStore) { //region private fields private val simulatorStateBuilder = SimulatorState.Builder() private val windBuilder = SimulatorWindData.Builder() @@ -89,7 +92,7 @@ class SimulatorControlWidgetModel(djiSdkModel: DJISDKModel, */ fun startSimulator(initializationData: InitializationData): Completable { val startSimulatorKey: DJIKey = FlightControllerKey.create(FlightControllerKey.START_SIMULATOR) - return djiSdkModel.performAction(startSimulatorKey, initializationData).subscribeOn(schedulerProvider.io()) + return djiSdkModel.performAction(startSimulatorKey, initializationData) } /** @@ -99,18 +102,19 @@ class SimulatorControlWidgetModel(djiSdkModel: DJISDKModel, */ fun stopSimulator(): Completable { val stopSimulatorKey: DJIKey = FlightControllerKey.create(FlightControllerKey.STOP_SIMULATOR) - return djiSdkModel.performAction(stopSimulatorKey).subscribeOn(schedulerProvider.io()) + return djiSdkModel.performAction(stopSimulatorKey) } /** * Set values to simulate wind in x, y and z directions + * The unit for wind speed is m/s * * @param simulatorWindData [SimulatorWindData] instance with values to simulate * @return Completable to determine status of the action */ fun setSimulatorWindData(simulatorWindData: SimulatorWindData): Completable { return if (simulatorActiveDataProcessor.value) { - djiSdkModel.setValue(simulatorWindDataKey, simulatorWindData).subscribeOn(schedulerProvider.io()) + djiSdkModel.setValue(simulatorWindDataKey, simulatorWindData) } else { Completable.error(UXSDKError(UXSDKErrorDescription.SIMULATOR_WIND_ERROR)) } @@ -127,6 +131,8 @@ class SimulatorControlWidgetModel(djiSdkModel: DJISDKModel, /** * Get the current wind simulation values. Includes * wind speed in x, y and z directions + * The unit for wind speed is m/s + * */ val simulatorWindData: Flowable get() = simulatorWindDataProcessor.toFlowable() diff --git a/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/preset/OnLoadPresetListener.kt b/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/preset/OnLoadPresetListener.kt index 215d1c99..0722e2c6 100644 --- a/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/preset/OnLoadPresetListener.kt +++ b/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/preset/OnLoadPresetListener.kt @@ -18,7 +18,7 @@ * 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.ux.beta.training.widget.simulatorcontrol.preset diff --git a/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/preset/PresetListDialog.kt b/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/preset/PresetListDialog.kt index b2d7d426..7d425aae 100644 --- a/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/preset/PresetListDialog.kt +++ b/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/preset/PresetListDialog.kt @@ -18,11 +18,12 @@ * 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.ux.beta.training.widget.simulatorcontrol.preset +import android.annotation.SuppressLint import android.app.Dialog import android.content.Context import android.os.Bundle @@ -49,7 +50,7 @@ class PresetListDialog @JvmOverloads constructor( private val dialogHeight: Int = context.resources.getDimension(R.dimen.uxsdk_simulator_dialog_height).toInt() ) : Dialog(context), View.OnClickListener { - //region fields + //region Fields private val presetListContainerLinearLayout: LinearLayout private val emptyPresetListTextView: TextView private val confirmDeleteTextView: TextView @@ -59,7 +60,7 @@ class PresetListDialog @JvmOverloads constructor( private var keyToDelete: String? = null //endregion - //region lifecycle + //region Lifecycle override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) window?.setBackgroundDrawableResource(R.drawable.uxsdk_background_dialog_rounded_corners) @@ -166,6 +167,7 @@ class PresetListDialog @JvmOverloads constructor( dismiss() } + @SuppressLint("InflateParams") private fun insertView(key: String, value: String) { val presetRow = LayoutInflater.from(context).inflate(R.layout.uxsdk_layout_simulator_preset_row, null) presetListContainerLinearLayout.addView(presetRow, presetListContainerLinearLayout.childCount) diff --git a/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/preset/SavePresetDialog.kt b/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/preset/SavePresetDialog.kt index 5c9f9d36..d68760d2 100644 --- a/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/preset/SavePresetDialog.kt +++ b/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/preset/SavePresetDialog.kt @@ -18,7 +18,7 @@ * 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.ux.beta.training.widget.simulatorcontrol.preset @@ -54,7 +54,7 @@ class SavePresetDialog ( simulatorPresetData: SimulatorPresetData ) : Dialog(context), View.OnClickListener { - //region fields + //region Fields private val simulatorPresetData: SimulatorPresetData private val titleTextView: TextView private val saveTextView: TextView @@ -62,7 +62,7 @@ class SavePresetDialog ( private val presetEditText: EditText //endregion - //region lifecycle + //region Lifecycle override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) window?.setBackgroundDrawableResource(R.drawable.uxsdk_background_dialog_rounded_corners) diff --git a/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/preset/SimulatorPresetData.kt b/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/preset/SimulatorPresetData.kt index 86e75371..066fe63b 100644 --- a/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/preset/SimulatorPresetData.kt +++ b/android-uxsdk-beta-training/src/main/java/dji/ux/beta/training/widget/simulatorcontrol/preset/SimulatorPresetData.kt @@ -18,7 +18,7 @@ * 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.ux.beta.training.widget.simulatorcontrol.preset diff --git a/android-uxsdk-beta-training/src/main/res/layout/uxsdk_widget_simulator_control.xml b/android-uxsdk-beta-training/src/main/res/layout/uxsdk_widget_simulator_control.xml index d5471f12..ae649945 100644 --- a/android-uxsdk-beta-training/src/main/res/layout/uxsdk_widget_simulator_control.xml +++ b/android-uxsdk-beta-training/src/main/res/layout/uxsdk_widget_simulator_control.xml @@ -24,7 +24,6 @@ @@ -32,10 +31,9 @@ android:id="@+id/textview_simulator_title" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginLeft="5dp" + android:layout_marginStart="5dp" android:layout_marginTop="5dp" android:drawableStart="@drawable/uxsdk_ic_simulator" - android:drawableLeft="@drawable/uxsdk_ic_simulator" android:drawablePadding="5dp" android:padding="@dimen/uxsdk_simulator_dialog_padding" android:text="@string/uxsdk_simulator_widget_title" @@ -50,7 +48,7 @@ android:layout_marginTop="5dp" android:thumb="@drawable/uxsdk_selector_switch_thumb" android:track="@drawable/uxsdk_switch_background" - android:layout_marginRight="5dp" + android:layout_marginEnd="5dp" app:layout_constraintHorizontal_weight="1" app:layout_constraintTop_toTopOf="parent" /> @@ -119,7 +117,7 @@ android:background="@color/uxsdk_white" android:hint="@string/uxsdk_simulator_widget_lat_hint" android:inputType="numberSigned|numberDecimal" - android:paddingLeft="2dp" + android:paddingStart="2dp" android:textColor="@color/uxsdk_black" android:textSize="@dimen/uxsdk_simulator_row_text_size" app:layout_constraintBottom_toTopOf="@+id/edit_text_simulator_lng" @@ -164,7 +162,7 @@ android:layout_marginTop="@dimen/uxsdk_simulator_row_margin" android:layout_marginBottom="@dimen/uxsdk_simulator_row_margin" android:background="@color/uxsdk_white" - android:paddingLeft="2dp" + android:paddingStart="2dp" android:hint="@string/uxsdk_simulator_widget_lng_hint" android:inputType="numberSigned|numberDecimal" android:textColor="@color/uxsdk_black" @@ -262,7 +260,7 @@ app:uxsdk_maxValueVisible="false" app:uxsdk_minValueVisible="false" /> - + + + + + textview_wind_speed_x_label, seek_bar_wind_speed_x, + textview_wind_speed_y_label, seek_bar_wind_speed_y, + textview_wind_speed_z_label, seek_bar_wind_speed_z" /> - - - - - + app:uxsdk_maxValueVisible="false" + app:uxsdk_minValueVisible="false" /> - - - - - + android:layout_marginLeft="@dimen/uxsdk_simulator_seek_bar_margin" + android:layout_marginRight="@dimen/uxsdk_simulator_seek_bar_margin" + android:textColor="@color/uxsdk_white" + app:layout_constraintBottom_toTopOf="@+id/seek_bar_wind_speed_z" + app:layout_constraintLeft_toRightOf="@+id/textview_wind_speed_y_label" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toBottomOf="@+id/seek_bar_wind_speed_x" + app:uxsdk_maxValueVisible="false" + app:uxsdk_minValueVisible="false" /> - - - - - + app:layout_constraintLeft_toRightOf="@+id/textview_wind_speed_z_label" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toBottomOf="@+id/seek_bar_wind_speed_y" + app:uxsdk_maxValueVisible="false" + app:uxsdk_minValueVisible="false" /> + exposureSettingsProcessor; private DataProcessor apertureProcessor; private int cameraIndex; + private SettingsDefinitions.LensType lensType = SettingsDefinitions.LensType.ZOOM; //endregion //region Constructor @@ -82,6 +83,26 @@ public void setCameraIndex(@NonNull SettingDefinitions.CameraIndex cameraIndex) restart(); } + /** + * Get the current type of the lens the widget model is reacting to + * + * @return current lens type + */ + @NonNull + public SettingsDefinitions.LensType getLensType() { + return lensType; + } + + /** + * Set the type of the lens for which the widget model should react + * + * @param lensType lens type + */ + public void setLensType(@NonNull SettingsDefinitions.LensType lensType) { + this.lensType = lensType; + restart(); + } + /** * Get the aperture. * @@ -95,7 +116,7 @@ public Flowable getAperture() { //region LifeCycle @Override protected void inSetup() { - DJIKey exposureSettingsKey = CameraKey.create(CameraKey.EXPOSURE_SETTINGS, cameraIndex); + DJIKey exposureSettingsKey = djiSdkModel.createLensKey(CameraKey.EXPOSURE_SETTINGS, cameraIndex, lensType.value()); bindDataProcessor(exposureSettingsKey, exposureSettingsProcessor, exposureSettings -> { ExposureSettings settings = (ExposureSettings) exposureSettings; if (settings.getAperture() != null) { diff --git a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/ev/CameraConfigEVWidget.java b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/ev/CameraConfigEVWidget.java index ebc0e9bb..f0afe69b 100644 --- a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/ev/CameraConfigEVWidget.java +++ b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/ev/CameraConfigEVWidget.java @@ -38,10 +38,10 @@ import androidx.annotation.StyleRes; import dji.common.camera.SettingsDefinitions; -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers; -import dji.ux.beta.core.base.ConstraintLayoutWidget; import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.base.SchedulerProvider; +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; import dji.ux.beta.core.util.CameraUtil; import dji.ux.beta.core.util.DisplayUtil; import dji.ux.beta.core.util.SettingDefinitions; @@ -58,7 +58,7 @@ public class CameraConfigEVWidget extends ConstraintLayoutWidget { private TextView evValueTextView; //endregion - //region Constructors + //region Constructor public CameraConfigEVWidget(@NonNull Context context) { super(context); } @@ -109,10 +109,10 @@ protected void onDetachedFromWindow() { @Override protected void reactToModelChanges() { addReaction(widgetModel.getExposureCompensation() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::updateUI)); addReaction(widgetModel.getExposureSensitivityMode() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::updateVisibility)); } //endregion @@ -155,7 +155,30 @@ public SettingDefinitions.CameraIndex getCameraIndex() { * @param cameraIndex {@link SettingDefinitions.CameraIndex} */ public void setCameraIndex(@NonNull SettingDefinitions.CameraIndex cameraIndex) { - widgetModel.setCameraIndex(cameraIndex); + if (!isInEditMode()) { + widgetModel.setCameraIndex(cameraIndex); + } + } + + /** + * Get the current type of the lens the widget is reacting to + * + * @return current lens type + */ + @NonNull + public SettingsDefinitions.LensType getLensType() { + return widgetModel.getLensType(); + } + + /** + * Set the type of the lens for which the widget should react + * + * @param lensType lens type + */ + public void setLensType(@NonNull SettingsDefinitions.LensType lensType) { + if (!isInEditMode()) { + widgetModel.setLensType(lensType); + } } /** @@ -351,9 +374,8 @@ public void setEVValueTextBackground(@Nullable Drawable drawable) { private void initAttributes(@NonNull Context context, @NonNull AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CameraConfigEVWidget); - if (!isInEditMode()) { - setCameraIndex(SettingDefinitions.CameraIndex.find(typedArray.getInt(R.styleable.CameraConfigEVWidget_uxsdk_cameraIndex, 0))); - } + setCameraIndex(SettingDefinitions.CameraIndex.find(typedArray.getInt(R.styleable.CameraConfigEVWidget_uxsdk_cameraIndex, 0))); + setLensType(SettingsDefinitions.LensType.find(typedArray.getInt(R.styleable.CameraConfigEVWidget_uxsdk_lensType, 0))); int evTitleTextAppearanceId = typedArray.getResourceId(R.styleable.CameraConfigEVWidget_uxsdk_evTitleTextAppearance, INVALID_RESOURCE); diff --git a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/ev/CameraConfigEVWidgetModel.java b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/ev/CameraConfigEVWidgetModel.java index 6dea2c18..8c84b9aa 100644 --- a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/ev/CameraConfigEVWidgetModel.java +++ b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/ev/CameraConfigEVWidgetModel.java @@ -26,6 +26,7 @@ import androidx.annotation.NonNull; import dji.common.camera.ExposureSettings; +import dji.common.camera.SettingsDefinitions; import dji.common.camera.SettingsDefinitions.Aperture; import dji.common.camera.SettingsDefinitions.ExposureCompensation; import dji.common.camera.SettingsDefinitions.ExposureMode; @@ -36,7 +37,7 @@ import dji.thirdparty.io.reactivex.Flowable; import dji.ux.beta.core.base.DJISDKModel; import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; import dji.ux.beta.core.util.DataProcessor; import dji.ux.beta.core.util.SettingDefinitions; @@ -52,6 +53,7 @@ public class CameraConfigEVWidgetModel extends WidgetModel { private DataProcessor exposureSensitivityModeProcessor; private DataProcessor consolidatedExposureCompensationProcessor; private int cameraIndex; + private SettingsDefinitions.LensType lensType = SettingsDefinitions.LensType.ZOOM; //endregion //region Constructor @@ -92,6 +94,26 @@ public void setCameraIndex(@NonNull SettingDefinitions.CameraIndex cameraIndex) restart(); } + /** + * Get the current type of the lens the widget model is reacting to + * + * @return current lens type + */ + @NonNull + public SettingsDefinitions.LensType getLensType() { + return lensType; + } + + /** + * Set the type of the lens for which the widget model should react + * + * @param lensType lens type + */ + public void setLensType(@NonNull SettingsDefinitions.LensType lensType) { + this.lensType = lensType; + restart(); + } + /** * Get the exposure sensitivity mode. * @@ -114,9 +136,9 @@ public Flowable getExposureCompensation() { //region Lifecycle @Override protected void inSetup() { - DJIKey exposureSettingsKey = CameraKey.create(CameraKey.EXPOSURE_SETTINGS, cameraIndex); - DJIKey exposureModeKey = CameraKey.create(CameraKey.EXPOSURE_MODE, cameraIndex); - DJIKey exposureCompensationKey = CameraKey.create(CameraKey.EXPOSURE_COMPENSATION, cameraIndex); + DJIKey exposureSettingsKey = djiSdkModel.createLensKey(CameraKey.EXPOSURE_SETTINGS, cameraIndex, lensType.value()); + DJIKey exposureModeKey = djiSdkModel.createLensKey(CameraKey.EXPOSURE_MODE, cameraIndex, lensType.value()); + DJIKey exposureCompensationKey = djiSdkModel.createLensKey(CameraKey.EXPOSURE_COMPENSATION, cameraIndex, lensType.value()); DJIKey exposureSensitivityModeKey = CameraKey.create(CameraKey.EXPOSURE_SENSITIVITY_MODE, cameraIndex); bindDataProcessor(exposureSettingsKey, exposureSettingsProcessor); diff --git a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/iso/CameraConfigISOAndEIWidget.java b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/iso/CameraConfigISOAndEIWidget.java index 1d0ff854..4d1f8ded 100644 --- a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/iso/CameraConfigISOAndEIWidget.java +++ b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/iso/CameraConfigISOAndEIWidget.java @@ -41,11 +41,11 @@ import dji.common.camera.SettingsDefinitions; import dji.thirdparty.io.reactivex.Flowable; -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers; import dji.thirdparty.io.reactivex.disposables.Disposable; -import dji.ux.beta.core.base.ConstraintLayoutWidget; import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.base.SchedulerProvider; +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; import dji.ux.beta.core.util.DisplayUtil; import dji.ux.beta.core.util.SettingDefinitions; import dji.ux.beta.visualcamera.R; @@ -61,7 +61,7 @@ public class CameraConfigISOAndEIWidget extends ConstraintLayoutWidget { private TextView isoValueTextView; //endregion - //region Constructors + //region Constructor public CameraConfigISOAndEIWidget(@NonNull Context context) { super(context); } @@ -112,7 +112,7 @@ protected void onDetachedFromWindow() { protected void reactToModelChanges() { addReaction(reactToUpdateTitle()); addReaction(widgetModel.getISOAndEIValue() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::updateValue)); } //endregion @@ -120,7 +120,7 @@ protected void reactToModelChanges() { //region Reactions to model private Disposable reactToUpdateTitle() { return Flowable.combineLatest(widgetModel.isEIMode(), widgetModel.getISO(), Pair::new) - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(values -> updateTitle(values.first, values.second), logErrorConsumer(TAG, "react to update title: ")); } @@ -169,7 +169,30 @@ public SettingDefinitions.CameraIndex getCameraIndex() { * @param cameraIndex {@link SettingDefinitions.CameraIndex} */ public void setCameraIndex(@NonNull SettingDefinitions.CameraIndex cameraIndex) { - widgetModel.setCameraIndex(cameraIndex); + if (!isInEditMode()) { + widgetModel.setCameraIndex(cameraIndex); + } + } + + /** + * Get the current type of the lens the widget is reacting to + * + * @return current lens type + */ + @NonNull + public SettingsDefinitions.LensType getLensType() { + return widgetModel.getLensType(); + } + + /** + * Set the type of the lens for which the widget should react + * + * @param lensType lens type + */ + public void setLensType(@NonNull SettingsDefinitions.LensType lensType) { + if (!isInEditMode()) { + widgetModel.setLensType(lensType); + } } /** @@ -365,9 +388,8 @@ public void setISOAndEIValueTextBackground(@Nullable Drawable drawable) { private void initAttributes(@NonNull Context context, @NonNull AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CameraConfigISOAndEIWidget); - if (!isInEditMode()) { - setCameraIndex(SettingDefinitions.CameraIndex.find(typedArray.getInt(R.styleable.CameraConfigISOAndEIWidget_uxsdk_cameraIndex, 0))); - } + setCameraIndex(SettingDefinitions.CameraIndex.find(typedArray.getInt(R.styleable.CameraConfigISOAndEIWidget_uxsdk_cameraIndex, 0))); + setLensType(SettingsDefinitions.LensType.find(typedArray.getInt(R.styleable.CameraConfigISOAndEIWidget_uxsdk_lensType, 0))); int isoAndEITitleTextAppearanceId = typedArray.getResourceId(R.styleable.CameraConfigISOAndEIWidget_uxsdk_isoAndEITitleTextAppearance, diff --git a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/iso/CameraConfigISOAndEIWidgetModel.java b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/iso/CameraConfigISOAndEIWidgetModel.java index 4fcf3d9a..9fe238c2 100644 --- a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/iso/CameraConfigISOAndEIWidgetModel.java +++ b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/iso/CameraConfigISOAndEIWidgetModel.java @@ -34,7 +34,7 @@ import dji.thirdparty.org.reactivestreams.Publisher; import dji.ux.beta.core.base.DJISDKModel; import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; import dji.ux.beta.core.util.DataProcessor; import dji.ux.beta.core.util.SettingDefinitions; @@ -58,6 +58,7 @@ public class CameraConfigISOAndEIWidgetModel extends WidgetModel { private final DataProcessor eiValueProcessor; private final DataProcessor isoAndEIValueProcessor; private int cameraIndex; + private SettingsDefinitions.LensType lensType = SettingsDefinitions.LensType.ZOOM; //endregion //region Constructor @@ -98,6 +99,26 @@ public void setCameraIndex(@NonNull SettingDefinitions.CameraIndex cameraIndex) restart(); } + /** + * Get the current type of the lens the widget model is reacting to + * + * @return current lens type + */ + @NonNull + public SettingsDefinitions.LensType getLensType() { + return lensType; + } + + /** + * Set the type of the lens for which the widget model should react + * + * @param lensType lens type + */ + public void setLensType(@NonNull SettingsDefinitions.LensType lensType) { + this.lensType = lensType; + restart(); + } + /** * Get the ISO. * @@ -131,8 +152,8 @@ public Flowable isEIMode() { //region LifeCycle @Override protected void inSetup() { - DJIKey exposureSettingsKey = CameraKey.create(CameraKey.EXPOSURE_SETTINGS, cameraIndex); - DJIKey isoKey = CameraKey.create(CameraKey.ISO, cameraIndex); + DJIKey exposureSettingsKey = djiSdkModel.createLensKey(CameraKey.EXPOSURE_SETTINGS, cameraIndex, lensType.value()); + DJIKey isoKey = djiSdkModel.createLensKey(CameraKey.ISO, cameraIndex, lensType.value()); DJIKey exposureSensitivityModeKey = CameraKey.create(CameraKey.EXPOSURE_SENSITIVITY_MODE, cameraIndex); DJIKey eiValueKey = CameraKey.create(CameraKey.EI_VALUE, cameraIndex); diff --git a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/shutter/CameraConfigShutterWidget.java b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/shutter/CameraConfigShutterWidget.java index 297bae24..7b161d94 100644 --- a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/shutter/CameraConfigShutterWidget.java +++ b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/shutter/CameraConfigShutterWidget.java @@ -38,10 +38,10 @@ import androidx.annotation.StyleRes; import dji.common.camera.SettingsDefinitions; -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers; -import dji.ux.beta.core.base.ConstraintLayoutWidget; import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.base.SchedulerProvider; +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; import dji.ux.beta.core.util.CameraUtil; import dji.ux.beta.core.util.DisplayUtil; import dji.ux.beta.core.util.SettingDefinitions; @@ -58,7 +58,7 @@ public class CameraConfigShutterWidget extends ConstraintLayoutWidget { private TextView shutterValueTextView; //endregion - //region Constructors + //region Constructor public CameraConfigShutterWidget(@NonNull Context context) { super(context); } @@ -108,7 +108,7 @@ protected void onDetachedFromWindow() { @Override protected void reactToModelChanges() { - addReaction(widgetModel.getShutterSpeed().observeOn(AndroidSchedulers.mainThread()).subscribe(this::updateUI)); + addReaction(widgetModel.getShutterSpeed().observeOn(SchedulerProvider.ui()).subscribe(this::updateUI)); } //endregion @@ -142,7 +142,30 @@ public SettingDefinitions.CameraIndex getCameraIndex() { * @param cameraIndex {@link SettingDefinitions.CameraIndex} */ public void setCameraIndex(@NonNull SettingDefinitions.CameraIndex cameraIndex) { - widgetModel.setCameraIndex(cameraIndex); + if (!isInEditMode()) { + widgetModel.setCameraIndex(cameraIndex); + } + } + + /** + * Get the current type of the lens the widget is reacting to + * + * @return current lens type + */ + @NonNull + public SettingsDefinitions.LensType getLensType() { + return widgetModel.getLensType(); + } + + /** + * Set the type of the lens for which the widget should react + * + * @param lensType lens type + */ + public void setLensType(@NonNull SettingsDefinitions.LensType lensType) { + if (!isInEditMode()) { + widgetModel.setLensType(lensType); + } } /** @@ -338,9 +361,8 @@ public void setShutterValueTextBackground(@Nullable Drawable drawable) { private void initAttributes(@NonNull Context context, @NonNull AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CameraConfigShutterWidget); - if (!isInEditMode()) { - setCameraIndex(SettingDefinitions.CameraIndex.find(typedArray.getInt(R.styleable.CameraConfigShutterWidget_uxsdk_cameraIndex, 0))); - } + setCameraIndex(SettingDefinitions.CameraIndex.find(typedArray.getInt(R.styleable.CameraConfigShutterWidget_uxsdk_cameraIndex, 0))); + setLensType(SettingsDefinitions.LensType.find(typedArray.getInt(R.styleable.CameraConfigShutterWidget_uxsdk_lensType, 0))); int shutterTitleTextAppearanceId = typedArray.getResourceId(R.styleable.CameraConfigShutterWidget_uxsdk_shutterTitleTextAppearance, diff --git a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/shutter/CameraConfigShutterWidgetModel.java b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/shutter/CameraConfigShutterWidgetModel.java index 401fd95a..a4674c84 100644 --- a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/shutter/CameraConfigShutterWidgetModel.java +++ b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/shutter/CameraConfigShutterWidgetModel.java @@ -32,7 +32,7 @@ import dji.thirdparty.io.reactivex.Flowable; import dji.ux.beta.core.base.DJISDKModel; import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; import dji.ux.beta.core.util.DataProcessor; import dji.ux.beta.core.util.SettingDefinitions; @@ -45,6 +45,7 @@ public class CameraConfigShutterWidgetModel extends WidgetModel { private DataProcessor exposureSettingsProcessor; private DataProcessor shutterSpeedProcessor; private int cameraIndex; + private SettingsDefinitions.LensType lensType = SettingsDefinitions.LensType.ZOOM; //endregion //region Constructor @@ -82,6 +83,26 @@ public void setCameraIndex(@NonNull SettingDefinitions.CameraIndex cameraIndex) restart(); } + /** + * Get the current type of the lens the widget model is reacting to + * + * @return current lens type + */ + @NonNull + public SettingsDefinitions.LensType getLensType() { + return lensType; + } + + /** + * Set the type of the lens for which the widget model should react + * + * @param lensType lens type + */ + public void setLensType(@NonNull SettingsDefinitions.LensType lensType) { + this.lensType = lensType; + restart(); + } + /** * Get the shutter speed. * @@ -95,7 +116,7 @@ public Flowable getShutterSpeed() { //region LifeCycle @Override protected void inSetup() { - DJIKey exposureSettingsKey = CameraKey.create(CameraKey.EXPOSURE_SETTINGS, cameraIndex); + DJIKey exposureSettingsKey = djiSdkModel.createLensKey(CameraKey.EXPOSURE_SETTINGS, cameraIndex, lensType.value()); bindDataProcessor(exposureSettingsKey, exposureSettingsProcessor, exposureSettings -> { ExposureSettings settings = (ExposureSettings) exposureSettings; if (settings.getShutterSpeed() != null) { diff --git a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/ssd/CameraConfigSSDWidget.java b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/ssd/CameraConfigSSDWidget.java index 02bc5cec..4b64b178 100644 --- a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/ssd/CameraConfigSSDWidget.java +++ b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/ssd/CameraConfigSSDWidget.java @@ -50,10 +50,10 @@ import dji.common.camera.SettingsDefinitions; import dji.thirdparty.io.reactivex.Flowable; import dji.thirdparty.io.reactivex.disposables.Disposable; -import dji.ux.beta.core.base.ConstraintLayoutWidget; import dji.ux.beta.core.base.DJISDKModel; import dji.ux.beta.core.base.SchedulerProvider; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; import dji.ux.beta.core.util.DisplayUtil; import dji.ux.beta.core.util.SettingDefinitions; import dji.ux.beta.visualcamera.R; @@ -85,7 +85,7 @@ public class CameraConfigSSDWidget extends ConstraintLayoutWidget { private Map ssdIconMap; //endregion - //region Constructors + //region Constructor public CameraConfigSSDWidget(@NonNull Context context) { super(context); } @@ -146,16 +146,16 @@ protected void onDetachedFromWindow() { @Override protected void reactToModelChanges() { addReaction(widgetModel.isSSDSupported() - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::updateWidgetVisibility)); addReaction(widgetModel.getSSDLicense() - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::updateCapacityTitle)); addReaction(widgetModel.getSSDRemainingSpace() - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::updateCapacityValue)); addReaction(widgetModel.getSSDResolutionAndFrameRate() - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::updateSSDResolutionAndFrameRate)); addReaction(reactToUpdateClipInfo()); addReaction(reactToUpdateSSDState()); @@ -165,7 +165,7 @@ protected void reactToModelChanges() { //region Reaction Helpers private Disposable reactToUpdateClipInfo() { return Flowable.combineLatest(widgetModel.getSSDClipName(), widgetModel.getSSDColor(), Pair::new) - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(values -> updateClipInfo(values.first, values.second), logErrorConsumer(TAG, "reactToUpdateClipInfo: ")); } @@ -184,7 +184,7 @@ private Flowable> getSSDState() { private Disposable reactToUpdateSSDState() { return getSSDState() - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(values -> updateSSDState(values.first, values.second), logErrorConsumer(TAG, "reactToUpdateSSDState: ")); } @@ -193,7 +193,7 @@ private void checkAndUpdateSSDState() { if (!isInEditMode()) { addDisposable(getSSDState() .firstOrError() - .observeOn(SchedulerProvider.getInstance().ui()) + .observeOn(SchedulerProvider.ui()) .subscribe(values -> updateSSDState(values.first, values.second), logErrorConsumer(TAG, "checkAndUpdateSSDState: "))); } @@ -259,9 +259,6 @@ private void updateSSDState(@NonNull SSDOperationState ssdOperationState, boolea statusInfoTextView.setText(R.string.uxsdk_ssd_status_error_nossd); needShowStatus = false; break; - case IDLE: - needShowStatus = false; - break; case SAVING: needShowStatus = false; statusInfoTextView.setText(R.string.uxsdk_camera_ssd_saving); @@ -367,7 +364,7 @@ private String convertResolutionAndFrameRateToString(@Nullable SettingsDefinitio } private Integer getSSDColorIndex(@NonNull SettingsDefinitions.SSDColor ssdColor) { - SettingsDefinitions.SSDColor[] ssdColorValueArray = SettingsDefinitions.SSDColor.values(); + SettingsDefinitions.SSDColor[] ssdColorValueArray = SettingsDefinitions.SSDColor.getValues(); for (int i = 0; i < ssdColorValueArray.length; i++) { if (ssdColorValueArray[i] == ssdColor) { return i; @@ -400,7 +397,9 @@ public SettingDefinitions.CameraIndex getCameraIndex() { * @param cameraIndex {@link SettingDefinitions.CameraIndex} */ public void setCameraIndex(@NonNull SettingDefinitions.CameraIndex cameraIndex) { - widgetModel.setCameraIndex(cameraIndex); + if (!isInEditMode()) { + widgetModel.setCameraIndex(cameraIndex); + } } /** @@ -993,9 +992,7 @@ public void setSSDIconBackground(@Nullable Drawable background) { private void initAttributes(@NonNull Context context, @NonNull AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CameraConfigSSDWidget); - if (!isInEditMode()) { - setCameraIndex(SettingDefinitions.CameraIndex.find(typedArray.getInt(R.styleable.CameraConfigSSDWidget_uxsdk_cameraIndex, 0))); - } + setCameraIndex(SettingDefinitions.CameraIndex.find(typedArray.getInt(R.styleable.CameraConfigSSDWidget_uxsdk_cameraIndex, 0))); int ssdClipInfoTextAppearanceId = typedArray.getResourceId(R.styleable.CameraConfigSSDWidget_uxsdk_ssdClipInfoTextAppearance, INVALID_RESOURCE); diff --git a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/ssd/CameraConfigSSDWidgetModel.java b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/ssd/CameraConfigSSDWidgetModel.java index e2b2fc6b..d49df2d0 100644 --- a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/ssd/CameraConfigSSDWidgetModel.java +++ b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/ssd/CameraConfigSSDWidgetModel.java @@ -34,7 +34,7 @@ import dji.thirdparty.io.reactivex.Flowable; import dji.ux.beta.core.base.DJISDKModel; import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; import dji.ux.beta.core.util.DataProcessor; import dji.ux.beta.core.util.SettingDefinitions; diff --git a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/storage/CameraConfigStorageWidget.java b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/storage/CameraConfigStorageWidget.java index 819f5e25..e2924877 100644 --- a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/storage/CameraConfigStorageWidget.java +++ b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/storage/CameraConfigStorageWidget.java @@ -42,14 +42,15 @@ import java.util.HashMap; import java.util.Map; +import dji.common.camera.SettingsDefinitions; import dji.common.camera.SettingsDefinitions.CameraColor; import dji.common.camera.SettingsDefinitions.CameraMode; import dji.common.camera.SettingsDefinitions.SDCardOperationState; import dji.common.camera.SettingsDefinitions.StorageLocation; -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers; -import dji.ux.beta.core.base.ConstraintLayoutWidget; import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.base.SchedulerProvider; +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; import dji.ux.beta.core.util.CameraUtil; import dji.ux.beta.core.util.DisplayUtil; import dji.ux.beta.core.util.SettingDefinitions; @@ -73,7 +74,7 @@ public class CameraConfigStorageWidget extends ConstraintLayoutWidget { private String[] cameraColorNameArray; //endregion - //region Constructors + //region Constructor public CameraConfigStorageWidget(@NonNull Context context) { super(context); } @@ -130,15 +131,15 @@ protected void onDetachedFromWindow() { @Override protected void reactToModelChanges() { addReaction(widgetModel.getImageFormat() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::updateImageFormatText, logErrorConsumer(TAG, "reactToUpdateImageFormat"))); addReaction(widgetModel.getCameraStorageState() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::updateStatus, logErrorConsumer(TAG, "reactToUpdateStatus"))); addReaction(widgetModel.getCameraColor() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::updateColor, logErrorConsumer(TAG, "reactToUpdateColor"))); } //endregion @@ -216,7 +217,7 @@ private void updateColor(CameraColor cameraColor) { private void checkAndUpdateForegroundImage() { if (!isInEditMode()) { addDisposable(widgetModel.getCameraStorageState().firstOrError() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::updateForegroundDrawable, logErrorConsumer(TAG, "checkAndUpdateForegroundImage"))); } } @@ -355,7 +356,30 @@ public SettingDefinitions.CameraIndex getCameraIndex() { * @param cameraIndex {@link SettingDefinitions.CameraIndex} */ public void setCameraIndex(@NonNull SettingDefinitions.CameraIndex cameraIndex) { - widgetModel.setCameraIndex(cameraIndex); + if (!isInEditMode()) { + widgetModel.setCameraIndex(cameraIndex); + } + } + + /** + * Get the current type of the lens the widget is reacting to + * + * @return current lens type + */ + @NonNull + public SettingsDefinitions.LensType getLensType() { + return widgetModel.getLensType(); + } + + /** + * Set the type of the lens for which the widget should react + * + * @param lensType lens type + */ + public void setLensType(@NonNull SettingsDefinitions.LensType lensType) { + if (!isInEditMode()) { + widgetModel.setLensType(lensType); + } } /** @@ -841,9 +865,8 @@ private void initDefaults() { private void initAttributes(Context context, AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CameraConfigStorageWidget); - if (!isInEditMode()) { - setCameraIndex(SettingDefinitions.CameraIndex.find(typedArray.getInt(R.styleable.CameraConfigStorageWidget_uxsdk_cameraIndex, 0))); - } + setCameraIndex(SettingDefinitions.CameraIndex.find(typedArray.getInt(R.styleable.CameraConfigStorageWidget_uxsdk_cameraIndex, 0))); + setLensType(SettingsDefinitions.LensType.find(typedArray.getInt(R.styleable.CameraConfigStorageWidget_uxsdk_lensType, 0))); if (typedArray.getDrawable(R.styleable.CameraConfigStorageWidget_uxsdk_internalStorageNotInsertedIcon) != null) { setInternalStorageIcon(StorageIconState.NOT_INSERTED, typedArray.getDrawable(R.styleable.CameraConfigStorageWidget_uxsdk_internalStorageNotInsertedIcon)); diff --git a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/storage/CameraConfigStorageWidgetModel.java b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/storage/CameraConfigStorageWidgetModel.java index 882b6e38..a71fdaee 100644 --- a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/storage/CameraConfigStorageWidgetModel.java +++ b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/storage/CameraConfigStorageWidgetModel.java @@ -34,7 +34,8 @@ import dji.thirdparty.io.reactivex.Flowable; import dji.ux.beta.core.base.DJISDKModel; import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.module.FlatCameraModule; import dji.ux.beta.core.util.DataProcessor; import dji.ux.beta.core.util.SettingDefinitions; @@ -57,7 +58,6 @@ public class CameraConfigStorageWidgetModel extends WidgetModel { //region Internal Data private final DataProcessor storageLocationProcessor; - private final DataProcessor cameraModeProcessor; private final DataProcessor resolutionAndFrameRateProcessor; private final DataProcessor photoFileFormatProcessor; private final DataProcessor sdCardState; @@ -73,6 +73,8 @@ public class CameraConfigStorageWidgetModel extends WidgetModel { //endregion private final DataProcessor cameraStorageState; private int cameraIndex; + private SettingsDefinitions.LensType lensType = SettingsDefinitions.LensType.ZOOM; + private FlatCameraModule flatCameraModule; //endregion //region Constructor @@ -81,7 +83,6 @@ public CameraConfigStorageWidgetModel(@NonNull DJISDKModel djiSdkModel, super(djiSdkModel, keyedStore); this.cameraIndex = SettingDefinitions.CameraIndex.CAMERA_INDEX_0.getIndex(); storageLocationProcessor = DataProcessor.create(SettingsDefinitions.StorageLocation.UNKNOWN); - cameraModeProcessor = DataProcessor.create(SettingsDefinitions.CameraMode.UNKNOWN); resolutionAndFrameRateProcessor = DataProcessor.create(new ResolutionAndFrameRate( SettingsDefinitions.VideoResolution.UNKNOWN, SettingsDefinitions.VideoFrameRate.UNKNOWN)); @@ -107,6 +108,8 @@ public CameraConfigStorageWidgetModel(@NonNull DJISDKModel djiSdkModel, INVALID_AVAILABLE_CAPTURE_COUNT_LONG, INVALID_AVAILABLE_RECORDING_TIME); cameraStorageState = DataProcessor.create(cameraSSDStorageState); + flatCameraModule = new FlatCameraModule(); + addModule(flatCameraModule); } //endregion @@ -129,6 +132,27 @@ public SettingDefinitions.CameraIndex getCameraIndex() { */ public void setCameraIndex(@NonNull SettingDefinitions.CameraIndex cameraIndex) { this.cameraIndex = cameraIndex.getIndex(); + flatCameraModule.setCameraIndex(cameraIndex); + restart(); + } + + /** + * Get the current type of the lens the widget model is reacting to + * + * @return current lens type + */ + @NonNull + public SettingsDefinitions.LensType getLensType() { + return lensType; + } + + /** + * Set the type of the lens for which the widget model should react + * + * @param lensType lens type + */ + public void setLensType(@NonNull SettingsDefinitions.LensType lensType) { + this.lensType = lensType; restart(); } @@ -164,9 +188,8 @@ public Flowable getCameraColor() { @Override protected void inSetup() { DJIKey storageLocationKey = CameraKey.create(CameraKey.CAMERA_STORAGE_LOCATION, cameraIndex); - DJIKey cameraModeKey = CameraKey.create(CameraKey.MODE, cameraIndex); - DJIKey resolutionAndFrameRateKey = CameraKey.create(CameraKey.RESOLUTION_FRAME_RATE, cameraIndex); - DJIKey photoFileFormatKey = CameraKey.create(CameraKey.PHOTO_FILE_FORMAT, cameraIndex); + DJIKey resolutionAndFrameRateKey = djiSdkModel.createLensKey(CameraKey.RESOLUTION_FRAME_RATE, cameraIndex, lensType.value()); + DJIKey photoFileFormatKey = djiSdkModel.createLensKey(CameraKey.PHOTO_FILE_FORMAT, cameraIndex, lensType.value()); DJIKey sdCardStateKey = CameraKey.create(CameraKey.SDCARD_STATE, cameraIndex); DJIKey storageStateKey = CameraKey.create(CameraKey.STORAGE_STATE, cameraIndex); DJIKey innerStorageStateKey = CameraKey.create(CameraKey.INNERSTORAGE_STATE, cameraIndex); @@ -177,7 +200,6 @@ protected void inSetup() { DJIKey cameraColorKey = CameraKey.create(CameraKey.CAMERA_COLOR, cameraIndex); bindDataProcessor(storageLocationKey, storageLocationProcessor); - bindDataProcessor(cameraModeKey, cameraModeProcessor); bindDataProcessor(resolutionAndFrameRateKey, resolutionAndFrameRateProcessor); bindDataProcessor(photoFileFormatKey, photoFileFormatProcessor); bindDataProcessor(sdCardStateKey, sdCardState); @@ -192,12 +214,12 @@ protected void inSetup() { @Override protected void inCleanup() { - //Nothing to cleanup + // do nothing } @Override protected void updateStates() { - imageFormatProcessor.onNext(new ImageFormat(cameraModeProcessor.getValue(), + imageFormatProcessor.onNext(new ImageFormat(flatCameraModule.getCameraModeDataProcessor().getValue(), photoFileFormatProcessor.getValue(), resolutionAndFrameRateProcessor.getValue().getResolution(), resolutionAndFrameRateProcessor.getValue().getFrameRate())); @@ -225,7 +247,7 @@ private void updateCameraStorageState() { } if (sdCardOperationState != null) { - cameraStorageState.onNext(new CameraStorageState(cameraModeProcessor.getValue(), + cameraStorageState.onNext(new CameraStorageState(flatCameraModule.getCameraModeDataProcessor().getValue(), currentStorageLocation, sdCardOperationState, getAvailableCaptureCount(currentStorageLocation), @@ -269,7 +291,7 @@ public static class ImageFormat { private SettingsDefinitions.VideoResolution resolution; private SettingsDefinitions.VideoFrameRate frameRate; - private ImageFormat(@Nullable SettingsDefinitions.CameraMode cameraMode, + protected ImageFormat(@Nullable SettingsDefinitions.CameraMode cameraMode, @Nullable SettingsDefinitions.PhotoFileFormat photoFileFormat, @Nullable SettingsDefinitions.VideoResolution resolution, @Nullable SettingsDefinitions.VideoFrameRate frameRate) { diff --git a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/wb/CameraConfigWBWidget.java b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/wb/CameraConfigWBWidget.java index 57e65aaf..2d990696 100644 --- a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/wb/CameraConfigWBWidget.java +++ b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/wb/CameraConfigWBWidget.java @@ -37,11 +37,12 @@ import androidx.annotation.Nullable; import androidx.annotation.StyleRes; +import dji.common.camera.SettingsDefinitions; import dji.common.camera.WhiteBalance; -import dji.thirdparty.io.reactivex.android.schedulers.AndroidSchedulers; -import dji.ux.beta.core.base.ConstraintLayoutWidget; import dji.ux.beta.core.base.DJISDKModel; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.base.SchedulerProvider; +import dji.ux.beta.core.base.widget.ConstraintLayoutWidget; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; import dji.ux.beta.core.util.DisplayUtil; import dji.ux.beta.core.util.SettingDefinitions; import dji.ux.beta.visualcamera.R; @@ -62,7 +63,7 @@ public class CameraConfigWBWidget extends ConstraintLayoutWidget { private String[] wbNameArray; //endregion - //region Constructors + //region Constructor public CameraConfigWBWidget(@NonNull Context context) { super(context); } @@ -114,7 +115,7 @@ protected void onDetachedFromWindow() { @Override protected void reactToModelChanges() { addReaction(widgetModel.getWhiteBalance() - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(SchedulerProvider.ui()) .subscribe(this::updateUI)); } //endregion @@ -157,7 +158,30 @@ public SettingDefinitions.CameraIndex getCameraIndex() { * @param cameraIndex {@link SettingDefinitions.CameraIndex} */ public void setCameraIndex(@NonNull SettingDefinitions.CameraIndex cameraIndex) { - widgetModel.setCameraIndex(cameraIndex); + if (!isInEditMode()) { + widgetModel.setCameraIndex(cameraIndex); + } + } + + /** + * Get the current type of the lens the widget is reacting to + * + * @return current lens type + */ + @NonNull + public SettingsDefinitions.LensType getLensType() { + return widgetModel.getLensType(); + } + + /** + * Set the type of the lens for which the widget should react + * + * @param lensType lens type + */ + public void setLensType(@NonNull SettingsDefinitions.LensType lensType) { + if (!isInEditMode()) { + widgetModel.setLensType(lensType); + } } /** @@ -353,9 +377,8 @@ public void setWBValueTextBackground(@Nullable Drawable drawable) { private void initAttributes(@NonNull Context context, @NonNull AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CameraConfigWBWidget); - if (!isInEditMode()) { - setCameraIndex(SettingDefinitions.CameraIndex.find(typedArray.getInt(R.styleable.CameraConfigWBWidget_uxsdk_cameraIndex, 0))); - } + setCameraIndex(SettingDefinitions.CameraIndex.find(typedArray.getInt(R.styleable.CameraConfigWBWidget_uxsdk_cameraIndex, 0))); + setLensType(SettingsDefinitions.LensType.find(typedArray.getInt(R.styleable.CameraConfigWBWidget_uxsdk_lensType, 0))); int wbTitleTextAppearanceId = typedArray.getResourceId(R.styleable.CameraConfigWBWidget_uxsdk_wbTitleTextAppearance, INVALID_RESOURCE); diff --git a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/wb/CameraConfigWBWidgetModel.java b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/wb/CameraConfigWBWidgetModel.java index c339f47b..7649b770 100644 --- a/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/wb/CameraConfigWBWidgetModel.java +++ b/android-uxsdk-beta-visualcamera/src/main/java/dji/ux/beta/visualcamera/widget/cameraconfig/wb/CameraConfigWBWidgetModel.java @@ -32,7 +32,7 @@ import dji.thirdparty.io.reactivex.Flowable; import dji.ux.beta.core.base.DJISDKModel; import dji.ux.beta.core.base.WidgetModel; -import dji.ux.beta.core.base.uxsdkkeys.ObservableInMemoryKeyedStore; +import dji.ux.beta.core.communication.ObservableInMemoryKeyedStore; import dji.ux.beta.core.util.DataProcessor; import dji.ux.beta.core.util.SettingDefinitions; @@ -44,6 +44,7 @@ public class CameraConfigWBWidgetModel extends WidgetModel { //region Fields private DataProcessor whiteBalanceProcessor; private int cameraIndex; + private SettingsDefinitions.LensType lensType = SettingsDefinitions.LensType.ZOOM; //endregion //region Constructor @@ -77,6 +78,26 @@ public void setCameraIndex(@NonNull SettingDefinitions.CameraIndex cameraIndex) restart(); } + /** + * Get the current type of the lens the widget model is reacting to + * + * @return current lens type + */ + @NonNull + public SettingsDefinitions.LensType getLensType() { + return lensType; + } + + /** + * Set the type of the lens for which the widget model should react + * + * @param lensType lens type + */ + public void setLensType(@NonNull SettingsDefinitions.LensType lensType) { + this.lensType = lensType; + restart(); + } + /** * Get the white balance. * @@ -90,7 +111,7 @@ public Flowable getWhiteBalance() { //region LifeCycle @Override protected void inSetup() { - DJIKey wbAndColorTempKey = CameraKey.create(CameraKey.WHITE_BALANCE, cameraIndex); + DJIKey wbAndColorTempKey = djiSdkModel.createLensKey(CameraKey.WHITE_BALANCE, cameraIndex, lensType.value()); bindDataProcessor(wbAndColorTempKey, whiteBalanceProcessor); } diff --git a/android-uxsdk-beta-visualcamera/src/main/res/layout/uxsdk_widget_camera_config_ssd.xml b/android-uxsdk-beta-visualcamera/src/main/res/layout/uxsdk_widget_camera_config_ssd.xml index 647e54b0..e5843eac 100644 --- a/android-uxsdk-beta-visualcamera/src/main/res/layout/uxsdk_widget_camera_config_ssd.xml +++ b/android-uxsdk-beta-visualcamera/src/main/res/layout/uxsdk_widget_camera_config_ssd.xml @@ -32,7 +32,6 @@ android:layout_width="wrap_content" android:layout_height="0dp" android:layout_marginStart="2dp" - android:layout_marginLeft="2dp" app:layout_constraintBottom_toBottomOf="@+id/textview_ssd_clip_info" app:layout_constraintDimensionRatio="5:4" app:layout_constraintStart_toStartOf="parent" @@ -45,7 +44,6 @@ android:layout_width="wrap_content" android:layout_height="0dp" android:layout_marginStart="2dp" - android:layout_marginLeft="2dp" android:visibility="gone" app:layout_constraintBottom_toBottomOf="@id/imageview_ssd_icon" app:layout_constraintDimensionRatio="1:1" @@ -99,6 +97,7 @@ app:layout_constraintTop_toBottomOf="@+id/textview_ssd_clip_info" tools:text="3840x2160/120.000" /> + + + @@ -37,6 +38,7 @@ + @@ -49,6 +51,7 @@ + @@ -61,6 +64,7 @@ + @@ -73,6 +77,7 @@ + @@ -85,6 +90,7 @@ + @@ -146,5 +152,4 @@ - \ No newline at end of file diff --git a/android-uxsdk-beta-visualcamera/src/main/res/values/strings.xml b/android-uxsdk-beta-visualcamera/src/main/res/values/strings.xml index 7317b3d4..a2fa0274 100644 --- a/android-uxsdk-beta-visualcamera/src/main/res/values/strings.xml +++ b/android-uxsdk-beta-visualcamera/src/main/res/values/strings.xml @@ -57,6 +57,9 @@ 3712x2088 3944x2088 2688x1512 + HQ + Full FOV + SLOW 23.976 @@ -80,6 +83,7 @@ Rec.709 CineLike RAW + Unknown TIME @@ -121,6 +125,7 @@ Saving to SSD Formatting… SSD initializing… + SSD recognition failed. SSD Verification Failed SSD Full Connection Error @@ -167,9 +172,12 @@ Film G Film H Film I + Rec.709 F + %1$d + %1$d.%2$d SHUTTER EV AUTO ISO diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index f6b961fd..7a3265ee 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 205c2ef6..7462fe8f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -19,8 +19,9 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # +# -#Fri Jul 31 14:37:30 PDT 2020 +#Fri Nov 06 19:17:04 PST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/scripts/uxsdkCreateAAR.py b/scripts/uxsdkCreateAAR.py index 6d23ad9f..8fe476b5 100644 --- a/scripts/uxsdkCreateAAR.py +++ b/scripts/uxsdkCreateAAR.py @@ -70,7 +70,7 @@ else: sp.Popen("mkdir android-uxsdk-beta", shell=True) outputDirectory = "./android-uxsdk-beta/" -modules =["android-uxsdk-beta-core", "android-uxsdk-beta-cameracore", "android-uxsdk-beta-hardwareaccessory", "android-uxsdk-beta-map", "android-uxsdk-beta-training", "android-uxsdk-beta-visualcamera"] +modules =["android-uxsdk-beta-core", "android-uxsdk-beta-cameracore", "android-uxsdk-beta-accessory", "android-uxsdk-beta-map", "android-uxsdk-beta-training", "android-uxsdk-beta-visualcamera", "android-uxsdk-beta-flight"] for module in modules: diff --git a/settings.gradle b/settings.gradle index 6a15947b..8dfa2489 100644 --- a/settings.gradle +++ b/settings.gradle @@ -23,18 +23,19 @@ include ':android-uxsdk-beta-core' include ':android-uxsdk-beta-cameracore' -include ':android-uxsdk-beta-hardwareaccessory' +include ':android-uxsdk-beta-accessory' include ':android-uxsdk-beta-map' include ':android-uxsdk-beta-training' include ':android-uxsdk-beta-visualcamera' -include ':android-uxsdk-beta-intelligentflight' +include ':android-uxsdk-beta-flight' include ':android-uxsdk-beta-sample' + project(':android-uxsdk-beta-core').projectDir = new File(rootProject.projectDir, 'android-uxsdk-beta-core') project(':android-uxsdk-beta-cameracore').projectDir = new File(rootProject.projectDir, 'android-uxsdk-beta-cameracore') -project(':android-uxsdk-beta-hardwareaccessory').projectDir = new File(rootProject.projectDir, 'android-uxsdk-beta-hardwareaccessory') +project(':android-uxsdk-beta-accessory').projectDir = new File(rootProject.projectDir, 'android-uxsdk-beta-accessory') project(':android-uxsdk-beta-map').projectDir = new File(rootProject.projectDir, 'android-uxsdk-beta-map') project(':android-uxsdk-beta-training').projectDir = new File(rootProject.projectDir, 'android-uxsdk-beta-training') project(':android-uxsdk-beta-visualcamera').projectDir = new File(rootProject.projectDir, 'android-uxsdk-beta-visualcamera') -project(':android-uxsdk-beta-intelligentflight').projectDir = new File(rootProject.projectDir, 'android-uxsdk-beta-intelligentflight') -project(':android-uxsdk-beta-sample').projectDir = new File(rootProject.projectDir, 'android-uxsdk-beta-sample') \ No newline at end of file +project(':android-uxsdk-beta-flight').projectDir = new File(rootProject.projectDir, 'android-uxsdk-beta-flight') +project(':android-uxsdk-beta-sample').projectDir = new File(rootProject.projectDir, 'android-uxsdk-beta-sample')