diff --git a/DJI-UXSDK-iOS-Beta-Communication.podspec b/DJI-UXSDK-iOS-Beta-Communication.podspec index 36ad3b2..2c930e2 100644 --- a/DJI-UXSDK-iOS-Beta-Communication.podspec +++ b/DJI-UXSDK-iOS-Beta-Communication.podspec @@ -1,14 +1,14 @@ Pod::Spec.new do |s| s.name = 'DJI-UXSDK-iOS-Beta-Communication' - s.version = '0.2' + s.version = '0.3' s.license = 'MIT' s.summary = 'Intra-framework, and system communication infrastructure for DJI iOS UX SDK.' s.homepage = 'https://github.com/dji-sdk/Mobile-UXSDK-Beta-iOS' s.authors = { 'DJI' => 'dev@dji.com' } s.documentation_url = 'https://github.com/dji-sdk/Mobile-UXSDK-Beta-iOS/wiki' s.ios.deployment_target = '11.0' - s.swift_version = '5.0' s.requires_arc = true + s.swift_version = '5.0' s.module_name = 'DJIUXSDKCommunication' s.xcconfig = { 'OTHER_LDFLAGS' => '-ObjC -all_load' } s.source = { :git => 'https://github.com/dji-sdk/Mobile-UXSDK-Beta-iOS.git', :tag => s.version.to_s } diff --git a/DJI-UXSDK-iOS-Beta-Core.podspec b/DJI-UXSDK-iOS-Beta-Core.podspec index 08ded0b..7a486bb 100644 --- a/DJI-UXSDK-iOS-Beta-Core.podspec +++ b/DJI-UXSDK-iOS-Beta-Core.podspec @@ -1,19 +1,20 @@ Pod::Spec.new do |s| s.name = 'DJI-UXSDK-iOS-Beta-Core' - s.version = '0.2' + s.version = '0.3' s.license = 'MIT' s.summary = 'Core utilities for DJI iOS UX SDK.' s.homepage = 'https://github.com/dji-sdk/Mobile-UXSDK-Beta-iOS' s.authors = { 'DJI' => 'dev@dji.com' } - s.documentation_url = 'https:/github.com//dji-sdk/Mobile-UXSDK-Beta-iOS/wiki' + s.documentation_url = 'https://github.com/dji-sdk/Mobile-UXSDK-Beta-iOS/wiki' s.ios.deployment_target = '11.0' s.requires_arc = true + s.swift_version = '5.0' s.module_name = 'DJIUXSDKCore' s.xcconfig = { 'OTHER_LDFLAGS' => '-ObjC -all_load' } s.source = { :git => 'https://github.com/dji-sdk/Mobile-UXSDK-Beta-iOS.git', :tag => s.version.to_s } s.pod_target_xcconfig = { 'ENABLE_BITCODE' => 'NO', 'DEFINES_MODULE' => 'YES'} s.cocoapods_version = '>= 1.7.1' s.source_files = 'DJIUXSDKCore/**/*.{h,m,swift}' - s.dependency 'DJI-SDK-iOS', '~> 4.12' - s.dependency 'DJI-UXSDK-iOS-Beta-Communication', '0.2' + s.dependency 'DJI-SDK-iOS', '~> 4.13' + s.dependency 'DJI-UXSDK-iOS-Beta-Communication', '0.3' end \ No newline at end of file diff --git a/DJI-UXSDK-iOS-Beta-Widgets.podspec b/DJI-UXSDK-iOS-Beta-Widgets.podspec index 14b5397..11d138d 100644 --- a/DJI-UXSDK-iOS-Beta-Widgets.podspec +++ b/DJI-UXSDK-iOS-Beta-Widgets.podspec @@ -1,13 +1,14 @@ Pod::Spec.new do |s| s.name = 'DJI-UXSDK-iOS-Beta-Widgets' - s.version = '0.2' + s.version = '0.3' s.license = 'MIT' - s.summary = 'A collection of widget, widget model, and related helpers for DJI iOS UX SDK 5.0 Open Source (Beta 2).' + s.summary = 'A collection of widget, widget model, and related helpers for DJI iOS UX SDK.' s.homepage = 'https://github.com/dji-sdk/Mobile-UXSDK-Beta-iOS' s.authors = { 'DJI' => 'dev@dji.com' } s.documentation_url = 'https://github.com/dji-sdk/Mobile-UXSDK-Beta-iOS/wiki' s.ios.deployment_target = '11.0' s.requires_arc = true + s.swift_version = '5.0' s.module_name = 'DJIUXSDKWidgets' s.xcconfig = { 'OTHER_LDFLAGS' => '-ObjC -all_load' } s.source = { :git => 'https://github.com/dji-sdk/Mobile-UXSDK-Beta-iOS.git', :tag => s.version.to_s } @@ -15,8 +16,8 @@ Pod::Spec.new do |s| s.cocoapods_version = '>= 1.7.1' s.source_files = 'DJIUXSDKWidgets/**/*.{h,m,swift}' s.resource_bundle = { 'DUXBetaAssets' => 'DJIUXSDKWidgets/**/*.{xcassets,html,otf}' } - s.dependency 'DJI-SDK-iOS', '~> 4.12' - s.dependency 'DJIWidget', '~> 1.6.2' - s.dependency 'DJI-UXSDK-iOS-Beta-Core', '0.2' - s.dependency 'DJI-UXSDK-iOS-Beta-Communication', '0.2' + s.dependency 'DJI-SDK-iOS', '~> 4.13' + s.dependency 'DJIWidget', '~> 1.6.3' + s.dependency 'DJI-UXSDK-iOS-Beta-Core', '0.3' + s.dependency 'DJI-UXSDK-iOS-Beta-Communication', '0.3' end \ No newline at end of file diff --git a/DJI-UXSDK-iOS-Beta.podspec b/DJI-UXSDK-iOS-Beta.podspec index 1b0f963..fb73926 100644 --- a/DJI-UXSDK-iOS-Beta.podspec +++ b/DJI-UXSDK-iOS-Beta.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'DJI-UXSDK-iOS-Beta' - s.version = '0.2' + s.version = '0.3' s.license = 'MIT' s.summary = 'DJI iOS UX SDK' s.homepage = 'https://github.com/dji-sdk/Mobile-UXSDK-Beta-iOS' @@ -8,13 +8,14 @@ Pod::Spec.new do |s| s.documentation_url = 'https://github.com/dji-sdk/Mobile-UXSDK-Beta-iOS/wiki' s.ios.deployment_target = '11.0' s.requires_arc = true + s.swift_version = '5.0' s.module_name = 'DJIUXSDKBeta' s.xcconfig = { 'OTHER_LDFLAGS' => '-ObjC -all_load' } s.source = { :git => 'https://github.com/dji-sdk/Mobile-UXSDK-Beta-iOS.git', :tag => s.version.to_s } s.pod_target_xcconfig = { 'ENABLE_BITCODE' => 'NO', 'DEFINES_MODULE' => 'YES'} s.cocoapods_version = '>= 1.7.1' s.source_files = 'DJIUXSDKBeta/**/*.{h,m,swift}' - s.dependency 'DJI-UXSDK-iOS-Beta-Core', '0.2' - s.dependency 'DJI-UXSDK-iOS-Beta-Communication', '0.2' - s.dependency 'DJI-UXSDK-iOS-Beta-Widgets', '0.2' + s.dependency 'DJI-UXSDK-iOS-Beta-Core', '0.3' + s.dependency 'DJI-UXSDK-iOS-Beta-Communication', '0.3' + s.dependency 'DJI-UXSDK-iOS-Beta-Widgets', '0.3' end \ No newline at end of file diff --git a/DJIUXSDKBeta/DJIUXSDK.h b/DJIUXSDKBeta/DJIUXSDK.h index 56b4be0..348aad0 100644 --- a/DJIUXSDKBeta/DJIUXSDK.h +++ b/DJIUXSDKBeta/DJIUXSDK.h @@ -2,7 +2,27 @@ // DJIUXSDK.h // DJIUXSDK // -// Copyright © 2018-2020 DJI. All rights reserved. +// MIT License +// +// Copyright © 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. // #import diff --git a/DJIUXSDKBeta/DUXBetaSDKAttributes.m b/DJIUXSDKBeta/DUXBetaSDKAttributes.m index 6b9d73c..b63986b 100644 --- a/DJIUXSDKBeta/DUXBetaSDKAttributes.m +++ b/DJIUXSDKBeta/DUXBetaSDKAttributes.m @@ -4,7 +4,7 @@ // // MIT License // -// Copyright © 2018-2019 DJI +// Copyright © 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 diff --git a/DJIUXSDKCommunication/GlobalPreferences.swift b/DJIUXSDKCommunication/GlobalPreferences.swift index f756f8e..2d28781 100644 --- a/DJIUXSDKCommunication/GlobalPreferences.swift +++ b/DJIUXSDKCommunication/GlobalPreferences.swift @@ -27,20 +27,20 @@ import Foundation -@objc(DUXMeasureUnitType) public enum MeasureUnitType: Int { +@objc(DUXBetaMeasureUnitType) public enum MeasureUnitType: Int { case Metric = 1, Imperial = 2, Unknown = 3 } -@objc(DUXFPVCenterViewType) public enum FPVCenterViewType: Int { - case Standard = 1, Cross, NarrowCross, Frame, FrameAndCross, Square, SquareAndCross, Unknown +@objc(DUXBetaFPVCenterViewType) public enum FPVCenterViewType: Int { + case None = 0, Standard, Cross, NarrowCross, Frame, FrameAndCross, Square, SquareAndCross, Unknown } -@objc(DUXFPVCenterViewColor) public enum FPVCenterViewColor: Int { - case White = 1, Yellow, Red, Green, Blue, Black, Unknown +@objc(DUXBetaFPVCenterViewColor) public enum FPVCenterViewColor: Int { + case None = 0, White, Yellow, Red, Green, Blue, Black, Unknown } -@objc(DUXFPVGridViewType) public enum FPVGridViewType: Int { - case Parallel = 1, ParallelDiagonal, Unknown +@objc(DUXBetaFPVGridViewType) public enum FPVGridViewType: Int { + case None = 0, Parallel, ParallelDiagonal, Unknown } enum GlobalPreference: RawRepresentable { @@ -48,7 +48,7 @@ enum GlobalPreference: RawRepresentable { case MeasureUnitType, AFCEnabled, FPVCenterViewType, FPVCenterViewColor, FPVGridViewType, Unknown - static let Prefix = "DUXGlobalPreference" + static let Prefix = "DUXBetaGlobalPreference" var rawValue: GlobalPreference.RawValue { switch self { @@ -86,7 +86,7 @@ enum GlobalPreference: RawRepresentable { } // To create a custom storage mechanism, implement this protocol then call setSharedGlobalPreferences: -// on DUXSingleton to replace the default implementation with a custom one +// on DUXBetaSingleton to replace the default implementation with a custom one @objc public protocol GlobalPreferences { func set(measureUnitType:MeasureUnitType) func measureUnitType() -> MeasureUnitType diff --git a/DJIUXSDKCommunication/ObservableInMemoryKeyedStore.swift b/DJIUXSDKCommunication/ObservableInMemoryKeyedStore.swift index f62d4ac..d50400b 100644 --- a/DJIUXSDKCommunication/ObservableInMemoryKeyedStore.swift +++ b/DJIUXSDKCommunication/ObservableInMemoryKeyedStore.swift @@ -1,5 +1,5 @@ // -// DUXBroadcaster.swift +// DUXBetaBroadcaster.swift // DJIUXSDK // // MIT License @@ -141,7 +141,7 @@ enum Type { } // ExternalKey bridges to the Key / ConcreteKey types that cannot be used in obj-c -@objc(DUXKey) public class ExternalKey : NSObject { +@objc(DUXBetaKey) public class ExternalKey : NSObject { var concreteKey:ConcreteKey { get { return ConcreteKey(index:self.index, parameter:self.internalParameter) @@ -179,12 +179,12 @@ enum Type { // 4. Make sure it is mapped by the appropriate conversion method back to the public type // 5. You're done! -@objc(DUXCameraParameter) public enum CameraParameter : UInt { +@objc(DUXBetaCameraParameter) public enum CameraParameter : UInt { case AFCEnabled = 1, Unknown = 2 } -@objc(DUXCameraKey) public class CameraKey : ExternalKey { +@objc(DUXBetaCameraKey) public class CameraKey : ExternalKey { public var param:CameraParameter { return self.internalParameter.cameraParameter() } @@ -195,13 +195,13 @@ enum Type { } } -@objc(DUXVideoParameter) public enum VideoParameter : UInt { +@objc(DUXBetaVideoParameter) public enum VideoParameter : UInt { case DecoderStatus = 1, PeakingThreshold = 2, Unknown = 3 } -@objc(DUXVideoKey) public class VideoKey : ExternalKey { +@objc(DUXBetaVideoKey) public class VideoKey : ExternalKey { @objc public init(index: Int, parameter: VideoParameter) { super.init(index: index, param: Parameter(videoParameter: parameter)) @@ -228,12 +228,12 @@ enum Type { } } -@objc(DUXVoiceNotificationParameter) public enum VoiceNotificationParameter : UInt { +@objc(DUXBetaVoiceNotificationParameter) public enum VoiceNotificationParameter : UInt { case Attitude = 1, Unknown = 3 } -@objc(DUXVoiceNotificationKey) public class VoiceNotificationKey : ExternalKey { +@objc(DUXBetaVoiceNotificationKey) public class VoiceNotificationKey : ExternalKey { @objc public init(index: Int, parameter: VoiceNotificationParameter) { super.init(index: index, param: Parameter(voiceNotificationParameter: parameter)) @@ -268,7 +268,7 @@ typealias ModelValueCompletionBlock = (ModelValue?) -> Void class FlatStore: NSObject { var underlyingStore:[ConcreteKey:ModelValue] = [:] - var queue:DispatchQueue = DispatchQueue(label: "DUXFlatStoreSerialQueue") + var queue:DispatchQueue = DispatchQueue(label: "DUXBetaFlatStoreSerialQueue") func update(modelValue:ModelValue?, for key:ConcreteKey) { self.queue.async { diff --git a/DJIUXSDKCore/Analytics/DUXStateChangeBaseData.h b/DJIUXSDKCore/Analytics/DUXBetaStateChangeBaseData.h similarity index 87% rename from DJIUXSDKCore/Analytics/DUXStateChangeBaseData.h rename to DJIUXSDKCore/Analytics/DUXBetaStateChangeBaseData.h index c7c3fd9..bf3e8e5 100644 --- a/DJIUXSDKCore/Analytics/DUXStateChangeBaseData.h +++ b/DJIUXSDKCore/Analytics/DUXBetaStateChangeBaseData.h @@ -1,5 +1,5 @@ // -// DUXStateChangeBaseData.h +// DUXBetaStateChangeBaseData.h // DJIUXSDKCore // // Copyright © 2018-2020 DJI @@ -27,14 +27,14 @@ NS_ASSUME_NONNULL_BEGIN /** - * This is DUXStateChangeBaseData, the base class for data transmitted by the UI and Model hooks supplied throughtout the UX SDK code base + * This is DUXBetaStateChangeBaseData, the base class for data transmitted by the UI and Model hooks supplied throughtout the UX SDK code base * to notify developer of chaniging events. * Each instance of this class or it's descendents contains a key and a value for that key. It is dependent on the receiver to know what kind of data to expect * with the hook. Convenience methods have been supplied in this class for easy access to the contents. * * The key can be retrieved as a string, using the key method. */ -@interface DUXStateChangeBaseData : NSObject +@interface DUXBetaStateChangeBaseData : NSObject /** * Initialization methods for each of the types of data to be delivered. */ @@ -45,7 +45,7 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithKey:(NSString*)key object:(id)object; /** - * Accessors for the contents of the DUXStateChangeBaseData instance. + * Accessors for the contents of the DUXBetaStateChangeBaseData instance. */ - (NSString*)key; - (NSValue*)value; diff --git a/DJIUXSDKCore/Analytics/DUXStateChangeBaseData.m b/DJIUXSDKCore/Analytics/DUXBetaStateChangeBaseData.m similarity index 94% rename from DJIUXSDKCore/Analytics/DUXStateChangeBaseData.m rename to DJIUXSDKCore/Analytics/DUXBetaStateChangeBaseData.m index a5e2285..e5766f1 100644 --- a/DJIUXSDKCore/Analytics/DUXStateChangeBaseData.m +++ b/DJIUXSDKCore/Analytics/DUXBetaStateChangeBaseData.m @@ -1,5 +1,5 @@ // -// DUXStateChangeBaseData.m +// DUXBetaStateChangeBaseData.m // DJIUXSDKCore // // Copyright © 2018-2020 DJI @@ -23,13 +23,13 @@ // SOFTWARE. // -#import "DUXStateChangeBaseData.h" +#import "DUXBetaStateChangeBaseData.h" -@interface DUXStateChangeBaseData () +@interface DUXBetaStateChangeBaseData () @property (nonatomic, strong) NSMutableDictionary *dict; @end -@implementation DUXStateChangeBaseData +@implementation DUXBetaStateChangeBaseData - (instancetype)initWithKey:(NSString*)key { if (self = [super init]) { diff --git a/DJIUXSDKCore/Analytics/DUXStateChangeBroadcaster.h b/DJIUXSDKCore/Analytics/DUXBetaStateChangeBroadcaster.h similarity index 78% rename from DJIUXSDKCore/Analytics/DUXStateChangeBroadcaster.h rename to DJIUXSDKCore/Analytics/DUXBetaStateChangeBroadcaster.h index c42673f..a50b38d 100644 --- a/DJIUXSDKCore/Analytics/DUXStateChangeBroadcaster.h +++ b/DJIUXSDKCore/Analytics/DUXBetaStateChangeBroadcaster.h @@ -1,5 +1,5 @@ // -// DUXStateChangeBroadcaster.h +// DUXBetaStateChangeBroadcaster.h // DJIUXSDKCore // // Copyright © 2018-2020 DJI @@ -24,20 +24,20 @@ // #import -#import "DUXStateChangeBaseData.h" +#import "DUXBetaStateChangeBaseData.h" NS_ASSUME_NONNULL_BEGIN -typedef void(^AnalyticsHandler)(DUXStateChangeBaseData *analyticsData); +typedef void(^AnalyticsHandler)(DUXBetaStateChangeBaseData *analyticsData); -@interface DUXStateChangeBroadcaster : NSObject -+ (DUXStateChangeBroadcaster*) instance; -+ (void)send:(DUXStateChangeBaseData*)analyticsData; // Convenience method to just send +@interface DUXBetaStateChangeBroadcaster : NSObject ++ (DUXBetaStateChangeBroadcaster *)instance; ++ (void)send:(DUXBetaStateChangeBaseData*)analyticsData; // Convenience method to just send - (void)registerListener:(id)listener analyticsClassName:(NSString*)analyticsClassName handler:(AnalyticsHandler)block; - (void)unregisterListener:(id)listener; - (void)unregisterListener:(id)listener forClassName:(NSString*)analyticsClassNane; -- (void)send:(DUXStateChangeBaseData*)analyticsData; +- (void)send:(DUXBetaStateChangeBaseData*)analyticsData; @end NS_ASSUME_NONNULL_END diff --git a/DJIUXSDKCore/Analytics/DUXStateChangeBroadcaster.m b/DJIUXSDKCore/Analytics/DUXBetaStateChangeBroadcaster.m similarity index 90% rename from DJIUXSDKCore/Analytics/DUXStateChangeBroadcaster.m rename to DJIUXSDKCore/Analytics/DUXBetaStateChangeBroadcaster.m index bae2fb8..6c4ce27 100644 --- a/DJIUXSDKCore/Analytics/DUXStateChangeBroadcaster.m +++ b/DJIUXSDKCore/Analytics/DUXBetaStateChangeBroadcaster.m @@ -1,5 +1,5 @@ // -// DUXStateChangeBroadcaster.m +// DUXBetaStateChangeBroadcaster.m // DJIUXSDKCore // // Copyright © 2018-2020 DJI @@ -23,8 +23,8 @@ // SOFTWARE. // -#import "DUXStateChangeBroadcaster.h" -#import "DUXStateChangeBaseData.h" +#import "DUXBetaStateChangeBroadcaster.h" +#import "DUXBetaStateChangeBaseData.h" @interface OwnerHandlerTuple : NSObject @property (nonatomic, strong) id owner; @@ -43,7 +43,7 @@ - (instancetype)initWithOwner:(id)owner handler:(AnalyticsHandler)handler { } @end -@interface DUXStateChangeBroadcaster () +@interface DUXBetaStateChangeBroadcaster () // The handlersDict is a dictionary of classnames as keys and an array of the handlers to // call when an item of that class is sent. @property (atomic, strong) NSMutableDictionary *> *handlersDict; @@ -56,22 +56,23 @@ @interface DUXStateChangeBroadcaster () @end -@implementation DUXStateChangeBroadcaster +@implementation DUXBetaStateChangeBroadcaster -+ (DUXStateChangeBroadcaster*) instance { - static DUXStateChangeBroadcaster *manager; ++ (DUXBetaStateChangeBroadcaster *)instance { + static DUXBetaStateChangeBroadcaster *manager; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - manager = [[DUXStateChangeBroadcaster alloc] init]; + manager = [[DUXBetaStateChangeBroadcaster alloc] init]; }); return manager; } // Convenience method to just send -+ (void)send:(DUXStateChangeBaseData*)analyticsData { ++ (void)send:(DUXBetaStateChangeBaseData*)analyticsData { [[self instance] send:analyticsData]; } + - (instancetype)init { if (self = [super init]) { _handlersDict = [[NSMutableDictionary alloc] init]; @@ -81,6 +82,10 @@ - (instancetype)init { return self; } +- (void)dealloc { + +} + // TODO: Wrap this in the sequential queue - (void)registerListener:(id)listener analyticsClassName:(NSString*)analyticsClassName handler:(AnalyticsHandler)block { @@ -125,7 +130,7 @@ - (void)unregisterListener:(id)listener forClassName:(NSString*)analyticsClassNa [handlersList removeObjectsInArray:removeObjects]; } -- (void)send:(DUXStateChangeBaseData*)analyticsData { +- (void)send:(DUXBetaStateChangeBaseData*)analyticsData { NSString *classname = NSStringFromClass([analyticsData class]); NSMutableArray *handlersList = _handlersDict[classname]; for (OwnerHandlerTuple *ownerHandler in handlersList) { @@ -135,5 +140,4 @@ - (void)send:(DUXStateChangeBaseData*)analyticsData { }); } } - @end diff --git a/DJIUXSDKCore/BindingConvenienceCategories/DUXBetaBindings.swift b/DJIUXSDKCore/BindingConvenienceCategories/DUXBetaBindings.swift index 1c92ee5..69275c6 100644 --- a/DJIUXSDKCore/BindingConvenienceCategories/DUXBetaBindings.swift +++ b/DJIUXSDKCore/BindingConvenienceCategories/DUXBetaBindings.swift @@ -1,5 +1,5 @@ // -// DUXBetaSwiftBindings.swift +// DUXBetaBindings.swift // DJIUXSDKCore // // Copyright © 2018-2020 DJI @@ -24,7 +24,7 @@ // /** - DUXSwiftBindings - This is an extension that allows the developer to bind the the SDK keys observe changes. It allows writting widgets + DUXBetaSwiftBindings - This is an extension that allows the developer to bind the the SDK keys observe changes. It allows writting widgets and models in Swift. These are the same functionality as the binding macros written in Objective-C (see NSObject+DUXBetaRKVOExtension.h) with one small change. When using bindRKVOModel to bind paths, all paths are put into a comma separated list inside a string. This list will diff --git a/DJIUXSDKCore/BindingConvenienceCategories/NSObject+DUXBetaCommand.m b/DJIUXSDKCore/BindingConvenienceCategories/NSObject+DUXBetaCommand.m index 79d1d19..10894fb 100644 --- a/DJIUXSDKCore/BindingConvenienceCategories/NSObject+DUXBetaCommand.m +++ b/DJIUXSDKCore/BindingConvenienceCategories/NSObject+DUXBetaCommand.m @@ -41,26 +41,26 @@ @implementation DUXBetaRKVOCommandTag //////////////////////// -static void* kDUXBetaObjectCommandKey = &kDUXBetaObjectCommandKey; -static void* kDUXBetaObjectCommandTagDictKey = &kDUXBetaObjectCommandTagDictKey; -static void* kDUXBetaObjectCommandLockKey = &kDUXBetaObjectCommandLockKey; +static void* kDUXObjectCommandKey = &kDUXObjectCommandKey; +static void* kDUXObjectCommandTagDictKey = &kDUXObjectCommandTagDictKey; +static void* kDUXObjectCommandLockKey = &kDUXObjectCommandLockKey; @implementation NSObject (DUXBetaCommand) - (NSLock *)duxbeta_commandLock { - NSLock* lock = objc_getAssociatedObject(self, kDUXBetaObjectCommandLockKey); + NSLock* lock = objc_getAssociatedObject(self, kDUXObjectCommandLockKey); if (!lock) { lock = [[NSLock alloc] init]; - objc_setAssociatedObject(self, kDUXBetaObjectCommandLockKey, lock, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(self, kDUXObjectCommandLockKey, lock, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } return lock; } - (NSMutableDictionary *)duxbeta_commandTagDict { - NSMutableDictionary* dictionary = objc_getAssociatedObject(self, kDUXBetaObjectCommandKey); + NSMutableDictionary* dictionary = objc_getAssociatedObject(self, kDUXObjectCommandKey); if (!dictionary) { dictionary = [[NSMutableDictionary alloc] init]; - objc_setAssociatedObject(self, kDUXBetaObjectCommandKey, dictionary, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(self, kDUXObjectCommandKey, dictionary, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } return dictionary; } diff --git a/DJIUXSDKCore/BindingConvenienceCategories/NSObject+DUXBetaMapping.m b/DJIUXSDKCore/BindingConvenienceCategories/NSObject+DUXBetaMapping.m index f8d9757..f8568e4 100644 --- a/DJIUXSDKCore/BindingConvenienceCategories/NSObject+DUXBetaMapping.m +++ b/DJIUXSDKCore/BindingConvenienceCategories/NSObject+DUXBetaMapping.m @@ -112,7 +112,7 @@ @interface DUXBetaBaseWidgetModelProperty : NSObject @implementation DUXBetaBaseWidgetModelProperty @end -static void* kDUXBetaBaseWidgetModelMappingKey = &kDUXBetaBaseWidgetModelMappingKey; +static void* kDUXBaseWidgetModelMappingKey = &kDUXBaseWidgetModelMappingKey; @implementation NSObject (DUXBetaMapping) @@ -339,10 +339,10 @@ - (DUXBetaBaseWidgetModelProperty *)propertyTypeFromPropertyName:(NSString *)pro } - (NSMutableDictionary *)propertyTypeMap { - NSMutableDictionary* propertyTypeMap = objc_getAssociatedObject(self, kDUXBetaBaseWidgetModelMappingKey); + NSMutableDictionary* propertyTypeMap = objc_getAssociatedObject(self, kDUXBaseWidgetModelMappingKey); if (!propertyTypeMap) { propertyTypeMap = [[NSMutableDictionary alloc] init]; - objc_setAssociatedObject(self, kDUXBetaBaseWidgetModelMappingKey, propertyTypeMap, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(self, kDUXBaseWidgetModelMappingKey, propertyTypeMap, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } return propertyTypeMap; } @@ -431,46 +431,46 @@ - (DUXBetaPropertyType)propertyTypeFromClass:(Class)cls { - (NSString *)propetyTypeNameWithType:(DUXBetaPropertyType)type { switch (type) { - case DUXBetaPropertyType_Unknown:return @"DUXBetaPropertyType_Unknown"; - case DUXBetaPropertyType_Void:return @"DUXBetaPropertyType_Void"; - case DUXBetaPropertyType_Bool:return @"DUXBetaPropertyType_Bool"; - case DUXBetaPropertyType_Int8:return @"DUXBetaPropertyType_Int8"; - case DUXBetaPropertyType_UInt8:return @"DUXBetaPropertyType_UInt8"; - case DUXBetaPropertyType_Int16:return @"DUXBetaPropertyType_Int16"; - case DUXBetaPropertyType_UInt16:return @"DUXBetaPropertyType_UInt16"; - case DUXBetaPropertyType_Int32:return @"DUXBetaPropertyType_Int32"; - case DUXBetaPropertyType_UInt32:return @"DUXBetaPropertyType_UInt32"; - case DUXBetaPropertyType_Int64:return @"DUXBetaPropertyType_Int64"; - case DUXBetaPropertyType_UInt64:return @"DUXBetaPropertyType_UInt64"; - case DUXBetaPropertyType_Float:return @"DUXBetaPropertyType_Float"; - case DUXBetaPropertyType_Double:return @"DUXBetaPropertyType_Double"; - case DUXBetaPropertyType_LongDouble:return @"DUXBetaPropertyType_LongDouble"; - case DUXBetaPropertyType_Class:return @"DUXBetaPropertyType_Class"; - case DUXBetaPropertyType_Pointer:return @"DUXBetaPropertyType_Pointer"; - case DUXBetaPropertyType_Selector:return @"DUXBetaPropertyType_Selector"; - case DUXBetaPropertyType_CFString:return @"DUXBetaPropertyType_CFString"; - case DUXBetaPropertyType_CFArray:return @"DUXBetaPropertyType_CFArray"; - case DUXBetaPropertyType_CFUnion:return @"DUXBetaPropertyType_CFUnion"; - case DUXBetaPropertyType_CFStruct:return @"DUXBetaPropertyType_CFStruct"; - case DUXBetaPropertyType_CFBitFiled:return @"DUXBetaPropertyType_CFBitFiled"; - case DUXBetaPropertyType_Id:return @"DUXBetaPropertyType_Id"; - case DUXBetaPropertyType_Block:return @"DUXBetaPropertyType_Block"; - case DUXBetaPropertyType_NSString:return @"DUXBetaPropertyType_NSString"; - case DUXBetaPropertyType_NSMutableString:return @"DUXBetaPropertyType_NSMutableString"; - case DUXBetaPropertyType_NSValue:return @"DUXBetaPropertyType_NSValue"; - case DUXBetaPropertyType_NSNumber:return @"DUXBetaPropertyType_NSNumber"; - case DUXBetaPropertyType_NSDecimalNumber:return @"DUXBetaPropertyType_NSDecimalNumber"; - case DUXBetaPropertyType_NSData:return @"DUXBetaPropertyType_NSData"; - case DUXBetaPropertyType_NSMutableData:return @"DUXBetaPropertyType_NSMutableData"; - case DUXBetaPropertyType_NSDate:return @"DUXBetaPropertyType_NSDate"; - case DUXBetaPropertyType_NSURL:return @"DUXBetaPropertyType_NSURL"; - case DUXBetaPropertyType_NSArray:return @"DUXBetaPropertyType_NSArray"; - case DUXBetaPropertyType_NSMutableArray:return @"DUXBetaPropertyType_NSMutableArray"; - case DUXBetaPropertyType_NSDictionary:return @"DUXBetaPropertyType_NSDictionary"; - case DUXBetaPropertyType_NSMutableDictionary:return @"DUXBetaPropertyType_NSMutableDictionary"; - case DUXBetaPropertyType_NSSet:return @"DUXBetaPropertyType_NSSet"; - case DUXBetaPropertyType_NSMutableSet:return @"DUXBetaPropertyType_NSMutableSet"; - case DUXBetaPropertyType_NSCustomObject:return @"DUXBetaPropertyType_NSCustomObject"; + case DUXBetaPropertyType_Unknown:return @"DUXPropertyType_Unknown"; + case DUXBetaPropertyType_Void:return @"DUXPropertyType_Void"; + case DUXBetaPropertyType_Bool:return @"DUXPropertyType_Bool"; + case DUXBetaPropertyType_Int8:return @"DUXPropertyType_Int8"; + case DUXBetaPropertyType_UInt8:return @"DUXPropertyType_UInt8"; + case DUXBetaPropertyType_Int16:return @"DUXPropertyType_Int16"; + case DUXBetaPropertyType_UInt16:return @"DUXPropertyType_UInt16"; + case DUXBetaPropertyType_Int32:return @"DUXPropertyType_Int32"; + case DUXBetaPropertyType_UInt32:return @"DUXPropertyType_UInt32"; + case DUXBetaPropertyType_Int64:return @"DUXPropertyType_Int64"; + case DUXBetaPropertyType_UInt64:return @"DUXPropertyType_UInt64"; + case DUXBetaPropertyType_Float:return @"DUXPropertyType_Float"; + case DUXBetaPropertyType_Double:return @"DUXPropertyType_Double"; + case DUXBetaPropertyType_LongDouble:return @"DUXPropertyType_LongDouble"; + case DUXBetaPropertyType_Class:return @"DUXPropertyType_Class"; + case DUXBetaPropertyType_Pointer:return @"DUXPropertyType_Pointer"; + case DUXBetaPropertyType_Selector:return @"DUXPropertyType_Selector"; + case DUXBetaPropertyType_CFString:return @"DUXPropertyType_CFString"; + case DUXBetaPropertyType_CFArray:return @"DUXPropertyType_CFArray"; + case DUXBetaPropertyType_CFUnion:return @"DUXPropertyType_CFUnion"; + case DUXBetaPropertyType_CFStruct:return @"DUXPropertyType_CFStruct"; + case DUXBetaPropertyType_CFBitFiled:return @"DUXPropertyType_CFBitFiled"; + case DUXBetaPropertyType_Id:return @"DUXPropertyType_Id"; + case DUXBetaPropertyType_Block:return @"DUXPropertyType_Block"; + case DUXBetaPropertyType_NSString:return @"DUXPropertyType_NSString"; + case DUXBetaPropertyType_NSMutableString:return @"DUXPropertyType_NSMutableString"; + case DUXBetaPropertyType_NSValue:return @"DUXPropertyType_NSValue"; + case DUXBetaPropertyType_NSNumber:return @"DUXPropertyType_NSNumber"; + case DUXBetaPropertyType_NSDecimalNumber:return @"DUXPropertyType_NSDecimalNumber"; + case DUXBetaPropertyType_NSData:return @"DUXPropertyType_NSData"; + case DUXBetaPropertyType_NSMutableData:return @"DUXPropertyType_NSMutableData"; + case DUXBetaPropertyType_NSDate:return @"DUXPropertyType_NSDate"; + case DUXBetaPropertyType_NSURL:return @"DUXPropertyType_NSURL"; + case DUXBetaPropertyType_NSArray:return @"DUXPropertyType_NSArray"; + case DUXBetaPropertyType_NSMutableArray:return @"DUXPropertyType_NSMutableArray"; + case DUXBetaPropertyType_NSDictionary:return @"DUXPropertyType_NSDictionary"; + case DUXBetaPropertyType_NSMutableDictionary:return @"DUXPropertyType_NSMutableDictionary"; + case DUXBetaPropertyType_NSSet:return @"DUXPropertyType_NSSet"; + case DUXBetaPropertyType_NSMutableSet:return @"DUXPropertyType_NSMutableSet"; + case DUXBetaPropertyType_NSCustomObject:return @"DUXPropertyType_NSCustomObject"; } } diff --git a/DJIUXSDKCore/BindingConvenienceCategories/NSObject+DUXBetaSDKBind.h b/DJIUXSDKCore/BindingConvenienceCategories/NSObject+DUXBetaSDKBind.h index 8db5c4e..36dd4df 100644 --- a/DJIUXSDKCore/BindingConvenienceCategories/NSObject+DUXBetaSDKBind.h +++ b/DJIUXSDKCore/BindingConvenienceCategories/NSObject+DUXBetaSDKBind.h @@ -28,9 +28,9 @@ #import #import -#define DUXKeypath(OBJ, PATH) \ +#define DUXBetaKeypath(OBJ, PATH) \ (((void)(NO && ((void)OBJ.PATH, NO)), # PATH)) -#define DUXBetaVMProperty(PATH) DUXKeypath(self, PATH) +#define DUXBetaVMProperty(PATH) DUXBetaKeypath(self, PATH) /** * If you are working in Objective C these macros are avaliable to use rather than the method calls. @@ -54,7 +54,7 @@ if(__strong##__TARGET__==nil)return; NS_ASSUME_NONNULL_BEGIN -@class DUXKey; +@class DUXBetaKey; /** * Use these methods to manage the binding of DJI SDK Keys to an associated property's keypath. diff --git a/DJIUXSDKCore/DJIUXSDKCore.h b/DJIUXSDKCore/DJIUXSDKCore.h index a745244..f2d8b15 100644 --- a/DJIUXSDKCore/DJIUXSDKCore.h +++ b/DJIUXSDKCore/DJIUXSDKCore.h @@ -56,6 +56,6 @@ FOUNDATION_EXPORT const unsigned char DJIUXSDKCoreVersionString[]; #import #import -#import +#import -#import +#import diff --git a/DJIUXSDKCore/DUXBetaVoiceNotification.h b/DJIUXSDKCore/DUXBetaVoiceNotification.h index 27e5a40..4d81d4a 100644 --- a/DJIUXSDKCore/DUXBetaVoiceNotification.h +++ b/DJIUXSDKCore/DUXBetaVoiceNotification.h @@ -2,6 +2,8 @@ // DUXBetaVoiceNotification.h // DJIUXSDKCore // +// MIT License +// // Copyright © 2018-2020 DJI // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -21,7 +23,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. -// +// #import #import diff --git a/DJIUXSDKCore/DUXBetaVoiceNotification.m b/DJIUXSDKCore/DUXBetaVoiceNotification.m index 7d77186..f4daa1a 100644 --- a/DJIUXSDKCore/DUXBetaVoiceNotification.m +++ b/DJIUXSDKCore/DUXBetaVoiceNotification.m @@ -2,6 +2,8 @@ // DUXBetaVoiceNotification.m // DJIUXSDKCore // +// MIT License +// // Copyright © 2018-2020 DJI // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -21,7 +23,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. -// +// #import "DUXBetaVoiceNotification.h" diff --git a/DJIUXSDKCore/DUXBetaWarningMessage.h b/DJIUXSDKCore/DUXBetaWarningMessage.h index 43889a6..1a59611 100644 --- a/DJIUXSDKCore/DUXBetaWarningMessage.h +++ b/DJIUXSDKCore/DUXBetaWarningMessage.h @@ -25,6 +25,7 @@ // SOFTWARE. // + typedef NS_ENUM(NSInteger, DUXBetaWarningMessageLevel) { DUXBetaWarningMessageLevelNotify, DUXBetaWarningMessageLevelWarning, diff --git a/DJIUXSDKCore/DUXBetaWarningMessage.m b/DJIUXSDKCore/DUXBetaWarningMessage.m index c75fc71..8d752a9 100644 --- a/DJIUXSDKCore/DUXBetaWarningMessage.m +++ b/DJIUXSDKCore/DUXBetaWarningMessage.m @@ -2,6 +2,8 @@ // DUXBetaWarningMessage.m // DJIUXSDKCore // +// MIT License +// // Copyright © 2018-2020 DJI // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -21,7 +23,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. -// +// #import #import "DUXBetaWarningMessage.h" diff --git a/DJIUXSDKCore/HelperCategories/NSObject+BetaSwiftHelpers.h b/DJIUXSDKCore/HelperCategories/NSObject+DUXBetaSwiftHelpers.h similarity index 95% rename from DJIUXSDKCore/HelperCategories/NSObject+BetaSwiftHelpers.h rename to DJIUXSDKCore/HelperCategories/NSObject+DUXBetaSwiftHelpers.h index 19cec6c..ff97879 100644 --- a/DJIUXSDKCore/HelperCategories/NSObject+BetaSwiftHelpers.h +++ b/DJIUXSDKCore/HelperCategories/NSObject+DUXBetaSwiftHelpers.h @@ -1,7 +1,9 @@ // -// NSObject+BetaSwiftHelpers.h +// NSObject+DUXBetaSwiftHelpers.h // DJIUXSDKCore // +// MIT License +// // Copyright © 2018-2020 DJI // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -21,7 +23,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. -// +// #import diff --git a/DJIUXSDKCore/HelperCategories/NSObject+BetaSwiftHelpers.m b/DJIUXSDKCore/HelperCategories/NSObject+DUXBetaSwiftHelpers.m similarity index 93% rename from DJIUXSDKCore/HelperCategories/NSObject+BetaSwiftHelpers.m rename to DJIUXSDKCore/HelperCategories/NSObject+DUXBetaSwiftHelpers.m index 0291bfb..722cb2a 100644 --- a/DJIUXSDKCore/HelperCategories/NSObject+BetaSwiftHelpers.m +++ b/DJIUXSDKCore/HelperCategories/NSObject+DUXBetaSwiftHelpers.m @@ -1,7 +1,9 @@ // -// NSObject+BetaSwiftHelpers.m +// NSObject+SwiftHelpers.m // DJIUXSDKCore // +// MIT License +// // Copyright © 2018-2020 DJI // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -21,9 +23,9 @@ // 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. -// +// -#import "NSObject+BetaSwiftHelpers.h" +#import "NSObject+DUXBetaSwiftHelpers.h" @implementation NSObject (DUXBetaSwiftHelpers) diff --git a/DJIUXSDKCore/HelperCategories/UIColor+DUXBetaColors.h b/DJIUXSDKCore/HelperCategories/UIColor+DUXBetaColors.h index c2de798..fdccee2 100644 --- a/DJIUXSDKCore/HelperCategories/UIColor+DUXBetaColors.h +++ b/DJIUXSDKCore/HelperCategories/UIColor+DUXBetaColors.h @@ -44,6 +44,8 @@ NS_ASSUME_NONNULL_BEGIN + (UIColor *)duxbeta_redColor; + (UIColor *)duxbeta_lightGrayColor; + (UIColor *)duxbeta_darkGrayColor; ++ (UIColor *)duxbeta_yellowColor; ++ (UIColor *)duxbeta_backgroundColor; + (UIColor *)duxbeta_disabledGrayColor; + (UIColor *)duxbeta_blueColor; + (UIColor *)duxbeta_linkBlueColor; @@ -54,9 +56,17 @@ NS_ASSUME_NONNULL_BEGIN + (UIColor *)duxbeta_remainingFlightTimeWidgetLowBatteryColor; + (UIColor *)duxbeta_remainingFlightTimeWidgetSeriouslyLowBatteryColor; +// Manual Focus Widget Colors ++ (UIColor *)duxbeta_manualFocusWidgetButtonBackgroundColor; + +// Histogram Widget Colors ++ (UIColor *)duxbeta_histogramWidgetBackgroundColor; ++ (UIColor *)duxbeta_histogramWidgetLineColor; ++ (UIColor *)duxbeta_histogramWidgetFillColor; ++ (UIColor *)duxbeta_histogramWidgetGridColor; + // System Status Widget Colors + (UIColor *)duxbeta_systemStatusWidgetGreenColor; -+ (UIColor *)duxbeta_systemStatusWidgetYellowColor; + (UIColor *)duxbeta_systemStatusWidgetRedColor; // Compass Widget Colors @@ -65,9 +75,17 @@ NS_ASSUME_NONNULL_BEGIN + (UIColor *)duxbeta_compassWidgetStrokeColor; + (UIColor *)duxbeta_compassWidgetHorizonColor; +// Auto Exposure Switch Widget Colors ++ (UIColor *)duxbeta_autoExposureSwitchWidgetWhiteColor; + // Battery Widget Colors + (UIColor *)duxbeta_batteryNormalGreen; -+ (UIColor *)duxbeta_batteryOverheatingYellowColor; + +// RTK Satellite Status Widget Colors ++ (UIColor *)duxbeta_rtkOverallStatusGreen; ++ (UIColor *)duxbeta_rtkOverallStatusYellow; ++ (UIColor *)duxbeta_rtkOverallStatusRed; ++ (UIColor *)duxbeta_rtkTableBorderColor; // AirSense Widget Colors + (UIColor *)duxbeta_airSenseYellowColor; @@ -75,7 +93,7 @@ NS_ASSUME_NONNULL_BEGIN // Simulator Indicator Widget Color + (UIColor *)duxbeta_simulatorIndicatorWidgetGreenColor; -// FPV camera name & side background color +// FPV camera name & side background color + (UIColor *)duxbeta_fpvBackgroundColor; + (UIColor *)duxbeta_fpvGridLineColor; + (UIColor *)duxbeta_fpvGridLineShadowColor; @@ -89,6 +107,17 @@ NS_ASSUME_NONNULL_BEGIN + (UIColor *)duxbeta_listPanelBackgroundColor; + (UIColor *)duxbeta_listPanelSeparatorColor; +// AlertView Mask Background Color ++ (UIColor *)duxbeta_alertViewMaskColor; + +// TakeOff and ReturnHome Dialog Colors ++ (UIColor *)duxbeta_alertBackgroundColor; ++ (UIColor *)duxbeta_alertActionBlueColor; ++ (UIColor *)duxbeta_slideTextColor; ++ (UIColor *)duxbeta_slideIconSelectedColor; ++ (UIColor *)duxbeta_slideSeparatorColor; ++ (UIColor *)duxbeta_alertWarningColor; + @end NS_ASSUME_NONNULL_END diff --git a/DJIUXSDKCore/HelperCategories/UIColor+DUXBetaColors.m b/DJIUXSDKCore/HelperCategories/UIColor+DUXBetaColors.m index e1fdc83..e0953f7 100644 --- a/DJIUXSDKCore/HelperCategories/UIColor+DUXBetaColors.m +++ b/DJIUXSDKCore/HelperCategories/UIColor+DUXBetaColors.m @@ -75,6 +75,18 @@ + (UIColor *)duxbeta_darkGrayColor { return [UIColor darkGrayColor]; } ++ (UIColor *)duxbeta_yellowColor { + return RGBA(255.0, 192.0, 0.0, 1.0); +} + ++ (UIColor *)duxbeta_backgroundColor { + return RGBA(0.0, 0.0, 0.0, 0.6); +} + ++ (UIColor *)duxbeta_rtkTableBorderColor { + return RGBA(180.0, 180.0, 52.0, 0.25); +} + + (UIColor *)duxbeta_lightGrayTransparentColor { return RGBA(100.0, 100.0, 100.0, 0.25); } @@ -99,16 +111,28 @@ + (UIColor *)duxbeta_remainingFlightTimeWidgetSeriouslyLowBatteryColor { return RGBA(238.0, 43.0, 42.0, 1.0); } -+ (UIColor *)duxbeta_systemStatusWidgetGreenColor { - return RGBA(97.0, 189.0, 23.0, 1.0); ++ (UIColor *)duxbeta_manualFocusWidgetButtonBackgroundColor { + return RGBA(0.0, 0.0, 0.0, 0.2); } -+ (UIColor *)duxbeta_systemStatusWidgetYellowColor { - return RGBA(255.0, 192.0, 10.0, 1.0); ++ (UIColor *)duxbeta_histogramWidgetBackgroundColor { + return RGBA(0.0, 0.0, 0.0, 0.47); } -+ (UIColor *)duxbeta_batteryOverheatingYellowColor { - return RGBA(255.0, 192.0, 0.0, 1.0); ++ (UIColor *)duxbeta_histogramWidgetLineColor { + return RGBA(255.0, 255.0, 255.0, 0.2); +} + ++ (UIColor *)duxbeta_histogramWidgetFillColor { + return RGBA(255.0, 255.0, 255.0, 0.7); +} + ++ (UIColor *)duxbeta_histogramWidgetGridColor { + return RGBA(255.0, 255.0, 255.0, 0.4); +} + ++ (UIColor *)duxbeta_systemStatusWidgetGreenColor { + return RGBA(97.0, 189.0, 23.0, 1.0); } + (UIColor *)duxbeta_systemStatusWidgetRedColor { @@ -131,6 +155,10 @@ + (UIColor *)duxbeta_compassWidgetHorizonColor { return RGBA(32.0, 163.0, 246.0, 0.4); } ++ (UIColor *)duxbeta_autoExposureSwitchWidgetWhiteColor { + return RGBA(140.0, 140.0, 140.0, 1.0); +} + + (UIColor *)duxbeta_linkBlueColor { return RGBA(61.0, 133.0, 199.0, 1.0); } @@ -147,6 +175,18 @@ + (UIColor *)duxbeta_batteryNormalGreen { return RGBA(63, 193, 77, 1.0); } ++ (UIColor *)duxbeta_rtkOverallStatusGreen { + return RGBA(126, 211, 33, 1.0); +} + ++ (UIColor *)duxbeta_rtkOverallStatusYellow { + return RGBA(248, 231, 28, 1.0); +} + ++ (UIColor *)duxbeta_rtkOverallStatusRed { + return RGBA(208, 2, 27, 1.0); +} + + (UIColor *)duxbeta_fpvBackgroundColor { return [UIColor colorWithWhite:0.15 alpha:1]; } @@ -187,4 +227,32 @@ + (UIColor *)duxbeta_listPanelSeparatorColor { return RGBA(255, 255, 255, .8); } ++ (UIColor *)duxbeta_alertViewMaskColor { + return RGBA(0, 0, 0, .4); +} + ++ (UIColor *)duxbeta_alertBackgroundColor { + return RGBA(66.0, 66.0, 66.0, 1.0); +} + ++ (UIColor *)duxbeta_alertActionBlueColor { + return RGBA(51.0, 156.0, 233.0, 1.0); +} + ++ (UIColor *)duxbeta_slideTextColor { + return RGBA(130.0, 80.0, 0.0, 1.0); +} + ++ (UIColor *)duxbeta_slideIconSelectedColor { + return RGBA(219.0, 221.0, 220.0, 1.0); +} + ++ (UIColor *)duxbeta_slideSeparatorColor { + return RGBA(91.0, 91.0, 91.0, 1.0); +} + ++ (UIColor *)duxbeta_alertWarningColor { + return RGBA(251.0, 225.0, 57.0, 1.0); +} + @end diff --git a/DJIUXSDKCore/HelperCategories/UIDevice+DUXBetaHelper.swift b/DJIUXSDKCore/HelperCategories/UIDevice+DUXBetaHelper.swift new file mode 100644 index 0000000..7f22f4d --- /dev/null +++ b/DJIUXSDKCore/HelperCategories/UIDevice+DUXBetaHelper.swift @@ -0,0 +1,207 @@ +// +// UIDevice+DUXBetaHelper.swift +// DJIUXSDKCore +// +// MIT License +// +// Copyright © 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. +// + +import UIKit + +@objc public enum DUXBetaDeviceScreenType: Int { + case Unknown = 0 + case resolution_480x320 //iphone3gs,iphone4s,iphone4 + case resolution_568x320 //iphone5,iphone5s,iphoneSE + case resolution_667x375 //iphone6,iphone6se + case resolution_736x414 //iphone6s + case resolution_1024x768 //ipad1,2,ipad mini,ipad3,4,air ipad mini retina + case resolution_812x375 //iphoneX iphoneXs + case resolution_896x414 //iphoneXR iphone Xs Max + case resolution_1080x810 //ipad 7th + case resolution_1112x834 //ipad air 3, ipad pro 10.5 + case resolution_1366x1024 //ipad pro, ipad pro 12.9 + case resolution_834x1194 //ipad pro 11 +} + +@objc public extension UIDevice { + + static var duxbeta_systemVersion: Float = -1 + static var duxbeta_screenType = DUXBetaDeviceScreenType.Unknown + + @objc static var duxbeta_isLandscape: Bool { + return UIApplication.shared.statusBarOrientation == .landscapeRight || UIApplication.shared.statusBarOrientation == .landscapeLeft + } + + @objc static var duxbeta_currentSystemVersion: Float { + if UIDevice.duxbeta_systemVersion < 0, let version = Float(UIDevice.current.systemVersion) { + UIDevice.duxbeta_systemVersion = version + } + return UIDevice.duxbeta_systemVersion + } + + @objc static var duxbeta_currentScreenType: DUXBetaDeviceScreenType { + if UIDevice.duxbeta_screenType == .Unknown { + let screenBounds = UIScreen.main.bounds + + if screenBounds.height*320 == screenBounds.width*480 || screenBounds.width*320 == screenBounds.height*480 { + UIDevice.duxbeta_screenType = .resolution_480x320 + } else if screenBounds.height*320 == screenBounds.width*568 || screenBounds.width*320 == screenBounds.height*568 { + UIDevice.duxbeta_screenType = .resolution_568x320 + } else if screenBounds.height*667 == screenBounds.width*375 || screenBounds.width*667 == screenBounds.height*375 { + UIDevice.duxbeta_screenType = .resolution_667x375 + } else if screenBounds.height*736 == screenBounds.width*414 || screenBounds.width*736 == screenBounds.height*414 { + UIDevice.duxbeta_screenType = .resolution_736x414 + } else if screenBounds.height*812 == screenBounds.width*375 || screenBounds.width*812 == screenBounds.height*375 { + UIDevice.duxbeta_screenType = .resolution_812x375 + } else if screenBounds.height*896 == screenBounds.width*414 || screenBounds.width*896 == screenBounds.height*414 { + UIDevice.duxbeta_screenType = .resolution_896x414 + } else if screenBounds.height*768 == screenBounds.width*1024 || screenBounds.width*768 == screenBounds.height*1024 { + UIDevice.duxbeta_screenType = .resolution_1024x768 + } else if screenBounds.height*1080 == screenBounds.width*810 || screenBounds.width*1080 == screenBounds.height*810 { + UIDevice.duxbeta_screenType = .resolution_1080x810 + } else if screenBounds.height*1112 == screenBounds.width*834 || screenBounds.width*1112 == screenBounds.height*834 { + UIDevice.duxbeta_screenType = .resolution_1112x834 + } else if screenBounds.height*1366 == screenBounds.width*1024 || screenBounds.width*1366 == screenBounds.height*1024 { + UIDevice.duxbeta_screenType = .resolution_1366x1024 + } else if screenBounds.height*834 == screenBounds.width*1194 || screenBounds.width*834 == screenBounds.height*1194 { + UIDevice.duxbeta_screenType = .resolution_834x1194 + } + } + + return UIDevice.duxbeta_screenType + } + + @objc static var duxbeta_isiPad: Bool { + let screenType = UIDevice.duxbeta_currentScreenType + return screenType == .resolution_1024x768 || screenType == .resolution_1024x768 || screenType == .resolution_1112x834 || screenType == .resolution_1366x1024 || screenType == .resolution_834x1194 + } + + @objc static var duxbeta_isiPhone4: Bool { + return UIDevice.duxbeta_currentScreenType == .resolution_480x320 + } + + @objc static var duxbeta_isiPhone5: Bool { + return UIDevice.duxbeta_currentScreenType == .resolution_568x320 + } + + @objc static var duxbeta_isiPhone6: Bool { + return UIDevice.duxbeta_currentScreenType == .resolution_667x375 + } + + @objc static var duxbeta_isiPhone6Plus: Bool { + return UIDevice.duxbeta_currentScreenType == .resolution_736x414 + } + + @objc static var duxbeta_isiPhoneXOrigin: Bool { + return UIDevice.duxbeta_currentScreenType == .resolution_812x375 + } + + @objc static var duxbeta_isiPhoneXR: Bool { + return UIDevice.duxbeta_currentScreenType == .resolution_896x414 && UIScreen.main.scale == 2 + } + + @objc static var duxbeta_isiPhoneXMax: Bool { + return UIDevice.duxbeta_currentScreenType == .resolution_896x414 && UIScreen.main.scale == 3 + } + + @objc static var duxbeta_isiPhoneX: Bool { + return UIDevice.duxbeta_isiPhoneXOrigin || UIDevice.duxbeta_isiPhoneXR || UIDevice.duxbeta_isiPhoneXMax + } + + @objc static func DUXBeta_autoiPhoneXScale(offset: CGFloat) -> CGFloat { + return UIDevice.duxbeta_isiPhoneXR || UIDevice.duxbeta_isiPhoneXMax ? floor(896.0/812.0 * offset) : offset + } + + @objc static var duxbeta_logicWidthPotrait: CGFloat { + if UIDevice.duxbeta_isiPad { return 768 } + if UIDevice.duxbeta_isiPhone5 { return 320 } + if UIDevice.duxbeta_isiPhone6 { return 375 } + if UIDevice.duxbeta_isiPhoneX { + if UIDevice.duxbeta_isiPhoneXR || UIDevice.duxbeta_isiPhoneXMax { return 414 } + else { return 375 } + } + return 414 + } + + @objc static var duxbeta_logicHeightPotrait: CGFloat { + if UIDevice.duxbeta_isiPad { return 1024 } + if UIDevice.duxbeta_isiPhone5 { return 568 } + if UIDevice.duxbeta_isiPhone6 { return 667 } + if UIDevice.duxbeta_isiPhoneX { + if UIDevice.duxbeta_isiPhoneXR || UIDevice.duxbeta_isiPhoneXMax { return 896 } + else { return 812 } + } + return 736 + } + + @objc static var duxbeta_logicHeightLandscape: CGFloat { + return UIDevice.duxbeta_logicWidthPotrait + } + + @objc static var duxbeta_logicWidthLandscape: CGFloat { + return UIDevice.duxbeta_logicHeightPotrait + } + + @objc static var duxbeta_widthScale: CGFloat { + if UIDevice.duxbeta_isiPad { return 0.4 } + if UIDevice.duxbeta_isiPhone5 { return 0.7 } + if UIDevice.duxbeta_isiPhone6 { return 0.6 } + + return 0.6 + } + + @objc static var duxbeta_smallHeightScale: CGFloat { + if UIDevice.duxbeta_isiPad { return 0.25 } + if UIDevice.duxbeta_isiPhone5 { return 0.5 } + if UIDevice.duxbeta_isiPhone6 { return 0.5 } + if UIDevice.duxbeta_isiPhone6Plus { return 0.4 } + if UIDevice.duxbeta_isiPhoneX { + if UIDevice.duxbeta_isiPhoneXR || UIDevice.duxbeta_isiPhoneXMax { return 0.4 } + else { return 0.5 } + } + return 0.4 + } + + @objc static var duxbeta_mediumHeightScale: CGFloat { + if UIDevice.duxbeta_isiPad { return 0.4 } + if UIDevice.duxbeta_isiPhone5 { return 0.7 } + if UIDevice.duxbeta_isiPhone6 { return 0.7 } + if UIDevice.duxbeta_isiPhone6Plus { return 0.7 } + if UIDevice.duxbeta_isiPhoneX { + if UIDevice.duxbeta_isiPhoneXR || UIDevice.duxbeta_isiPhoneXMax { return 0.6 } + else { return 0.7 } + } + return 0.6 + } + + @objc static var duxbeta_largeHeightScale: CGFloat { + if UIDevice.duxbeta_isiPad { return 0.6 } + if UIDevice.duxbeta_isiPhone5 { return 0.9 } + if UIDevice.duxbeta_isiPhone6 { return 0.9 } + if UIDevice.duxbeta_isiPhone6Plus { return 0.9 } + if UIDevice.duxbeta_isiPhoneX { + if UIDevice.duxbeta_isiPhoneXR || UIDevice.duxbeta_isiPhoneXMax { return 0.8 } + else { return 0.9 } + } + return 0.9 + } +} diff --git a/DJIUXSDKCore/RemoteKVO/DUXBetaCustomAsyncCache.m b/DJIUXSDKCore/RemoteKVO/DUXBetaCustomAsyncCache.m index c2afbcf..b77d49e 100644 --- a/DJIUXSDKCore/RemoteKVO/DUXBetaCustomAsyncCache.m +++ b/DJIUXSDKCore/RemoteKVO/DUXBetaCustomAsyncCache.m @@ -49,8 +49,8 @@ @implementation DUXBetaCustomAsyncCache - (void)dealloc { //通知所有回调操作被取消。 - [self cacheDidSetCustomValue:nil withError:[NSError errorForDUXBetaCustomKVO:DUXBetaCustomKVOError_Cancel]]; - [self callbackGettingBlock:nil withError:[NSError errorForDUXBetaCustomKVO:DUXBetaCustomKVOError_Cancel]]; + [self cacheDidSetCustomValue:nil withError:[NSError errorForDUXCustomKVO:DUXBetaCustomKVOError_Cancel]]; + [self callbackGettingBlock:nil withError:[NSError errorForDUXCustomKVO:DUXBetaCustomKVOError_Cancel]]; pthread_mutex_destroy(&_getmutex); pthread_mutex_destroy(&_setmutex); } @@ -97,7 +97,7 @@ - (DUXBetaCustomValueConfirmation *)cacheWillSetCustomValue:(nullable id)value c pthread_mutex_unlock(&_setmutex); if (lastBlock) { - lastBlock([NSError errorForDUXBetaCustomKVO:DUXBetaCustomKVOError_Cancel]); + lastBlock([NSError errorForDUXCustomKVO:DUXBetaCustomKVOError_Cancel]); } } else diff --git a/DJIUXSDKCore/RemoteKVO/NSError+DUXBetaCustomKVO.h b/DJIUXSDKCore/RemoteKVO/NSError+DUXBetaCustomKVO.h index 4898283..8a2282d 100644 --- a/DJIUXSDKCore/RemoteKVO/NSError+DUXBetaCustomKVO.h +++ b/DJIUXSDKCore/RemoteKVO/NSError+DUXBetaCustomKVO.h @@ -42,7 +42,7 @@ typedef NS_ENUM(NSInteger,DUXBetaCustomKVOError){ @interface NSError (DUXBetaCustomKVO) -+ (nullable instancetype)errorForDUXBetaCustomKVO:(DUXBetaCustomKVOError)errorCode; -- (BOOL)isDUXBetaCustomKVOError; ++ (nullable instancetype)errorForDUXCustomKVO:(DUXBetaCustomKVOError)errorCode; +- (BOOL)isDUXCustomKVOError; @end diff --git a/DJIUXSDKCore/RemoteKVO/NSError+DUXBetaCustomKVO.m b/DJIUXSDKCore/RemoteKVO/NSError+DUXBetaCustomKVO.m index a9da0c5..2695825 100644 --- a/DJIUXSDKCore/RemoteKVO/NSError+DUXBetaCustomKVO.m +++ b/DJIUXSDKCore/RemoteKVO/NSError+DUXBetaCustomKVO.m @@ -26,11 +26,11 @@ #import "NSError+DUXBetaCustomKVO.h" -NSString * const DUXBetaCustomKVOErrorDomain = @"DUXBetaCustomKVOErrorDomain"; +NSString * const DUXBetaCustomKVOErrorDomain = @"DUXCustomKVOErrorDomain"; @implementation NSError (DUXBetaCustomKVOError) -+ (nullable instancetype)errorForDUXBetaCustomKVO:(DUXBetaCustomKVOError)errorCode ++ (nullable instancetype)errorForDUXCustomKVO:(DUXBetaCustomKVOError)errorCode { return errorCode == 0 ? nil : [NSError errorWithDomain:DUXBetaCustomKVOErrorDomain code:errorCode userInfo:nil]; } diff --git a/DJIUXSDKCore/RemoteKVO/NSObject+DUXBetaCustomKVO.m b/DJIUXSDKCore/RemoteKVO/NSObject+DUXBetaCustomKVO.m index f1b3e20..2ae0616 100644 --- a/DJIUXSDKCore/RemoteKVO/NSObject+DUXBetaCustomKVO.m +++ b/DJIUXSDKCore/RemoteKVO/NSObject+DUXBetaCustomKVO.m @@ -30,8 +30,8 @@ #import #import -#define DUXBetaCustomObserverKey "__DUXBetaCustomObserver" //If conflict, fix it. -#define GetDUXBetaCustomObserverRuntime(__name__) objc_getAssociatedObject(self, DUXBetaCustomObserverKey); \ +#define DUXBetaCustomObserverKey "__DUXCustomObserver" //If conflict, fix it. +#define GetDUXCustomObserverRuntime(__name__) objc_getAssociatedObject(self, DUXBetaCustomObserverKey); \ if(__name__ == nil){ \ __name__ = [[DUXBetaCustomObserverRuntime alloc] initWithObject:self]; \ objc_setAssociatedObject(self, DUXBetaCustomObserverKey, __name__, OBJC_ASSOCIATION_RETAIN_NONATOMIC); \ @@ -40,18 +40,18 @@ @implementation NSObject (DUXBetaCustomKVO) - (void)duxbeta_addCustomObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(void(^)(id oldValue,id newValue))block{ - DUXBetaCustomObserverRuntime *runtime = GetDUXBetaCustomObserverRuntime(runtime); + DUXBetaCustomObserverRuntime *runtime = GetDUXCustomObserverRuntime(runtime); [runtime registerCustomObserver:observer forKeyPath:keyPath block:block]; } - (void)duxbeta_addCustomObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath selector:(SEL)selector{ - DUXBetaCustomObserverRuntime *runtime = GetDUXBetaCustomObserverRuntime(runtime); + DUXBetaCustomObserverRuntime *runtime = GetDUXCustomObserverRuntime(runtime); [runtime registerCustomObserver:observer forKeyPath:keyPath selector:selector]; } - (void)duxbeta_addCustomObserver:(nonnull NSObject *)observer forKeyPaths:(nonnull NSArray *)keyPaths block:(nonnull void(^)(NSString *_Nullable keypath,id _Nullable oldValue,id _Nullable newValue))block { - DUXBetaCustomObserverRuntime *runtime = GetDUXBetaCustomObserverRuntime(runtime); + DUXBetaCustomObserverRuntime *runtime = GetDUXCustomObserverRuntime(runtime); for (int i = 0; i < keyPaths.count; i++) { [runtime registerCustomObserver:observer forKeyPath:keyPaths[i] block:^(id _Nullable oldValue, id _Nullable newValue) { if (block) @@ -63,26 +63,26 @@ - (void)duxbeta_addCustomObserver:(nonnull NSObject *)observer forKeyPaths:(nonn } - (void)duxbeta_removeCustomObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{ - DUXBetaCustomObserverRuntime *runtime = GetDUXBetaCustomObserverRuntime(runtime); + DUXBetaCustomObserverRuntime *runtime = GetDUXCustomObserverRuntime(runtime); [runtime unregisterCustomObserver:observer forKeyPath:keyPath]; } - (void)duxbeta_removeCustomObserver:(nonnull NSObject *)observer forKeyPaths:(nonnull NSArray *)keyPaths { - DUXBetaCustomObserverRuntime *runtime = GetDUXBetaCustomObserverRuntime(runtime); + DUXBetaCustomObserverRuntime *runtime = GetDUXCustomObserverRuntime(runtime); for (int i = 0; i < keyPaths.count; i++) { [runtime unregisterCustomObserver:observer forKeyPath:keyPaths[i]]; } } - (void)duxbeta_removeCustomObserver:(NSObject *)observer{ - DUXBetaCustomObserverRuntime *runtime = GetDUXBetaCustomObserverRuntime(runtime); + DUXBetaCustomObserverRuntime *runtime = GetDUXCustomObserverRuntime(runtime); [runtime unregisterCustomObserver:observer]; } - (void)duxbeta_removeAllCustomObservers{ - DUXBetaCustomObserverRuntime *runtime = GetDUXBetaCustomObserverRuntime(runtime); + DUXBetaCustomObserverRuntime *runtime = GetDUXCustomObserverRuntime(runtime); [runtime unregisterAllCustomObservers]; } @@ -122,61 +122,61 @@ - (nullable id)duxbeta_customValueForKeyPath:(nonnull NSString *)keyPath - (void)duxbeta_setCustomValue:(nullable id)value forKeyPath:(nonnull NSString *)keyPath completion:(nullable DUXBetaCustomValueSetCompletionBlock)block { - DUXBetaCustomObserverRuntime *runtime = GetDUXBetaCustomObserverRuntime(runtime); + DUXBetaCustomObserverRuntime *runtime = GetDUXCustomObserverRuntime(runtime); [runtime runtimeSetCustomValue:value forKeyPath:keyPath completion:block]; } - (void)duxbeta_getCustomValueForKeyPath:(nonnull NSString *)keyPath completion:(nullable DUXBetaCustomValueGetCompletionBlock)block { - DUXBetaCustomObserverRuntime *runtime = GetDUXBetaCustomObserverRuntime(runtime); + DUXBetaCustomObserverRuntime *runtime = GetDUXCustomObserverRuntime(runtime); [runtime runtimeGetCustomValueForKeyPath:keyPath completion:block]; } - (DUXBetaCustomValueConfirmation *)duxbeta_willSetCustomValue:(nullable id)value forKeyPath:(nonnull NSString *)keyPath completion:(nullable DUXBetaCustomValueSetCompletionBlock)block { - DUXBetaCustomObserverRuntime *runtime = GetDUXBetaCustomObserverRuntime(runtime); + DUXBetaCustomObserverRuntime *runtime = GetDUXCustomObserverRuntime(runtime); return [runtime runtimeWillSetCustomValue:value forKeyPath:keyPath completion:block]; } - (nullable id)duxbeta_settingCustomValueForKeyPath:(nonnull NSString *)keyPath { - DUXBetaCustomObserverRuntime *runtime = GetDUXBetaCustomObserverRuntime(runtime); + DUXBetaCustomObserverRuntime *runtime = GetDUXCustomObserverRuntime(runtime); return [runtime runtimeSettingCustomValueForKeyPath:keyPath]; } - (DUXBetaCustomValueConfirmation *)duxbeta_willGetCustomValueForKeyPath:(NSString *)keyPath completion:(nullable DUXBetaCustomValueGetCompletionBlock)block { - DUXBetaCustomObserverRuntime *runtime = GetDUXBetaCustomObserverRuntime(runtime); + DUXBetaCustomObserverRuntime *runtime = GetDUXCustomObserverRuntime(runtime); return [runtime runtimeWillGetCustomValueForKeyPath:keyPath completion:block]; } - (void)duxbeta_updateCustomValue:(nullable id)value forKeyPath:(nonnull NSString *)keyPath { - DUXBetaCustomObserverRuntime *runtime = GetDUXBetaCustomObserverRuntime(runtime); + DUXBetaCustomObserverRuntime *runtime = GetDUXCustomObserverRuntime(runtime); [runtime runtimeUpdateCustomValue:value forKeyPath:keyPath]; } - (BOOL)duxbeta_isSettingCustomValueForKeyPath:(nonnull NSString *)keyPath { - DUXBetaCustomObserverRuntime *runtime = GetDUXBetaCustomObserverRuntime(runtime); + DUXBetaCustomObserverRuntime *runtime = GetDUXCustomObserverRuntime(runtime); return [runtime runtimeIsSettingCustomValueForkeyPath:keyPath]; } - (BOOL)duxbeta_isInitCustomValueForKeyPath:(nonnull NSString *)keyPath { - DUXBetaCustomObserverRuntime *runtime = GetDUXBetaCustomObserverRuntime(runtime); + DUXBetaCustomObserverRuntime *runtime = GetDUXCustomObserverRuntime(runtime); return [runtime runtimeIsInitCustomValueForKeyPath:keyPath]; } - (void)duxbeta_initCustomValue:(nullable id)value forKeyPath:(nonnull NSString *)keyPath { - DUXBetaCustomObserverRuntime *runtime = GetDUXBetaCustomObserverRuntime(runtime); + DUXBetaCustomObserverRuntime *runtime = GetDUXCustomObserverRuntime(runtime); [runtime runtimeInitCustomValue:value forKeyPath:keyPath]; } - (void)duxbeta_deinitCustomValueForKeyPath:(nonnull NSString *)keyPath { - DUXBetaCustomObserverRuntime *runtime = GetDUXBetaCustomObserverRuntime(runtime); + DUXBetaCustomObserverRuntime *runtime = GetDUXCustomObserverRuntime(runtime); [runtime runtimeDeinitCustomValueForKeyPath:keyPath]; } @@ -186,25 +186,25 @@ @implementation NSObject (CustomAsyncKVOReplace) - (void)duxbeta_replaceSetCustomValueBlock:(nonnull void(^)(id _Nullable value,DUXBetaCustomValueSetCompletionBlock _Nullable completion))method forKeyPath:(nonnull NSString *)keyPath { - DUXBetaCustomObserverRuntime *runtime = GetDUXBetaCustomObserverRuntime(runtime); + DUXBetaCustomObserverRuntime *runtime = GetDUXCustomObserverRuntime(runtime); [runtime runtimeReplaceSetCustomValueBlock:method forKeyPath:keyPath]; } - (void)duxbeta_replaceGetCustomValueBlock:(nonnull void(^)(DUXBetaCustomValueGetCompletionBlock _Nullable completion))method forKeyPath:(nonnull NSString *)keyPath { - DUXBetaCustomObserverRuntime *runtime = GetDUXBetaCustomObserverRuntime(runtime); + DUXBetaCustomObserverRuntime *runtime = GetDUXCustomObserverRuntime(runtime); [runtime runtimeReplaceGetCustomValueBlock:method forKeyPath:keyPath]; } - (void)duxbeta_replaceSetCustomValueMethod:(nonnull SEL)method forKeyPath:(nonnull NSString *)keyPath { - DUXBetaCustomObserverRuntime *runtime = GetDUXBetaCustomObserverRuntime(runtime); + DUXBetaCustomObserverRuntime *runtime = GetDUXCustomObserverRuntime(runtime); [runtime runtimeReplaceSetCustomValueMethod:method forKeyPath:keyPath]; } - (void)duxbeta_replaceGetCustomValueMethod:(nonnull SEL)method forKeyPath:(nonnull NSString *)keyPath { - DUXBetaCustomObserverRuntime *runtime = GetDUXBetaCustomObserverRuntime(runtime); + DUXBetaCustomObserverRuntime *runtime = GetDUXCustomObserverRuntime(runtime); [runtime runtimeReplaceGetCustomValueMethod:method forKeyPath:keyPath]; } diff --git a/DJIUXSDKCore/module.modulemap b/DJIUXSDKCore/module.modulemap deleted file mode 100644 index abf8095..0000000 --- a/DJIUXSDKCore/module.modulemap +++ /dev/null @@ -1,6 +0,0 @@ -module DJIUXSDKCore { - umbrella header "DJIUXSDKCore.h" - - export * - module * { export * } -} \ No newline at end of file diff --git a/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseDialogViewController.h b/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseDialogViewController.h index 7183422..4814539 100644 --- a/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseDialogViewController.h +++ b/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseDialogViewController.h @@ -3,9 +3,9 @@ // DJIUXSDKWidgets // // MIT License -// +// // Copyright © 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 @@ -27,7 +27,8 @@ #import -@class DUXDialogCustomizations; +@class DUXBetaDialogCustomizations; +@class DUXBetaAirSenseWidget; NS_ASSUME_NONNULL_BEGIN @@ -78,6 +79,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, strong) UIFont *warningMessageTextFont; +/** + * The widget that presented this dialog. Used to pass back the dontShowAgainCheckBoxTap data. +*/ +@property (nonatomic, weak) DUXBetaAirSenseWidget *presentingWidget; + /** * Construct with title and message. */ diff --git a/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseDialogViewController.m b/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseDialogViewController.m index c8a87ea..1be0f9a 100644 --- a/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseDialogViewController.m +++ b/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseDialogViewController.m @@ -1,11 +1,11 @@ // -// DUXBetaAirSenseHtmlViewController.m +// DUXBetaAirSenseDialogViewController.m // DJIUXSDKWidgets // // MIT License -// +// // Copyright © 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 @@ -30,8 +30,9 @@ #import "UIImage+DUXBetaAssets.h" #import "UIColor+DUXBetaColors.h" #import -#import "DUXStateChangeBroadcaster.h" +#import "DUXBetaStateChangeBroadcaster.h" #import "DUXBetaAirSenseWidgetUIState.h" +#import "DUXBetaAirSenseWidget.h" @import DJIUXSDKCommunication; @import DJIUXSDKCore; @@ -97,7 +98,7 @@ - (void)viewWillDisappear:(BOOL)animated { - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; - [[DUXStateChangeBroadcaster instance] send:[DUXBetaAirSenseWidgetUIState warningDialogDismiss]]; + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaAirSenseWidgetUIState warningDialogDismiss]]; } - (void)setupUI { @@ -204,7 +205,7 @@ - (void)updateUI { } - (void)presentTermsDialog { - [[DUXStateChangeBroadcaster instance] send:[DUXBetaAirSenseWidgetUIState termsLinkTap]]; + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaAirSenseWidgetUIState termsLinkTap]]; UIViewController *htmlViewController = [[DUXBetaAirSenseHtmlViewController alloc] init]; [self presentViewController:htmlViewController animated:YES completion:nil]; @@ -212,7 +213,7 @@ - (void)presentTermsDialog { // Function to toggle "Don't show again" checkbox - (void)toggleButton:(UIButton *)pressedButton { - [[DUXStateChangeBroadcaster instance] send:[DUXBetaAirSenseWidgetUIState dontShowAgainCheckBoxTap:!pressedButton.selected]]; + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaAirSenseWidgetUIState dontShowAgainCheckBoxTap:!pressedButton.selected]]; pressedButton.selected = !pressedButton.selected; } @@ -220,6 +221,9 @@ - (void)toggleButton:(UIButton *)pressedButton { - (void)dismissViewController { [[NSUserDefaults standardUserDefaults] setBool:self.checkboxButton.selected forKey:@"optOutAirSense"]; [[NSUserDefaults standardUserDefaults] synchronize]; + if (self.presentingWidget && self.checkboxButton.selected) { + self.presentingWidget.hasOptedOutDialog = YES; + } [self dismissViewControllerAnimated:YES completion:nil]; } diff --git a/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseHtmlViewController.h b/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseHtmlViewController.h index 942a0ab..63f1ca2 100644 --- a/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseHtmlViewController.h +++ b/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseHtmlViewController.h @@ -3,9 +3,9 @@ // DJIUXSDKWidgets // // MIT License -// +// // Copyright © 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 diff --git a/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseHtmlViewController.m b/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseHtmlViewController.m index 6857a5c..18e4f55 100644 --- a/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseHtmlViewController.m +++ b/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseHtmlViewController.m @@ -3,9 +3,9 @@ // DJIUXSDKWidgets // // MIT License -// +// // Copyright © 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 @@ -27,10 +27,11 @@ #import "DUXBetaAirSenseHtmlViewController.h" #import -#import "DUXStateChangeBroadcaster.h" +#import "DUXBetaStateChangeBroadcaster.h" #import "DUXBetaAirSenseWidgetUIState.h" +#import "NSBundle+DUXBetaAssets.h" -static NSInteger const DUXAirSenseHTMLBackButtonSideLength = 44; +static NSInteger const DUXBetaAirSenseHTMLBackButtonSideLength = 44; @interface DUXBetaAirSenseHtmlViewController () @@ -53,12 +54,12 @@ - (void)viewDidAppear:(BOOL)animated { - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; - [[DUXStateChangeBroadcaster instance] send:[DUXBetaAirSenseWidgetUIState termsDialogDismiss]]; + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaAirSenseWidgetUIState termsDialogDismiss]]; } - (void)setupWebviewBounds { - CGPoint htmlFrameOrigin = CGPointMake(self.view.frame.origin.x, self.view.frame.origin.y + DUXAirSenseHTMLBackButtonSideLength); - CGSize htmlFrameSize = CGSizeMake(self.view.frame.size.width, self.view.frame.size.height - DUXAirSenseHTMLBackButtonSideLength); + CGPoint htmlFrameOrigin = CGPointMake(self.view.frame.origin.x, self.view.frame.origin.y + DUXBetaAirSenseHTMLBackButtonSideLength); + CGSize htmlFrameSize = CGSizeMake(self.view.frame.size.width, self.view.frame.size.height - DUXBetaAirSenseHTMLBackButtonSideLength); self.htmlView.frame = (CGRect) { .origin = htmlFrameOrigin, .size = htmlFrameSize @@ -71,11 +72,12 @@ - (void)setupView { NSString *language = localeComponents[NSLocaleLanguageCode]; NSString *path; + NSBundle *currentBundle = [NSBundle duxbeta_currentBundle]; if ([language isEqualToString:@"zh"]) { - path = [[NSBundle bundleForClass:[self class]] pathForResource:@"air_sense_terms_of_use_chinese" + path = [currentBundle pathForResource:@"air_sense_terms_of_use_chinese" ofType:@"html"]; } else { - path = [[NSBundle bundleForClass:[self class]] pathForResource:@"air_sense_terms_of_use" + path = [currentBundle pathForResource:@"air_sense_terms_of_use" ofType:@"html"]; } @@ -96,7 +98,7 @@ - (void)setupView { - (void)addBackButton { UIButton *backButton = [[UIButton alloc] init]; - CGRect backButtonFrame = CGRectMake(0, 0, DUXAirSenseHTMLBackButtonSideLength, DUXAirSenseHTMLBackButtonSideLength); + CGRect backButtonFrame = CGRectMake(0, 0, DUXBetaAirSenseHTMLBackButtonSideLength, DUXBetaAirSenseHTMLBackButtonSideLength); backButton.frame = backButtonFrame; [backButton setTitle:@"<" forState:UIControlStateNormal]; [backButton.titleLabel setFont:[UIFont fontWithName:@"HelveticaNeue" size:18]]; diff --git a/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseWidget.h b/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseWidget.h index 1f4819e..59fa710 100644 --- a/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseWidget.h +++ b/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseWidget.h @@ -3,9 +3,9 @@ // DJIUXSDK // // MIT License -// +// // Copyright © 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 @@ -106,6 +106,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, strong) UIFont *dialogMessageTextFont; +/** + * The property determining if the user has opted to not show the "Another Aircraft is Nearby..." dialog again. +*/ +@property (nonatomic) BOOL hasOptedOutDialog; + /** * Set tint color for given warning level. */ diff --git a/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseWidget.m b/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseWidget.m index d349e5d..0ade2b1 100644 --- a/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseWidget.m +++ b/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseWidget.m @@ -1,5 +1,5 @@ // -// DUXBetAirSenseWidget.m +// DUXBetaAirSenseWidget.m // DJIUXSDK // // MIT License @@ -31,18 +31,17 @@ #import "UIFont+DUXBetaFonts.h" #import "DUXBetaAirSenseHtmlViewController.h" #import "DUXBetaAirSenseDialogViewController.h" -#import "DUXStateChangeBroadcaster.h" +#import "DUXBetaStateChangeBroadcaster.h" #import "NSLayoutConstraint+DUXBetaMultiplier.h" @import DJIUXSDKCore; -static NSString * const DUXAirSenseWidgetDialogTitle = @"Another aircraft is nearby. Fly with caution."; -static NSString * const DUXAirSenseWidgetDialogMessage = @"Please make sure you have read and understood the DJI AirSense Warnings."; +static NSString * const DUXBetaAirSenseWidgetDialogTitle = @"Another aircraft is nearby. Fly with caution."; +static NSString * const DUXBetaAirSenseWidgetDialogMessage = @"Please make sure you have read and understood the DJI AirSense Warnings."; @interface DUXBetaAirSenseWidget () @property (nonatomic) NSMutableDictionary *airSenseColorMapping; -@property (nonatomic) BOOL hasOptedOutDialog; @property (nonatomic) BOOL hasViewAppeared; @property (nonatomic, strong) UIImageView *airSenseImageView; @property (nonatomic, strong) DUXBetaAirSenseDialogViewController *dialogViewController; @@ -64,7 +63,7 @@ @interface DUXBetaAirSenseWidget () * * Key: airSnseWarningStateUpdate Type: NSNumber - Sends an NSNumber containing the AirSense warning state as an integer */ -@interface AirSenseWidgetModelState : DUXStateChangeBaseData +@interface AirSenseWidgetModelState : DUXBetaStateChangeBaseData + (instancetype)productConnected:(BOOL)isConnected; + (instancetype)airSenseConnected:(BOOL)isAirSenseConnected; @@ -101,8 +100,8 @@ - (void)setupInstanceVariables { _checkboxLabelTextFont = [UIFont systemFontOfSize:18]; _dialogMessageTextColor = [UIColor duxbeta_linkBlueColor]; _dialogMessageTextFont = [UIFont systemFontOfSize:18]; - _dialogTitle = DUXAirSenseWidgetDialogTitle; - _dialogMessage = DUXAirSenseWidgetDialogMessage; + _dialogTitle = DUXBetaAirSenseWidgetDialogTitle; + _dialogMessage = DUXBetaAirSenseWidgetDialogMessage; _dialogBackgroundColor = [UIColor duxbeta_whiteColor]; _dialogTitleTextColor = [UIColor duxbeta_blackColor]; _hasViewAppeared = NO; @@ -145,7 +144,7 @@ - (void)viewWillAppear:(BOOL)animated { BindRKVOModel(self.widgetModel, @selector(sendAirSenseWarningLevelUpdate), airSenseWarningLevel); BindRKVOModel(self.widgetModel, @selector(sendAirSenseWarningStateUpdate), airSenseWarningState); - BindRKVOModel(self.widgetModel, @selector(updateUI), airSenseWarningLevel, isAirSenseConnected, isProductConnected); + BindRKVOModel(self.widgetModel, @selector(updateUI), airSenseWarningState, airSenseWarningLevel, isAirSenseConnected, isProductConnected); BindRKVOModel(self, @selector(updateUI), iconBackgroundColor, checkedCheckboxImage, @@ -201,7 +200,7 @@ - (void)updateUI { // Check if product has an AirSense receiver if (self.widgetModel.isAirSenseConnected || !self.widgetModel.isProductConnected) { [self.view setHidden:NO]; - }else{ + } else { [self.view setHidden:YES]; return; } @@ -240,7 +239,10 @@ - (void)presentDialogIfAppropriate { if (isPresentingDialog) { [self setDialogCustomizations]; } else { - if (self.widgetModel.airSenseWarningLevel >= 1 && !self.hasOptedOutDialog && self.hasViewAppeared) { + if (self.widgetModel.airSenseWarningLevel >= 1 && + !self.hasOptedOutDialog && + self.hasViewAppeared && + self.widgetModel.isProductConnected) { [self presentAlertDialog]; } } @@ -264,6 +266,7 @@ - (void)presentAlertDialog { andMessage:self.dialogMessage]; [self setDialogCustomizations]; self.dialogViewController.modalPresentationStyle = UIModalPresentationFormSheet; + self.dialogViewController.presentingWidget = self; [self presentViewController:self.dialogViewController animated:YES completion:nil]; } @@ -277,21 +280,21 @@ - (UIColor *)getTintColorForWarningState:(DUXBetaAirSenseState)state { } - (void)sendIsProductConnected { - [[DUXStateChangeBroadcaster instance] send:[AirSenseWidgetModelState productConnected:self.widgetModel.isProductConnected]]; + [[DUXBetaStateChangeBroadcaster instance] send:[AirSenseWidgetModelState productConnected:self.widgetModel.isProductConnected]]; } - (void)sendIsAirSenseConnected { - [[DUXStateChangeBroadcaster instance] send:[AirSenseWidgetModelState airSenseConnected:self.widgetModel.isAirSenseConnected]]; + [[DUXBetaStateChangeBroadcaster instance] send:[AirSenseWidgetModelState airSenseConnected:self.widgetModel.isAirSenseConnected]]; } - (void)sendAirSenseWarningLevelUpdate { NSNumber *numberToSend = [[NSNumber alloc] initWithUnsignedInteger:self.widgetModel.airSenseWarningLevel]; - [[DUXStateChangeBroadcaster instance] send:[AirSenseWidgetModelState airSenseWarningLevelUpdate:numberToSend]]; + [[DUXBetaStateChangeBroadcaster instance] send:[AirSenseWidgetModelState airSenseWarningLevelUpdate:numberToSend]]; } - (void)sendAirSenseWarningStateUpdate { NSNumber *numberToSend = [[NSNumber alloc] initWithUnsignedInteger:self.widgetModel.airSenseWarningState]; - [[DUXStateChangeBroadcaster instance] send:[AirSenseWidgetModelState airSenseWarningStateUpdate:numberToSend]]; + [[DUXBetaStateChangeBroadcaster instance] send:[AirSenseWidgetModelState airSenseWarningStateUpdate:numberToSend]]; } - (void)updateMinImageDimensions { diff --git a/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseWidgetModel.m b/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseWidgetModel.m index 11b6702..f3ca081 100644 --- a/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseWidgetModel.m +++ b/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseWidgetModel.m @@ -32,11 +32,11 @@ @import DJIUXSDKCommunication; @import DJIUXSDKCore; -static NSString * const DUXAirSenseWidgetWarningMessageReasonForLevel2 = @"Another aircraft is nearby. Fly with caution."; -static NSString * const DUXAirSenseWidgetWarningMessageReasonForLevels3to5 = @"Another aircraft is nearby. Fly with caution."; +static NSString * const DUXBetaAirSenseWidgetWarningMessageReasonForLevel2 = @"Another aircraft is nearby. Fly with caution."; +static NSString * const DUXBetaAirSenseWidgetWarningMessageReasonForLevels3to5 = @"Another aircraft is nearby. Fly with caution."; -static NSString * const DUXAirSenseWidgetWarningMessageSolutionForLevel2 = @"Another aircraft is too close, please descend to a safer altitude."; -static NSString * const DUXAirSenseWidgetWarningMessageSolutionForLevel3to5 = @"Another aircraft is dangerously close, please descend to a safer altitude."; +static NSString * const DUXBetaAirSenseWidgetWarningMessageSolutionForLevel2 = @"Another aircraft is too close, please descend to a safer altitude."; +static NSString * const DUXBetaAirSenseWidgetWarningMessageSolutionForLevel3to5 = @"Another aircraft is dangerously close, please descend to a safer altitude."; @interface DUXBetaAirSenseWidgetModel() @@ -106,18 +106,18 @@ - (void)updateAirSenseState { - (void)presentWarningMessageIfAppropriate { if (self.airSenseWarningLevel == 2) { - [self sendWarningMessageWithReason:DUXAirSenseWidgetWarningMessageReasonForLevel2 - andSolution:DUXAirSenseWidgetWarningMessageSolutionForLevel2]; + [self sendWarningMessageWithReason:DUXBetaAirSenseWidgetWarningMessageReasonForLevel2 + andSolution:DUXBetaAirSenseWidgetWarningMessageSolutionForLevel2]; } if (self.airSenseWarningLevel > 2) { - [self sendWarningMessageWithReason:DUXAirSenseWidgetWarningMessageReasonForLevels3to5 - andSolution:DUXAirSenseWidgetWarningMessageSolutionForLevel3to5]; + [self sendWarningMessageWithReason:DUXBetaAirSenseWidgetWarningMessageReasonForLevels3to5 + andSolution:DUXBetaAirSenseWidgetWarningMessageSolutionForLevel3to5]; } } - (void)sendWarningMessageWithReason:(NSString *)reason andSolution:(NSString *)solution { DUXBetaWarningMessageKey *warningMessageKey = [[DUXBetaWarningMessageKey alloc] initWithIndex:0 - parameter:DUXBetaWarningMessageParameterSendWarningMessage]; + parameter:DUXBetaWarningMessageParameterSendWarningMessage]; DUXBetaWarningMessage *warningMessage = [[DUXBetaWarningMessage alloc] init]; warningMessage.reason = reason; diff --git a/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseWidgetUIState.h b/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseWidgetUIState.h index 4bcee4d..7acbb14 100644 --- a/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseWidgetUIState.h +++ b/DJIUXSDKWidgets/AirSenseWidget/DUXBetaAirSenseWidgetUIState.h @@ -30,7 +30,7 @@ NS_ASSUME_NONNULL_BEGIN /** - * DUXAirSenseWidgetUIState contains the hooks for UI changes in the widget class AirSenseWidget. + * DUXBetaAirSenseWidgetUIState contains the hooks for UI changes in the widget class DUXBetaAirSenseWidget. * It implements the hooks: * * Key: warningDialogDismiss Type: NSNumber - Sends a boolean YES as an NSNumber when the AirSense warning dialog is dismissed @@ -43,7 +43,7 @@ NS_ASSUME_NONNULL_BEGIN * Key: dontShowAgainCheckboxTapped Type: NSNumber - Sends a boolean as an NSNumber indicating the state of the Don't Show Again * checkbox for the Terms dialog. */ -@interface DUXBetaAirSenseWidgetUIState : DUXStateChangeBaseData +@interface DUXBetaAirSenseWidgetUIState : DUXBetaStateChangeBaseData + (instancetype)warningDialogDismiss; + (instancetype)termsLinkTap; diff --git a/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryState.h b/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryState.h index e559321..d5a6c44 100644 --- a/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryState.h +++ b/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryState.h @@ -71,7 +71,7 @@ typedef NS_ENUM(NSUInteger, DUXBetaBatteryStatus) { * * @param voltage An voltage value using `NSMeasurement`. * @param batteryPercentage numerical value to represent the current percentage of the battery. - * @param state An enum value of `DUXBetaBatteryStatus`. + * @param state An enum value of `DUXBatteryStatus`. */ - (instancetype)initWithVoltage:(NSMeasurement *)voltage andBatteryPercentage:(float)batteryPercentage withWarningLevel:(DUXBetaBatteryStatus)state; @@ -91,14 +91,14 @@ typedef NS_ENUM(NSUInteger, DUXBetaBatteryStatus) { @end -@interface DUXDualBatteryState : DUXBetaBatteryState +@interface DUXBetaDualBatteryState : DUXBetaBatteryState @property (nonatomic) float battery2Percentage; @property (strong, nonatomic) NSMeasurement *battery2Voltage; @end -@interface DUXAggregateBatteryState : DUXBetaBatteryState +@interface DUXBetaAggregateBatteryState : DUXBetaBatteryState @end diff --git a/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryState.m b/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryState.m index 74f5a2b..332fde6 100644 --- a/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryState.m +++ b/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryState.m @@ -49,13 +49,13 @@ - (instancetype)initWithVoltage:(NSMeasurement *)voltage andBatteryPercentage:(f @end -@implementation DUXDualBatteryState +@implementation DUXBetaDualBatteryState @synthesize battery2Voltage; @synthesize battery2Percentage; @end -@implementation DUXAggregateBatteryState +@implementation DUXBetaAggregateBatteryState @end diff --git a/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryWidget.h b/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryWidget.h index a83a4b6..3741f08 100644 --- a/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryWidget.h +++ b/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryWidget.h @@ -78,7 +78,7 @@ typedef NS_ENUM(NSUInteger, DUXBetaBatteryWidgetDisplayState) { @property (nonatomic, weak) id delegate; /** - * The current state of the widget. This will either be `DUXBetaBatteryWidgetDisplayStateSingleBattery` or `DUXBetaBatteryWidgetDisplayStateDualBattery`. + * The current state of the widget. This will either be `DUXBatteryWidgetDisplayStateSingleBattery` or `DUXBatteryWidgetDisplayStateDualBattery`. */ @property (nonatomic, readonly) DUXBetaBatteryWidgetDisplayState widgetDisplayState; diff --git a/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryWidget.m b/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryWidget.m index fe532e4..11283d7 100644 --- a/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryWidget.m +++ b/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryWidget.m @@ -29,7 +29,7 @@ #import "UIImage+DUXBetaAssets.h" #import "UIFont+DUXBetaFonts.h" #import "DUXBetaBatteryWidgetModel.h" -#import "DUXStateChangeBroadcaster.h" +#import "DUXBetaStateChangeBroadcaster.h" @import DJIUXSDKCore; static CGFloat const kVoltageAndPercentFontSize = 110.0; @@ -77,27 +77,27 @@ @interface DUXBetaBatteryWidget () @end /** - * DUXBatteryWidgetUIState contains the hooks for UI changes in the widget class DUXBatteryWidget. + * DUXBetaBatteryWidgetUIState contains the hooks for UI changes in the widget class DUXBetaBatteryWidget. * It implements the hook: * * Key: widgetTapped Type: NSNumber - Sends a boolean YES value as an NSNumber indicating the widget was tapped. */ -@interface DUXBatteryWidgetUIState : DUXStateChangeBaseData +@interface DUXBetaBatteryWidgetUIState : DUXBetaStateChangeBaseData + (instancetype)widgetTap; @end /** - * DUXBatteryWidgetModelState contains the hooks for model changes for the DUXBatteryWidget. + * DUXBetaBatteryWidgetModelState contains the hooks for model changes for the DUXBetaBatteryWidget. * It implements the hooks: * * Key: productConnected Type: NSNumber - Sends a boolean value as an NSNumber when the product connects or disconnects. * YES means connected, NO means disconnected. * - * Key: batteryStateUpdate Type: id - Sends a DUXBateryState object when the battery state changes. + * Key: batteryStateUpdate Type: id - Sends a DUXBetaBateryState object when the battery state changes. */ -@interface DUXBatteryWidgetModelState : DUXStateChangeBaseData +@interface DUXBetaBatteryWidgetModelState : DUXBetaStateChangeBaseData + (instancetype)productConnected:(BOOL)isConnected; + (instancetype)batteryStateUpdate:(DUXBetaBatteryState *)batteryState; @@ -144,7 +144,7 @@ - (void)setupInstanceVariables { @(DUXBetaBatteryStatusError) : [UIColor duxbeta_redColor], @(DUXBetaBatteryStatusWarningLevel1) : [UIColor duxbeta_redColor], @(DUXBetaBatteryStatusWarningLevel2) : [UIColor duxbeta_redColor], - @(DUXBetaBatteryStatusOverheating) : [UIColor duxbeta_batteryOverheatingYellowColor], + @(DUXBetaBatteryStatusOverheating) : [UIColor duxbeta_yellowColor], @(DUXBetaBatteryStatusUnknown) : [UIColor duxbeta_disabledGrayColor] }]; _statusToVoltageColorMapping = [[NSMutableDictionary alloc] initWithDictionary:@{ @@ -152,7 +152,7 @@ - (void)setupInstanceVariables { @(DUXBetaBatteryStatusError) : [UIColor duxbeta_redColor], @(DUXBetaBatteryStatusWarningLevel1) : [UIColor duxbeta_redColor], @(DUXBetaBatteryStatusWarningLevel2) : [UIColor duxbeta_redColor], - @(DUXBetaBatteryStatusOverheating) : [UIColor duxbeta_batteryOverheatingYellowColor], + @(DUXBetaBatteryStatusOverheating) : [UIColor duxbeta_yellowColor], @(DUXBetaBatteryStatusUnknown) : [UIColor duxbeta_disabledGrayColor] }]; _statusToPercentageColorMapping = [[NSMutableDictionary alloc] initWithDictionary:@{ @@ -160,7 +160,7 @@ - (void)setupInstanceVariables { @(DUXBetaBatteryStatusError) : [UIColor duxbeta_redColor], @(DUXBetaBatteryStatusWarningLevel1) : [UIColor duxbeta_redColor], @(DUXBetaBatteryStatusWarningLevel2) : [UIColor duxbeta_redColor], - @(DUXBetaBatteryStatusOverheating) : [UIColor duxbeta_batteryOverheatingYellowColor], + @(DUXBetaBatteryStatusOverheating) : [UIColor duxbeta_yellowColor], @(DUXBetaBatteryStatusUnknown) : [UIColor duxbeta_disabledGrayColor] }]; _voltageFontDual = [UIFont boldSystemFontOfSize:kVoltageAndPercentFontSize]; @@ -180,7 +180,7 @@ - (void)viewDidLoad { - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; - if ([self.widgetModel.batteryState isMemberOfClass:[DUXDualBatteryState class]]) { + if ([self.widgetModel.batteryState isMemberOfClass:[DUXBetaDualBatteryState class]]) { [self updateUI]; } } @@ -246,7 +246,6 @@ - (void)drawSingleBatteryView { self.singleBatteryPercentageLabel = [[UILabel alloc] init]; [self.singleBatteryView addSubview:self.singleBatteryPercentageLabel]; self.singleBatteryPercentageLabel.translatesAutoresizingMaskIntoConstraints = NO; - CGFloat labelWidthAsPercentOfView = 1.0 - kSingleIconWidthToWidgetWidthRatio; [self.singleBatteryPercentageLabel.widthAnchor constraintEqualToAnchor:self.view.widthAnchor multiplier:labelWidthAsPercentOfView].active = YES; [self.singleBatteryPercentageLabel.leadingAnchor constraintEqualToAnchor:self.singleBatteryImageView.trailingAnchor].active = YES; @@ -264,7 +263,7 @@ - (void)drawSingleBatteryView { } - (void)updateSingleBatteryView { - if ([self.widgetModel.batteryState isKindOfClass:[DUXBetaBatteryState class]] || [self.widgetModel.batteryState isKindOfClass:[DUXAggregateBatteryState class]]) { + if ([self.widgetModel.batteryState isKindOfClass:[DUXBetaBatteryState class]] || [self.widgetModel.batteryState isKindOfClass:[DUXBetaAggregateBatteryState class]]) { self.singleBatteryImageView.image = [self currentIndicatorImageForSingleBattery:self.widgetModel.batteryState]; [self.singleBatteryImageView setTintColor:[self.imageTintMapping objectForKey:@(self.widgetModel.batteryState.warningStatus)]]; self.singleBatteryPercentageLabel.textColor = [self.statusToPercentageColorMapping objectForKey:@(self.widgetModel.batteryState.warningStatus)]; @@ -404,7 +403,7 @@ - (void)drawDualBatteryView { self.dualBatteryBatteryTwoVoltageLabel.lineBreakMode = NSLineBreakByClipping; } -- (void)updateDualBatteryView:(DUXDualBatteryState *)dualBatteryState { +- (void)updateDualBatteryView:(DUXBetaDualBatteryState *)dualBatteryState { self.dualBatteryImageView.image = [self currentIndicatorImageForDualBattery:dualBatteryState]; [self.dualBatteryImageView setTintColor:[self.imageTintMapping objectForKey:@(dualBatteryState.warningStatus)]]; @@ -438,7 +437,7 @@ - (void)updateDualBatteryView:(DUXDualBatteryState *)dualBatteryState { } - (void)updateUI { - if ([self.widgetModel.batteryState isMemberOfClass:[DUXDualBatteryState class]]) { + if ([self.widgetModel.batteryState isMemberOfClass:[DUXBetaDualBatteryState class]]) { self.singleBatteryViewAspectRatioConstraint.active = NO; if (self.widgetDisplayState == DUXBetaBatteryWidgetDisplayStateSingleBattery) { self.widgetDisplayState = DUXBetaBatteryWidgetDisplayStateDualBattery; @@ -451,8 +450,8 @@ - (void)updateUI { } self.singleBatteryView.hidden = YES; self.dualBatteryView.hidden = NO; - [self updateDualBatteryView:(DUXDualBatteryState *)self.widgetModel.batteryState]; - } else if ([self.widgetModel.batteryState isMemberOfClass:[DUXBetaBatteryState class]] || [self.widgetModel.batteryState isMemberOfClass:[DUXAggregateBatteryState class]]) { + [self updateDualBatteryView:(DUXBetaDualBatteryState *)self.widgetModel.batteryState]; + } else if ([self.widgetModel.batteryState isMemberOfClass:[DUXBetaBatteryState class]] || [self.widgetModel.batteryState isMemberOfClass:[DUXBetaAggregateBatteryState class]]) { self.singleBatteryViewAspectRatioConstraint.active = YES; if (self.widgetDisplayState == DUXBetaBatteryWidgetDisplayStateDualBattery) { self.widgetDisplayState = DUXBetaBatteryWidgetDisplayStateSingleBattery; @@ -467,15 +466,15 @@ - (void)updateUI { } - (void)widgetTapped { - [[DUXStateChangeBroadcaster instance] send:[DUXBatteryWidgetUIState widgetTap]]; + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaBatteryWidgetUIState widgetTap]]; } - (void)sendIsProductConnected { - [[DUXStateChangeBroadcaster instance] send:[DUXBatteryWidgetModelState productConnected:self.widgetModel.isProductConnected]]; + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaBatteryWidgetModelState productConnected:self.widgetModel.isProductConnected]]; } - (void)sendBatteryStateUpdate { - [[DUXStateChangeBroadcaster instance] send:[DUXBatteryWidgetModelState batteryStateUpdate:self.widgetModel.batteryState]]; + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaBatteryWidgetModelState batteryStateUpdate:self.widgetModel.batteryState]]; } - (UIImage *)currentIndicatorImageForSingleBattery:(DUXBetaBatteryState *)state { @@ -533,7 +532,7 @@ - (UIColor *)getTintColorForBatteryStatus:(DUXBetaBatteryStatus)batteryStatus { - (void)updateCurrentBatteryView { if (self.widgetDisplayState == DUXBetaBatteryWidgetDisplayStateDualBattery) { - [self updateDualBatteryView:(DUXDualBatteryState *)self.widgetModel.batteryState]; + [self updateDualBatteryView:(DUXBetaDualBatteryState *)self.widgetModel.batteryState]; } else { [self updateSingleBatteryView]; } @@ -577,22 +576,22 @@ - (DUXBetaWidgetSizeHint)widgetSizeHint { @end -@implementation DUXBatteryWidgetUIState +@implementation DUXBetaBatteryWidgetUIState + (instancetype)widgetTap { - return [[DUXBatteryWidgetUIState alloc] initWithKey:@"widgetTap" number:@(0)]; + return [[DUXBetaBatteryWidgetUIState alloc] initWithKey:@"widgetTap" number:@(0)]; } @end -@implementation DUXBatteryWidgetModelState +@implementation DUXBetaBatteryWidgetModelState + (instancetype)productConnected:(BOOL)isConnected { - return [[DUXBatteryWidgetModelState alloc] initWithKey:@"productConnected" number:@(isConnected)]; + return [[DUXBetaBatteryWidgetModelState alloc] initWithKey:@"productConnected" number:@(isConnected)]; } + (instancetype)batteryStateUpdate:(DUXBetaBatteryState *)batteryState { - return [[DUXBatteryWidgetModelState alloc] initWithKey:@"batteryStateUpdate" object:batteryState]; + return [[DUXBetaBatteryWidgetModelState alloc] initWithKey:@"batteryStateUpdate" object:batteryState]; } @end diff --git a/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryWidgetModel.h b/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryWidgetModel.h index f9f14e2..773d6c8 100644 --- a/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryWidgetModel.h +++ b/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryWidgetModel.h @@ -1,5 +1,5 @@ // -// DUXBatteryWidgetModel.h +// DUXBetaBatteryWidgetModel.h // DJIUXSDK // // MIT License diff --git a/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryWidgetModel.m b/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryWidgetModel.m index a8f3d7b..7dc90bf 100644 --- a/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryWidgetModel.m +++ b/DJIUXSDKWidgets/BatteryWidget/DUXBetaBatteryWidgetModel.m @@ -1,5 +1,5 @@ // -// DUXBatteryWidgetModel.m +// DUXBetaBatteryWidgetModel.m // DJIUXSDK // // MIT License @@ -114,12 +114,12 @@ - (DUXBetaBatteryStatus)getAggregateStatus { return DUXBetaBatteryStatusNormal; } -- (DUXAggregateBatteryState *)getAggregateState { +- (DUXBetaAggregateBatteryState *)getAggregateState { NSMeasurement *voltage = [[NSMeasurement alloc] initWithDoubleValue:self.batteryAggregationState.voltage / 1000.0 unit:NSUnitElectricPotentialDifference.volts]; DUXBetaBatteryStatus status = [self getAggregateStatus]; - return [[DUXAggregateBatteryState alloc] initWithVoltage:voltage + return [[DUXBetaAggregateBatteryState alloc] initWithVoltage:voltage andBatteryPercentage:self.batteryAggregationState.chargeRemainingInPercent withWarningLevel:status]; } @@ -166,7 +166,7 @@ - (void)updateStates { DUXBetaBatteryStatus overallState = [self worstBatteryStatusForBatteryStates:@[battery1State, battery2State]]; - DUXDualBatteryState *dualBatteryState = [[DUXDualBatteryState alloc] initWithVoltage:battery1State.voltage andBatteryPercentage:battery1State.batteryPercentage withWarningLevel:overallState]; + DUXBetaDualBatteryState *dualBatteryState = [[DUXBetaDualBatteryState alloc] initWithVoltage:battery1State.voltage andBatteryPercentage:battery1State.batteryPercentage withWarningLevel:overallState]; dualBatteryState.battery2Percentage = battery2State.batteryPercentage; dualBatteryState.battery2Voltage = battery2State.voltage; @@ -178,6 +178,7 @@ - (void)updateStates { NSMeasurement *voltage = [[NSMeasurement alloc] initWithDoubleValue:0 unit:NSUnitElectricPotentialDifference.volts]; DUXBetaBatteryState *state = [[DUXBetaBatteryState alloc] initWithVoltage:voltage andBatteryPercentage:0.0 withWarningLevel:DUXBetaBatteryStatusUnknown]; self.batteryState = state; + self.batteryAggregationState = nil; } } diff --git a/DJIUXSDKWidgets/CompassWidget/DUXBetaCompassWidget.h b/DJIUXSDKWidgets/CompassWidget/DUXBetaCompassWidget.h index 551b51f..8609c67 100644 --- a/DJIUXSDKWidgets/CompassWidget/DUXBetaCompassWidget.h +++ b/DJIUXSDKWidgets/CompassWidget/DUXBetaCompassWidget.h @@ -1,5 +1,5 @@ // -// DUXBetaCompassWidget.h +// DUXBetaVPSWidget.h // DJIUXSDK // // MIT License diff --git a/DJIUXSDKWidgets/CompassWidget/Layers/DUXBetaCompassAircraftWorldLayer.h b/DJIUXSDKWidgets/CompassWidget/Layers/DUXBetaCompassAircraftWorldLayer.h index ed2ec30..a3099f4 100644 --- a/DJIUXSDKWidgets/CompassWidget/Layers/DUXBetaCompassAircraftWorldLayer.h +++ b/DJIUXSDKWidgets/CompassWidget/Layers/DUXBetaCompassAircraftWorldLayer.h @@ -1,5 +1,5 @@ // -// DUXCompassGyroHorizonLayer.h +// DUXBetaCompassGyroHorizonLayer.h // DJIUXSDK // // MIT License diff --git a/DJIUXSDKWidgets/CompassWidget/Layers/DUXBetaCompassAircraftWorldLayer.m b/DJIUXSDKWidgets/CompassWidget/Layers/DUXBetaCompassAircraftWorldLayer.m index 1dc7ff8..aebed85 100644 --- a/DJIUXSDKWidgets/CompassWidget/Layers/DUXBetaCompassAircraftWorldLayer.m +++ b/DJIUXSDKWidgets/CompassWidget/Layers/DUXBetaCompassAircraftWorldLayer.m @@ -1,5 +1,5 @@ // -// DUXCompassGyroHorizonLayer.m +// DUXBetaCompassGyroHorizonLayer.m // DJIUXSDK // // MIT License diff --git a/DJIUXSDKWidgets/ConnectionWidget/DUXBetaConnectionWidget.h b/DJIUXSDKWidgets/ConnectionWidget/DUXBetaConnectionWidget.h index 7b8b625..41b8e91 100644 --- a/DJIUXSDKWidgets/ConnectionWidget/DUXBetaConnectionWidget.h +++ b/DJIUXSDKWidgets/ConnectionWidget/DUXBetaConnectionWidget.h @@ -31,7 +31,7 @@ NS_ASSUME_NONNULL_BEGIN /** -* Widget that reflects the connected to aircraft state +* Widget that reflects the connected to aircraft state. */ @interface DUXBetaConnectionWidget : DUXBetaBaseWidget diff --git a/DJIUXSDKWidgets/ConnectionWidget/DUXBetaConnectionWidget.m b/DJIUXSDKWidgets/ConnectionWidget/DUXBetaConnectionWidget.m index 260eae9..231016d 100644 --- a/DJIUXSDKWidgets/ConnectionWidget/DUXBetaConnectionWidget.m +++ b/DJIUXSDKWidgets/ConnectionWidget/DUXBetaConnectionWidget.m @@ -26,10 +26,11 @@ // #import "DUXBetaConnectionWidget.h" +@import DJIUXSDKCore; #import "UIImage+DUXBetaAssets.h" -#import "DUXStateChangeBroadcaster.h" +#import "DUXBetaStateChangeBroadcaster.h" #import "NSLayoutConstraint+DUXBetaMultiplier.h" -@import DJIUXSDKCore; + @interface DUXBetaConnectionWidget () @@ -51,12 +52,12 @@ @interface DUXBetaConnectionWidget () @end /** - * DUXConnectionWidgetModelState contains the model hooks for the DUXConnectionWidget. + * DUXBetaConnectionWidgetModelState contains the model hooks for the DUXBetaConnectionWidget. * It implements the hook: * * Key: productConnected Type: NSNumber - Sends a boolean value as an NSNumber indicating if an aircraft is connected. */ -@interface DUXConnectionWidgetModelState : DUXStateChangeBaseData +@interface DUXBetaConnectionWidgetModelState : DUXBetaStateChangeBaseData + (instancetype)productConnected:(BOOL)isConnected; @@ -149,8 +150,8 @@ - (void)setupUI { } - (void)updateUI { - // Forward isProductConnected change detected to custom DUXConnectionWidgetState implementation - [[DUXStateChangeBroadcaster instance] send:[DUXConnectionWidgetModelState productConnected:self.widgetModel.isProductConnected]]; + // Forward isProductConnected change detected to custom DUXBetaConnectionWidgetState implementation + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaConnectionWidgetModelState productConnected:self.widgetModel.isProductConnected]]; UIImage *image = self.widgetModel.isProductConnected ? self.connectedImage : self.disconnectedImage; [self.connectionImageView setImage:image]; @@ -172,12 +173,12 @@ - (void)updateMinImageDimensions { @end /** -* Implementation of the custom class used by DUXStateChangeBroadcaster +* Implementation of the custom class used by DUXBetaStateChangeBroadcaster */ -@implementation DUXConnectionWidgetModelState: DUXStateChangeBaseData +@implementation DUXBetaConnectionWidgetModelState: DUXBetaStateChangeBaseData + (instancetype)productConnected:(BOOL)isConnected { - return [[DUXConnectionWidgetModelState alloc] initWithKey:@"productConnected" number:@(isConnected)]; + return [[DUXBetaConnectionWidgetModelState alloc] initWithKey:@"productConnected" number:@(isConnected)]; } @end diff --git a/DJIUXSDKWidgets/DJIUXSDKWidgets.h b/DJIUXSDKWidgets/DJIUXSDKWidgets.h index 360bfe0..477fbc3 100644 --- a/DJIUXSDKWidgets/DJIUXSDKWidgets.h +++ b/DJIUXSDKWidgets/DJIUXSDKWidgets.h @@ -36,6 +36,7 @@ FOUNDATION_EXPORT const unsigned char DJIUXSDKWidgetsVersionString[]; /*********************************************************************************/ // Helpers /*********************************************************************************/ +#import #import #import #import @@ -52,14 +53,13 @@ FOUNDATION_EXPORT const unsigned char DJIUXSDKWidgetsVersionString[]; /*********************************************************************************/ // Panel Widgets /*********************************************************************************/ -#import - -#import -#import -#import -#import -#import +#import +#import +#import +#import +#import +#import /*********************************************************************************/ // AirSense Widget @@ -160,6 +160,22 @@ FOUNDATION_EXPORT const unsigned char DJIUXSDKWidgetsVersionString[]; #import /*********************************************************************************/ +// RTK Enabled Widget +/*********************************************************************************/ +#import +#import + +/*********************************************************************************/ +// RTK Satellite Status Widget +/*********************************************************************************/ +#import +#import + +/*********************************************************************************/ +// RTK Widget +/*********************************************************************************/ +#import + // Simulator Indicator Widget /*********************************************************************************/ #import @@ -174,26 +190,39 @@ FOUNDATION_EXPORT const unsigned char DJIUXSDKWidgetsVersionString[]; /*********************************************************************************/ // SystemStatusList - MaxAltitudeListItem Widget /*********************************************************************************/ -#import -#import +#import +#import /*********************************************************************************/ // SystemStatusList - SDCardRemainingCapacityListItem Widget /*********************************************************************************/ -#import -#import +#import +#import /*********************************************************************************/ // SystemStatusList - RCStickModeListItem Widget /*********************************************************************************/ -#import -#import +#import +#import /*********************************************************************************/ // SystemStatusList - FlightModeListItem Widget /*********************************************************************************/ -#import -#import +#import +#import + +/*********************************************************************************/ + +// SystemStatusList - DUXBetaMaxFlightDistanceListItemWidget Widget +/*********************************************************************************/ +#import +#import + +/*********************************************************************************/ +// SystemStatusList - DUXBetaReturnToHomeAltitudeListItemWidget Widget +/*********************************************************************************/ +#import +#import /*********************************************************************************/ // Vertical Velocity Widget diff --git a/DJIUXSDKWidgets/Dialogs/Alert/DUXBetaAlertLayoutController.swift b/DJIUXSDKWidgets/Dialogs/Alert/DUXBetaAlertLayoutController.swift new file mode 100644 index 0000000..af544cb --- /dev/null +++ b/DJIUXSDKWidgets/Dialogs/Alert/DUXBetaAlertLayoutController.swift @@ -0,0 +1,122 @@ +// +// DUXBetaAlertLayoutController.swift +// DJIUXSDKWidgets +// +// Copyright © 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. +// + +import Foundation + +/** + * This class is responsible with controlling the position of the alert view on screen + * also factoring in the keyboard appearance. +*/ +@objcMembers public class DUXBetaAlertLayoutController: NSObject { + + /// The alert view used by this layout instance. + public var alert: DUXBetaAlertView? + + // MARK: - Private Methods + + fileprivate var isKeyboardVisible = false + + fileprivate var keyboardFrame = CGRect.zero + fileprivate var keyboardAnimationDureation: Double = 0.0 + fileprivate var keyboardAnimationOptions: UIView.AnimationOptions = .curveEaseIn + + fileprivate var keyboardYOffset: CGFloat { + if isKeyboardVisible { + return -keyboardFrame.height + } + return 0.0 + } + + // MARK: - Initialization Method + + public override init() { + super.init() + + bindKeyboardObservers() + } + + // MARK: - Public Methods + + /** + * This method is responsible for creating the center X and Y layout constraints + * needed to position the alert in the parent view. + * + * - Parameters: + * - alertView: The alert view instance to be positioned. + * - containerView: The parent view instance of the given alert. + */ + public func layout(alertView: DUXBetaAlertView?, inView containerView: UIView?) { + alert = alertView + + guard let alertView = alertView else { return } + guard let containerView = containerView else { return } + + alertView.centerXConstraint?.isActive = false + alertView.centerYConstraint?.isActive = false + alertView.centerXConstraint = alertView.view.centerXAnchor.constraint(equalTo: containerView.centerXAnchor) + alertView.centerYConstraint = alertView.view.centerYAnchor.constraint(equalTo: containerView.centerYAnchor, constant: keyboardYOffset) + NSLayoutConstraint.activate([alertView.centerXConstraint!, alertView.centerYConstraint!]) + alertView.view.layoutIfNeeded() + } + + // MARK: - Private Methods + + fileprivate func bindKeyboardObservers() { + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil) + } + + @objc func keyboardWillShow(notification: NSNotification) { + isKeyboardVisible = true + + if let value = ((notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey]) as AnyObject?)?.cgRectValue { + keyboardFrame = value + } + + if let value = ((notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey]) as AnyObject?)?.doubleValue { + keyboardAnimationDureation = value + } + + if let value = ((notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey]) as AnyObject?)?.unsignedIntegerValue { + keyboardAnimationOptions = UIView.AnimationOptions(rawValue: value) + } + + internalLayout() + } + + @objc func keyboardWillHide(notification: NSNotification) { + isKeyboardVisible = false + internalLayout() + } + + fileprivate func internalLayout() { + UIView.animate(withDuration: keyboardAnimationDureation, + delay: 0, + options: keyboardAnimationOptions, + animations: { [weak self] in + self?.alert?.centerYConstraint?.constant = self?.keyboardYOffset ?? 0.0 + }, completion: nil) + } +} diff --git a/DJIUXSDKWidgets/Dialogs/Alert/DUXBetaAlertStackController.swift b/DJIUXSDKWidgets/Dialogs/Alert/DUXBetaAlertStackController.swift new file mode 100644 index 0000000..cb33c86 --- /dev/null +++ b/DJIUXSDKWidgets/Dialogs/Alert/DUXBetaAlertStackController.swift @@ -0,0 +1,94 @@ +// +// DUXBetaAlertStackController.swift +// DJIUXSDKWidgets +// +// Copyright © 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 class keeps track of the visible alerts currently displayed on screen. +*/ +@objcMembers public class DUXBetaAlertStackController: NSObject { + + // MARK: Public Class Property + + /// The singleton instance to be used by an alert view. + public static var defaultStack: DUXBetaAlertStackController { + return self.instance + } + + // MARK: Public Property + + /// The alert instance that is currenlty visible on screen. + public var topAlert: DUXBetaAlertView? { + return stack.last + } + + // MARK: Private Properties + + fileprivate var stack: [DUXBetaAlertView] = [] + fileprivate static let instance = DUXBetaAlertStackController() + + // MARK: Public Methods + + /** + * This method is reposnsible for verifying if an alert was already displayed. + * + * - Parameters: + * - alertView: The alert view instance that needs to be verified. + * + * - Returns: A boolean value indicating if the alert view is already displayed + */ + public func contains(alertView: DUXBetaAlertView) -> Bool { + if topAlert == alertView { return true } + return stack.contains(alertView) + } + + /** + * This method adds the alert view to the list of alerts already on screen. + * + * - Parameters: + * - alertView: The alert view that is visibile. + */ + public func push(alertView: DUXBetaAlertView) { + guard contains(alertView: alertView) == false else { return } + stack.append(alertView) + } + + /** + * This methods removes the visible alert from the list of alerts already on screen. + * + * - Returns: The alert view that was top-most on screen. + */ + @discardableResult + public func pop() -> DUXBetaAlertView { + return stack.removeLast() + } + + /** + * This method is responsible for closing the currenlty displayed alert view and it removes + * all the refrences to any alert view that were previsoulsy displayed. + */ + public func clear() { + topAlert?.close() + stack.removeAll() + } +} diff --git a/DJIUXSDKWidgets/Dialogs/Alert/DUXBetaAlertView.swift b/DJIUXSDKWidgets/Dialogs/Alert/DUXBetaAlertView.swift new file mode 100644 index 0000000..73122c8 --- /dev/null +++ b/DJIUXSDKWidgets/Dialogs/Alert/DUXBetaAlertView.swift @@ -0,0 +1,611 @@ +// +// DUXBetaAlertView.swift +// DJIUXSDKWidgets +// +// Copyright © 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. +// + +public typealias DUXBetaAlertActionCompletionBlock = () -> Void + +/** + * Enum that defines the animation types + * which will be used when showing or closing + * an alert view instance. + */ +@objc public enum DUXBetaAlertViewAnimation: Int { + case standard, fade +} + +/** + * Enum that defines the possible orientations + * supported by the alert view. + */ +@objc public enum DUXBetaAlertViewLayout: Int { + case horizontal, vertical +} + +/** + * Enum that defines the possible height scales + * supported by the alert view. + */ +@objc public enum DUXBetaAlertViewHeightScale: Int { + case small, medium, large + + var factor: CGFloat { + switch self { + case .small: return UIDevice.duxbeta_smallHeightScale + case .medium: return UIDevice.duxbeta_mediumHeightScale + case .large: return UIDevice.duxbeta_largeHeightScale + default: return 1.0 + } + } +} + +/** + * An object that displays an alert message to the user. + * Use this class to configure alerts with the title and message that you want to display and the actions to choose from. +*/ +@objcMembers public class DUXBetaAlertView: UIViewController { + + /** + * Struct that encapsulates all the customization properties + * that can be set on an alert view instance. + */ + @objc(DUXBetaAlertViewAppearance) public class DUXBetaAlertViewAppearance: NSObject { + + /// The tint color for the image icon. + @objc public var imageTintColor: UIColor? + /// The font to be used for the title text. + @objc public var titleFont: UIFont? = .boldSystemFont(ofSize: 17.0) + /// The color to be used for the title text. + @objc public var titleColor: UIColor? = .duxbeta_white() + /// The alignment to be used for the title text. + @objc public var titleTextAlignment: NSTextAlignment = .left + /// The color to be used for the title background. + @objc public var titleBackgroundColor: UIColor? = .duxbeta_clear() + /// The font to be used for the details. + @objc public var messageFont: UIFont? = .systemFont(ofSize: 15.0) + /// The color to be used for the details text. + @objc public var messageColor: UIColor? = .duxbeta_white() + /// The alignment to be used for the details text. + @objc public var messageTextAlignment: NSTextAlignment = .left + /// The color to be used for the details background. + @objc public var messageBackgroundColor: UIColor? = .duxbeta_clear() + /// The spacing between the message text and any custom view, if one is available. + @objc public var bodyLayoutSpacing: CGFloat = 2.0 + /// The color to be used for the alert mask view container. + @objc public var maskViewColor: UIColor? = .duxbeta_alertViewMask() + /// The border corner radius for the alert view container. + @objc public var borderCornerRadius: CGFloat = 0.0 + /// The border width for the alert view container. + @objc public var borderWidth: CGFloat = 0.0 + /// The border color for the alert view container. + @objc public var borderColor: UIColor? + /// The background color for the alert view container. + @objc public var backgroundColor: UIColor = .duxbeta_alertBackground() + /// The top and bottom vertical offset of the view. + @objc public var verticalOffset: CGFloat = 20.0 + /// The left and right horizontal offsets of the view. + @objc public var horizaontalOffset: CGFloat = 10.0 + /// The height scale that controls the actual height of the alert. + @objc public var heightScale: DUXBetaAlertViewHeightScale = .medium + /// Show/hide animation type. + @objc public var animationType: DUXBetaAlertViewAnimation = .standard + /// Show/hide animation duration in seconds. + @objc public var animationDuration: Double = 0.3 + /// The layout type of the header components: the image icon and the title. + @objc public var headerLayoutType: DUXBetaAlertViewLayout = .horizontal + /// The spacing between the header components: the image icon and the title. + @objc public var headerLayoutSpacing: CGFloat = 2.0 + /// The layout type of the alerts actions. + @objc public var actionsLayoutType: DUXBetaAlertViewLayout = .horizontal + /// The spacing between the action buttons. + @objc public var actionLayoutSpacing: CGFloat = 2.0 + /// The spacing between the header, body and action containers. + @objc public var alertLayoutSpacing: CGFloat = 10.0 + /// The boolean value that controls if the alert view + /// should close when tapping outside the alert. + public var shouldDismissOnTap: Bool? = true + } + /// The image to be displayed into the left side of the header. + public var image: UIImage? + /// The text to be displayed as a title in the header. + public var titleText: String? + /// The text to be displayed in the details section. + public var message: String? + /// The appearance structure that encloses all the customization properties. + public var appearance = DUXBetaAlertViewAppearance() { + didSet { + applyAppearance() + } + } + /// Indicates if the alert view is visible. + public var isVisible: Bool = false + /// Indicates if the alert view shoud hide when tapping outside the alert. + public var shouldHideOnTap: Bool = false + /// Customizable view that will be added under the message. + public var customizedView: UIView? + /// The X center constraint of the alert. + public var centerXConstraint: NSLayoutConstraint? + /// The Y center constraint of the alert. + public var centerYConstraint: NSLayoutConstraint? + + /// The stack controller that keeps track of the visible alerts currently displayed on screen. + public let stackController = DUXBetaAlertStackController.defaultStack + + /// The layout controller responsible for managing the position of the alert. + public let layoutController = DUXBetaAlertLayoutController() + + /// The mask view placed under the current alert view. + public var maskView = DUXBetaAlertViewMask(frame: UIScreen.main.bounds) + /// The callback invoked when the alert is dismissed by tapping outside its content. + public var dissmissCompletion: (() -> ())? + + // MARK: - Private properties + + fileprivate var imageView = UIImageView() + fileprivate var titleLabel = UILabel() + fileprivate var messageLabel = UILabel() + fileprivate var actionButtons = [DUXBetaAlertAction]() + + fileprivate var scrollView = UIScrollView() + fileprivate var stackView = UIStackView() + fileprivate var headerStackView = UIStackView() + fileprivate var bodyStackView = UIStackView() + fileprivate var actionsStackView = UIStackView() + + fileprivate var topConstraint: NSLayoutConstraint? + fileprivate var leadingConstraint: NSLayoutConstraint? + fileprivate var traillingConstraint: NSLayoutConstraint? + fileprivate var bottomConstraint: NSLayoutConstraint? + + // MARK: - Private static constants + + fileprivate static let kPopupAnimationKey = "DUXBetaAlertView_kPopupAnimationKey" + fileprivate static let kAlphaAnimationKey = "DUXBetaAlertView_kAlphaAnimationKey" + + // MARK: - Private computed properties + + fileprivate var defaultWidth: CGFloat { + return UIDevice.duxbeta_logicWidthLandscape * UIDevice.duxbeta_widthScale + } + + fileprivate var defaultHeight: CGFloat { + return UIDevice.duxbeta_logicHeightLandscape * appearance.heightScale.factor + } + + // MARK: - Initialization Mehods + + public required init?(coder: NSCoder) { + super.init(coder: coder) + } + + public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + } + + public override func viewDidLoad() { + super.viewDidLoad() + + setupUI() + applyAppearance() + maskView.alertView = self + } + + // MARK: - Public Mehods + + /** + * This method attaches an action object to the alert. + * + * - Parameters: + * - action: The action object to disaplay as part of the alert. Actions are displayed as buttons. + */ + public func add(_ action: DUXBetaAlertAction) { + action.addTarget(self, action: #selector(buttonTapped(action:)), for: .touchUpInside) + actionButtons.append(action) + actionsStackView.addArrangedSubview(action) + } + + /** + * This method shows the alert object in the view hierarchy. + * + * - Parameters: + * - completion: The callback method when the show action is done. + */ + public func show(withCompletion completion: (() -> ())? = nil) { + guard let topViewController = topMostViewController else { + completion?() + return + } + guard canShowOnTop(of: topViewController) else { + completion?() + return + } + guard !stackController.contains(alertView: self) else { + completion?() + return + } + + if stackController.contains(alertView: self) { + completion?() + } else { + internalShow(withCompletion: completion) + } + } + + /** + * This method verifies if the current alert can be displayed over the given view controller. + * + * - Parameters: + * - viewController: The viewcontroller on top of which the alert needs to be shown. + * + * - Returns: A boolean value indicating if the current alert can be displayed on top of the given view controller. + */ + public func canShowOnTop(of viewController: UIViewController) -> Bool { + return !(viewController is UIAlertController) + } + + /** + * This method hides the alert instance. + * + * - Parameters: + * - completion: The callback method when the close action is done. + */ + public func close(withCompletion completion: (() -> ())? = nil) { + if isVisible { + internalClose(withCompletion: completion) + } + } + + // MARK: - Module Methods + + func setupUI() { + view.translatesAutoresizingMaskIntoConstraints = false + stackView.translatesAutoresizingMaskIntoConstraints = false + scrollView.translatesAutoresizingMaskIntoConstraints = false + scrollView.showsVerticalScrollIndicator = false + scrollView.showsHorizontalScrollIndicator = false + + imageView.translatesAutoresizingMaskIntoConstraints = false + imageView.image = image?.withRenderingMode(.alwaysTemplate) + imageView.contentMode = .scaleAspectFit + + titleLabel.translatesAutoresizingMaskIntoConstraints = false + titleLabel.text = titleText + titleLabel.lineBreakMode = .byWordWrapping + titleLabel.numberOfLines = 0 + titleLabel.sizeToFit() + + messageLabel.text = message + messageLabel.lineBreakMode = .byWordWrapping + messageLabel.numberOfLines = 0 + messageLabel.sizeToFit() + + stackView.axis = .vertical + stackView.alignment = .fill + stackView.distribution = .equalCentering + + headerStackView.axis = .horizontal + headerStackView.alignment = .center + headerStackView.distribution = .fill + + bodyStackView.axis = .vertical + bodyStackView.alignment = .fill + bodyStackView.distribution = .fillProportionally + + actionsStackView.alignment = .center + actionsStackView.distribution = .fillProportionally + + headerStackView.addArrangedSubview(imageView) + headerStackView.addArrangedSubview(titleLabel) + + let imageSize = imageView.image?.size ?? .zero + NSLayoutConstraint.activate([ + imageView.leadingAnchor.constraint(equalTo: headerStackView.leadingAnchor), + imageView.topAnchor.constraint(equalTo: headerStackView.topAnchor), + imageView.widthAnchor.constraint(equalToConstant: imageSize.width), + imageView.heightAnchor.constraint(equalToConstant: imageSize.height), + + titleLabel.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: appearance.headerLayoutSpacing), + titleLabel.trailingAnchor.constraint(equalTo: headerStackView.trailingAnchor), + titleLabel.centerYAnchor.constraint(equalTo: headerStackView.centerYAnchor) + ]) + + bodyStackView.addArrangedSubview(messageLabel) + + // Insert the custom view if available + if let v = customizedView { + bodyStackView.addArrangedSubview(v) + } + + stackView.addArrangedSubview(headerStackView) + stackView.addArrangedSubview(bodyStackView) + stackView.addArrangedSubview(actionsStackView) + + scrollView.addSubview(stackView) + view.addSubview(scrollView) + + topConstraint = scrollView.topAnchor.constraint(equalTo: view.topAnchor) + leadingConstraint = scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor) + traillingConstraint = scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor) + bottomConstraint = scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor) + + guard let _ = topConstraint, let _ = leadingConstraint, let _ = traillingConstraint, let _ = bottomConstraint else { return } + NSLayoutConstraint.activate([ + view.widthAnchor.constraint(equalToConstant: defaultWidth), + view.heightAnchor.constraint(equalToConstant: defaultHeight), + + topConstraint!, + leadingConstraint!, + traillingConstraint!, + bottomConstraint!, + + stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor), + stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor), + stackView.topAnchor.constraint(equalTo: scrollView.topAnchor), + stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor), + stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor), + ]) + } + + // MARK:- Private Methods + + fileprivate func applyAppearance() { + // Update container properties + view.layer.borderWidth = appearance.borderWidth + view.layer.cornerRadius = appearance.borderCornerRadius + view.layer.borderColor = appearance.borderColor?.cgColor + view.backgroundColor = appearance.backgroundColor + + // Update title label properties + imageView.tintColor = appearance.imageTintColor + titleLabel.font = appearance.titleFont + titleLabel.textColor = appearance.titleColor + titleLabel.textAlignment = appearance.titleTextAlignment + titleLabel.backgroundColor = appearance.titleBackgroundColor + + // Update message label properties + messageLabel.font = appearance.messageFont + messageLabel.textColor = appearance.messageColor + messageLabel.textAlignment = appearance.messageTextAlignment + messageLabel.backgroundColor = appearance.messageBackgroundColor + + // Update DUXBetaAlertViewMask background color + maskView.maskColor = appearance.maskViewColor + maskView.shouldDismissAlertOnTap = appearance.shouldDismissOnTap + + // Update spacing used by all the stacks views + stackView.spacing = appearance.alertLayoutSpacing + headerStackView.spacing = appearance.headerLayoutSpacing + bodyStackView.spacing = appearance.bodyLayoutSpacing + actionsStackView.spacing = appearance.actionLayoutSpacing + + // Update stack orientation + headerStackView.axis = appearance.headerLayoutType == .horizontal ? .horizontal : .vertical + actionsStackView.axis = appearance.actionsLayoutType == .horizontal ? .horizontal : .vertical + + // Adjust margins + topConstraint?.constant = appearance.verticalOffset + leadingConstraint?.constant = appearance.horizaontalOffset + traillingConstraint?.constant = -appearance.horizaontalOffset + bottomConstraint?.constant = -appearance.verticalOffset + } + + fileprivate func internalShow(withCompletion completion: (() -> ())? = nil) { + view.layer.transform = CATransform3DMakeScale(1.0, 1.0, 1.0) + view.alpha = 1.0 + + var animationObject: CABasicAnimation? + switch appearance.animationType { + case .standard: + animationObject = CABasicAnimation(keyPath: "transform") + animationObject?.fromValue = NSValue(caTransform3D: CATransform3DMakeScale(0.1, 0.1, 0.1)) + animationObject?.toValue = NSValue(caTransform3D: CATransform3DMakeScale(1.0, 1.0, 1.0)) + animationObject?.duration = appearance.animationDuration + case .fade: + animationObject = CABasicAnimation(keyPath: "opacity") + animationObject?.fromValue = NSNumber(0) + animationObject?.toValue = NSNumber(1) + animationObject?.duration = appearance.animationDuration + default: + break + } + + if let keyView = UIApplication.shared.keyWindow, keyView != view.superview { + view.removeFromSuperview() + keyView.addSubview(maskView) + keyView.addSubview(view) + + // Dispatch position control to the layout instance + layoutController.layout(alertView: self, inView: keyView) + } + + isVisible = true + + view.layer.removeAllAnimations() + maskView.removeAnimations() + + if let animation = animationObject { + let opacityAnimation = CABasicAnimation(keyPath: "opacity") + opacityAnimation.fromValue = NSNumber(0) + opacityAnimation.toValue = NSNumber(1) + opacityAnimation.duration = appearance.animationDuration + + CATransaction.begin() + CATransaction.setCompletionBlock { completion?() } + view.layer.add(animation, forKey: Self.kPopupAnimationKey) + view.layer.add(opacityAnimation, forKey: Self.kAlphaAnimationKey) + maskView.addShowAnimation() + CATransaction.commit() + + } else { + completion?() + } + + stackController.push(alertView: self) + } + + fileprivate func internalClose(withCompletion completion: (() -> ())? = nil) { + switch appearance.animationType { + case .standard: + UIView.animate(withDuration: appearance.animationDuration, + delay: 0, + options: .curveEaseOut, + animations: { + self.view.transform = CGAffineTransform(scaleX: 0.1, y: 0.1) + self.maskView.alpha = 0 + }) { [weak self] (completed) in + self?.view.removeFromSuperview() + self?.maskView.removeFromSuperview() + completion?() + } + case .fade: + UIView.animate(withDuration: appearance.animationDuration, + animations: { + self.view.alpha = 0 + self.maskView.alpha = 0 + }) { [weak self] (completed) in + self?.view.removeFromSuperview() + self?.maskView.removeFromSuperview() + completion?() + } + default: + view.removeFromSuperview() + maskView.removeFromSuperview() + completion?() + break + } + + isVisible = false + stackController.pop() + } + + @IBAction func buttonTapped(action: DUXBetaAlertAction) { + if action.style == .cancel { + close() + } else { + switch action.actionType { + case .closure: + action.action?() + case .selector: + guard let selector = action.selector else { return } + UIControl().sendAction(selector, to: action.target, for: nil) + default: + break + } + close() + } + } +} + +/** + * Enum that defines how the alert action callbacks + * are being handled when the button is tapped. + */ +@objc public enum DUXBetaAlertActionType: Int { + case none, selector, closure +} + +/** + The action object to display as part of the alert. + Actions are displayed as buttons in the alert. + The action object provides the button text and the action to be + performed when that button is tapped, along with customization properties. + */ +@objc public class DUXBetaAlertAction: UIButton { + /** + * Class that encapsulates all the customization properties + * that can be set on an alert view action object. + */ + @objc public class DUXBetaAlertActionAppearance: NSObject { + /// The font to used for the button text. + @objc public var actionFont = UIFont.systemFont(ofSize: 17.0) + /// The color to be used for the button text. + @objc public var actionColor = UIColor.duxbeta_alertActionBlue() + /// The border color to be used for the button. + @objc public var actionBorderColor: UIColor? + /// The border width to be used for the button. + @objc public var actionBorderWidth: CGFloat = 0 + /// The corner radius to be used for the button. + @objc public var actionCornerRadius: CGFloat = 0 + /// The background color to be used for the button. + @objc public var actionBackgroundColor: UIColor? + /// The background image to be used for the button in normal state. + @objc public var actionNormalBackgroundImage: UIImage? + /// The background image to be used for the button in selected state. + @objc public var actionSelectedBackgroundImage: UIImage? + } + + /// The style that is applied to the action. + @objc public var style: UIAlertAction.Style = .default + /// The appearance object that encloses all the customization properties. + @objc public var appearance = DUXBetaAlertActionAppearance() + /// The completion block that gets called on button tap. + @objc public var action: DUXBetaAlertActionCompletionBlock? + /// The action type applied to the alert action. + @objc public var actionType = DUXBetaAlertActionType.none + /// The title to be used as the button title. + @objc public var actionTitle: String? + /// The target instance that gets used on button tap. + @objc public var target: AnyObject? + /// The selector that gets called on button tap. + @objc public var selector: Selector? + + @objc public static func action(actionTitle: String?, style: UIAlertAction.Style, actionType: DUXBetaAlertActionType, target: AnyObject? = nil, selector: Selector? = nil, completionAction: DUXBetaAlertActionCompletionBlock? = nil) -> DUXBetaAlertAction { + let action = DUXBetaAlertAction() + action.style = style + action.target = target + action.selector = selector + action.actionType = actionType + action.actionTitle = actionTitle + action.action = completionAction + + action.applyAppearance() + return action + } + + public init() { + super.init(frame: CGRect.zero) + applyAppearance() + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder:aDecoder) + applyAppearance() + } + + override public init(frame:CGRect) { + super.init(frame:frame) + applyAppearance() + } + + fileprivate func applyAppearance() { + setTitle(actionTitle, for: .normal) + titleLabel?.font = appearance.actionFont + setTitleColor(appearance.actionColor, for: .normal) + titleLabel?.backgroundColor = appearance.actionBackgroundColor + layer.cornerRadius = appearance.actionCornerRadius + layer.borderWidth = appearance.actionBorderWidth + layer.borderColor = appearance.actionBorderColor?.cgColor + setImage(appearance.actionNormalBackgroundImage, for: .normal) + setImage(appearance.actionSelectedBackgroundImage, for: .selected) + } +} diff --git a/DJIUXSDKWidgets/Dialogs/Alert/DUXBetaAlertViewMask.swift b/DJIUXSDKWidgets/Dialogs/Alert/DUXBetaAlertViewMask.swift new file mode 100644 index 0000000..32e8006 --- /dev/null +++ b/DJIUXSDKWidgets/Dialogs/Alert/DUXBetaAlertViewMask.swift @@ -0,0 +1,98 @@ +// +// DUXBetaAlertViewMask.swift +// DJIUXSDKWidgets +// +// Copyright © 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. +// + +/** + * The object placed under a DUXBetaAlertView instance + * that allows the user to tap to close the visible alert view. + * This container expands as wide as the screen of the device. + */ +@objcMembers public class DUXBetaAlertViewMask: UIView { + + // MARK: - Public Properties + + /// The alert view instane displayed on top. + public var alertView: DUXBetaAlertView? + /// The boolean value that controls if the alert view + /// should close when tapping this container. + public var shouldDismissAlertOnTap: Bool? = true + /// The background color of the view. + public var maskColor: UIColor? { + didSet { + backgroundColor = maskColor + } + } + + // MARK: - Private Properties + + fileprivate static let kAnimationKey = "DUXBetaAlertViewMask_kAnimationKey" + + // MARK: - Initialization Methods + + public override init(frame: CGRect) { + super.init(frame: frame) + setupUI() + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + setupUI() + } + + // MARK: - Public Methods + + public func addShowAnimation() { + let animationObject = CABasicAnimation(keyPath: "opacity") + animationObject.fromValue = NSNumber(0) + animationObject.toValue = NSNumber(1) + animationObject.duration = alertView?.appearance.animationDuration ?? 0.3 + layer.add(animationObject, forKey: Self.kAnimationKey) + } + + public func addCloseAnimation() { + let animationObject = CABasicAnimation(keyPath: "opacity") + animationObject.fromValue = NSNumber(1) + animationObject.toValue = NSNumber(0) + animationObject.duration = alertView?.appearance.animationDuration ?? 0.3 + layer.add(animationObject, forKey: Self.kAnimationKey) + } + + public func removeAnimations() { + layer.removeAllAnimations() + } + + // MARK: - Private Methods + + fileprivate func setupUI() { + let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(onTap)) + addGestureRecognizer(tapGestureRecognizer) + + backgroundColor = maskColor + } + + @IBAction func onTap() { + guard let shouldClose = shouldDismissAlertOnTap, shouldClose else { return } + alertView?.close(withCompletion: alertView?.dissmissCompletion) + } +} diff --git a/DJIUXSDKWidgets/Dialogs/Slide/DUXBetaSlideAlertView.swift b/DJIUXSDKWidgets/Dialogs/Slide/DUXBetaSlideAlertView.swift new file mode 100644 index 0000000..5a04520 --- /dev/null +++ b/DJIUXSDKWidgets/Dialogs/Slide/DUXBetaSlideAlertView.swift @@ -0,0 +1,208 @@ +// +// DUXBetaSlideAlertView.swift +// DJIUXSDKWidgets +// +// Copyright © 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. +// + +import Foundation + +/** + * An object that displays an alert message that has + * a customtizable slide view control as the main action. + */ +@objcMembers public class DUXBetaSlideAlertView: DUXBetaAlertView { + + /** + * Struct that encapsulates all the customization properties + * that can be set on a sliding alert view instance. + */ + @objc(DUXBetaSlideAlertViewAppearance) public class DUXBetaSlideAlertViewAppearance: NSObject { + /// The vertical spacing between the checkbox details and the slider view. + @objc public var verticalSpacing: CGFloat = 10.0 + /// The horizontal spacing between the checkbox buttons and the checkbox details label. + @objc public var checkboxSpacing: CGFloat = 10.0 + /// The visibility of the checkbox button and details label. + @objc public var isCheckboxVisible = true + /// The image displayed by the checkbox button when selected. + @objc public var checkboxSelectedImage = UIImage.duxbeta_image(withAssetNamed: "CheckboxSelected") + /// The image displayed by the checkbox button when not selected. + @objc public var checkboxUnselectedImage = UIImage.duxbeta_image(withAssetNamed: "Checkbox") + /// The tint color of the image displayed by the checkbox button when selected. + @objc public var checkboxTintColor = UIColor.duxbeta_alertActionBlue() + /// The text displayed in the checkbox label. + @objc public var checkboxText = "Precisely record takeoff point" + /// The color of text displayed in the checkbox label. + @objc public var checkboxTextColor = UIColor.duxbeta_white() + /// The font of the text displayed in the checkbox label. + @objc public var checkboxTextFont = UIFont.systemFont(ofSize: 17.0) + /// The background color of the checkbox label. + @objc public var checkboxTextBackgroundColor = UIColor.duxbeta_clear() + /// The image used for the slider thumb icon. + @objc public var slideIconImage = UIImage.duxbeta_image(withAssetNamed: "Slider") + /// The image tint color used for the slider thumb icon for the normal state. + @objc public var slideIconTintColor = UIColor.duxbeta_white() + /// The image tint color used for the slider thumb icon for the selected state. + @objc public var slideIconSelectedTintColor = UIColor.duxbeta_slideIconSelected() + /// The image for the on state of the slide control. + @objc public var slideOnImage = UIImage.duxbeta_image(withAssetNamed: "SlideOn") + /// The image tint color for the on state of the slide control. + @objc public var slideOnTintColor = UIColor.duxbeta_success() + /// The image for the off state of the slide control. + @objc public var slideOffImage = UIImage.duxbeta_image(withAssetNamed: "SlideOff") + /// The image tint color for the off state of the slide control. + @objc public var slideOffTintColor = UIColor.duxbeta_yellow() + /// The text displayed into the label positioned in the slide off area. + @objc public var slideMessageText = "Slide to Take Off" + /// The font for text displayed into the label positioned in the slide off area. + @objc public var slideMessageFont = UIFont.systemFont(ofSize: 15.0) + /// The text color displayed into the label positioned in the slide off area. + @objc public var slideMessageTextColor = UIColor.duxbeta_slideText() + /// The image displayed in the slide off area. + @objc public var slideMessageImage = UIImage.duxbeta_image(withAssetNamed: "SliderChevron") + /// The tint color of the image displayed in the slide off area. + @objc public var slideMessageTintColor = UIColor.duxbeta_slideText() + /// The color of the line separator displayed below the slide view. + @objc public var separatorColor = UIColor.duxbeta_slideSeparator() + } + + // MARK:- Public Properties + + /// The callback invoked when the checkbox selection state changes. + public var onCheckboxChanged: ((Bool) -> ())? + /// The appearance structure that encloses all the customization properties for the sliding alert. + public var slideAppearance = DUXBetaSlideAlertViewAppearance() { + didSet { + applySlidingAppearance() + } + } + + // MARK:- Private Properties + + fileprivate let checkboxButton = UIButton() + fileprivate let checkboxLabel = UILabel() + fileprivate let slideView = DUXBetaSlideView() + fileprivate let separatorView = UIView() + fileprivate let checkboxStackView = UIStackView() + fileprivate let customStackView = UIStackView() + + fileprivate var isSelected = false + + // MARK:- Static Method + /** + * This method is creating an instance of the DUXBetaSlideAlertView + * that is being used to show alerts in the take off and return home widgets. + * + * - Returns: A customized instance of the DUXBetaSlideAlertView. + */ + public static func defaultSlideAlert() -> DUXBetaSlideAlertView { + let alert = DUXBetaSlideAlertView() + alert.image = UIImage.duxbeta_image(withAssetNamed: "TakeOff") + alert.appearance.backgroundColor = .duxbeta_alertBackground() + alert.appearance.titleColor = .duxbeta_yellow() + alert.appearance.imageTintColor = .duxbeta_yellow() + alert.appearance.titleFont = .boldSystemFont(ofSize: 17.0) + alert.appearance.messageFont = .systemFont(ofSize: 17.0) + alert.appearance.headerLayoutType = .horizontal + alert.appearance.headerLayoutSpacing = 10.0 + alert.appearance.bodyLayoutSpacing = 10.0 + alert.appearance.alertLayoutSpacing = 5.0 + alert.appearance.verticalOffset = 0.0 + alert.appearance.horizaontalOffset = 20.0 + return alert + } + + // MARK:- Private Methods + + override func setupUI() { + checkboxButton.addTarget(self, action: #selector(checkboxTapped), for: .touchUpInside) + separatorView.translatesAutoresizingMaskIntoConstraints = false + + checkboxStackView.axis = .horizontal + checkboxStackView.alignment = .center + checkboxStackView.distribution = .fillProportionally + + checkboxStackView.addArrangedSubview(checkboxButton) + checkboxStackView.addArrangedSubview(checkboxLabel) + + customStackView.axis = .vertical + customStackView.alignment = .center + customStackView.distribution = .fillProportionally + + customStackView.addArrangedSubview(checkboxStackView) + customStackView.addArrangedSubview(slideView) + customStackView.addArrangedSubview(separatorView) + + NSLayoutConstraint.activate([ + separatorView.heightAnchor.constraint(equalToConstant: 1.0), + separatorView.leadingAnchor.constraint(equalTo: customStackView.leadingAnchor), + separatorView.trailingAnchor.constraint(equalTo: customStackView.trailingAnchor) + ]) + + customizedView = customStackView + + super.setupUI() + + applySlidingAppearance() + } + + fileprivate func applySlidingAppearance() { + // Update stackviews customization properties + checkboxStackView.isHidden = !slideAppearance.isCheckboxVisible + checkboxStackView.spacing = slideAppearance.checkboxSpacing + customStackView.spacing = slideAppearance.verticalSpacing + + // Update checkbox customization properties + checkboxLabel.text = slideAppearance.checkboxText + checkboxLabel.font = slideAppearance.checkboxTextFont + checkboxLabel.textColor = slideAppearance.checkboxTextColor + checkboxLabel.backgroundColor = slideAppearance.checkboxTextBackgroundColor + checkboxButton.tintColor = slideAppearance.checkboxTintColor + checkboxButton.setImage(slideAppearance.checkboxUnselectedImage?.withRenderingMode(.alwaysTemplate), for: .normal) + + // Update slideView customization properties + slideView.slideOnImage = slideAppearance.slideOnImage + slideView.slideOnTintColor = slideAppearance.slideOnTintColor + slideView.slideOffImage = slideAppearance.slideOffImage + slideView.slideOffTintColor = slideAppearance.slideOffTintColor + slideView.slideIconImage = slideAppearance.slideIconImage + slideView.slideIconTintColor = slideAppearance.slideIconTintColor + slideView.slideIconSelectedTintColor = slideAppearance.slideIconSelectedTintColor + slideView.slideMessageText = slideAppearance.slideMessageText + slideView.slideMessageFont = slideAppearance.slideMessageFont + slideView.slideMessageTextColor = slideAppearance.slideMessageTextColor + slideView.slideMessageImage = slideAppearance.slideMessageImage + slideView.slideMessageTintColor = slideAppearance.slideMessageTintColor + + // Update separator view + separatorView.backgroundColor = slideAppearance.separatorColor + } + + @IBAction func checkboxTapped() { + isSelected = !isSelected + + // Trigger callback + onCheckboxChanged?(isSelected) + + // Update checkbox image + checkboxButton.setImage((isSelected ? slideAppearance.checkboxSelectedImage : slideAppearance.checkboxUnselectedImage)?.withRenderingMode(.alwaysTemplate), for: .normal) + } +} diff --git a/DJIUXSDKWidgets/Dialogs/Slide/DUXBetaSlideView.swift b/DJIUXSDKWidgets/Dialogs/Slide/DUXBetaSlideView.swift new file mode 100644 index 0000000..7bd61a8 --- /dev/null +++ b/DJIUXSDKWidgets/Dialogs/Slide/DUXBetaSlideView.swift @@ -0,0 +1,286 @@ +// +// DUXBetaSlideView.swift +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// + +/** + * A custom slide view control. + */ +@objcMembers public class DUXBetaSlideView: UIView { + static let kSpacing: CGFloat = 0.0 + static let kAlignment: CGFloat = 1.0 + static let kWidthPercent: CGFloat = 0.5 + static let kScrollThreshold: CGFloat = 0.6 + + // MARK:- Public Properties + + /// The image used for the slider thumb icon. + public var slideIconImage = UIImage.duxbeta_image(withAssetNamed: "Slider") { + didSet { + slideIcon.image = slideIconImage?.withRenderingMode(.alwaysTemplate) + } + } + /// The image tint color used for the slider thumb icon for the normal state. + public var slideIconTintColor = UIColor.white { + didSet { + slideIcon.tintColor = slideIconTintColor + } + } + /// The image tint color used for the slider thumb icon for the selected state. + public var slideIconSelectedTintColor = UIColor.white { + didSet { + slideIcon.tintColor = slideIconTintColor + } + } + /// The image for the on state of the slide. + public var slideOnImage = UIImage.duxbeta_image(withAssetNamed: "SlideOn") { + didSet { + slideOnImageView.image = slideOnImage?.withRenderingMode(.alwaysTemplate) + } + } + /// The image tint color for the on state of the slide. + public var slideOnTintColor = UIColor.duxbeta_success() { + didSet { + slideOnImageView.tintColor = slideOnTintColor + } + } + /// The image for the off state of the slide. + public var slideOffImage = UIImage.duxbeta_image(withAssetNamed: "SlideOff") { + didSet { + slideOffImageView.image = slideOffImage?.withRenderingMode(.alwaysTemplate) + } + } + /// The image tint color for the off state of the slide. + public var slideOffTintColor = UIColor.duxbeta_slideIconSelected() { + didSet { + slideOffImageView.tintColor = slideOffTintColor + } + } + /// The text displayed into the label positioned in the slide off area. + public var slideMessageText = "Slide to Take Off" { + didSet { + slideMessageLabel.text = slideMessageText + } + } + /// The font for text displayed into the label positioned in the slide off area. + public var slideMessageFont = UIFont.systemFont(ofSize: 12.0) { + didSet { + slideMessageLabel.font = slideMessageFont + } + } + /// The text color displayed into the label positioned in the slide off area. + public var slideMessageTextColor = UIColor.black { + didSet { + slideMessageLabel.textColor = slideMessageTextColor + } + } + /// The image displayed in the slide off area. + public var slideMessageImage = UIImage.duxbeta_image(withAssetNamed: "SliderChevron") { + didSet { + slideMessageImageView.image = slideMessageImage?.withRenderingMode(.alwaysTemplate) + } + } + /// The tint color of the image displayed in the slide off area. + public var slideMessageTintColor = UIColor.gray { + didSet { + slideMessageImageView.tintColor = slideMessageTintColor + } + } + + /// The callback invoked when the sliding action starts. + public var onSlideStarted: (() -> ())? + /// The callback invoked when the sliding action completes. + public var onSlideCompleted: ((Bool) -> ())? + + // MARK:- Private Properties + + fileprivate var slideIcon: UIImageView! + fileprivate var slideOnView: UIView! + fileprivate var slideOnImageView: UIImageView! + fileprivate var slideOffView: UIView! + fileprivate var slideOffImageView: UIImageView! + fileprivate var slideMessageLabel: UILabel! + fileprivate var slideMessageImageView: UIImageView! + + fileprivate var minimSize = CGSize.zero + fileprivate var slideOnSize = CGSize.zero + fileprivate var slideIconStartX: CGFloat = 0.0 + fileprivate var slideIconDeltaX: CGFloat = 0.0 { + didSet { + UIView.animate(withDuration: 0.4) { + self.slideIconCenterXConstraint?.constant = self.slideIconDeltaX + self.setNeedsLayout() + } + } + } + fileprivate var slideIconInitialX: CGFloat { minimSize.width * Self.kWidthPercent - Self.kAlignment } + fileprivate var slideIconCenterXConstraint: NSLayoutConstraint? + + public override init(frame: CGRect) { + super.init(frame: frame) + + setupUI() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + + setupUI() + } + + fileprivate func setupUI() { + clipsToBounds = true + backgroundColor = .clear + translatesAutoresizingMaskIntoConstraints = false + + slideOnView = UIView() + slideOnView.translatesAutoresizingMaskIntoConstraints = false + slideOnView.isOpaque = true + slideOnView.clipsToBounds = true + slideOnView.clearsContextBeforeDrawing = true + slideOnView.backgroundColor = .clear + + if let size = slideOnImage?.size { + slideOnSize = size + } + + slideOnImageView = UIImageView(image: slideOnImage?.withRenderingMode(.alwaysTemplate)) + slideOnImageView.tintColor = slideOnTintColor + slideOnImageView.clearsContextBeforeDrawing = true + + slideOnView.addSubview(slideOnImageView) + + if let size = slideIconImage?.size { + minimSize = size + } + + slideIcon = UIImageView(image: slideIconImage?.withRenderingMode(.alwaysTemplate)) + slideIcon.translatesAutoresizingMaskIntoConstraints = false + slideIcon.tintColor = slideIconTintColor + slideIcon.contentMode = .scaleAspectFit + slideIcon.clearsContextBeforeDrawing = true + slideIcon.backgroundColor = .clear + slideIcon.isMultipleTouchEnabled = true + slideIcon.isUserInteractionEnabled = true + + let panGestureRecongnizer = UIPanGestureRecognizer(target: self, action: #selector(onDrag(_:))) + slideIcon.addGestureRecognizer(panGestureRecongnizer) + + slideOffView = UIView() + slideOffView.translatesAutoresizingMaskIntoConstraints = false + slideOffView.backgroundColor = .clear + slideOffView.isOpaque = true + slideOffView.clipsToBounds = true + slideOffView.clearsContextBeforeDrawing = true + + slideOffImageView = UIImageView(image: slideOffImage?.withRenderingMode(.alwaysTemplate)) + slideOffImageView.tintColor = slideOffTintColor + slideOffImageView.clearsContextBeforeDrawing = true + + slideMessageLabel = UILabel() + slideMessageLabel.translatesAutoresizingMaskIntoConstraints = false + slideMessageLabel.text = slideMessageText + slideMessageLabel.font = slideMessageFont + slideMessageLabel.textColor = slideMessageTextColor + slideMessageLabel.textAlignment = .right + slideMessageLabel.numberOfLines = 1 + + slideMessageImageView = UIImageView(image: slideMessageImage?.withRenderingMode(.alwaysTemplate)) + slideMessageImageView.translatesAutoresizingMaskIntoConstraints = false + slideMessageImageView.tintColor = slideMessageTintColor + slideMessageImageView.contentMode = .scaleAspectFit + slideMessageImageView.clipsToBounds = true + slideMessageImageView.clearsContextBeforeDrawing = true + + slideOffView.addSubview(slideOffImageView) + slideOffView.addSubview(slideMessageLabel) + slideOffView.addSubview(slideMessageImageView) + + addSubview(slideOffView) + addSubview(slideOnView) + addSubview(slideIcon) + + NSLayoutConstraint.activate([ + slideOnView.topAnchor.constraint(equalTo: topAnchor, constant: Self.kAlignment), + slideOnView.bottomAnchor.constraint(equalTo: bottomAnchor), + slideOnView.leftAnchor.constraint(equalTo: leftAnchor), + slideOnView.rightAnchor.constraint(equalTo: slideIcon.centerXAnchor), + slideOffView.topAnchor.constraint(equalTo: topAnchor, constant: Self.kAlignment), + slideOffView.bottomAnchor.constraint(equalTo: bottomAnchor), + slideOffView.leftAnchor.constraint(equalTo: leftAnchor), + slideOffView.rightAnchor.constraint(equalTo: rightAnchor), + slideMessageLabel.centerYAnchor.constraint(equalTo: slideOffView.centerYAnchor), + slideMessageLabel.rightAnchor.constraint(equalTo: slideMessageImageView.leftAnchor, constant: -Self.kSpacing), + slideMessageImageView.centerYAnchor.constraint(equalTo: slideOffView.centerYAnchor), + slideMessageImageView.rightAnchor.constraint(equalTo: slideOffView.rightAnchor, constant: -Self.kSpacing), + slideIcon.centerYAnchor.constraint(equalTo: centerYAnchor), + heightAnchor.constraint(equalToConstant: minimSize.height), + widthAnchor.constraint(equalToConstant: slideOnSize.width) + ]) + + slideIconDeltaX = slideIconInitialX + slideIconCenterXConstraint = slideIcon.centerXAnchor.constraint(equalTo: leadingAnchor, constant: slideIconDeltaX) + slideIconCenterXConstraint?.isActive = true + } + + @objc func onDrag(_ sender: UIPanGestureRecognizer) { + if sender.state == .began { + slideIconStartX = slideIcon.center.x + slideIcon.tintColor = slideIconSelectedTintColor + + // Notify dragging start + onSlideStarted?() + } + + let dPoint = sender.translation(in: self) + slideIconDeltaX = guarded(value: slideIconStartX + dPoint.x, + byMin: slideIcon.frame.width * Self.kWidthPercent, + andMax: frame.width - slideIcon.frame.width * Self.kWidthPercent) + + if sender.state == .ended { + slideIcon.tintColor = slideIconTintColor + if slideIconDeltaX > frame.width * Self.kScrollThreshold { + slideIconDeltaX = frame.width - minimSize.width * Self.kWidthPercent + + // Trigger callback with a successful completion + onSlideCompleted?(true) + } else { + slideIconDeltaX = slideIconInitialX + + // Trigger callback with an unsuccessful completion + onSlideCompleted?(false) + } + } + } + + fileprivate func guarded(value: CGFloat, byMin min: CGFloat, andMax max: CGFloat) -> CGFloat { + if value < min { + return min + } else if value > max { + return max + } + return value + } +} diff --git a/DJIUXSDKWidgets/FPVWidget/DUXBetaFPVCenterView.swift b/DJIUXSDKWidgets/FPVWidget/DUXBetaFPVCenterView.swift index 7d9f847..2198c35 100644 --- a/DJIUXSDKWidgets/FPVWidget/DUXBetaFPVCenterView.swift +++ b/DJIUXSDKWidgets/FPVWidget/DUXBetaFPVCenterView.swift @@ -3,9 +3,9 @@ // DJIUXSDKWidgets // // MIT License -// +// // Copyright © 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 @@ -33,10 +33,8 @@ import DJIUXSDKCommunication */ @objc public class DUXBetaFPVCenterView: UIImageView { - /** - * The image view type enum value that is read and stored into DefaultGlobalPreferences. - * Default value .unkwnown - */ + /// The image view type enum value that is read and stored into DefaultGlobalPreferences. + /// Default value .unkwnown @objc public var imageType: FPVCenterViewType = DUXBetaSingleton.sharedGlobalPreferences().centerViewType() { didSet { //Save the current image type to DefaultGlobalPreferences @@ -45,10 +43,8 @@ import DJIUXSDKCommunication } } - /** - * The color view type enum value that is read and stored into DefaultGlobalPreferences. - * Default value .unkwnown - */ + /// The color view type enum value that is read and stored into DefaultGlobalPreferences. + /// Default value .unkwnown @objc public var colorType: FPVCenterViewColor = DUXBetaSingleton.sharedGlobalPreferences().centerViewColor() { didSet { //Save the current color type to DefaultGlobalPreferences @@ -57,9 +53,7 @@ import DJIUXSDKCommunication } } - /** - * The color of the center view image. - */ + /// The color of the center view image. @objc public var color: UIColor? = nil { didSet { if let c = color { @@ -99,7 +93,7 @@ extension FPVCenterViewType { switch self { case .Standard: imageName = "CenterPointCircle" case .Cross: imageName = "CenterPointCross" - case .NarrowCross: imageName = "CenterPointNarrowedCross" + case .NarrowCross: imageName = "CenterPointNarrowCross" case .Frame: imageName = "CenterPointFrame" case .FrameAndCross: imageName = "CenterPointFrameAndCross" case .Square: imageName = "CenterPointSquare" diff --git a/DJIUXSDKWidgets/FPVWidget/DUXBetaFPVGridView.swift b/DJIUXSDKWidgets/FPVWidget/DUXBetaFPVGridView.swift index 563915b..bdfb842 100644 --- a/DJIUXSDKWidgets/FPVWidget/DUXBetaFPVGridView.swift +++ b/DJIUXSDKWidgets/FPVWidget/DUXBetaFPVGridView.swift @@ -3,9 +3,9 @@ // DJIUXSDKWidgets // // MIT License -// +// // Copyright © 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 @@ -29,14 +29,12 @@ import UIKit import DJIUXSDKCommunication /** - * Displays a grid centered in the view. +* Displays a grid centered in the view. */ @objc public class DUXBetaFPVGridView: UIView { - /** - * The grid view type enum value that is read and stored into the DefaultGlobalPreferences. - * Default value .unkwnown - */ + /// The grid view type enum value that is read and stored into the DefaultGlobalPreferences. + /// Default value .unkwnown @objc public var gridViewType = DUXBetaSingleton.sharedGlobalPreferences().gridViewType() { didSet { DUXBetaSingleton.sharedGlobalPreferences().set(gridViewType: gridViewType) @@ -44,10 +42,8 @@ import DJIUXSDKCommunication } } - /** - * The number of rows displayed by the grid view. - * Default value 3 - */ + /// The number of rows displayed by the grid view. + /// Default value 3 @objc public var rowCount: Int = 3 { didSet { if rowCount < 1 { rowCount = 1 } @@ -55,10 +51,8 @@ import DJIUXSDKCommunication } } - /** - * The number of columns displayed by the grid view. - * Default value 3 - */ + /// The number of columns displayed by the grid view. + /// Default value 3 @objc public var columnCount: Int = 3 { didSet { if columnCount < 1 { columnCount = 1 } @@ -66,59 +60,48 @@ import DJIUXSDKCommunication } } - /** - * The line thickness used for drawing the lines. - * Default value 0.5. - */ + /// The line thickness used for drawing the lines. + /// Default value 0.5. @objc public var lineWidth: CGFloat = 0.5 { didSet { redraw() } } - /** - * The line color used for drawing the lines. - * Default value 0.5 - */ + + /// The line color used for drawing the lines. + /// Default value 0.5 @objc public var lineColor: UIColor = UIColor.duxbeta_fpvGridLine() { didSet { redraw() } } - /** - * The line shadow's visibility. - * Visible by default - */ + /// The line shadow's visibility. + /// Visible by default @objc public var isShadowVisible = true { didSet { redraw() } } - /** - * The line shadow's blue value. - * Default value 1.0 - */ + /// The line shadow's blue value. + /// Default value 1.0 @objc public var shadowBlur: CGFloat = 1.0 { didSet { redraw() } } - /** - * The line shadow's color value. - */ + /// The line shadow's color value. @objc public var shadowColor: UIColor = UIColor.duxbeta_fpvGridLineShadow() { didSet { redraw() } } - /** - * The line shadow's offset as a translation in the rendering context. - * Default value (0.0, 0.0) size - */ + /// The line shadow's offset as a translation in the rendering context. + /// Default value (0.0, 0.0) size. @objc public var shadowOffset: CGSize = CGSize(width: 0.0, height: 0.0) { didSet { redraw() diff --git a/DJIUXSDKWidgets/FPVWidget/DUXBetaFPVWidget.swift b/DJIUXSDKWidgets/FPVWidget/DUXBetaFPVWidget.swift index 23c14bf..6dcfce0 100644 --- a/DJIUXSDKWidgets/FPVWidget/DUXBetaFPVWidget.swift +++ b/DJIUXSDKWidgets/FPVWidget/DUXBetaFPVWidget.swift @@ -3,9 +3,9 @@ // DJIUXSDKWidgets // // MIT License -// +// // Copyright © 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 @@ -29,181 +29,184 @@ * This widget shows the video feed from the camera. */ @objc public class DUXBetaFPVWidget: DUXBetaBaseWidget { - /** - * Preferred widget size - */ + + /// Preferred widget size let kDesignSize = CGSize(width: 230, height: 100) - /** - * The widgetSizeHint indicates the actual widget size and its aspect ratio - */ + /// The widgetSizeHint indicates the actual widget size and its aspect ratio. public override var widgetSizeHint : DUXBetaWidgetSizeHint { get { return DUXBetaWidgetSizeHint(preferredAspectRatio: kDesignSize.width/kDesignSize.height, minimumWidth: kDesignSize.width, minimumHeight: kDesignSize.height)} set { } } - /** - * The view displaying a custom center point image. - */ + /// The camera feed currently being rendered on screen. + @objc public var videoFeed: DJIVideoFeed? = DJISDKManager.videoFeeder()?.primaryVideoFeed { + didSet { + widgetModel.videoFeed = videoFeed + decodeAdapter.videoFeed = videoFeed + } + } + + /// The view displaying a custom center point image. @objc public var centerView: DUXBetaFPVCenterView! - /** - * The view displaying the customized grid lines. - */ + /// The view displaying the customized grid lines. @objc public var gridView: DUXBetaFPVGridView! - /** - * The preferred camera index used when displaying the feed. - */ + /// The preferred camera index used when displaying the feed. @objc public var preferredCameraIndex = 0 { didSet { - widgetModel?.preferredCameraIndex = preferredCameraIndex + widgetModel.preferredCameraIndex = preferredCameraIndex } } - /** - * The vertical offset in percentage used when positioning the camera name and camera side details relative to the top margin. - */ + /// The vertical offset in percentage used when positioning the camera name and camera side details relative to the top margin. @objc public var cameraDetailsVerticalAlignment: CGFloat = 0.2 { didSet { - cameraStackView.topAnchor.constraint(equalTo: view.topAnchor, constant: cameraDetailsVerticalAlignment * view.frame.height).isActive = true + cameraDetailsVerticalConstraint.isActive = false + cameraDetailsVerticalConstraint = cameraStackView.topAnchor.constraint(equalTo: view.topAnchor, constant: cameraDetailsVerticalAlignment * view.frame.height) + cameraDetailsVerticalConstraint.isActive = true view.setNeedsLayout() } } - /** - * Controls the visibility of the displayed camera name - */ + + /// The horizontal offset in percentage used when positioning the camera name and camera side details relative to the left margin. + @objc public var cameraDetailsHorizontalAlignment: CGFloat = 0.0 { + didSet { + cameraDetailsHorizontalConstraint.isActive = false + cameraDetailsHorizontalConstraint = cameraStackView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: cameraDetailsHorizontalAlignment * view.frame.width) + cameraDetailsHorizontalConstraint.isActive = true + view.setNeedsLayout() + } + } + + /// Controls the visibility of the displayed camera name. @objc public var isCameraNameVisible: Bool = true { didSet { cameraNameLabel.isHidden = !isCameraNameVisible } } - - /** - * The font used to display the camera name. - */ + + /// The font used to display the camera name. @objc public var cameraNameFont: UIFont = UIFont.systemFont(ofSize: 10.0) { didSet { cameraNameLabel.font = cameraNameFont } } - /** - * The text color used to display the camera name. - */ + /// The text color used to display the camera name. @objc public var cameraNameTextColor: UIColor = UIColor.duxbeta_white() { didSet { cameraNameLabel.textColor = cameraNameTextColor } } - /** - * The background color of the displayed camera name. - */ + /// The background color of the displayed camera name. @objc public var cameraNameBackgroundColor: UIColor = UIColor.duxbeta_fpvTextBackground() { didSet { cameraNameLabel.backgroundColor = cameraNameBackgroundColor } } - /** - * Controls the visibility of the displayed camera side. - */ + /// Controls the visibility of the displayed camera side. @objc public var isCameraSideVisible: Bool = true { didSet { cameraSideLabel.isHidden = !isCameraSideVisible } } - - /** - * The font used to display the camera side. - */ + + /// The font used to display the camera side. @objc public var cameraSideFont: UIFont = UIFont.systemFont(ofSize: 10.0) { didSet { cameraSideLabel.font = cameraSideFont } } - - /** - * The text color used to display the camera side. - */ + + /// The text color used to display the camera side. @objc public var cameraSideTextColor: UIColor = UIColor.duxbeta_white() { didSet { cameraSideLabel.textColor = cameraSideTextColor } } - - /** - * The background color of the displayed camera side. - */ + + /// The background color of the displayed camera side. @objc public var cameraSideBackgroundColor = UIColor.duxbeta_fpvTextBackground() { didSet { cameraSideLabel.backgroundColor = cameraSideBackgroundColor } } - /** - * Controls the visibility of the grid line view. - */ + /// Controls the visibility of the grid line view. @objc public var isGridViewVisible = true { didSet { gridView.isHidden = !isGridViewVisible } } - /** - * Controls the visibility of the center point view. - */ + /// Controls the visibility of the center point view. @objc public var isCenterViewVisible = true { didSet { centerView.isHidden = !isCenterViewVisible } } - /** - * Controls the height of the center point view by specifying a height percentage related to the wideget's center point. - */ + /// Controls the height of the center point view by specifying a height percentage related to the wideget's center point. @objc public var centerViewHeightPercentage: CGFloat = 0.1 { didSet { - centerView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: centerViewHeightPercentage).isActive = true + centerViewHeightConstraint.isActive = false + centerViewHeightConstraint = centerView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: centerViewHeightPercentage) + centerViewHeightConstraint.isActive = true view.setNeedsLayout() } } - /** - * Controls the width of the center point view by specifying a height percentage related to the widget's center point. - */ + /// Controls the width of the center point view by specifying a height percentage related to the widget's center point. @objc public var centerViewWidthPercentage: CGFloat = 0.1 { didSet { - centerView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: centerViewWidthPercentage).isActive = true + centerViewWidthConstraint.isActive = false + centerViewWidthConstraint = centerView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: centerViewWidthPercentage) + centerViewWidthConstraint.isActive = true view.setNeedsLayout() } } - @objc public var decodeAdapter: DUXBetaFPVDecodeAdapter? - @objc public var widgetModel: DUXBetaFPVWidgetModel? + /// Controls if the stream decoding is done using the software decoder or the hardware decoder of the device. + @objc public var enableHardwareDecode: Bool = true { + didSet { + updateHardwareDecoding() + } + } + + @objc public var widgetModel = DUXBetaFPVWidgetModel() + @objc public var decodeAdapter = DUXBetaFPVDecodeAdapter() fileprivate var fpvView: UIView! fileprivate var cameraNameLabel: UILabel! fileprivate var cameraSideLabel: UILabel! fileprivate var cameraStackView: UIStackView! + fileprivate var centerViewWidthConstraint: NSLayoutConstraint! + fileprivate var centerViewHeightConstraint: NSLayoutConstraint! + fileprivate var cameraDetailsVerticalConstraint: NSLayoutConstraint! + fileprivate var cameraDetailsHorizontalConstraint: NSLayoutConstraint! + override public func viewDidLoad() { super.viewDidLoad() view.translatesAutoresizingMaskIntoConstraints = false setupUI() - if let videoFeed = DJISDKManager.videoFeeder()?.primaryVideoFeed { - //Initialize and setup the widget's model - widgetModel = DUXBetaFPVWidgetModel(withVideoFeed: videoFeed) - widgetModel?.setup() - - //Initialize and setup the decoding model - decodeAdapter = DUXBetaFPVDecodeAdapter(videoFeed: videoFeed) - decodeAdapter?.widgetModel = widgetModel - decodeAdapter?.start() + + //Setup the widget's model + widgetModel.videoFeed = videoFeed + widgetModel.setup() + + //Setup the decoding model + decodeAdapter.widgetModel = widgetModel + decodeAdapter.enableHardwareDecode = enableHardwareDecode + if let videoFeed = videoFeed { + decodeAdapter.start(with: videoFeed) } } @@ -211,19 +214,19 @@ super.viewWillAppear(animated) //Bind widget to react to model updates - bindRKVOModel(self, #selector(updateIsConnected), (\DUXBetaFPVWidget.widgetModel?.isProductConnected).toString) - bindRKVOModel(self, #selector(updateCameraName), (\DUXBetaFPVWidget.widgetModel?.displayedCameraName).toString) - bindRKVOModel(self, #selector(updateCameraSide), (\DUXBetaFPVWidget.widgetModel?.displayedCameraSide).toString) - bindRKVOModel(self, #selector(updateGridFrame), (\DUXBetaFPVWidget.widgetModel?.cameraMode).toString, (\DUXBetaFPVWidget.widgetModel?.photoAspectRatio).toString, (\DUXBetaFPVWidget.widgetModel?.videoResolutionAndFrameRate).toString) + bindRKVOModel(self, #selector(updateIsConnected), (\DUXBetaFPVWidget.widgetModel.isProductConnected).toString) + bindRKVOModel(self, #selector(updateCameraName), (\DUXBetaFPVWidget.widgetModel.displayedCameraName).toString) + bindRKVOModel(self, #selector(updateCameraSide), (\DUXBetaFPVWidget.widgetModel.displayedCameraSide).toString) + bindRKVOModel(self, #selector(updateGridFrame), (\DUXBetaFPVWidget.widgetModel.cameraMode).toString, (\DUXBetaFPVWidget.widgetModel.photoAspectRatio).toString, (\DUXBetaFPVWidget.widgetModel.videoResolutionAndFrameRate).toString) - decodeAdapter?.setRenderingView(fpvView) + decodeAdapter.setRenderingView(fpvView) } public override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - - decodeAdapter?.stop() - decodeAdapter?.removeRenderingView() + + decodeAdapter.stop() + decodeAdapter.removeRenderingView() unbindRKVOModel(self) } @@ -232,7 +235,7 @@ super.viewWillLayoutSubviews() activateConstraints() - decodeAdapter?.adjustPreviewer() + decodeAdapter.adjustPreviewer() } public override func viewDidLayoutSubviews() { @@ -242,46 +245,35 @@ } deinit { - widgetModel?.cleanup() + widgetModel.cleanup() } @objc func updateIsConnected() { - guard let widgetModel = widgetModel else { - return - } - //Forward the model change - DUXStateChangeBroadcaster.send(DUXFPVWidgetModelState.productConnected(widgetModel.isProductConnected)) + DUXBetaStateChangeBroadcaster.send(DUXBetaFPVWidgetModelState.productConnected(widgetModel.isProductConnected)) + } + + func updateHardwareDecoding() { + decodeAdapter.enableHardwareDecode = enableHardwareDecode } @objc func updateCameraName() { - guard let widgetModel = widgetModel else { - return - } cameraNameLabel.text = widgetModel.displayedCameraName //Forward the model change - DUXStateChangeBroadcaster.send(DUXFPVWidgetModelState.cameraNameUpdate(widgetModel.displayedCameraName)) + DUXBetaStateChangeBroadcaster.send(DUXBetaFPVWidgetModelState.cameraNameUpdate(widgetModel.displayedCameraName)) } @objc func updateCameraSide() { - guard let widgetModel = widgetModel else { - return - } - cameraSideLabel.text = widgetModel.displayedCameraSide //Forward the model change if let cameraSide = widgetModel.displayedCameraSide { - DUXStateChangeBroadcaster.send(DUXFPVWidgetModelState.cameraSideUpdate(cameraSide)) + DUXBetaStateChangeBroadcaster.send(DUXBetaFPVWidgetModelState.cameraSideUpdate(cameraSide)) } } @objc func updateGridFrame() { - guard let widgetModel = widgetModel else { - return - } - //Set the container frame in the widget model in order to compute the gridFrame widgetModel.containerFrame = fpvView.bounds @@ -291,7 +283,7 @@ gridView.redraw() //Forward the user interface change - DUXStateChangeBroadcaster.send(DUXFPVWidgetUIState.gridFrameUpdate(widgetModel.gridFrame)) + DUXBetaStateChangeBroadcaster.send(DUXBetaFPVWidgetUIState.gridFrameUpdate(widgetModel.gridFrame)) } } @@ -323,10 +315,19 @@ cameraStackView.distribution = .fillProportionally cameraStackView.backgroundColor = .clear + if DUXBetaSingleton.sharedGlobalPreferences().centerViewType() == .Unknown { + DUXBetaSingleton.sharedGlobalPreferences().set(centerViewType: .None) + } + if DUXBetaSingleton.sharedGlobalPreferences().centerViewColor() == .Unknown { + DUXBetaSingleton.sharedGlobalPreferences().set(centerViewColor: .None) + } centerView = DUXBetaFPVCenterView(frame: CGRect.zero) centerView.isHidden = !isCenterViewVisible centerView.translatesAutoresizingMaskIntoConstraints = false + if DUXBetaSingleton.sharedGlobalPreferences().gridViewType() == .Unknown { + DUXBetaSingleton.sharedGlobalPreferences().set(gridViewType: .None) + } gridView = DUXBetaFPVGridView() gridView.isHidden = !isGridViewVisible gridView.translatesAutoresizingMaskIntoConstraints = false @@ -338,94 +339,99 @@ } private func activateConstraints() { + centerViewWidthConstraint = centerView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: centerViewWidthPercentage) + centerViewHeightConstraint = centerView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: centerViewHeightPercentage) + cameraDetailsVerticalConstraint = cameraStackView.topAnchor.constraint(equalTo: view.topAnchor, constant: cameraDetailsVerticalAlignment * view.frame.height) + cameraDetailsHorizontalConstraint = cameraStackView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: cameraDetailsHorizontalAlignment * view.frame.width) + NSLayoutConstraint.activate([ fpvView.topAnchor.constraint(equalTo: view.topAnchor), fpvView.bottomAnchor.constraint(equalTo: view.bottomAnchor), fpvView.leftAnchor.constraint(equalTo: view.leftAnchor), fpvView.rightAnchor.constraint(equalTo: view.rightAnchor), - cameraStackView.leftAnchor.constraint(equalTo: view.leftAnchor), + cameraDetailsVerticalConstraint, + cameraDetailsHorizontalConstraint, cameraStackView.rightAnchor.constraint(equalTo: view.rightAnchor), - cameraStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor), - cameraStackView.topAnchor.constraint(equalTo: view.topAnchor, constant: cameraDetailsVerticalAlignment * view.frame.height), centerView.centerXAnchor.constraint(equalTo: view.centerXAnchor), centerView.centerYAnchor.constraint(equalTo: view.centerYAnchor), - centerView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: centerViewWidthPercentage), - centerView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: centerViewHeightPercentage), + centerViewWidthConstraint, + centerViewHeightConstraint, view.widthAnchor.constraint(equalTo: view.heightAnchor, multiplier: widgetSizeHint.preferredAspectRatio) ]) } } -/* - * Abstraction that provides hooks in events received by the fpv widget from the widget model. - */ -@objc public class DUXFPVWidgetModelState: DUXStateChangeBaseData { - - /* - * Event sent when product is connected or disconnected. - */ - @objc public static func productConnected(_ isConnected: Bool) -> DUXFPVWidgetModelState { - return DUXFPVWidgetModelState(key: "productConnected", number: NSNumber(value: isConnected)) +/** + * DUXBetaFPVWidgetModelState contains the hooks for the model changes in the + * DUXBetaFPVWidget implementation. + * + * Key: productConnected Type: NSNumber - Sends a boolean value as an NSNumber + * when the product connection state changes. + * + * Key: cameraNameUpdate Type: NSString - Sends a NSString value when camera name is updated. + * + * Key: cameraSideUpdate Type: NSString - Sends a NSString value when camera side is updated. + * + * Key: encodeTypeUpdate Type: NSNumber - Sends the encode type as an NSNumber + * when encoding type is updated. + * + * Key: decodingDidSucceedWithTimestamp Type: NSNumber - Sends the timestamp as an NSNumber + * when decoding is successful for a given timestamp. + * + * Key: decodingDidFail Type: NSNumber - Sends 0 as an NSNumber when decoding fails. + * + * Key: physicalSourceUpdate Type: NSNumber - Sends the video physical value as an NSNumber + * when the physical source is updated. +*/ + +@objc public class DUXBetaFPVWidgetModelState: DUXBetaStateChangeBaseData { + + @objc public static func productConnected(_ isConnected: Bool) -> DUXBetaFPVWidgetModelState { + return DUXBetaFPVWidgetModelState(key: "productConnected", number: NSNumber(value: isConnected)) } - - /* - * Event sent when camera name is updated. - */ - @objc public static func cameraNameUpdate(_ cameraName: String) -> DUXFPVWidgetModelState { - return DUXFPVWidgetModelState(key: "cameraNameUpdate", string: cameraName) + + @objc public static func cameraNameUpdate(_ cameraName: String) -> DUXBetaFPVWidgetModelState { + return DUXBetaFPVWidgetModelState(key: "cameraNameUpdate", string: cameraName) } - /* - * Event sent when camera side is updated. - */ - @objc public static func cameraSideUpdate(_ cameraSide: String) -> DUXFPVWidgetModelState { - return DUXFPVWidgetModelState(key: "cameraSideUpdate", string: cameraSide) + @objc public static func cameraSideUpdate(_ cameraSide: String) -> DUXBetaFPVWidgetModelState { + return DUXBetaFPVWidgetModelState(key: "cameraSideUpdate", string: cameraSide) } - /* - * Event sent when encoding type is updated. - */ - @objc public static func encodeTypeUpdate(_ encodeType: H264EncoderType) -> DUXFPVWidgetModelState { - return DUXFPVWidgetModelState(key: "encodeTypeUpdate", number: NSNumber(value: encodeType.rawValue)) + @objc public static func encodeTypeUpdate(_ encodeType: H264EncoderType) -> DUXBetaFPVWidgetModelState { + return DUXBetaFPVWidgetModelState(key: "encodeTypeUpdate", number: NSNumber(value: encodeType.rawValue)) } - /* - * Event sent when decoding is successful for a given timestamp. - */ - @objc public static func decodingDidSucceedWithTimestamp(_ timestamp: UInt32) -> DUXFPVWidgetModelState { - return DUXFPVWidgetModelState(key: "decodingDidSucceedWithTimestamp", number: NSNumber(value: timestamp)) + @objc public static func decodingDidSucceedWithTimestamp(_ timestamp: UInt32) -> DUXBetaFPVWidgetModelState { + return DUXBetaFPVWidgetModelState(key: "decodingDidSucceedWithTimestamp", number: NSNumber(value: timestamp)) } - /* - * Event sent when decoding failed. - */ - @objc public static func decodingDidFail() -> DUXFPVWidgetModelState { - return DUXFPVWidgetModelState(key: "decodingDidFail", number: NSNumber(value: 0)) + @objc public static func decodingDidFail() -> DUXBetaFPVWidgetModelState { + return DUXBetaFPVWidgetModelState(key: "decodingDidFail", number: NSNumber(value: 0)) } - /* - * Event sent when the physical source is updated. - */ - @objc public static func physicalSourceUpdate(_ physicalSource: DJIVideoFeedPhysicalSource) -> DUXFPVWidgetModelState { - return DUXFPVWidgetModelState(key: "physicalSourceUpdate", number: NSNumber(value: physicalSource.rawValue)) + @objc public static func physicalSourceUpdate(_ physicalSource: DJIVideoFeedPhysicalSource) -> DUXBetaFPVWidgetModelState { + return DUXBetaFPVWidgetModelState(key: "physicalSourceUpdate", number: NSNumber(value: physicalSource.rawValue)) } } -/* -* Abstraction that provides hooks in events received by the fpv widget from the user interface. -*/ -@objc public class DUXFPVWidgetUIState: DUXStateChangeBaseData { - /* - * Event sent when the grid line view frame is updated. - */ - @objc public static func gridFrameUpdate(_ gridFrame: CGRect) -> DUXFPVWidgetModelState { - return DUXFPVWidgetModelState(key: "gridFrameUpdated", value: NSValue(cgRect: gridFrame)) - } - - /* - * Event sent when the fpv content view frame is updated - */ - @objc public static func contentFrameUpdate(_ contentFrame: CGRect) -> DUXFPVWidgetModelState { - return DUXFPVWidgetModelState(key: "contentFrameUpdate", value: NSValue(cgRect: contentFrame)) +/** + * DUXBetaFPVWidgetUIState contains the hooks for the UI changes in the + * DUXBetaFPVWidget implementation. + * + * Key: gridFrameUpdate Type: NSValue - Sends a CGRrect as an NSValue + * when the grid line view frame is updated. + * + * Key: contentFrameUpdate Type: NSString - Sends a CGRrect as an NSValue + * when the fpv content view frame is updated. + */ +@objc public class DUXBetaFPVWidgetUIState: DUXBetaStateChangeBaseData { + + @objc public static func gridFrameUpdate(_ gridFrame: CGRect) -> DUXBetaFPVWidgetModelState { + return DUXBetaFPVWidgetModelState(key: "gridFrameUpdated", value: NSValue(cgRect: gridFrame)) + } + + @objc public static func contentFrameUpdate(_ contentFrame: CGRect) -> DUXBetaFPVWidgetModelState { + return DUXBetaFPVWidgetModelState(key: "contentFrameUpdate", value: NSValue(cgRect: contentFrame)) } } diff --git a/DJIUXSDKWidgets/FPVWidget/DUXBetaFPVWidgetModel.swift b/DJIUXSDKWidgets/FPVWidget/DUXBetaFPVWidgetModel.swift index 72f54c3..dfdb3ff 100644 --- a/DJIUXSDKWidgets/FPVWidget/DUXBetaFPVWidgetModel.swift +++ b/DJIUXSDKWidgets/FPVWidget/DUXBetaFPVWidgetModel.swift @@ -3,9 +3,9 @@ // DJIUXSDKWidgets // // MIT License -// +// // Copyright © 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 @@ -29,14 +29,19 @@ import Foundation import DJISDK /** - * Data model for the DUXFPVWidgetModel used to define - * the underlying logic and communication. + * Data model for the DUXBetaFPVWidgetModel used to define + * the underlying logic and communication. */ -@objc public class DUXBetaFPVWidgetModel: DUXBetaBaseWidgetModel { - - @objc public var displayedCameraName: String = "" - @objc public var displayedCameraSide: String? - @objc public var preferredCameraIndex = 0 { +@objcMembers public class DUXBetaFPVWidgetModel: DUXBetaBaseWidgetModel { + + /// The camera name displayed in the fpv widget. + dynamic public var displayedCameraName: String = "" + + /// The camera side displayed in the fpv widget. + dynamic public var displayedCameraSide: String? + + /// The current camera index displayed in the fpv widget. + dynamic public var preferredCameraIndex = 0 { didSet { cleanup() setup() @@ -44,58 +49,48 @@ import DJISDK } } - /** - * The camera name value needed to compute the displayed camera name. - */ - @objc public var cameraName: String = "" + /// The model name of the aircraft. + dynamic public var aircraftModel: String? - /** - * The camera mode value needed to compute the displayed camera side. - */ - @objc public var cameraMode: DJICameraMode = .unknown + /// The camera name value needed to compute the displayed camera name. + dynamic public var cameraName: String = "" - /** - * The camera photo aspect ratio needed to compute the gridline frame. - */ - @objc public var photoAspectRatio: DJICameraPhotoAspectRatio = .ratioUnknown + /// The camera mode value needed to compute the displayed camera side. + dynamic public var cameraMode: DJICameraMode = .unknown - /** - * The video resolution and frame information needed to compute the gridline frame. - */ - @objc public var videoResolutionAndFrameRate: DJICameraVideoResolutionAndFrameRate? + /// The camera photo aspect ratio needed to compute the gridline frame. + dynamic public var photoAspectRatio: DJICameraPhotoAspectRatio = .ratioUnknown - /** - * The percentage allocation for left camera needed to compute the current camera index. - */ - @objc public var bandwidthAllocationForLeftCamera: CGFloat = 0.0 + /// The video resolution and frame information needed to compute the gridline frame. + dynamic public var videoResolutionAndFrameRate: DJICameraVideoResolutionAndFrameRate? - /** - * The index of the current camera. - */ - @objc public var currentCameraIndex: DUXFPVWidgetCameraIndex = .unknown + /// The percentage allocation for left camera needed to compute the current camera index. + dynamic public var bandwidthAllocationForLeftCamera: CGFloat = 0.0 - /** - * The frame used to draw the gridlines. - */ - @objc public var gridFrame: CGRect = .zero + /// The index of the current camera. + dynamic public var currentCameraIndex: DUXBetaFPVWidgetCameraIndex = .unknown - /** - * The frame needed to compute the gridline frame. - */ - @objc public var containerFrame = CGRect.zero { + /// The frame used to draw the gridlines. + dynamic public var gridFrame: CGRect = .zero + + /// The frame needed to compute the gridline frame. + dynamic public var containerFrame = CGRect.zero { didSet { updateDrawingRect() } } - fileprivate var videoFeed: DJIVideoFeed! - - @objc convenience public init(withVideoFeed videoFeed: DJIVideoFeed) { - self.init() - self.videoFeed = videoFeed + /// The video feed currently displayed in the FPV. + dynamic public var videoFeed: DJIVideoFeed? { + didSet { + updateDisplayedValues() + } } override public func inSetup() { + if let key = DJIProductKey(param: DJIProductParamModelName) { + bindSDKKey(key, (\DUXBetaFPVWidgetModel.aircraftModel).toString) + } if let key = DJICameraKey(index: preferredCameraIndex, andParam: DJICameraParamDisplayName) { bindSDKKey(key, (\DUXBetaFPVWidgetModel.cameraName).toString) } @@ -115,7 +110,7 @@ import DJISDK bindSDKKey(key, (\DUXBetaFPVWidgetModel.videoResolutionAndFrameRate).toString) } - bindRKVOModel(self, #selector(updateDisplayedValues), (\DUXBetaFPVWidgetModel.cameraName).toString) + bindRKVOModel(self, #selector(updateDisplayedValues), (\DUXBetaFPVWidgetModel.aircraftModel).toString) bindRKVOModel(self, #selector(updateCurrentCameraIndex), (\DUXBetaFPVWidgetModel.cameraName).toString) } @@ -125,23 +120,58 @@ import DJISDK } @objc public func updateDisplayedValues() { - displayedCameraName = cameraName - updateCameraSide(videoFeed.physicalSource) - } - - @objc public func updateCameraSide(_ physicalSource: DJIVideoFeedPhysicalSource) { - var widgetCameraSide: DUXFPVWidgetCameraSide = .unknown - switch physicalSource { - case .leftCamera: - widgetCameraSide = .port - case .rightCamera: - widgetCameraSide = .starboard - case .fpvCamera: - widgetCameraSide = .fpv - default: - break + guard let displayName0Key = DJICameraKey(index: 0, andParam: DJICameraParamDisplayName) else { return } + guard let displayName1Key = DJICameraKey(index: 1, andParam: DJICameraParamDisplayName) else { return } + + let displayName0 = DJISDKManager.keyManager()?.getValueFor(displayName0Key)?.stringValue ?? "" + let displayName1 = DJISDKManager.keyManager()?.getValueFor(displayName1Key)?.stringValue ?? "" + + + var displayName = displayName0 + var displaySide = DUXBetaFPVWidgetCameraSide.unknown + + guard let physicalSource = videoFeed?.physicalSource else { return } + + if aircraftModel == DJIAircraftModelNameA3 || + aircraftModel == DJIAircraftModelNameN3 || + aircraftModel == DJIAircraftModelNameMatrice600 || + aircraftModel == DJIAircraftModelNameMatrice600Pro { + if physicalSource == .mainCamera { + displayName = displayName0 + } else if physicalSource == .EXT { + displayName = "\(DJIVideoFeedPhysicalSource.EXT)" + } else if physicalSource == .HDMI { + displayName = "\(DJIVideoFeedPhysicalSource.HDMI)" + } else if physicalSource == .AV { + displayName = "\(DJIVideoFeedPhysicalSource.AV)" + } + } else if aircraftModel == DJIAircraftModelNameMatrice210 || + aircraftModel == DJIAircraftModelNameMatrice210RTK || + aircraftModel == DJIAircraftModelNameMatrice210V2 || + aircraftModel == DJIAircraftModelNameMatrice210RTKV2 { + if physicalSource == .leftCamera { + displayName = displayName0 + displaySide = .port + } else if physicalSource == .rightCamera { + displayName = displayName1 + displaySide = .starboard + } else if physicalSource == .fpvCamera { + displayName = NSLocalizedString("FPV Camera", comment: "FPV Camera") + } else if physicalSource == .mainCamera { + displayName = displayName0 + } + } else if aircraftModel == DJIAircraftModelNameInspire2 || + aircraftModel == DJIAircraftModelNameMatrice200V2 { + if physicalSource == .mainCamera { + displayName = displayName0 + } else if physicalSource == .fpvCamera { + displayName = NSLocalizedString("FPV Camera", comment: "FPV Camera") + } } - displayedCameraSide = widgetCameraSide.rawValue + displayName = displayName.replacingOccurrences(of: "-Visual", with: "") + + displayedCameraName = displayName + displayedCameraSide = displaySide.rawValue } @objc public func updateCurrentCameraIndex() { @@ -163,7 +193,7 @@ import DJISDK currentCameraIndex = .index_0 } } - } else if videoFeed.physicalSource != .fpvCamera { + } else if let physicalSource = videoFeed?.physicalSource, physicalSource != .fpvCamera { currentCameraIndex = .index_0 } else { currentCameraIndex = .unknown @@ -302,16 +332,15 @@ import DJISDK } } -@objc public enum DUXFPVWidgetCameraIndex: NSInteger { +@objc public enum DUXBetaFPVWidgetCameraIndex: NSInteger { case unknown = -1 case index_0 case index_1 case index_2 } -enum DUXFPVWidgetCameraSide: String { +enum DUXBetaFPVWidgetCameraSide: String { case unknown = "" case port = "Port-side" case starboard = "Starboard-side" - case fpv = "FPV Camera" } diff --git a/DJIUXSDKWidgets/FPVWidget/Decode/DUXBetaFPVDecodeAdapter.h b/DJIUXSDKWidgets/FPVWidget/Decode/DUXBetaFPVDecodeAdapter.h index 95b0ab0..1da1aae 100644 --- a/DJIUXSDKWidgets/FPVWidget/Decode/DUXBetaFPVDecodeAdapter.h +++ b/DJIUXSDKWidgets/FPVWidget/Decode/DUXBetaFPVDecodeAdapter.h @@ -3,9 +3,9 @@ // DJIUXSDKWidgets // // MIT License -// +// // Copyright © 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 @@ -31,17 +31,17 @@ NS_ASSUME_NONNULL_BEGIN /** - * Decoding adapter for the DUXFPVWidgetModel used to define + * Decoding adapter for the DUXBetaFPVWidgetModel used to define * the decoding and encoding logic. */ @class DUXBetaFPVWidgetModel; @interface DUXBetaFPVDecodeAdapter : NSObject @property (nonatomic, weak) DUXBetaFPVWidgetModel *widgetModel; +@property (nonatomic, weak) DJIVideoFeed *videoFeed; +@property (nonatomic) BOOL enableHardwareDecode; -- (instancetype)initWithVideoFeed:(DJIVideoFeed *)videoFeed; - -- (void)start; +- (void)startWithVideoFeed:(DJIVideoFeed *)videoFeed; - (void)stop; - (void)setRenderingView:(UIView *)view; diff --git a/DJIUXSDKWidgets/FPVWidget/Decode/DUXBetaFPVDecodeAdapter.m b/DJIUXSDKWidgets/FPVWidget/Decode/DUXBetaFPVDecodeAdapter.m index dfc84e1..762812f 100644 --- a/DJIUXSDKWidgets/FPVWidget/Decode/DUXBetaFPVDecodeAdapter.m +++ b/DJIUXSDKWidgets/FPVWidget/Decode/DUXBetaFPVDecodeAdapter.m @@ -3,9 +3,9 @@ // DJIUXSDKWidgets // // MIT License -// +// // Copyright © 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 @@ -28,15 +28,14 @@ #import "DUXBetaFPVDecodeModel.h" #import "DUXBetaFPVDecodeAdapter.h" #import - #import "DUXBetaBaseWidgetModel+Protected.h" - #import +#define IS_FLOAT_EQUAL(a, b) (fabs(a - b) < 0.0005) + @interface DUXBetaFPVDecodeAdapter () @property (nonatomic, weak) DJIVideoPreviewer *videoPreviewer; -@property (nonatomic, weak) DJIVideoFeed *videoFeed; @property (nonatomic, strong) DUXBetaFPVDecodeModel *decodeModel; @@ -44,20 +43,22 @@ @interface DUXBetaFPVDecodeAdapter () @implementation DUXBetaFPVDecodeAdapter -- (instancetype)initWithVideoFeed:(DJIVideoFeed *)videoFeed { +- (instancetype)init { self = [super init]; if (self) { - self.videoFeed = videoFeed; - self.videoPreviewer = [DJIVideoPreviewer instance]; + _videoPreviewer = [DJIVideoPreviewer instance]; } return self; } -- (void)start { +- (void)startWithVideoFeed:(DJIVideoFeed *)videoFeed { + _videoFeed = videoFeed; + [self modelSetup]; //Start the videoPreviewer self.videoPreviewer.type = DJIVideoPreviewerTypeAutoAdapt; + self.videoPreviewer.enableHardwareDecode = YES; [self.videoPreviewer start]; //Setup delegates @@ -90,13 +91,27 @@ - (void)adjustPreviewer { [self.videoPreviewer adjustViewSize]; } +- (void)setVideoFeed:(DJIVideoFeed *)videoFeed { + [self.videoPreviewer pause]; + [self.videoFeed removeListener:self]; + + _videoFeed = videoFeed; + + [self.videoFeed addListener:self withQueue:nil]; + [self.videoPreviewer safeResume]; +} + #pragma mark - Private Methods - (void)modelSetup { - self.decodeModel = [[DUXBetaFPVDecodeModel alloc] initWithVideoFeed:self.videoFeed]; + self.decodeModel = [[DUXBetaFPVDecodeModel alloc] init]; [self.decodeModel setup]; + BindRKVOModel(self, @selector(updatedDecodingRect), self.decodeModel.contentClipRect); BindRKVOModel(self, @selector(updateEncodeType), self.decodeModel.encodeType); + BindRKVOModel(self, @selector(updateHardwareDecode), self.enableHardwareDecode); + BindRKVOModel(self, @selector(updateOrientation), self.decodeModel.orientation); + BindRKVOModel(self, @selector(updateVideoFeed), self.decodeModel.isEXTPortEnabled, self.decodeModel.LBEXTPercent, self.decodeModel.HDMIAVPercent); } - (void)modelCleanup { @@ -105,19 +120,94 @@ - (void)modelCleanup { UnBindRKVOModel(self); } +- (void)updateHardwareDecode { + self.videoPreviewer.enableHardwareDecode = self.enableHardwareDecode; +} + - (void)updatedDecodingRect { //Update videpreviewer contentClipRect self.videoPreviewer.contentClipRect = self.decodeModel.contentClipRect; //Broadcast the updated contentClipRect - [DUXStateChangeBroadcaster send:[DUXFPVWidgetUIState contentFrameUpdate:self.decodeModel.contentClipRect]]; + [DUXBetaStateChangeBroadcaster send:[DUXBetaFPVWidgetUIState contentFrameUpdate:self.decodeModel.contentClipRect]]; } - (void)updateEncodeType { self.videoPreviewer.encoderType = self.decodeModel.encodeType; //Forward the model change - [DUXStateChangeBroadcaster send:[DUXFPVWidgetModelState encodeTypeUpdate:self.decodeModel.encodeType]]; + [DUXBetaStateChangeBroadcaster send:[DUXBetaFPVWidgetModelState encodeTypeUpdate:self.decodeModel.encodeType]]; +} + +- (void)updateOrientation { + if (self.decodeModel.orientation == DJICameraOrientationLandscape) { + self.videoPreviewer.rotation = VideoStreamRotationDefault; + } else { + self.videoPreviewer.rotation = VideoStreamRotationCW90; + } +} + +// MARK: Lightbridge2 support methods + +- (void)updateVideoFeed { + if (self.decodeModel.isEXTPortEnabled == nil) { + [self swapToPrimaryVideoFeedIfNecessary]; + return; + } + + if ([self.decodeModel.isEXTPortEnabled boolValue]) { + if (self.decodeModel.LBEXTPercent == nil) { + [self swapToPrimaryVideoFeedIfNecessary]; + return; + } + + if (IS_FLOAT_EQUAL(self.decodeModel.LBEXTPercent.floatValue, 1.0)) { + if (![self isUsingPrimaryVideoFeed]) { + [self swapVideoFeed]; + } + return; + } else if (self.decodeModel.LBEXTPercent.floatValue < 0.95) { + if ([self isUsingPrimaryVideoFeed]) { + [self swapVideoFeed]; + } + return; + } + } else { + if (self.decodeModel.HDMIAVPercent == nil) { + [self swapToPrimaryVideoFeedIfNecessary]; + return; + } + + if (IS_FLOAT_EQUAL(self.decodeModel.HDMIAVPercent.floatValue, 1.0)) { + if (![self isUsingPrimaryVideoFeed]) { + [self swapVideoFeed]; + } + return; + } else if (IS_FLOAT_EQUAL(self.decodeModel.HDMIAVPercent.floatValue, 0.0)) { + if ([self isUsingPrimaryVideoFeed]) { + [self swapVideoFeed]; + } + return; + } + } +} + +- (BOOL)isUsingPrimaryVideoFeed { + return (self.videoFeed == [DJISDKManager videoFeeder].primaryVideoFeed); +} + +- (void)swapToPrimaryVideoFeedIfNecessary { + if (![self isUsingPrimaryVideoFeed]) { + [self swapVideoFeed]; + } +} + +- (void)swapVideoFeed { + if ([self isUsingPrimaryVideoFeed]) { + self.videoFeed = [DJISDKManager videoFeeder].secondaryVideoFeed; + } else { + self.videoFeed = [DJISDKManager videoFeeder].primaryVideoFeed; + } } #pragma mark - DJIVideoFeedListener Method @@ -132,7 +222,7 @@ - (void)videoFeed:(nonnull DJIVideoFeed *)videoFeed didChangePhysicalSource:(DJI if (self.videoFeed == videoFeed) { //Forward the user interface change - [DUXStateChangeBroadcaster send:[DUXFPVWidgetModelState physicalSourceUpdate:physicalSource]]; + [DUXBetaStateChangeBroadcaster send:[DUXBetaFPVWidgetModelState physicalSourceUpdate:physicalSource]]; if (physicalSource == DJIVideoFeedPhysicalSourceUnknown) { @@ -140,6 +230,7 @@ - (void)videoFeed:(nonnull DJIVideoFeed *)videoFeed didChangePhysicalSource:(DJI //Update models [self.decodeModel updateEncodeType]; [self.decodeModel updateContentRect]; + [self.widgetModel updateDisplayedValues]; [self.widgetModel updateCurrentCameraIndex]; } } @@ -163,14 +254,14 @@ - (void)decodingDidSucceedWithTimestamp:(uint32_t)timestamp { [self.videoFeed decodingDidSucceedWithTimestamp:(NSUInteger)timestamp]; //Forward the model change - [DUXStateChangeBroadcaster send:[DUXFPVWidgetModelState decodingDidSucceedWithTimestamp:timestamp]]; + [DUXBetaStateChangeBroadcaster send:[DUXBetaFPVWidgetModelState decodingDidSucceedWithTimestamp:timestamp]]; } - (void)decodingDidFail { [self.videoFeed decodingDidFail]; //Forward the model change - [DUXStateChangeBroadcaster send:[DUXFPVWidgetModelState decodingDidFail]]; + [DUXBetaStateChangeBroadcaster send:[DUXBetaFPVWidgetModelState decodingDidFail]]; } @end diff --git a/DJIUXSDKWidgets/FPVWidget/Decode/DUXBetaFPVDecodeModel.h b/DJIUXSDKWidgets/FPVWidget/Decode/DUXBetaFPVDecodeModel.h index 7932331..78cf798 100644 --- a/DJIUXSDKWidgets/FPVWidget/Decode/DUXBetaFPVDecodeModel.h +++ b/DJIUXSDKWidgets/FPVWidget/Decode/DUXBetaFPVDecodeModel.h @@ -3,9 +3,9 @@ // DJIUXSDKWidgets // // MIT License -// +// // Copyright © 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 @@ -32,25 +32,32 @@ NS_ASSUME_NONNULL_BEGIN /** - * Data model for the decoding adapter used to define - * the underlying logic and communication. + * Data model for the decoding adapter used to define + * the underlying logic and communication. */ @interface DUXBetaFPVDecodeModel : DUXBetaBaseWidgetModel /** -* The frame needed by the videopreviewer to display the video feed. + * The frame needed by the video previewer to display the video feed. */ @property (nonatomic, assign, readonly) CGRect contentClipRect; /** -* The encoding type needed by the videopreviewer to display the video feed. + * The encoding type needed by the video previewer to display the video feed. */ @property (nonatomic, assign, readonly) H264EncoderType encodeType; /** -* Initialization method that takes a DJIVideoFeed as a parameter + * The camera orientation needed by the video previewer. + * This property gets populated only for the camera that support multiple orientations. */ -- (instancetype)initWithVideoFeed:(DJIVideoFeed *)videoFeed; +@property (nonatomic, assign, readonly) DJICameraOrientation orientation; + +@property (nonatomic, assign, readonly, nullable) NSNumber *isEXTPortEnabled; + +@property (nonatomic, assign, readonly, nullable) NSNumber *LBEXTPercent; + +@property (nonatomic, assign, readonly, nullable) NSNumber *HDMIAVPercent; - (void)updateContentRect; - (void)updateEncodeType; diff --git a/DJIUXSDKWidgets/FPVWidget/Decode/DUXBetaFPVDecodeModel.m b/DJIUXSDKWidgets/FPVWidget/Decode/DUXBetaFPVDecodeModel.m index 368025c..3338426 100644 --- a/DJIUXSDKWidgets/FPVWidget/Decode/DUXBetaFPVDecodeModel.m +++ b/DJIUXSDKWidgets/FPVWidget/Decode/DUXBetaFPVDecodeModel.m @@ -3,9 +3,9 @@ // DJIUXSDKWidgets // // MIT License -// +// // Copyright © 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 @@ -28,36 +28,43 @@ #import "DUXBetaFPVDecodeModel.h" #import "DUXBetaBaseWidgetModel+Protected.h" -@interface DUXBetaFPVDecodeModel() +#import -@property (nonatomic, weak) DJIVideoFeed *videoFeed; +@interface DUXBetaFPVDecodeModel() @property (nonatomic, strong) NSString *cameraName; +@property (nonatomic, strong) NSString *aircraftModel; @property (nonatomic, assign) DJICameraMode cameraMode; +@property (nonatomic, assign) DJICameraOrientation orientation; @property (nonatomic, assign) DJICameraPhotoAspectRatio photoRatio; @property (nonatomic, assign, readwrite) CGRect contentClipRect; @property (nonatomic, assign, readwrite) H264EncoderType encodeType; +@property (nonatomic, assign, readwrite, nullable) NSNumber *isEXTPortEnabled; +@property (nonatomic, assign, readwrite, nullable) NSNumber *LBEXTPercent; +@property (nonatomic, assign, readwrite, nullable) NSNumber *HDMIAVPercent; + @end @implementation DUXBetaFPVDecodeModel -- (instancetype)initWithVideoFeed:(DJIVideoFeed *)videoFeed { - self = [super init]; - if (self) { - self.videoFeed = videoFeed; - } - return self; -} - - (void)inSetup { BindSDKKey([DJICameraKey keyWithParam:DJICameraParamMode], cameraMode); + BindSDKKey([DJIProductKey keyWithParam: DJIProductParamModelName], aircraftModel); BindSDKKey([DJICameraKey keyWithParam:DJICameraParamDisplayName], cameraName); BindSDKKey([DJICameraKey keyWithParam:DJICameraParamPhotoAspectRatio], photoRatio); + // Bind to DJICameraParamOrientation key only if the model supports multiple orientations + DJIAircraft *product = (DJIAircraft *)[DJISDKManager product]; + DJICamera *camera = product.camera; + if (camera.capabilities.orientationRange.count > 1) { + BindSDKKey([DJICameraKey keyWithParam:DJICameraParamOrientation], orientation); + } + BindRKVOModel(self, @selector(updateEncodeType), cameraName) BindRKVOModel(self, @selector(updateContentRect), cameraMode, cameraName, photoRatio); + BindRKVOModel(self, @selector(updateAircraftModel), aircraftModel); } - (void)inCleanup { @@ -78,6 +85,27 @@ - (void)updateEncodeType { self.encodeType = self.computedEncodeType; } +- (void)updateAircraftModel { + if ([self.aircraftModel isEqualToString:DJIAircraftModelNameMatrice600] || + [self.aircraftModel isEqualToString:DJIAircraftModelNameMatrice600Pro] || + [self.aircraftModel isEqualToString:DJIAircraftModelNameA3] || + [self.aircraftModel isEqualToString:DJIAircraftModelNameN3]) { + + BindSDKKey([DJIAirLinkKey keyWithIndex:0 + subComponent:DJIAirLinkLightbridgeLinkSubComponent + subComponentIndex:0 + andParam:DJILightbridgeLinkParamEXTVideoInputPortEnabled], isEXTPortEnabled); + BindSDKKey([DJIAirLinkKey keyWithIndex:0 + subComponent:DJIAirLinkLightbridgeLinkSubComponent + subComponentIndex:0 + andParam:DJILightbridgeLinkParamBandwidthAllocationForLBVideoInputPort], LBEXTPercent); + BindSDKKey([DJIAirLinkKey keyWithIndex:0 + subComponent:DJIAirLinkLightbridgeLinkSubComponent + subComponentIndex:0 + andParam:DJILightbridgeLinkParamBandwidthAllocationForHDMIVideoInputPort], HDMIAVPercent); + } +} + #pragma mark - Private Methods - (CGRect)defaultContentRect { @@ -118,21 +146,34 @@ - (CGRect)contentRectInPhotoMode { } - (H264EncoderType)computedEncodeType { + DJIAircraft *product = (DJIAircraft *)[DJISDKManager product]; + DJICamera *camera = product.camera; + NSString *productName = product.model; + BOOL isAircraft = [product isKindOfClass:[DJIAircraft class]]; - if ([self.cameraName isEqualToString:DJICameraDisplayNameX3]) { - DJIAircraft *product = (DJIAircraft *)[DJISDKManager product]; - DJICamera *camera = product.camera; - BOOL isAircraft = [product isKindOfClass:[DJIAircraft class]]; + if (isAircraft && + ([productName isEqual:DJIAircraftModelNameA3] || + [productName isEqual:DJIAircraftModelNameN3] || + [productName isEqual:DJIAircraftModelNameMatrice600] || + [productName isEqual:DJIAircraftModelNameMatrice600Pro])) { + return H264EncoderType_LightBridge2; + } + + //Special case: can be stand-alone Lightbridge 2 + if (isAircraft && + [productName isEqual:DJIAircraftModelNameUnknownAircraft] && + camera.displayName == nil) { + return H264EncoderType_LightBridge2; + } + + if ([self.cameraName isEqualToString:DJICameraDisplayNameX3] || + [self.cameraName isEqualToString:DJICameraDisplayNameZ3]) { if (!isAircraft && [camera isDigitalZoomSupported]) { return H264EncoderType_A9_OSMO_NO_368; } return H264EncoderType_DM368_inspire; } - if ([self.cameraName isEqualToString:DJICameraDisplayNameZ3]) { - return H264EncoderType_A9_OSMO_NO_368; - } - if ([self.cameraName isEqualToString:DJICameraDisplayNameX5] || [self.cameraName isEqualToString:DJICameraDisplayNameX5R]) { return H264EncoderType_DM368_inspire; @@ -182,6 +223,10 @@ - (H264EncoderType)computedEncodeType { if ([self.cameraName isEqualToString:DJICameraDisplayNameMavicAirCamera]) { return H264EncoderType_MavicAir; } + + if ([self.cameraName isEqualToString:DJICameraDisplayNameMavicMiniCamera]) { + return H264EncoderType_MavicMini; + } return H264EncoderType_unknown; } diff --git a/DJIUXSDKWidgets/FlightModeWidget/DUXBetaFlightModeWidget.h b/DJIUXSDKWidgets/FlightModeWidget/DUXBetaFlightModeWidget.h index 4a9a442..2de5725 100644 --- a/DJIUXSDKWidgets/FlightModeWidget/DUXBetaFlightModeWidget.h +++ b/DJIUXSDKWidgets/FlightModeWidget/DUXBetaFlightModeWidget.h @@ -1,11 +1,11 @@ // // DUXBetaFlightModeWidget.h // DJIUXSDK -// +// // MIT License -// +// // Copyright © 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 diff --git a/DJIUXSDKWidgets/FlightModeWidget/DUXBetaFlightModeWidget.m b/DJIUXSDKWidgets/FlightModeWidget/DUXBetaFlightModeWidget.m index cf89714..5c4d26d 100644 --- a/DJIUXSDKWidgets/FlightModeWidget/DUXBetaFlightModeWidget.m +++ b/DJIUXSDKWidgets/FlightModeWidget/DUXBetaFlightModeWidget.m @@ -3,9 +3,9 @@ // DJIUXSDK // // MIT License -// +// // Copyright © 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 @@ -29,7 +29,7 @@ #import "UIImage+DUXBetaAssets.h" #import "UIFont+DUXBetaFonts.h" #import "UIColor+DUXBetaColors.h" -#import "DUXStateChangeBroadcaster.h" +#import "DUXBetaStateChangeBroadcaster.h" @import DJIUXSDKCore; static const CGFloat kDesignFontSize = 70.0; @@ -52,14 +52,14 @@ @interface DUXBetaFlightModeWidget () @end /** - * FlightModeWidgetModelState contains the model hooks for the DUXFlightModeWidget. + * FlightModeWidgetModelState contains the model hooks for the DUXBetaFlightModeWidget. * It implements the hooks: * * Key: productConnected Type: NSNumber - Sends a boolean value as an NSNumber indicating if an aircraft is connected. * * Key: flightModeTextUpdate: Type: NSString - Sends the flight mode string whenever is is updated. */ -@interface FlightModeWidgetModelState : DUXStateChangeBaseData +@interface FlightModeWidgetModelState : DUXBetaStateChangeBaseData + (instancetype)productConnected:(BOOL)isConnected; + (instancetype)flightModeTextUpdate:(NSString *)flightModeText; @end @@ -226,11 +226,11 @@ - (void)updateUI { } - (void)sendIsProductConnected { - [[DUXStateChangeBroadcaster instance] send:[FlightModeWidgetModelState productConnected:self.widgetModel.isProductConnected]]; + [[DUXBetaStateChangeBroadcaster instance] send:[FlightModeWidgetModelState productConnected:self.widgetModel.isProductConnected]]; } - (void)sendFlightModeString { - [[DUXStateChangeBroadcaster instance] send:[FlightModeWidgetModelState flightModeTextUpdate:self.widgetModel.flightModeString]]; + [[DUXBetaStateChangeBroadcaster instance] send:[FlightModeWidgetModelState flightModeTextUpdate:self.widgetModel.flightModeString]]; } - (void)updateMinWidgetSize { diff --git a/DJIUXSDKWidgets/FlightModeWidget/DUXBetaFlightModeWidgetModel.h b/DJIUXSDKWidgets/FlightModeWidget/DUXBetaFlightModeWidgetModel.h index 60aa00f..00d6d4a 100644 --- a/DJIUXSDKWidgets/FlightModeWidget/DUXBetaFlightModeWidgetModel.h +++ b/DJIUXSDKWidgets/FlightModeWidget/DUXBetaFlightModeWidgetModel.h @@ -1,11 +1,11 @@ // // DUXBetaFlightModeWidgetModel.h // DJIUXSDK -// +// // MIT License -// +// // Copyright © 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 diff --git a/DJIUXSDKWidgets/FlightModeWidget/DUXBetaFlightModeWidgetModel.m b/DJIUXSDKWidgets/FlightModeWidget/DUXBetaFlightModeWidgetModel.m index aa931bb..f82f3e1 100644 --- a/DJIUXSDKWidgets/FlightModeWidget/DUXBetaFlightModeWidgetModel.m +++ b/DJIUXSDKWidgets/FlightModeWidget/DUXBetaFlightModeWidgetModel.m @@ -1,11 +1,11 @@ // // DUXBetaFlightModeWidgetModel.m // DJIUXSDK -// +// // MIT License -// +// // Copyright © 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 diff --git a/DJIUXSDKWidgets/GPSSignalWidget/DUXBetaGPSSignalWidget.m b/DJIUXSDKWidgets/GPSSignalWidget/DUXBetaGPSSignalWidget.m index e5a4063..789addf 100644 --- a/DJIUXSDKWidgets/GPSSignalWidget/DUXBetaGPSSignalWidget.m +++ b/DJIUXSDKWidgets/GPSSignalWidget/DUXBetaGPSSignalWidget.m @@ -29,7 +29,7 @@ #import "UIImage+DUXBetaAssets.h" #import "UIFont+DUXBetaFonts.h" @import DJIUXSDKCore; -#import "DUXStateChangeBroadcaster.h" +#import "DUXBetaStateChangeBroadcaster.h" // image names used for various strength levels of the signal indicator static NSString * const kGPSLevel0ImageName = @"SignalLevel0"; @@ -43,7 +43,7 @@ static NSString * const kGPSIconImageName = @"GPSSignalIcon"; static CGFloat const kRTKIndicatorFontSize = 30.0; -static CGFloat const kCountFontSize = 60.0; +static CGFloat const kCountFontSize = 30.0; static CGFloat const kCountLeadingGuideProportionOfIcon = 1.1; static CGFloat const kRTKIndicatorProportionOfIcon = 0.45; static CGFloat const kSatelliteCountProportionOfHeight = 0.4; @@ -67,19 +67,19 @@ @interface DUXBetaGPSSignalWidget () @end /** - * GPSSignalWidgetUIState contains the hooks for UI changes in the widget class DUXGPSSignalWidget. + * GPSSignalWidgetUIState contains the hooks for UI changes in the widget class DUXBetaGPSSignalWidget. * It implements the hook: * * Key: widgetTap Type: NSNumber - Sends a boolean YES value as an NSNumber indicating the widget was tapped. */ -@interface GPSSignalWidgetUIState : DUXStateChangeBaseData +@interface GPSSignalWidgetUIState : DUXBetaStateChangeBaseData + (instancetype)widgetTap; @end /** - * GPSSignalWidgetModelState contains the model hooks for the DUXGPSSignalWidget. + * GPSSignalWidgetModelState contains the model hooks for the DUXBetaGPSSignalWidget. * It implements the hook: * * Key: productConnected Type: NSNumber - Sends a boolean value as an NSNumber indicating if an aircraft is connected. @@ -94,7 +94,7 @@ + (instancetype)widgetTap; * Key: isRTKAccurateUpdate Type: NSNumber - Sends a boolean as an NSNumber indicating that RTK mode is accurate whenever the * status changes. */ -@interface GPSSignalWidgetModelState : DUXStateChangeBaseData +@interface GPSSignalWidgetModelState : DUXBetaStateChangeBaseData + (instancetype)productConnected:(BOOL)isConnected; + (instancetype)gpsSignalQualityUpdate:(NSInteger)signalQuality; @@ -243,27 +243,27 @@ - (void)setupUI { } - (void)widgetTapped { - [[DUXStateChangeBroadcaster instance] send:[GPSSignalWidgetUIState widgetTap]]; + [[DUXBetaStateChangeBroadcaster instance] send:[GPSSignalWidgetUIState widgetTap]]; } - (void)sendIsProductConnected { - [[DUXStateChangeBroadcaster instance] send:[GPSSignalWidgetModelState productConnected:self.widgetModel.isProductConnected]]; + [[DUXBetaStateChangeBroadcaster instance] send:[GPSSignalWidgetModelState productConnected:self.widgetModel.isProductConnected]]; } - (void)sendGPSSignalQualityUpdate { - [[DUXStateChangeBroadcaster instance] send:[GPSSignalWidgetModelState gpsSignalQualityUpdate:self.widgetModel.satelliteSignal]]; + [[DUXBetaStateChangeBroadcaster instance] send:[GPSSignalWidgetModelState gpsSignalQualityUpdate:self.widgetModel.satelliteSignal]]; } - (void)sendSatelliteCountUpdate { - [[DUXStateChangeBroadcaster instance] send:[GPSSignalWidgetModelState satelliteCountUpdate:self.widgetModel.satelliteCount]]; + [[DUXBetaStateChangeBroadcaster instance] send:[GPSSignalWidgetModelState satelliteCountUpdate:self.widgetModel.satelliteCount]]; } - (void)sendRTKEnabledUpdate { - [[DUXStateChangeBroadcaster instance] send:[GPSSignalWidgetModelState isRTKEnabledUpdate:self.widgetModel.isRTKEnabled]]; + [[DUXBetaStateChangeBroadcaster instance] send:[GPSSignalWidgetModelState isRTKEnabledUpdate:self.widgetModel.isRTKEnabled]]; } - (void)sendRTKAccurateUpdate { - [[DUXStateChangeBroadcaster instance] send:[GPSSignalWidgetModelState isRTKAccurateUpdate:self.widgetModel.isRTKAccurate]]; + [[DUXBetaStateChangeBroadcaster instance] send:[GPSSignalWidgetModelState isRTKAccurateUpdate:self.widgetModel.isRTKAccurate]]; } - (void)updateWidgetBackground { @@ -310,7 +310,7 @@ - (void)updateCountUI { self.satelliteCountLabel.backgroundColor = self.satelliteCountBackgroundColor; self.satelliteCountLabel.textColor = self.satelliteCountNumberColor; self.satelliteCountLabel.text = [NSString stringWithFormat:@"%ld", (long)self.widgetModel.satelliteCount]; - CGFloat pointSize = self.satelliteCountNumberFont.pointSize * (self.satelliteCountLabel.frame.size.height / self.widgetSizeHint.minimumHeight); + CGFloat pointSize = self.satelliteCountNumberFont.pointSize * (self.view.frame.size.height / self.minWidgetSize.height); self.satelliteCountLabel.font = [self.satelliteCountNumberFont fontWithSize:pointSize]; if (self.widgetModel.isProductConnected) { diff --git a/DJIUXSDKWidgets/GPSSignalWidget/DUXBetaGPSSignalWidgetModel.m b/DJIUXSDKWidgets/GPSSignalWidget/DUXBetaGPSSignalWidgetModel.m index 696f21a..a432d40 100644 --- a/DJIUXSDKWidgets/GPSSignalWidget/DUXBetaGPSSignalWidgetModel.m +++ b/DJIUXSDKWidgets/GPSSignalWidget/DUXBetaGPSSignalWidgetModel.m @@ -61,7 +61,7 @@ - (instancetype)init { self = [super init]; if (self) { self.isRTKEnabled = NO; - self.isUsingExternalGPS = NO; + self.isUsingExternalGPS = NO;//TODO: Actually update this value... self.satelliteSignal = DUXBetaGPSSatelliteStrengthLevel0; self.isRTKAccurate = NO; } diff --git a/DJIUXSDKWidgets/MapWidget/DUXBetaMapWidget.m b/DJIUXSDKWidgets/MapWidget/DUXBetaMapWidget.m index ebf7818..e0b908b 100644 --- a/DJIUXSDKWidgets/MapWidget/DUXBetaMapWidget.m +++ b/DJIUXSDKWidgets/MapWidget/DUXBetaMapWidget.m @@ -835,7 +835,7 @@ - (void)flyZoneColorChanged { } /*********************************************************************************/ -#pragma mark - DUXFlyZoneDataProviderDelegate +#pragma mark - DUXBetaFlyZoneDataProviderDelegate /*********************************************************************************/ - (void)flyZoneDataProvider:(nonnull DUXBetaFlyZoneDataProvider *)flyZoneDataProvider didUpdateFlyZones:(nonnull NSDictionary *)flyZones { diff --git a/DJIUXSDKWidgets/MapWidget/DUXBetaMapWidgetModel.m b/DJIUXSDKWidgets/MapWidget/DUXBetaMapWidgetModel.m index b4fc3b8..8435c5d 100644 --- a/DJIUXSDKWidgets/MapWidget/DUXBetaMapWidgetModel.m +++ b/DJIUXSDKWidgets/MapWidget/DUXBetaMapWidgetModel.m @@ -56,6 +56,8 @@ - (void)inSetup { BindSDKKey([DJIFlightControllerKey keyWithParam:DJIFlightControllerParamAircraftLocation],aircraftLocation); BindSDKKey([DJIFlightControllerKey keyWithParam:DJIFlightControllerParamHomeLocation],homeLocation); BindSDKKey([DJIFlightControllerKey keyWithParam:DJIFlightControllerParamIsHomeLocationSet],isHomeLocationSet); + //TODO Add this once we figure out our communication system for UXSDK + //[DUXBetaKey keyWithParam:DUXParamUserAccountState] } - (void)inCleanup { diff --git a/DJIUXSDKWidgets/MapWidget/MapViews/DUXBetaMapViewLegendViewController.m b/DJIUXSDKWidgets/MapWidget/MapViews/DUXBetaMapViewLegendViewController.m index cf3de60..55bf09f 100644 --- a/DJIUXSDKWidgets/MapWidget/MapViews/DUXBetaMapViewLegendViewController.m +++ b/DJIUXSDKWidgets/MapWidget/MapViews/DUXBetaMapViewLegendViewController.m @@ -218,7 +218,7 @@ - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSe // The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath: - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { DUXBetaMapViewLegendCollectionViewCell *cell = (DUXBetaMapViewLegendCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:DUXBetaMapViewLegendCollectionViewCellReuseIdentifier - forIndexPath:indexPath]; + forIndexPath:indexPath]; cell.titleLabel.text = [self titleForIndexPath:indexPath]; cell.color = [self colorForIndexPath:indexPath]; cell.alpha = [self alphaForIndexPath:indexPath]; diff --git a/DJIUXSDKWidgets/MapWidget/MapViews/DUXBetaMapViewRenderer.m b/DJIUXSDKWidgets/MapWidget/MapViews/DUXBetaMapViewRenderer.m index eee6314..5292c42 100644 --- a/DJIUXSDKWidgets/MapWidget/MapViews/DUXBetaMapViewRenderer.m +++ b/DJIUXSDKWidgets/MapWidget/MapViews/DUXBetaMapViewRenderer.m @@ -270,7 +270,7 @@ - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id DUXPanelWidget { + override public func configure(_ configuration:DUXBetaPanelWidgetConfiguration) -> DUXBetaPanelWidget { self.orientation = configuration.widgetVariant let _ = super.configure(configuration) if self.isConfigured { @@ -579,7 +579,7 @@ typealias LayoutInfoTuple = (widget: DUXBetaBaseWidget, isVisible: Bool, heightC * - Returns: An NSLayoutConstraint for the widthAnchor or heightAnchor with * the computed ratio and given constant. */ - public func spacingConstraint(forOrientation orientation: DUXPanelVariant, + public func spacingConstraint(forOrientation orientation: DUXBetaPanelVariant, andWorkBar workBar: UIView, withSize size: CGSize, andConstant constant: CGFloat) -> NSLayoutConstraint { @@ -592,7 +592,7 @@ typealias LayoutInfoTuple = (widget: DUXBetaBaseWidget, isVisible: Bool, heightC } //MARK: - internal methods - func findBarDimensions(_ bar: UIStackView, orientation: DUXPanelVariant) -> (CGSize, CGFloat) { + func findBarDimensions(_ bar: UIStackView, orientation: DUXBetaPanelVariant) -> (CGSize, CGFloat) { var outSize: CGSize = CGSize(width: 0.0, height: 0.0) var totalHeight: CGFloat = 0.0 var totalWidth: CGFloat = 0.0 diff --git a/DJIUXSDKWidgets/Panels/DUXBetaListItemTrivialSwitchWidget.h b/DJIUXSDKWidgets/Panels/DUXBetaListItemTrivialSwitchWidget.h new file mode 100644 index 0000000..0036af9 --- /dev/null +++ b/DJIUXSDKWidgets/Panels/DUXBetaListItemTrivialSwitchWidget.h @@ -0,0 +1,51 @@ +// +// DUXBetaListItemTrivialSwitchWidget.h +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// + +//#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface DUXBetaListItemTrivialSwitchWidget : DUXBetaListItemSwitchWidget + +-(instancetype)setTitle:(NSString*)titleString andKey:(DJIKey*)theKey; + +@end + +/** + * ListItemTrivalModelUIState contains the hooks for UI changes in the widget class DUXBetaListItemTrivialSwitchWidget. + * It implements the hook: + * + * Key: switchModelValueChanged Type: NSNumber - Sends a boolean value as an NSNumber indicating the new value + * of the switch value when it changes. + * +*/ +@interface ListItemTrivalModelUIState : DUXBetaStateChangeBaseData ++ (instancetype)switchModelValueChanged:(BOOL)isOn; +@end + +NS_ASSUME_NONNULL_END diff --git a/DJIUXSDKWidgets/Panels/DUXBetaListItemTrivialSwitchWidget.m b/DJIUXSDKWidgets/Panels/DUXBetaListItemTrivialSwitchWidget.m new file mode 100644 index 0000000..530812d --- /dev/null +++ b/DJIUXSDKWidgets/Panels/DUXBetaListItemTrivialSwitchWidget.m @@ -0,0 +1,100 @@ +// +// DUXBetaListItemTrivialSwitchWidget.m +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// + +#import "DUXBetaListItemTrivialSwitchWidget.h" +#import "DUXBetaListItemSwitchWidgetModel.h" + +@import DJIUXSDKCore; + +@interface DUXBetaListItemTrivialSwitchWidget () +@property (nonatomic, strong) DUXBetaListItemSwitchWidgetModel *widgetModel; +@property (nonatomic, strong) DJIKey *widgetModelKey; + +@end + +@implementation DUXBetaListItemTrivialSwitchWidget + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view. + if (_widgetModelKey) { + self.widgetModel = [[DUXBetaListItemSwitchWidgetModel alloc] initWithKey:self.widgetModelKey]; + [self.widgetModel setup]; + } +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + BindRKVOModel(self, @selector(valueForSwitchChanged), self.widgetModel.genericBool); + BindRKVOModel(self, @selector(updateEnabledStates), self.widgetModel.isProductConnected); +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + UnBindRKVOModel(self); +} + +- (void)dealloc { + [self.widgetModel cleanup]; +} + +// return instancetype to allow chaining on instantiation +- (instancetype)setTitle:(NSString*)titleString andKey:(DJIKey*)theKey { + // This tears down the old model if it exists and builds a fresh one + _widgetModelKey = theKey; + self.widgetModel = [[DUXBetaListItemSwitchWidgetModel alloc] initWithKey:self.widgetModelKey]; + + __weak DUXBetaListItemTrivialSwitchWidget *weakSelf = self; + [self setSwitchAction:^(BOOL newSwitchValue) { + __strong DUXBetaListItemTrivialSwitchWidget *strongSelf = weakSelf; + [strongSelf.widgetModel toggleSettingWithCompletionBlock:^(NSError *error) { + + }]; + }]; + [self setTitle:titleString andIconName:nil]; + return self; +} + +- (void)valueForSwitchChanged { + [DUXBetaStateChangeBroadcaster send:[ListItemTrivalModelUIState switchModelValueChanged:self.widgetModel.genericBool]]; + [self.onOffSwitch setOn:self.widgetModel.genericBool]; +} + +- (void)updateEnabledStates { + self.onOffSwitch.enabled = self.widgetModel.isProductConnected; +} + + +@end + +@implementation ListItemTrivalModelUIState + ++ (instancetype)switchModelValueChanged:(BOOL)isOn { + return [[self alloc] initWithKey:@"switchModelValueChanged" number:@(isOn)]; +} + +@end diff --git a/DJIUXSDKWidgets/Panels/DUXListPanelWidget.swift b/DJIUXSDKWidgets/Panels/DUXBetaListPanelWidget.swift similarity index 81% rename from DJIUXSDKWidgets/Panels/DUXListPanelWidget.swift rename to DJIUXSDKWidgets/Panels/DUXBetaListPanelWidget.swift index e3fc7ac..14ecab1 100644 --- a/DJIUXSDKWidgets/Panels/DUXListPanelWidget.swift +++ b/DJIUXSDKWidgets/Panels/DUXBetaListPanelWidget.swift @@ -1,9 +1,11 @@ // -// DUXListPanelWidget.swift +// DUXBetaListPanelWidget.swift // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -21,9 +23,8 @@ // 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. -// -// + // The List Panel architecure deviates slightly from normal widget architecture. A normal // UINavigationController architecture has a single navigation controller which is referenced // in each subvue. In order to do that, additional extensive plumbing is required as well as @@ -49,21 +50,21 @@ let kDesignWidthHint: CGFloat = 400.0 let kDesignHeighthHint: CGFloat = 300.0 /** - * DUXListPanelWidget implements the List Pane which descends from the DUXListPanelWidget. It implements a list using + * DUXBetaListPanelWidget implements the List Pane which descends from the DUXBetaListPanelWidget. It implements a list using * UITableView. The list can consist of widgets or strings for making a selection. Widgets are placed into UITableViewCells * for display. - * Individual widgets can adopt the DUXListPanelSupportProtocol to present sublists via the normal detail view mechanism. + * Individual widgets can adopt the DUXBetaListPanelSupportProtocol to present sublists via the normal detail view mechanism. * * List panels provides a title bar area which can contain a close box, title, and back button. These are configured using * the standard configuration object. */ -@objcMembers open class DUXListPanelWidget : DUXPanelWidget, UITableViewDelegate, UITableViewDataSource, UINavigationControllerDelegate { +@objcMembers open class DUXBetaListPanelWidget : DUXBetaPanelWidget, UITableViewDelegate, UITableViewDataSource, UINavigationControllerDelegate { //MARK: - Public Variables - /// Used by sublcasses of DUXListPanelWidget and to know if the model has already been created and setup. + /// Used by sublcasses of DUXBetaListPanelWidget and to know if the model has already been created and setup. var isWidgetModelSetup = false /// The model used by the panel for holding display widgets. May be replaced by subclasses - var model: DUXListPanelWidgetBaseModel = DUXListPanelWidgetBaseModel() - /// The standard widgetSizeHint indicating the minimum size for this widget and prefered aspect ratio. Always returns the same size for DUXListPanelWidget. Atual size is controlled by constrains on class added to parent view. + var model: DUXBetaListPanelWidgetBaseModel = DUXBetaListPanelWidgetBaseModel() + /// The standard widgetSizeHint indicating the minimum size for this widget and prefered aspect ratio. Always returns the same size for DUXBetaListPanelWidget. Atual size is controlled by constrains on class added to parent view. public override var widgetSizeHint : DUXBetaWidgetSizeHint { get { return DUXBetaWidgetSizeHint(preferredAspectRatio: kDesignWidthHint/kDesignHeighthHint, minimumWidth: kDesignWidthHint, minimumHeight: kDesignHeighthHint)} set { @@ -71,7 +72,7 @@ let kDesignHeighthHint: CGFloat = 300.0 } //MARK: - Private Variables - fileprivate var listType: DUXListType = .none + fileprivate var listType: DUXBetaListType = .none // Tabel Support fileprivate var tableView = UITableView() @@ -82,7 +83,7 @@ let kDesignHeighthHint: CGFloat = 300.0 // Used by the root list widget only - fileprivate var viewControllerStack: [(DUXListPanelWidget, NSLayoutConstraint?)]? // This only exists for the base rootViewController. Widget and left edge constraint + fileprivate var viewControllerStack: [(DUXBetaListPanelWidget, NSLayoutConstraint?)]? // This only exists for the base rootViewController. Widget and left edge constraint // Special setup value for .selectOne and .selectOneAndReturn lists to show an existing selectiin fileprivate var hasInitialSelection = false @@ -94,7 +95,7 @@ let kDesignHeighthHint: CGFloat = 300.0 fileprivate var setupUIDone = false //This may need to either move up in the panels, or become a pattern for all widgets // SmartModel support - fileprivate var smartModelCarrier: DUXSmartListModel? + fileprivate var smartModelCarrier: DUXBetaSmartListModel? fileprivate var hasSmartModel = false fileprivate var initPhaseDone = false @@ -121,7 +122,7 @@ let kDesignHeighthHint: CGFloat = 300.0 * * - Parameter smartModel: The SmartModel object which will be used to add and remove widgets/options from the list. */ - public init(smartModel: DUXSmartListModel) { + public init(smartModel: DUXBetaSmartListModel) { super.init() setupTableVisuals() let _ = setupSmartModel(smartModel) @@ -132,7 +133,7 @@ let kDesignHeighthHint: CGFloat = 300.0 * * - Parameter variant: The variant defines what types of items the list accepts */ - public init(variant: DUXPanelVariant) { + public init(variant: DUXBetaPanelVariant) { super.init() setupTableVisuals() } @@ -149,7 +150,7 @@ let kDesignHeighthHint: CGFloat = 300.0 * * - Returns: false if setup is rejected because init phase is complete, true if smart model was set. */ - public func setupSmartModel(_ smartModel: DUXSmartListModel) -> Bool { + public func setupSmartModel(_ smartModel: DUXBetaSmartListModel) -> Bool { if initPhaseDone { return false } @@ -175,19 +176,19 @@ let kDesignHeighthHint: CGFloat = 300.0 * The method createStandardModel creates and returns the standard model for containing the widgets to be shown in the list. * This model will be used by the SmartModel if one is used. * - * - returns: a new DUXListPanelWidgetBaseModel. + * - returns: a new DUXBetaListPanelWidgetBaseModel. */ - public func createStandardModel() -> DUXListPanelWidgetBaseModel { - return DUXListPanelWidgetBaseModel() + public func createStandardModel() -> DUXBetaListPanelWidgetBaseModel { + return DUXBetaListPanelWidgetBaseModel() } /** * The method createStringModel creates and returns the standard model for containing string to be shown in an options list. * - * - returns: a new DUXListPanelWidgetStringsModel. + * - returns: a new DUXBetaListPanelWidgetStringsModel. */ - public func createStringsModel() -> DUXListPanelWidgetBaseModel { - return DUXListPanelWidgetStringsModel() + public func createStringsModel() -> DUXBetaListPanelWidgetBaseModel { + return DUXBetaListPanelWidgetStringsModel() } /** @@ -211,7 +212,7 @@ let kDesignHeighthHint: CGFloat = 300.0 } break case .selectOne, .selectOneAndReturn: - if let _ = self.model as? DUXListPanelWidgetStringsModel { + if let _ = self.model as? DUXBetaListPanelWidgetStringsModel { // The type was already correct somehow. Don't do anything } else { self.model = createStringsModel() @@ -306,7 +307,7 @@ let kDesignHeighthHint: CGFloat = 300.0 func isTopListWidget() -> Bool { var currentCheckController: UIViewController? = self.parent while currentCheckController != nil { - if currentCheckController!.isMember(of:DUXListPanelWidget.self) { + if currentCheckController!.isKind(of:DUXBetaListPanelWidget.self) { return false } currentCheckController = currentCheckController?.parent @@ -319,20 +320,20 @@ let kDesignHeighthHint: CGFloat = 300.0 * Method topListWidget walks the view controller hierarchy and returns the root list panel widget in the current * list stack. * - * - Returns: DUXListPanelWidget at the root of the list panel hierarchy. + * - Returns: DUXBetaListPanelWidget at the root of the list panel hierarchy. */ - func topListWidget() -> DUXListPanelWidget { - var currentCheckController = self.parent as? DUXListPanelWidget + func topListWidget() -> DUXBetaListPanelWidget { + var currentCheckController = self.parent as? DUXBetaListPanelWidget while currentCheckController != nil { - if currentCheckController!.isMember(of:DUXListPanelWidget.self) { + if currentCheckController!.isMember(of:DUXBetaListPanelWidget.self) { if currentCheckController?.isRootViewController == true { return currentCheckController! } } - currentCheckController = currentCheckController?.parent as? DUXListPanelWidget + currentCheckController = currentCheckController?.parent as? DUXBetaListPanelWidget } - assert(false, "No rootViewController in DUXListPanelWidget!") - return DUXListPanelWidget() + assert(false, "No rootViewController in DUXBetaListPanelWidget!") + return DUXBetaListPanelWidget() } @@ -342,7 +343,7 @@ let kDesignHeighthHint: CGFloat = 300.0 * * - Returns: the list panel after configuration. */ - @objc override public func configure(_ configuration:DUXPanelWidgetConfiguration) -> DUXPanelWidget { + @objc override public func configure(_ configuration:DUXBetaPanelWidgetConfiguration) -> DUXBetaPanelWidget { listType = configuration.widgetListType let configOverride = configuration configOverride.showTitleBar = true @@ -351,10 +352,10 @@ let kDesignHeighthHint: CGFloat = 300.0 case .widgets, .widgetNames: break case .selectOne, .selectOneAndReturn: - if let _ = self.model as? DUXListPanelWidgetStringsModel { + if let _ = self.model as? DUXBetaListPanelWidgetStringsModel { } else { - self.model = DUXListPanelWidgetStringsModel() + self.model = DUXBetaListPanelWidgetStringsModel() } break @@ -408,7 +409,7 @@ let kDesignHeighthHint: CGFloat = 300.0 tableView.reloadData() self.updateUI() } - + /** * The method insert adds a single widget into the list panel after any existing widgets. Also updates the * internal model and automatically calls updateUI. @@ -423,6 +424,7 @@ let kDesignHeighthHint: CGFloat = 300.0 if hasSmartModel { return } + model.insertWidget(inWidget: widget, at: atIndex) tableView.reloadData() self.updateUI() @@ -471,7 +473,7 @@ let kDesignHeighthHint: CGFloat = 300.0 if hasSmartModel { return } - if let m = model as? DUXListPanelWidgetStringsModel { + if let m = model as? DUXBetaListPanelWidgetStringsModel { m.setOptionStrings(stringArray) } } @@ -489,7 +491,7 @@ let kDesignHeighthHint: CGFloat = 300.0 if hasSmartModel { return } - if let m = model as? DUXListPanelWidgetStringsModel { + if let m = model as? DUXBetaListPanelWidgetStringsModel { m.insertString(inString: optionString, at: atIndex) tableView.reloadData() self.updateUI() @@ -505,7 +507,7 @@ let kDesignHeighthHint: CGFloat = 300.0 if hasSmartModel { return } - if let m = model as? DUXListPanelWidgetStringsModel { + if let m = model as? DUXBetaListPanelWidgetStringsModel { m.removeWidget(atIndex: atIndex) tableView.reloadData() self.updateUI() @@ -521,7 +523,7 @@ let kDesignHeighthHint: CGFloat = 300.0 if hasSmartModel { return } - if let m = model as? DUXListPanelWidgetStringsModel { + if let m = model as? DUXBetaListPanelWidgetStringsModel { m.removeAllWidgets() tableView.reloadData() self.updateUI() @@ -587,14 +589,14 @@ let kDesignHeighthHint: CGFloat = 300.0 } // MARK: - List Hierarchy Utilities - func listRootViewControler() -> DUXListPanelWidget? { + func listRootViewControler() -> DUXBetaListPanelWidget? { if self.isRootViewController { return self } var testWidget = self.parent while testWidget != nil { - if let panelWidget = testWidget as? DUXListPanelWidget { + if let panelWidget = testWidget as? DUXBetaListPanelWidget { if panelWidget.isRootViewController { return panelWidget } @@ -606,7 +608,7 @@ let kDesignHeighthHint: CGFloat = 300.0 //MARK: - Internal methods - func previousListController(for target: DUXListPanelWidget) -> DUXListPanelWidget? { + func previousListController(for target: DUXBetaListPanelWidget) -> DUXBetaListPanelWidget? { if target == self && self.isRootViewController { return nil } else { @@ -614,7 +616,7 @@ let kDesignHeighthHint: CGFloat = 300.0 } } - func internalPreviousListController(for target: DUXListPanelWidget) -> DUXListPanelWidget? { + func internalPreviousListController(for target: DUXBetaListPanelWidget) -> DUXBetaListPanelWidget? { for i in 0...self.viewControllerStack!.count-1 { let tuple = self.viewControllerStack![i] if tuple.0 == target { @@ -627,7 +629,7 @@ let kDesignHeighthHint: CGFloat = 300.0 return nil } - func addChildList(_ nextList: DUXListPanelWidget) { + func addChildList(_ nextList: DUXBetaListPanelWidget) { // Two ways to do this. Add as child list to this parent, or add as a child list to the root. if self.isRootViewController { @@ -659,7 +661,7 @@ let kDesignHeighthHint: CGFloat = 300.0 } // This method must only be called on the rootview - func internalConstraintLookup(for target: DUXListPanelWidget) -> NSLayoutConstraint? { + func internalConstraintLookup(for target: DUXBetaListPanelWidget) -> NSLayoutConstraint? { for (controller, constraint) in self.viewControllerStack! { if controller == target { return constraint @@ -668,8 +670,8 @@ let kDesignHeighthHint: CGFloat = 300.0 return nil } - func internalSetEdgeConstraint(for target: DUXListPanelWidget, newConstraint: NSLayoutConstraint) { - for i in 1...self.viewControllerStack!.count { + func internalSetEdgeConstraint(for target: DUXBetaListPanelWidget, newConstraint: NSLayoutConstraint) { + for i in 0 ..< self.viewControllerStack!.count { let tuple = self.viewControllerStack![i] if tuple.0 == target { self.viewControllerStack![i] = (tuple.0, newConstraint) @@ -678,14 +680,14 @@ let kDesignHeighthHint: CGFloat = 300.0 } } - func edgeConstraint(for widget: DUXListPanelWidget) -> NSLayoutConstraint? { + func edgeConstraint(for widget: DUXBetaListPanelWidget) -> NSLayoutConstraint? { if let root = self.listRootViewControler() { return root.internalConstraintLookup(for: widget) } return nil } - func setEdgeConstraint(for widget: DUXListPanelWidget, newConstraint: NSLayoutConstraint) { + func setEdgeConstraint(for widget: DUXBetaListPanelWidget, newConstraint: NSLayoutConstraint) { if let root = self.listRootViewControler() { return root.internalSetEdgeConstraint(for: widget, newConstraint: newConstraint) } @@ -741,6 +743,8 @@ let kDesignHeighthHint: CGFloat = 300.0 } else { super.closeTapped() } + + popViewControllerStack() } @IBAction override func backButtonTapped() { @@ -786,7 +790,7 @@ let kDesignHeighthHint: CGFloat = 300.0 } /** - * Standard protocol method for getting the display cell for a row in the list. Creates a DUXWidgetCell instance with the + * Standard protocol method for getting the display cell for a row in the list. Creates a DUXBetaWidgetCell instance with the * widget inserted inside and setup with autolayout. * * - Returns: UITableViewCell for display for a particular row. @@ -800,7 +804,7 @@ let kDesignHeighthHint: CGFloat = 300.0 if let testCell = widgetToCell[workWidget] { widgetCell = testCell } else { - widgetCell = DUXWidgetCell(widget: workWidget) + widgetCell = DUXBetaWidgetCell(widget: workWidget) widgetToCell[workWidget] = widgetCell } break @@ -812,7 +816,7 @@ let kDesignHeighthHint: CGFloat = 300.0 widgetCell?.backgroundColor = .duxbeta_clear() widgetCell?.textLabel?.textColor = .duxbeta_white() } - if let workModel = self.model as? DUXListPanelWidgetStringsModel { + if let workModel = self.model as? DUXBetaListPanelWidgetStringsModel { widgetCell?.textLabel?.text = workModel.optionString(at: indexPath.row) } else { widgetCell?.textLabel?.text = "Missting Option String" @@ -844,11 +848,11 @@ let kDesignHeighthHint: CGFloat = 300.0 default: let workWidget = model.widget(at:indexPath.row) - if let protocolWidget = workWidget as? DUXListPanelSupportProtocol { + if let protocolWidget = workWidget as? DUXBetaListPanelSupportProtocol { if let hasDetailList = protocolWidget.hasDetailList { if hasDetailList() { if let sublistType = protocolWidget.detailListType { - if (sublistType() != DUXListType.none) { + if (sublistType() != DUXBetaListType.none) { if (sublistType() == .selectOne) { } @@ -874,21 +878,23 @@ let kDesignHeighthHint: CGFloat = 300.0 case .widgets, .widgetNames: tableView.deselectRow(at: indexPath, animated: true) let widget = self.model.widget(at:indexPath.row) - if let protocolWidget = widget as? DUXListPanelSupportProtocol { + if let protocolWidget = widget as? DUXBetaListPanelSupportProtocol { if let hasDetailList = protocolWidget.hasDetailList, hasDetailList() { if let sublistType = protocolWidget.detailListType?() { if (sublistType == .selectOne) || (sublistType == .selectOneAndReturn) { + //TODO: Alow widget to set title. let _ = self.listRootViewControler()?.panelTitle ?? "" let _ = protocolWidget.selectionUpdate ?? nil - let pushList = DUXListPanelWidget() + let pushList = DUXBetaListPanelWidget() - let _ = pushList.configure(DUXPanelWidgetConfiguration(type:.list, listKind:sublistType) - .configureTitlebar(visible: true, withCloseBox: true, withBackButton: true, title: protocolWidget.detailsTitle?() ?? "Second List")) + let _ = pushList.configure(DUXBetaPanelWidgetConfiguration(type:.list, listKind:sublistType) + .configureTitlebar(visible: true, withCloseBox: true, withBackButton: true, title: protocolWidget.detailsTitle?() ?? title ?? "") + . configureColors(background: self.panelBackgroundColor, border:self.panelBorderColor, titleBarBackground:self.titlebar.backgroundColor ?? .duxbeta_listPanelBackground() )) + // This is a List specfic hack to prevent obscuring the existing title // while this list is pushed in pushList.titlebar.backgroundColor = .duxbeta_black() - let optionDict:[String: Any] = protocolWidget.oneOfListOptions!() if let stringArray = optionDict["list"] as? [String] { @@ -902,14 +908,34 @@ let kDesignHeighthHint: CGFloat = 300.0 } self.addChildList(pushList) + animateSublistIn(pushList) + } else if (sublistType == .widgets) || (sublistType == .widgetNames) { + var childWidgets: [DUXBetaBaseWidget]? + if (sublistType == .widgets) { + childWidgets = protocolWidget.listOfSubwidgets?() + } else { + // Convert the childWidgetNames into an actual list of widgets + if let childWidgetNames = protocolWidget.listOfSubwidgetNames?() { + childWidgets = [DUXBetaBaseWidget]() + for className in childWidgetNames { + let classInst = NSClassFromString(className) as? NSObject.Type + if let widget = classInst?.init() as? DUXBetaBaseWidget { + childWidgets?.append(widget) + } + } + } + } - DispatchQueue.main.asyncAfter(deadline: .now()+0.01) { [weak self] in - pushList.animateIn() - UIView.animate(withDuration: 0.3, delay: 0.01, options: .curveLinear, animations: { [weak self] in - self?.view.layoutIfNeeded() - }, completion: {complete in - pushList.showTitleBar() - }) + if let childWidgets = childWidgets { + + let pushList = DUXBetaListPanelWidget() + let _ = pushList.configure(DUXBetaPanelWidgetConfiguration(type:.list, listKind:sublistType) + .configureTitlebar(visible: true, withCloseBox: true, withBackButton: true, title: protocolWidget.detailsTitle?() ?? title ?? "") + .configureColors(background: self.panelBackgroundColor, border:self.panelBorderColor, titleBarBackground:self.titlebar.backgroundColor ?? .duxbeta_listPanelBackground() )) + pushList.addWidgetArray(childWidgets) + + self.addChildList(pushList) + animateSublistIn(pushList) } } } @@ -930,9 +956,20 @@ let kDesignHeighthHint: CGFloat = 300.0 break } } + + private func animateSublistIn(_ pushList: DUXBetaListPanelWidget) { + DispatchQueue.main.asyncAfter(deadline: .now()+0.01) { [weak self] in + pushList.animateIn() + UIView.animate(withDuration: 0.3, delay: 0.01, options: .curveLinear, animations: { [weak self] in + self?.view.layoutIfNeeded() + }, completion: {complete in + pushList.showTitleBar() + }) + } + } } -class DUXWidgetCell : UITableViewCell { +class DUXBetaWidgetCell : UITableViewCell { init(widget: DUXBetaBaseWidget) { super.init(style: .`default`, reuseIdentifier: nil) self.selectionStyle = .none @@ -941,11 +978,11 @@ class DUXWidgetCell : UITableViewCell { var hasDetailList = false var forceAspect = true - if let test = widget as? DUXListItemTitleWidget { + if let test = widget as? DUXBetaListItemTitleWidget { // This class supports a method to indicate if it must be proportional or not forceAspect = test.forceAspectRatio() } - if let test = widget as? DUXListPanelSupportProtocol { + if let test = widget as? DUXBetaListPanelSupportProtocol { if let hasDetailMethod = test.hasDetailList { hasDetailList = hasDetailMethod() } @@ -966,7 +1003,7 @@ class DUXWidgetCell : UITableViewCell { widget.view.centerYAnchor.constraint(equalTo:backView.centerYAnchor).isActive = true widget.view.leadingAnchor.constraint(equalTo:backView.leadingAnchor).isActive = true widget.view.widthAnchor.constraint(equalTo:backView.widthAnchor).isActive = true - + // TODO: Debug why addding a height anchor causes the entire cell height to collapse. if forceAspect { widget.view.heightAnchor.constraint(equalTo:widget.view.widthAnchor, multiplier:1.0/sizeHint.preferredAspectRatio).isActive = true } else { @@ -975,8 +1012,7 @@ class DUXWidgetCell : UITableViewCell { if hasDetailList { self.accessoryType = .disclosureIndicator - self.accessoryView = UIImageView(image: UIImage.duxbeta_image(withAssetNamed:"PanelBackArrow")) - self.accessoryView?.transform = CGAffineTransform(scaleX: -1, y: 1) + self.accessoryView = UIImageView(image: UIImage.duxbeta_image(withAssetNamed:"PanelNextArrow")) } } @@ -984,4 +1020,5 @@ class DUXWidgetCell : UITableViewCell { required public init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + } diff --git a/DJIUXSDKWidgets/Panels/DUXListPanelWidgetBaseModel.swift b/DJIUXSDKWidgets/Panels/DUXBetaListPanelWidgetBaseModel.swift similarity index 50% rename from DJIUXSDKWidgets/Panels/DUXListPanelWidgetBaseModel.swift rename to DJIUXSDKWidgets/Panels/DUXBetaListPanelWidgetBaseModel.swift index 0a7a556..491762b 100644 --- a/DJIUXSDKWidgets/Panels/DUXListPanelWidgetBaseModel.swift +++ b/DJIUXSDKWidgets/Panels/DUXBetaListPanelWidgetBaseModel.swift @@ -1,9 +1,11 @@ // -// DUXListPanelWidgetBaseModel.swift +// DUXBetaListPanelWidgetBaseModel.swift // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -25,66 +27,116 @@ import Foundation -@objc open class DUXListPanelWidgetBaseModel : DUXBetaBaseWidgetModel { +/** + * DUXBetaListPanelWidgetBaseModel is the base widget model for DUXBetaListPanelWidgets. It contains the widgets to display in the ListPanel. + */ +@objc open class DUXBetaListPanelWidgetBaseModel : DUXBetaBaseWidgetModel { + // MARK: - Public Variables + /// The number of widgets contained in this model @objc dynamic var count: Int { get { return widgetList.count } // No setter as this is a dynamic read only property } - fileprivate var smartModel: DUXSmartListModel? = nil + // The widgetList array will be replaced every time a change is made. That allows us to receive + // a KVO notification and update the UI + @objc dynamic public var widgetList: [DUXBetaBaseWidget] = [DUXBetaBaseWidget]() + + // MARK: - Private Variables + // Internal variable used to support SmartListModels + fileprivate var smartModel: DUXBetaSmartListModel? = nil - @objc open func set(smartModel: DUXSmartListModel) { + // MARK: - Public Methods + /** + * The method setSmartModel is used to add a SmartListModel into this model when it is first created. + * + * - Parameter smartModel: The DUXBetaSmartListModel to be used to changes this list for a normal ListPanel to a SmartListPanel + */ + @objc open func set(smartModel: DUXBetaSmartListModel) { self.smartModel = smartModel self.smartModel?.setListPanelModel(self) } - // This list will be replaced every time a change is made. That allows us to receive - // a KVO notification and update the UI - @objc dynamic public var widgetList: [DUXBetaBaseWidget] = [DUXBetaBaseWidget]() - + /** + * The method setup is an override of the setup method for the DUXBetaBaseModelWidget which also sets up the smartModel if it exists. + */ @objc open override func setup() { super.setup() self.smartModel?.setup() } + /** + * The method setup is an override of the cleanup method for the DUXBetaBaseModelWidget which also cleans up the smartModel if it exists. + */ @objc open override func cleanup() { super.cleanup() self.smartModel?.cleanup() } + /** + * The method setWidgetsArray is used to set all the widgets to be displayed in the ListPanel if this is not a SmartListPanel. + * + * - Parameter inWidgetList: An array of DUXBetaBaseWidgets to set or replace the existing widgets in this model. + */ @objc func setWidgetsArray(_ inWidgetList: [DUXBetaBaseWidget]) { self.widgetList = [DUXBetaBaseWidget](inWidgetList) } + /** + * The method addWidgetsArray is used to add widgets to be displayed in the ListPanel if this is not a SmartListPanel. + * + * - Parameter inWidgetList: An array of DUXBetaBaseWidgets to be added to the existing widgets in the model. + */ @objc func addWidgetsArray(_ inWidgetList: [DUXBetaBaseWidget]) { var workList = self.widgetList workList.append(contentsOf:inWidgetList) self.widgetList = workList } + /** + * The method addWidget appends a single widget to be displayed in the ListPanel if this is not a SmartListPanel. + * + * - Parameter inWidget: A widget to be added to the existing widgets in the model. + */ @objc func addWidget(_ inWidget: DUXBetaBaseWidget) { var workList = self.widgetList workList.append(inWidget) self.widgetList = workList } + /** + * The method insertWidget inserts a single widget to be displayed in the ListPanel at the given index. + * + * - Parameter inWidget: A widget to be added to the existing widgets in the model. + * - Parameter at: The location where the widget is to be inserted. + */ @objc func insertWidget(inWidget: DUXBetaBaseWidget, at index:Int) { var workList = self.widgetList workList.insert(inWidget, at: index) self.widgetList = workList } + /** + * The method removeWidget removes a single widget from the list of widgets. + * + * - Parameter atIndex: The index in the widget list to remove the widget from. + */ @objc func removeWidget(atIndex: Int) { var workList = self.widgetList workList.remove(at: atIndex) self.widgetList = workList } + /** + * The method removeAllWidgets removes all the widgets from the widget list, leaving an empty list. + */ @objc func removeAllWidgets() { self.widgetList = [DUXBetaBaseWidget]() } - + /** + * The method widget is used to return the widget at a given index in the widget model list. + */ @objc func widget(at index: Int) -> DUXBetaBaseWidget { return widgetList[index] } diff --git a/DJIUXSDKWidgets/Panels/DUXListPanelWidgetStringsModel.swift b/DJIUXSDKWidgets/Panels/DUXBetaListPanelWidgetStringsModel.swift similarity index 58% rename from DJIUXSDKWidgets/Panels/DUXListPanelWidgetStringsModel.swift rename to DJIUXSDKWidgets/Panels/DUXBetaListPanelWidgetStringsModel.swift index 6da8330..c52929a 100644 --- a/DJIUXSDKWidgets/Panels/DUXListPanelWidgetStringsModel.swift +++ b/DJIUXSDKWidgets/Panels/DUXBetaListPanelWidgetStringsModel.swift @@ -1,9 +1,11 @@ // -// DUXListPanelWidgetStringsModel.swift +// DUXBetaListPanelWidgetStringsModel.swift // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -25,38 +27,75 @@ import Foundation -@objc open class DUXListPanelWidgetStringsModel : DUXListPanelWidgetBaseModel { - +/** + * DUXBetaListPanelWidgetStringsModel subclasses DUXBetaListPanelWidgetBaseModel to support strings for lists of options. +*/ +@objc open class DUXBetaListPanelWidgetStringsModel : DUXBetaListPanelWidgetBaseModel { + // MARK: - Public Variables + /// The nuber of strings contained in this model @objc dynamic override var count: Int { get { return optionStrings.count } // No setter as this is a dynamic read only property } + + // An array of strings available in this model public var optionStrings: [String] = [] + /** + * The method setOptionStrings replaces any existing string list with a new array of strings for the model. + * + * - Parameter stringList: An array of new strings for use in the model. + */ @objc func setOptionStrings(_ stringList: [String]) { optionStrings = stringList } + + /** + * The method addString appends a new string to the list of strings containd in the model. + * + * - Parameter inString: The new string to add to the model strings list. + */ @objc func addString(_ inString: String) { var workList = self.optionStrings workList.append(inString) self.optionStrings = workList } + + /** + * The method insertString inserts a new string at a specified position in the existing strings list. + * + * - Parameter inString: The new string to add to the strings list. + * - Parameter: at: - The index to insert the new string into. + */ @objc func insertString(inString: String, at index:Int) { var workList = self.optionStrings workList.insert(inString, at: index) self.optionStrings = workList } + /** + * The method removeString removes the string at the specified index from the strings list. + * + * - Parameter atIndex: The index to remove the string from. + */ @objc func removeString(atIndex: Int) { var workList = self.optionStrings workList.remove(at: atIndex) self.optionStrings = workList } + /** + * The method removeAllStrings removes all the strings from the string list in the model. + */ @objc func removeAllStrings() { self.optionStrings = [String]() } + /** + * The method optionString returns the string from the specified index. + * + * - Parameter at: The index to return the option string from. + */ @objc func optionString(at index: Int) -> String { return optionStrings[index] } diff --git a/DJIUXSDKWidgets/Panels/DUXPanelWidget.swift b/DJIUXSDKWidgets/Panels/DUXBetaPanelWidget.swift similarity index 83% rename from DJIUXSDKWidgets/Panels/DUXPanelWidget.swift rename to DJIUXSDKWidgets/Panels/DUXBetaPanelWidget.swift index 1c000e9..6213786 100644 --- a/DJIUXSDKWidgets/Panels/DUXPanelWidget.swift +++ b/DJIUXSDKWidgets/Panels/DUXBetaPanelWidget.swift @@ -1,9 +1,11 @@ // -// DUXPanelWidget.swift +// DUXBetaPanelWidget.swift // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -26,22 +28,22 @@ import Foundation /** - * DUXPanelType is an enum used to define/identify the style of a panel + * DUXBetaPanelType is an enum used to define/identify the style of a panel * Current values are: bar, toobar, or list */ -@objc public enum DUXPanelType: Int { +@objc public enum DUXBetaPanelType: Int { case bar = 0 case toolbar case list } /** - * DUXPanelVariant defines positoning and layout for bar and toobar panels. + * DUXBetaPanelVariant defines positoning and layout for bar and toobar panels. * Current values: * horizontal, vertical - The orientation for a bar panel * top, left, right - The edge tools icons/labels for a toobar panel will appear on */ -@objc public enum DUXPanelVariant: Int { +@objc public enum DUXBetaPanelVariant: Int { case horizontal = 0 case vertical case top @@ -50,12 +52,12 @@ import Foundation } /** - DUXPanelTitleBarAligmnet defines the positioning of the title in the titlebar for - DUXListPanelWidget and DUXToolbarPanelWidget. + DUXBetaPanelTitleBarAligmnet defines the positioning of the title in the titlebar for + DUXBetaListPanelWidget and DUXBetaToolbarPanelWidget. center - The title is horizontally centered. leading - The title is aligned to the leading edge of the toolbar with spacing always left for the back button whch may be shown. */ -@objc public enum DUXPanelTitleBarAlignment: Int { +@objc public enum DUXBetaPanelTitleBarAlignment: Int { case center = 0 case leading } @@ -67,7 +69,7 @@ let kCloseBoxHeightDefault: CGFloat = 24.0 let kCloseBoxWidthDefault: CGFloat = 24.0 /** - * DUXPanelWidgetConfiguration - This class is used to define configuration options for panels in the DUXListPanel cluster. + * DUXBetaPanelWidgetConfiguration - This class is used to define configuration options for panels in the DUXBetaListPanel cluster. * There are two init methods for list and non-list panel types and three configuration methods used to prepare the configuration * object. * @@ -75,16 +77,16 @@ let kCloseBoxWidthDefault: CGFloat = 24.0 * passing in the configuration object. * * Initialization Methods: - * init(type: DUXPanelType, variant: DUXPanelVariant) - This method starts configuration of a Bar or Toolbar panel. - * init(type: DUXPanelType, listKind: DUXListType) - This method starts configuration of a List panel. + * init(type: DUXBetaPanelType, variant: DUXBetaPanelVariant) - This method starts configuration of a Bar or Toolbar panel. + * init(type: DUXBetaPanelType, listKind: DUXBetaListType) - This method starts configuration of a List panel. * * Configuration Setup methods * configureColors - Sets the colors which will be used for drawing elements of the panel. * configureTitlebar - Defines the titlebar information for List and Toolbar panels. * configureToolbar - Defines the size of icons to be shown in the Toolbar panel tools list. */ -@objcMembers open class DUXPanelWidgetConfiguration : NSObject { - public var widgetVariant: DUXPanelVariant = .horizontal +@objcMembers open class DUXBetaPanelWidgetConfiguration : NSObject { + public var widgetVariant: DUXBetaPanelVariant = .horizontal public var panelToolbarColor = UIColor.duxbeta_lightGray() // MARK: - Internal configuration settings @@ -93,8 +95,8 @@ let kCloseBoxWidthDefault: CGFloat = 24.0 * The general configuration settings for type of panel and if a list panel, which type of panel. * */ - var widgetType: DUXPanelType = .list - var widgetListType: DUXListType = .none + var widgetType: DUXBetaPanelType = .list + var widgetListType: DUXBetaListType = .none // Title bar configuration settings var showTitleBar = false @@ -106,7 +108,7 @@ let kCloseBoxWidthDefault: CGFloat = 24.0 var titlebarHeight: CGFloat = 32.0 var titleBarFont: UIFont? var titlebar: UIView? - var titleBarAlignment: DUXPanelTitleBarAlignment = .center + var titleBarAlignment: DUXBetaPanelTitleBarAlignment = .center // The specific ToolbarPanel configuration settings var toolbarDimension: CGFloat = 44.0 // Default height along top, or width for edges. @@ -117,28 +119,28 @@ let kCloseBoxWidthDefault: CGFloat = 24.0 var toolbarTintColor = UIColor.duxbeta_white() /** - * Initializes the configuration object for use with DUXBarPanel and DUXToolbarPanel. + * Initializes the configuration object for use with DUXBetaBarPanel and DUXBetaToolbarPanel. * * - Parameters: * - type: The type of panel to be configured. * - variant: The positioning style of the panel being configured. * */ - public init(type: DUXPanelType, variant: DUXPanelVariant) { + public init(type: DUXBetaPanelType, variant: DUXBetaPanelVariant) { widgetType = type widgetVariant = variant super.init() } /** - * Initializes the configuration object for use with DUXBarPanel and DUXToolbarPanel. + * Initializes the configuration object for use with DUXBetaBarPanel and DUXBetaToolbarPanel. * * - Parameters: * - type: The type of panel to be configured. * - listKind: The style of the list to be displayed which defines how list items are added. * */ - public init(type: DUXPanelType, listKind: DUXListType) { + public init(type: DUXBetaPanelType, listKind: DUXBetaListType) { widgetType = type widgetListType = listKind super.init() @@ -149,13 +151,13 @@ let kCloseBoxWidthDefault: CGFloat = 24.0 * are supplied for omitted parameters. * * - Parameters: - * - background: The background color for the panel. Defaults to duxbeta_black. - * - border:The border color to be drawn around the panel. Defaults to duxbeta_clear. + * - background: The background color for the panel. Defaults to DUXBeta_black. + * - border:The border color to be drawn around the panel. Defaults to DUXBeta_clear. * - title: The color to draw the title in the titlebar. Defaults to dex_white. - * - titleBarBackground: The color to use for the background of the titlebar. Defaults to duxbeta_black. - * - selection: Selection color to use for selections in panels. Only used in Toolbar panels. Defaults to duxbeta_darkGray. - * - toolbarBackground: The background color for the tool icons Toolbar panels. Defaults to duxbeta_black. - * - toolbarTint: The tint color to use for colorizing the selected tool in Toolbar panels. Currently unused. Defaults to duxbeta_white. + * - titleBarBackground: The color to use for the background of the titlebar. Defaults to DUXBeta_black. + * - selection: Selection color to use for selections in panels. Only used in Toolbar panels. Defaults to DUXBeta_darkGray. + * - toolbarBackground: The background color for the tool icons Toolbar panels. Defaults to DUXBeta_black. + * - toolbarTint: The tint color to use for colorizing the selected tool in Toolbar panels. Currently unused. Defaults to DUXBeta_white. * * - Returns: A configuration object suitable for chaining or passing to the panel configure method. */ @@ -165,7 +167,7 @@ let kCloseBoxWidthDefault: CGFloat = 24.0 titleBarBackground: UIColor = .duxbeta_black(), selection: UIColor = .duxbeta_darkGray(), toolbarBackground: UIColor = .duxbeta_black(), - toolbarTint : UIColor = .duxbeta_white()) -> DUXPanelWidgetConfiguration { + toolbarTint : UIColor = .duxbeta_white()) -> DUXBetaPanelWidgetConfiguration { panelBackgroundColor = background panelBorderColor = border panelSelectionColor = selection @@ -194,7 +196,7 @@ let kCloseBoxWidthDefault: CGFloat = 24.0 withBackButton: Bool = false, title: String = "", titleHeight: CGFloat = 32.0, - titleAlignment: DUXPanelTitleBarAlignment = .center) -> DUXPanelWidgetConfiguration { + titleAlignment: DUXBetaPanelTitleBarAlignment = .center) -> DUXBetaPanelWidgetConfiguration { showTitleBar = visible hasCloseBox = withCloseBox hasBackButton = withBackButton @@ -212,32 +214,32 @@ let kCloseBoxWidthDefault: CGFloat = 24.0 * * - Returns: A configuration object suitable for chaining or passing to the panel configure method. */ - public func configureToolbar(dimension: CGFloat) -> DUXPanelWidgetConfiguration { + public func configureToolbar(dimension: CGFloat) -> DUXBetaPanelWidgetConfiguration { self.toolbarDimension = dimension return self } } /** - * DUXPanelWidget is the base class for the cluster of panel classes in the DJI UXSDK. It defines the common methods for adding + * DUXBetaPanelWidget is the base class for the cluster of panel classes in the DJI UXSDK. It defines the common methods for adding * and removing widgets from a panel and common configuraton code. */ -@objcMembers open class DUXPanelWidget : DUXBetaBaseWidget { +@objcMembers open class DUXBetaPanelWidget : DUXBetaBaseWidget { // MARK: - Public Variables /// Has this panel finished configuration yet public var isConfigured = false /// The title of the panel in the titlebar for List and Toolbar panels public var panelTitle = "" - /// The color for the background of the panel. Defaults to duxbeta_black + /// The color for the background of the panel. Defaults to DUXBeta_black public var panelBackgroundColor = UIColor.duxbeta_black() - /// The color for the border of the panel. Defaults to duxbeta_clear (edgeless) + /// The color for the border of the panel. Defaults to DUXBeta_clear (edgeless) public var panelBorderColor = UIColor.duxbeta_clear() - /// The color for the background of the tool icon area in a toolbar panel. Defaults to duxbeta_black + /// The color for the background of the tool icon area in a toolbar panel. Defaults to DUXBeta_black public var panelToolbarBackgroundColor = UIColor.duxbeta_black() - /// The tint color used to colorize the tool icon currently selected in a toolbar panel. Defaults to duxbeta_white + /// The tint color used to colorize the tool icon currently selected in a toolbar panel. Defaults to DUXBeta_white public var panelToobarTintColor = UIColor.duxbeta_white() - /// The color used to hilight the icon selected in a toolbar panel. Defaults to duxbeta_dakrGray + /// The color used to hilight the icon selected in a toolbar panel. Defaults to DUXBeta_dakrGray public var panelSelectionColor = UIColor.duxbeta_darkGray() /// The UIView used for drawing the titlebar on a List or Toolbar panel public var titlebar = UIView(frame: CGRect(x: 0.0, y: 0.0, width:300.0, height: 0)) @@ -259,28 +261,30 @@ let kCloseBoxWidthDefault: CGFloat = 24.0 fileprivate var backButtonImage: UIImage? = nil fileprivate var titleColor = UIColor.duxbeta_white() fileprivate var titleBarBackgroundColor = UIColor.duxbeta_black() - fileprivate var titleBarAlignment: DUXPanelTitleBarAlignment = .center + fileprivate var titleBarAlignment: DUXBetaPanelTitleBarAlignment = .center + + internal var closeHandler: ((DUXBetaPanelWidget) -> ())? = nil // MARK: - Class Factory Methods /** * Class method widget creates a panel based off a defined configuration item and returns it. * - * - Parameters configuration: The DUXPanelWidgetConfiguration object specifiying the type of panel, variants, and configuration options. + * - Parameters configuration: The DUXBetaPanelWidgetConfiguration object specifiying the type of panel, variants, and configuration options. * - * - Returns: A DUXPanelWidget instance, fully configured. + * - Returns: A DUXBetaPanelWidget instance, fully configured. */ - class public func widget(configuration: DUXPanelWidgetConfiguration) -> DUXPanelWidget? { - var returnObject : DUXPanelWidget? + class public func widget(configuration: DUXBetaPanelWidgetConfiguration) -> DUXBetaPanelWidget? { + var returnObject : DUXBetaPanelWidget? switch configuration.widgetType { case .bar: - returnObject = DUXBarPanelWidget(variant: configuration.widgetVariant).configure(configuration) + returnObject = DUXBetaBarPanelWidget(variant: configuration.widgetVariant).configure(configuration) case .toolbar: - returnObject = DUXToolbarPanelWidget(variant: configuration.widgetVariant).configure(configuration) + returnObject = DUXBetaToolbarPanelWidget(variant: configuration.widgetVariant).configure(configuration) case .list: - returnObject = DUXListPanelWidget(variant: configuration.widgetVariant).configure(configuration) + returnObject = DUXBetaListPanelWidget(variant: configuration.widgetVariant).configure(configuration) } return returnObject @@ -306,9 +310,9 @@ let kCloseBoxWidthDefault: CGFloat = 24.0 * * - Parameter config: unnamed parameter which takes a configuration object defining the panel. * - * - Returns: A configured DUXPanelWidget subclass item. + * - Returns: A configured DUXBetaPanelWidget subclass item. */ - open func configure(_ config:DUXPanelWidgetConfiguration) -> DUXPanelWidget { + open func configure(_ config:DUXBetaPanelWidgetConfiguration) -> DUXBetaPanelWidget { func copyColorConfig() { panelBackgroundColor = config.panelBackgroundColor panelBorderColor = config.panelBorderColor @@ -350,6 +354,15 @@ let kCloseBoxWidthDefault: CGFloat = 24.0 return self } + /** + * Call onCloseTapped to install any external cleanup handler needed when a panel widget is closed via the close button for the panel. + * + * - Parameter closeHanlder: The bloock/closure which will be called when the close button on the widget is tapped. It will pass a single + * argument, the panel being closed. + */ + public func onCloseTapped(_ closeHandler: @escaping ((DUXBetaPanelWidget) -> ())) { + self.closeHandler = closeHandler; + } /** * The abstraction method for adding an array of existing widgets into the panel. Each item in the passed in array * will be added as appropriate for the specific panel type. @@ -488,13 +501,13 @@ let kCloseBoxWidthDefault: CGFloat = 24.0 titleLabel.textAlignment = .center localTitleBar.addSubview(titleLabel) - closeboxView?.widthAnchor.constraint(equalToConstant: closeBoxImage?.size.width ?? kCloseBoxHeightDefault).isActive = true - closeboxView?.heightAnchor.constraint(equalToConstant: closeBoxImage?.size.height ?? kCloseBoxWidthDefault).isActive = true + closeboxView?.widthAnchor.constraint(equalToConstant: kCloseBoxWidthDefault).isActive = true + closeboxView?.heightAnchor.constraint(equalToConstant: kCloseBoxHeightDefault).isActive = true titleLabel.heightAnchor.constraint(equalToConstant: titlebarHeight - 4.0).isActive = true titleLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 40.0).isActive = true - backButtonView?.widthAnchor.constraint(equalToConstant: kCloseBoxHeightDefault).isActive = true - backButtonView?.heightAnchor.constraint(equalToConstant: kCloseBoxWidthDefault).isActive = true + backButtonView?.widthAnchor.constraint(equalToConstant: kCloseBoxWidthDefault ).isActive = true + backButtonView?.heightAnchor.constraint(equalToConstant: kCloseBoxHeightDefault).isActive = true closeboxView?.centerYAnchor.constraint(equalTo: localTitleBar.centerYAnchor).isActive = true titleLabel.centerYAnchor.constraint(equalTo: localTitleBar.centerYAnchor).isActive = true @@ -530,6 +543,10 @@ let kCloseBoxWidthDefault: CGFloat = 24.0 // TODO: Add a bock callback to notify the original installer that this is about to close. self.view.removeFromSuperview() self.removeFromParent() + + if let closeCompletionBlock = self.closeHandler { + closeCompletionBlock(self) + } } @IBAction func backButtonTapped() { @@ -569,4 +586,5 @@ let kCloseBoxWidthDefault: CGFloat = 24.0 } } } + } diff --git a/DJIUXSDKWidgets/Panels/DUXPanelWidgetSupport.h b/DJIUXSDKWidgets/Panels/DUXBetaPanelWidgetSupport.h similarity index 75% rename from DJIUXSDKWidgets/Panels/DUXPanelWidgetSupport.h rename to DJIUXSDKWidgets/Panels/DUXBetaPanelWidgetSupport.h index 1f81959..0eb9b5e 100644 --- a/DJIUXSDKWidgets/Panels/DUXPanelWidgetSupport.h +++ b/DJIUXSDKWidgets/Panels/DUXBetaPanelWidgetSupport.h @@ -1,9 +1,11 @@ // -// DUXPanelWidgetSupport.h +// DUXBetaPanelWidgetSupport.h // DJIUXSDK // +// MIT License +// // Copyright © 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 @@ -22,18 +24,19 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // -#ifndef DUXPanelWidgetSupport_h -#define DUXPanelWidgetSupport_h + +#ifndef DUXBetaPanelWidgetSupport_h +#define DUXBetaPanelWidgetSupport_h /** - * @protocol: The DUXToolbarPanelSupportProtocol is a protocol to be adoped by any widget which will be added into a - * Toobar panel (DUXToolbarPanel). It defines two optional methods, at least one of the two methods must be implemented + * @protocol: The DUXBetaToolbarPanelSupportProtocol is a protocol to be adoped by any widget which will be added into a + * Toobar panel (DUXBetaToolbarPanel). It defines two optional methods, at least one of the two methods must be implemented * for the tool widget to become visible in the Toolbar. * * - toolbatItemTitle - Returns a string which will be used to label the selectable tool. * - toolbarItemIcon - Returns a UIImage for the icon for the tool to be shown in the toolbar area. */ -@protocol DUXToolbarPanelSupportProtocol +@protocol DUXBetaToolbarPanelSupportProtocol @optional /** * This method, if implemented, returns the title to use for labeling the tool item in the toolbar. @@ -45,31 +48,31 @@ /** * This method, if implemented, returns the icon to use for showing the tool item in the toolbar. * - * @return DUXListSelectOne UIImage + * @return DUXBetaListSelectOne UIImage */ - (UIImage * _Nullable) toolbarItemIcon; @end /** - * DUXListType defines the type and behavior of a DUXListPanelWidget and the type of objects it will accept to + * DUXBetaListType defines the type and behavior of a DUXBetaListPanelWidget and the type of objects it will accept to * construct the list. */ -typedef NS_ENUM(NSInteger, DUXListType) { +typedef NS_ENUM(NSInteger, DUXBetaListType) { /// Default value which indicates the list is unconfigured - DUXListNone = 0, + DUXBetaListNone = 0, /// List will show a number of string options, only allowing a single option to be selected - DUXListSelectOne, + DUXBetaListSelectOne, /// List will show a number of string options, only allowing a single option to be selected. List returns to parent list on selection - DUXListSelectOneAndReturn, + DUXBetaListSelectOneAndReturn, /// List will show widgets which have already been created and setup for display. - DUXListWidgets, + DUXBetaListWidgets, /// List will take an widget classnames and create the widgets internally - DUXListWidgetNames + DUXBetaListWidgetNames }; -@class DUXListPanelWidget, DUXBetaBaseWidget; +@class DUXBetaListPanelWidget, DUXBetaBaseWidget; -@protocol DUXListPanelSupportProtocol +@protocol DUXBetaListPanelSupportProtocol @optional /** @@ -103,14 +106,14 @@ typedef NS_ENUM(NSInteger, DUXListType) { /** * This method returns the type of the sublist to be displayed. This is one of the standard list types. * - * @return DUXListType. + * @return DUXBetaListType. */ -- (DUXListType)detailListType; +- (DUXBetaListType)detailListType; /** * selectionUpdate returns the executable block which will be called whenever a selection is made lists of type - * DUXListSelectOne or DUXListSelectOneAndReturn. + * DUXBetaListSelectOne or DUXBetaListSelectOneAndReturn. * * @return an executable block taking a single integer index for the selected item. * @@ -123,7 +126,7 @@ typedef NS_ENUM(NSInteger, DUXListType) { - (void(^_Nonnull)(NSInteger))selectionUpdate; /** - * Implement this for DUXListSublistSelectOne and DUXListSublistSelectOneAndReturn + * Implement this for DUXBetaListSublistSelectOne and DUXBetaListSublistSelectOneAndReturn * The returned dictionary contains two keys. * * Dictionary keys @@ -137,7 +140,7 @@ typedef NS_ENUM(NSInteger, DUXListType) { - (NSDictionary* _Nonnull)oneOfListOptions; /** - * This method is caled if the sublist type is DUXListSublistWidgetNames. It returns the names of the widgets to create + * This method is caled if the sublist type is DUXBetaListSublistWidgetNames. It returns the names of the widgets to create * and populate into the sublist. * * @return NSArray of classnames. @@ -145,7 +148,7 @@ typedef NS_ENUM(NSInteger, DUXListType) { - (NSArray * _Nonnull)listOfSubwidgetNames; /** - * This method is caled if the sublist type is DUXListSublistWidgets. It returns an array of widgets to populate into the sublist. + * This method is caled if the sublist type is DUXBetaListSublistWidgets. It returns an array of widgets to populate into the sublist. * * @return NSArray of widgets descended from DUXBetaBaseWidget. */ @@ -153,4 +156,4 @@ typedef NS_ENUM(NSInteger, DUXListType) { @end -#endif /* DUXPanelWidgetSupport_h */ +#endif /* DUXBetaPanelWidgetSupport_h */ diff --git a/DJIUXSDKWidgets/Panels/DUXBetaSmartListInternalModel.swift b/DJIUXSDKWidgets/Panels/DUXBetaSmartListInternalModel.swift new file mode 100644 index 0000000..123f1e7 --- /dev/null +++ b/DJIUXSDKWidgets/Panels/DUXBetaSmartListInternalModel.swift @@ -0,0 +1,53 @@ +// +// DUXBetaSmartListInternalModel.swift +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// + +import Foundation + +/** + * DUXBetaSmartListInternalModel - This class is used internally by the default SmartList and subclasses. It listens for isProductConnected + * and aircraftModel for making decidions in a smartlist. + * + * If additional keys need to be added, inSetup to add your own bindings and properties. + * Then in your SmartListWidget class override DUXBetaSmartListModel.createSmartListInternalsModel() to create your specific model + */ +@objc open class DUXBetaSmartListInternalModel : DUXBetaBaseWidgetModel { + @objc public var aircraftModel: NSString = "" + + /** + * Override inSetup in a custom internal model to listen to additional keys. + */ + @objc open override func inSetup() { + if let key = DJIProductKey(param: DJIProductParamModelName) { + bindSDKKey(key, (\DUXBetaSmartListInternalModel.aircraftModel).toString) + // For testing purposes, create a subclass and then override the DUXBetaSmartListModel.createSmartListInternalsModel() + } + } + + @objc open override func inCleanup() { + unbindSDK(self) + } +} diff --git a/DJIUXSDKWidgets/Panels/DUXBetaSmartListModel.swift b/DJIUXSDKWidgets/Panels/DUXBetaSmartListModel.swift new file mode 100644 index 0000000..c6789ae --- /dev/null +++ b/DJIUXSDKWidgets/Panels/DUXBetaSmartListModel.swift @@ -0,0 +1,428 @@ +// +// DUXBetaSmartListModel.swift +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// + +import Foundation + +public typealias widgetID = String + +/** + * DUXBetaSmartListModel is the base class for implementing smart models for ListPanelWidgets. The SmartListModel is passed into a ListPanelWidget + * where it controls which widgets will added/removed in the list, removing the need for maintaining the list externally. + */ +@objc open class DUXBetaSmartListModel : NSObject { + // Mark: - Public Variables + // A lazy instantiated model which is used to do any key bindings needed to support decision making in the SmartModel + @objc public lazy var internalModel = DUXBetaSmartListInternalModel() + + /// The list of class names which will be shown in this model. Filled in by the method buildModelLists + open var modelClassnameList: [String] = [String]() + + // MARK: - Private Variables + fileprivate var containerModel: DUXBetaListPanelWidgetBaseModel? = nil + fileprivate var activeWidgetCount = 0 + fileprivate var totalWidgetCount = 0 + fileprivate var totalWidgetArray: [DUXBetaBaseWidget] = [] + fileprivate var activeWidgetArray: [DUXBetaBaseWidget] = [] + + fileprivate var blacklist: [widgetID] = [widgetID]() + + // TODO: Investigate. This map is for future expansion for more efficient widget lookup from identifer. + // fileprivate var classnameToWidgetMap: [widgetID: DUXBetaBaseWidget] = [String: DUXBetaBaseWidget]() + + // MARK: - Public Methods + + /** + * Init method which then builds the model lists. It does not initialize the blacklist + */ + public override init() { + // This method sets up anything needed by the SmartModel and is invoked during creation, before injection happens + super.init() + internalModel = self.createSmartListInternalsModel() + self.internalModel.setup() + // Set up all the default models here and install key listeners to + // monitor for changes + buildModelLists() + } + + /** + * Init method which then builds the model lists. It also initializes a blacklist of widgetIDs which should not be shown in this list. + */ + @objc public init(blacklist: [widgetID]) { + // This method sets up anything needed by the SmartModel and is invoked during creation, before injection happens + super.init() + internalModel = self.createSmartListInternalsModel() + self.internalModel.setup() + // Set up all the default models here and install key listeners to + // monitor for changes + self.blacklist = blacklist + buildModelLists() + } + + + deinit { + for widget in totalWidgetArray { + widget.removeObserver(self, forKeyPath: "view.hidden") + } + unbindRKVOModel(self) + + self.internalModel.cleanup() + } + + /* + * The createSmartListInternalsModel method is used to create the custom bindings needed for this particular SmartListModel. Overriding + * this creation model allows a custom internal model with additional key listening to be added to the SmartModel. + */ + @objc public func createSmartListInternalsModel() -> (DUXBetaSmartListInternalModel) { + return DUXBetaSmartListInternalModel() + } + + /** + * This method is used to set the ListPanelWidgetModel which actually contains the widgets which will be displayed. The SmartModel adjusts + * display widgets by manipulating this model. + */ + @objc public func setListPanelModel(_ lpwm : DUXBetaListPanelWidgetBaseModel) -> Void { + // This method should ideally be private and protected if possible, allowing only + // the ListPanelWidget to access it as a friend (in C++ terminology) + containerModel = lpwm + + buildAndInstallWidgets() + containerModel!.setWidgetsArray(activeWidgetArray) + } + + // MARK: - Widget and count finding for visible and full lists + + /** + * Call activeCount to get the number of displayed widgets at the current time. This number may change dynamically depending on + * conditions monitored by the SmartModel. + * + * - Returns: Int of the number of widgets currently displayed by the SmartListModel. + */ + @objc open func activeCount() -> Int { // How many widgets are actually being shown in the list + return activeWidgetCount + } + + /** + * Call totalCount to get the number of widgets under the control of the SmartModel. + * + * - Returns: Int of the number of widgets controlled by the SmartListModel. + */ + @objc open func totalCount() -> Int { + // How many widgets are alive, even any hidden widgets + return totalWidgetCount + } + + /** + * The method activeWidgetAtIndex is used to find a widget at the given index in the curretly active display list. + * + * - Parameter index: The index in the display list to return the widget for. + * - Returns: The widget of base type DUXBetaBaseWidget at the given index from the widgets actively displayed in the list. + */ + @objc open func activeWidgetAtIndex(index: Int) -> DUXBetaBaseWidget? { + // Fetch the widget from the list of displayed widgets. This will throw an exception if the + // index is out of range + return activeWidgetArray[index] + } + + /** + * The method widgetAtIndex is used to find a widget at the given index in the entire SmartList widget list. + * + * - Parameter index: The index in the entire widget list to return the widget for. + * - Returns: The widget of base type DUXBetaBaseWidget at the given index from the entire widget list. + */ + @objc open func widgetAtIndex(index: Int) -> DUXBetaBaseWidget? { + // Fetch the widget from the list of all widgets, even hidden. This will throw an exception + // if the index is out of range + return totalWidgetArray[index] + } + + /** + * The method findActiveWidgetWithID is used to find the widget instance by widgetID in the curretly active display list. May return nil. + * + * - Parameter widgetID: The widgetID to look for. + * - Returns: The widget of base type DUXBetaBaseWidget in the actve display list or nil. + */ + @objc open func findActiveWidgetWithID(widgetID: String) -> DUXBetaBaseWidget? { + // Search for a particular widget and return it if it exists and is shown or nil + let (found, index) = findIndexByWidgetID(widgetID: widgetID, list: activeWidgetArray) + if found { + return activeWidgetArray[index] + } + return nil + } + + /** + * The method findWidgetWithID is used to find the widget instance by widgetID in the entire SmartList widget list. May return nil. + * + * - Parameter widgetID: The widgetID to look for. + * - Returns: The widget of base type DUXBetaBaseWidget in widget list or nil. + */ + @objc open func findWidgetWithID(widgetID: String) -> DUXBetaBaseWidget? { + // Search for a particular widget and return it or nil + let (found, index) = findIndexByWidgetID(widgetID: widgetID, list: totalWidgetArray) + if found { + return totalWidgetArray[index] + } + return nil + } + + /** + * The method findActiveWidgetIndexWithID is used to find the index for a widget using the widgetID in the curretly active display list. + * + * - Parameter widgetID: The widgetID to look for. + * - Returns: The index of the widget in the actve display list or NSNotFound. + */ + @objc open func findActiveWidgetIndexWithID(widgetID: String) -> Int { + // Search for a particular widget and return it if it exists and is shown or nil + let (found, index) = findIndexByWidgetID(widgetID: widgetID, list: activeWidgetArray) + if found { + return index + } + return NSNotFound + } + + /** + * The method findActiveWidgetIndexWithID is used to find the index for a widget using the widgetID in the entire widget list. + * + * - Parameter widgetID: The widgetID to look for. + * - Returns: The index of the widget in the entire widget list or NSNotFound. + */ + @objc open func findWidgetIndexWithID(widgetID: String) -> Int { + // Search for a particular widget and return it or nil + let (found, index) = findIndexByWidgetID(widgetID: widgetID, list: totalWidgetArray) + if found { + return index + } + return NSNotFound + } + + /** + * The method move is used to reposition the widget specified from by widgetID to a new index. + * IMPORTANT: The move functionality only works on the full widget list + * + * Parameter identifier: The widgetID to move. + * Parameter toFullIndex: The index to move the widget to in the full widget list. + */ + @objc open func move(identifier: widgetID, toFullIndex: Int) { + // This moves actual widgets in the display list order. Hidden widgets are included in the ordering + let indexTuple = findIndexByWidgetID(widgetID: identifier, list: totalWidgetArray) + if indexTuple.0 { + let widget = totalWidgetArray.remove(at:indexTuple.1) + totalWidgetArray.insert(widget, at:toFullIndex) + } + } + + /** + * The move method moves the widget specified from one index to another in the full widget list. + * IMPORTANT: The move functionality only works on the full widget list + * + * - Parameter fromFullIndex: The index to move the widget from in the full widget list. + * - Parameter toFullIndex: The index to move the widget to in the full widget list. + */ + @objc open func move(fromFullIndex: Int, toFullIndex: Int) { + let widget = totalWidgetArray.remove(at:fromFullIndex) + totalWidgetArray.insert(widget, at:toFullIndex) + } + + /** + * Override the method setup to implment the key binding code needed specifically for the SmartModel here. + * This is called by the DUXBetaListPanelWidgetBaseModel from its setup/cleanup methods. + */ + @objc open func setup() { + bindRKVOModel(self, #selector(productConnectedChanged), (\DUXBetaSmartListModel.internalModel.isProductConnected).toString) + bindRKVOModel(self, #selector(aircraftModelUpdated), (\DUXBetaSmartListModel.internalModel.aircraftModel).toString) + } + + /** + * Override the method cleanup to tear down key bindings established in setup(). + */ + @objc open func cleanup() { + + } + + + @objc public func aircraftModelUpdated() { + buildModelLists() // This wlll be called on initial binding to the internalModel. That is done in setup() + // which is called from the full model during it's setup phase. At this point we know that + // we can at least tell which product we are using. If buildModelLists is called again later + // it should reconcile any changes. + if self.internalModel.isProductConnected && (self.internalModel.aircraftModel.length > 1) { + buildAndInstallWidgets() + } + } + + @objc public func productConnectedChanged() { + // The aircraft name changing is the key monitor in the default class at present because it happens after the + // productConnectedChange call happens. + } + + // MARK: - List Crafting and Construction + /** + * The method buildModelLists should be overriden by every class which overrides SmartModelList. It creates the full list of widgetIDs which will be + * created and used by this SmartModel. Any additional maintenance information can also be created in here. + */ + @objc open func buildModelLists() { + // In concrete class, use the KeyManager to monitor which product you are connected to and adjust this list as needed, then call buildAndInstallWidgets to replace the currently list of widgets + // This is a brief sample (incomplete) on how to add the business logic into this method to determine what to show. +/* + if (self.internalModel.isProductConnected) { + let isInspire = ((self.internalModel.aircraftModel == DJIAircraftModelNameInspire1 as NSString) + || (self.internalModel.aircraftModel == DJIAircraftModelNameInspire2 as NSString) + || (self.internalModel.aircraftModel == DJIAircraftModelNameInspire1Pro as NSString) + || (self.internalModel.aircraftModel == DJIAircraftModelNameInspire1RAW as NSString)) + + modelClassnameList = [ + DUXBetaMaxAltitudeListItemWidget.duxbeta_className(), + DUXBetaMaxFlightDistanceListItemWidget.duxbeta_className() + ] + if (isInspire) { + modelClassnameList.append(DUXBetaTravelModeListItemWidget.duxbeta_className()) + } + } else { + modelClassnameList = [ + DUXBetaMaxAltitudeListItemWidget.duxbeta_className(), + DUXBetaMaxFlightDistanceListItemWidget.duxbeta_className(), + DUXBetaTravelModeListItemWidget.duxbeta_className() + ] + } + */ + } + + // MARK: - Widget Custom Initialization + /** + * The method customizeWidgetSetup is used for custom customizations of different widgets. + * This method can be overridden and any additional customization for select widgets can be done after they are created + * + * - Parameter widget: The widget to customize for display. + */ + @objc open func customizeWidgetSetup(widget: DUXBetaBaseWidget) { + // For custom customizations of different widgets, this method can be overridden + // and any additional customization for select widgets can be done after they are created + } + + // MARK: - Internal functions which may be overridden + + /** + * The method buildAndInstallWidgets is used to create all the widgets from the modelClassnameList. It usually won't need to be overridden + * but if widgets have special needs for parameters which can not be handled in customizeWidgetSetup, this method can be overridden to handle it. + */ + @objc open func buildAndInstallWidgets() { + // Decide which list to use. Base class just uses the modelClassnameList. Specialized + // ListSmartModels may overide this method to select different lists based on connected + // aircraft or other customizations + activeWidgetArray.removeAll() + + var listIndex = 0; + for className in modelClassnameList { + let classInst = NSClassFromString(className) as? NSObject.Type + // See if in blacklist and prevent adding if it is in there + if !self.blacklist.contains(className) { + let foundIndex = findWidgetIndexWithID(widgetID: className); + if foundIndex != NSNotFound { + // Widget already existed, make sure it is in the right place. + if foundIndex != listIndex { + move(fromFullIndex:foundIndex, toFullIndex: listIndex) + } + if !totalWidgetArray[listIndex].view.isHidden { + activeWidgetArray.append(totalWidgetArray[listIndex]) + } + } else { + if let widget = classInst!.init() as? DUXBetaBaseWidget { + customizeWidgetSetup(widget: widget) + totalWidgetArray.append(widget) + // Need to do standard KVO for this since we aren't holding the widget in + // a path accessible manner for this model class so there is no valid + // key path from this object to the widget property + widget.addObserver(self, forKeyPath: "view.hidden", options: .new, context: nil) + + if !widget.view.isHidden { + activeWidgetArray.append(widget) + } + } + } + } + listIndex += 1 + } + + } + + /** + * The method observeValue handles KVO monitoring for installed widgets. Override for custom handling as necessary. + * The default implementation handles showing and removing widgets based on widget view.hidden property. + * + * - Parameter forKeyPath: The keypath being observed. + * - Parameter of: The object which had a change to the observed keypath. + * - Parameter change: A dictionary of change values such as new value, old value. + * - Parameter context: Contextual data included when the observation was created. + */ + @objc open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if let _ = object as? DUXBetaBaseWidget { + if keyPath == "view.hidden" { + // Visibility changed. Update the visible widget list + self.buildVisibleWidgetList() + if let cm = containerModel { + cm.setWidgetsArray(activeWidgetArray) + } + } else { + print("Bad keyPath observed (\(String(describing: keyPath))) in ListSmartModel") + } + } else { + print("Bad observing for non-widget (\(String(describing:object)) in ListSmartModel") + } + } + + /** + * Method buildVisibleWidgetList is a shorter rebuild version of the buildAndInstallWidgets which only rebuilds + * the visible widdet list. + */ + @objc open func buildVisibleWidgetList() { + var newVisibleList: [DUXBetaBaseWidget] = [DUXBetaBaseWidget]() + for widget in totalWidgetArray { + if !widget.view.isHidden { + newVisibleList.append(widget) + } + } + activeWidgetArray = newVisibleList + } + + // MARK: - Internal functions to not be overridden + internal func findIndexByWidgetID(widgetID : String, list: [DUXBetaBaseWidget]) -> (Bool, Int) { + for (index, aWidget) in list.enumerated() { + let fullString = aWidget.duxbeta_className() + let className = String(fullString.split(separator: ".").last!) + if className == widgetID { + return (true, index) + } + } + return (false, NSNotFound) + } + + // This method returns the class name with no module attached to the name + internal func className(_ some: Any) -> String { + return (some is Any.Type) ? "\(some)" : "\(type(of: some))" + } + +} diff --git a/DJIUXSDKWidgets/Panels/DUXSystemStatusListSmartModel.swift b/DJIUXSDKWidgets/Panels/DUXBetaSystemStatusListSmartModel.swift similarity index 55% rename from DJIUXSDKWidgets/Panels/DUXSystemStatusListSmartModel.swift rename to DJIUXSDKWidgets/Panels/DUXBetaSystemStatusListSmartModel.swift index 2454625..e25176f 100644 --- a/DJIUXSDKWidgets/Panels/DUXSystemStatusListSmartModel.swift +++ b/DJIUXSDKWidgets/Panels/DUXBetaSystemStatusListSmartModel.swift @@ -1,8 +1,10 @@ // -// DUXSystemStatusListSmartModel.swift +// DUXBetaSystemStatusListSmartModel.swift // DJIUXSDKWidgets // -// Copyright © 2018-2020 DJI +// MIT License +// +// Copyright © 2019-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 @@ -25,15 +27,23 @@ import Foundation -@objc open class DUXSystemStatusListSmartModel : DUXSmartListModel { +/** + * DUXBetaSystemStatusListSmartModel is the concrete class for implementing the SystemStatusSmartListModel. It defines + * the widgets which will be included in the SmartList. + */ +@objc open class DUXBetaSystemStatusListSmartModel : DUXBetaSmartListModel { + /** + * Override of buildModelLists to set up the widgts to display in the SystemStatusListWidget. + */ @objc open override func buildModelLists() { - // In concrete class, use the KeyManager to monitor which product you are connected to and adjust this list as needed, then call buildAndInstallWidgets to replace the currently list of widgets - self.modelClassnameList = [ - DUXFlightModeListItemWidget.duxbeta_className(), - DUXRCStickModeListItemWidget.duxbeta_className(), - DUXSDCardRemainingCapacityListItemWidget.duxbeta_className(), - DUXMaxAltitudeListItemWidget.duxbeta_className(), - ] + modelClassnameList = [ + DUXBetaReturnToHomeAltitudeListItemWidget.duxbeta_className(), + DUXBetaMaxAltitudeListItemWidget.duxbeta_className(), + DUXBetaMaxFlightDistanceListItemWidget.duxbeta_className(), + DUXBetaFlightModeListItemWidget.duxbeta_className(), + DUXBetaRCStickModeListItemWidget.duxbeta_className(), + DUXBetaSDCardStatusListItemWidget.duxbeta_className() + ] } } diff --git a/DJIUXSDKWidgets/Panels/DUXSystemStatusListWidget.swift b/DJIUXSDKWidgets/Panels/DUXBetaSystemStatusListWidget.swift similarity index 75% rename from DJIUXSDKWidgets/Panels/DUXSystemStatusListWidget.swift rename to DJIUXSDKWidgets/Panels/DUXBetaSystemStatusListWidget.swift index 46238fb..17459ab 100644 --- a/DJIUXSDKWidgets/Panels/DUXSystemStatusListWidget.swift +++ b/DJIUXSDKWidgets/Panels/DUXBetaSystemStatusListWidget.swift @@ -1,9 +1,11 @@ // -// DUXSystemStatusListWidget.swift +// DUXBetaSystemStatusListWidget.swift // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -26,36 +28,36 @@ import Foundation /** - * DUXSystemStatusListWidget is the ListPanel using a SmartModel to construct + * DUXBetaSystemStatusListWidget is the ListPanel using a SmartModel to construct * and display a standardized system status list. This current release displays * the following widgets: * - * - DUXFlightModeWidget - * - DUXRCStickModeListItemWidget - * - DUXSDCardRemainingCapacityListItemWidget - * - DUXMaxAltitudeListItemWidget + * - DUXBetaFlightModeWidget + * - DUXBetaRCStickModeListItemWidget + * - DUXBetaSDCardStatusListItemWidget + * - DUXBetaMaxAltitudeListItemWidget * */ -@objc public class DUXSystemStatusListWidget: DUXListPanelWidget { +@objc public class DUXBetaSystemStatusListWidget: DUXBetaListPanelWidget { required public init?(coder: NSCoder) { super.init(coder: coder) - let _ = self.setupSmartModel(DUXSystemStatusListSmartModel()) + let _ = self.setupSmartModel(DUXBetaSystemStatusListSmartModel()) } override public init() { super.init() - let _ = self.setupSmartModel(DUXSystemStatusListSmartModel()) + let _ = self.setupSmartModel(DUXBetaSystemStatusListSmartModel()) } - override public init(smartModel: DUXSmartListModel) { + override public init(smartModel: DUXBetaSmartListModel) { super.init(smartModel: smartModel) } public override func defaultConfigurationSetup() { // Must configure this here before parent viewDidLoad since we are internally configuring // And must have the list type set properly - let config = DUXPanelWidgetConfiguration(type: .list, listKind: .widgets) + let config = DUXBetaPanelWidgetConfiguration(type: .list, listKind: .widgets) .configureTitlebar(visible: true, withCloseBox: true, title: NSLocalizedString("System Status", comment: "System Status")) .configureColors(background: .duxbeta_listPanelBackground(), border: .duxbeta_clear(), titleBarBackground: .duxbeta_listPanelBackground()) let _ = configure(config) diff --git a/DJIUXSDKWidgets/Panels/DUXToolbarPanelItemTemplate.swift b/DJIUXSDKWidgets/Panels/DUXBetaToolbarPanelItemTemplate.swift similarity index 91% rename from DJIUXSDKWidgets/Panels/DUXToolbarPanelItemTemplate.swift rename to DJIUXSDKWidgets/Panels/DUXBetaToolbarPanelItemTemplate.swift index c30ed6a..c58367d 100644 --- a/DJIUXSDKWidgets/Panels/DUXToolbarPanelItemTemplate.swift +++ b/DJIUXSDKWidgets/Panels/DUXBetaToolbarPanelItemTemplate.swift @@ -1,9 +1,11 @@ // -// DUXToolbarPanelItemTemplate.swift +// DUXBetaToolbarPanelItemTemplate.swift // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -26,10 +28,10 @@ import Foundation /** - * The DUXToolbarPanelItemTemplate allows for describing a widget to be added to a DUXToolbarPanelWidget without instantiating + * The DUXBetaToolbarPanelItemTemplate allows for describing a widget to be added to a DUXBetaToolbarPanelWidget without instantiating * the widget beforehand. It defines the widget by classname, icon and title, and a lifetime hint called keepWidgetAlive. */ -@objcMembers public class DUXToolbarPanelItemTemplate : NSObject { +@objcMembers public class DUXBetaToolbarPanelItemTemplate : NSObject { /// The name of the class to use when creating the widget public var klassname: String? /// The icon image to use for the tool item in the toolbar @@ -70,7 +72,7 @@ import Foundation /** * ToolRecognizer is a class which installs a gesture recognizer on an individual tool item in a Toolbar pane; and will - * select the item for display when it is tapped. It is only used internally by the DUXToolbarPanelWidget + * select the item for display when it is tapped. It is only used internally by the DUXBetaToolbarPanelWidget */ class ToolRecognizer : NSObject { /// The tap gesture recognizer for this helper @@ -78,7 +80,7 @@ class ToolRecognizer : NSObject { /// The iconView to attach the gesture recognizer onto weak var iconView: UIView? // The owning toolbar panel to perform the selection on. - weak var panel: DUXToolbarPanelWidget? + weak var panel: DUXBetaToolbarPanelWidget? /** * Initialization method taking the toolIcon view and the unnamed panel to send selection to. @@ -87,7 +89,7 @@ class ToolRecognizer : NSObject { * - toolIconView: The icon in the tool list for the toolbar pane being observed. * - panel: unnamed panel which will recevie the seletion once a tap is seen. */ - init(toolIconView: UIView, _ panel:DUXToolbarPanelWidget) { + init(toolIconView: UIView, _ panel:DUXBetaToolbarPanelWidget) { super.init() self.iconView = toolIconView diff --git a/DJIUXSDKWidgets/Panels/DUXToolbarPanelWidget.swift b/DJIUXSDKWidgets/Panels/DUXBetaToolbarPanelWidget.swift similarity index 92% rename from DJIUXSDKWidgets/Panels/DUXToolbarPanelWidget.swift rename to DJIUXSDKWidgets/Panels/DUXBetaToolbarPanelWidget.swift index 3521e5a..f493511 100644 --- a/DJIUXSDKWidgets/Panels/DUXToolbarPanelWidget.swift +++ b/DJIUXSDKWidgets/Panels/DUXBetaToolbarPanelWidget.swift @@ -1,9 +1,11 @@ // -// DUXToolbarWidget.swift +// DUXBetaToolbarWidget.swift // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -31,12 +33,12 @@ fileprivate let kMinPanelWidth: CGFloat = 100.0 fileprivate let kMinPanelHeight: CGFloat = 144.0 /** - * DUXToolbarPanelWidget descends from DUXPanelWidget and implements the DUXToolbarPanelSupportProtocol. It implements + * DUXBetaToolbarPanelWidget descends from DUXBetaPanelWidget and implements the DUXBetaToolbarPanelSupportProtocol. It implements * the Toolbar Panel. * * The Toolbar shows a row of icons, selecting a single icon shows the widget in the panel area. */ -@objcMembers open class DUXToolbarPanelWidget : DUXPanelWidget, DUXToolbarPanelSupportProtocol { +@objcMembers open class DUXBetaToolbarPanelWidget : DUXBetaPanelWidget, DUXBetaToolbarPanelSupportProtocol { // MARK: - Public Variables /// Is this toolbar using template icons or actual images public var usingTemplates: Bool = false @@ -55,13 +57,13 @@ fileprivate let kMinPanelHeight: CGFloat = 144.0 // MARK: - Private Variables fileprivate var templateOrWidgetSelected = false - fileprivate var toolbarEdge: DUXPanelVariant = .top + fileprivate var toolbarEdge: DUXBetaPanelVariant = .top fileprivate var panelSize = CGSize(width: kMinPanelWidth, height: kMinPanelHeight) // If we init with the PanelItemTemplates, we can not allow adding via the addWidgetArray or addWidget methods // Unless we create a PanelItemTemplate for each one. fileprivate var widgetList : [DUXBetaBaseWidget] = [DUXBetaBaseWidget]() - fileprivate var panelItemList: [DUXToolbarPanelItemTemplate] = [DUXToolbarPanelItemTemplate]() + fileprivate var panelItemList: [DUXBetaToolbarPanelItemTemplate] = [DUXBetaToolbarPanelItemTemplate]() fileprivate var recognizerList : [ToolRecognizer] = [ToolRecognizer]() fileprivate var currentSelectedIndex = NSNotFound @@ -75,7 +77,7 @@ fileprivate let kMinPanelHeight: CGFloat = 144.0 fileprivate var initialToolbarUIDone = false //MARK: - Instance Methods - //MARK: DUXToolbarPanelWidget + //MARK: DUXBetaToolbarPanelWidget /** * The default init method for the class when loading from a Storyboard or Xib. @@ -96,7 +98,7 @@ fileprivate let kMinPanelHeight: CGFloat = 144.0 * * - Parameter variant: The variant specifying which edge the tools are along. */ - public init(variant: DUXPanelVariant) { + public init(variant: DUXBetaPanelVariant) { self.toolbarEdge = variant super.init() } @@ -107,14 +109,14 @@ fileprivate let kMinPanelHeight: CGFloat = 144.0 * * - Returns: This object instance for call chaining. */ - override public func configure(_ configuration:DUXPanelWidgetConfiguration) -> DUXToolbarPanelWidget { + override public func configure(_ configuration:DUXBetaPanelWidgetConfiguration) -> DUXBetaToolbarPanelWidget { let _ = super.configure(configuration) guard ((configuration.widgetVariant == .top) || (configuration.widgetVariant == .left) || (configuration.widgetVariant == .right)) else { assertionFailure("DUXToolbarWidget varient must be one of .top, .left, right") - return DUXToolbarPanelWidget() + return DUXBetaToolbarPanelWidget() } self.toolbarEdge = configuration.widgetVariant @@ -122,6 +124,7 @@ fileprivate let kMinPanelHeight: CGFloat = 144.0 return self } + /** * Override of the standard viewdDidLoad. Sets the parent view for autolayout and if the widget has already been * configured, calls setupUI. @@ -142,7 +145,7 @@ fileprivate let kMinPanelHeight: CGFloat = 144.0 */ public override func addWidgetArray(_ displayWidgets: [DUXBetaBaseWidget]) { if templateOrWidgetSelected && usingTemplates { - assertionFailure("DUXPanelWidget: panel already using DUXToolbarPanelItemTemplate objects") + assertionFailure("DUXPanelWidget: panel already using DUXBetaToolbarPanelItemTemplate objects") } usingTemplates = false templateOrWidgetSelected = true // Just blind update instead of lots of state checking @@ -152,13 +155,13 @@ fileprivate let kMinPanelHeight: CGFloat = 144.0 } /** - * The addPanelToolsArray method adds an array of predefined DUXToolbarPanelItemsTemplates into the tool array + * The addPanelToolsArray method adds an array of predefined DUXBetaToolbarPanelItemsTemplates into the tool array * using the template to define the displayed tools by classname and other information. It then calls updateUI to refresh * the toolbar. * - * - Parameter panelTools: unnamed array of DUXToolbarPanelItemTemplate items. + * - Parameter panelTools: unnamed array of DUXBetaToolbarPanelItemTemplate items. */ - public func addPanelToolsArray(_ panelTools: [DUXToolbarPanelItemTemplate]) { + public func addPanelToolsArray(_ panelTools: [DUXBetaToolbarPanelItemTemplate]) { if templateOrWidgetSelected && !usingTemplates { assertionFailure("DUXPanelWidget: panel already using widget objects") } @@ -197,7 +200,7 @@ fileprivate let kMinPanelHeight: CGFloat = 144.0 */ override public func insert(widget: DUXBetaBaseWidget, atIndex: Int) { if usingTemplates { - assertionFailure("DUXPanelWidget: inserting DUXBetaBaseWidget in DUXToolbarPanelWidget when expecting DUXToolbarPanelItemTemplate") + assertionFailure("DUXPanelWidget: inserting DUXBetaBaseWidget in DUXBetaToolbarPanelWidget when expecting DUXBetaToolbarPanelItemTemplate") } else { if (currentSelectedIndex != NSNotFound) && (atIndex <= currentSelectedIndex) { // Inserting before out selected index. We need to massage the selection. @@ -213,14 +216,14 @@ fileprivate let kMinPanelHeight: CGFloat = 144.0 /** * The insert method inserts the given itemTemplate into the toolbar array at the given index using the settings in - * the DUXToolbarPanelItemTemplate object. + * the DUXBetaToolbarPanelItemTemplate object. * Indexing is from leading to trailing side or top to bottom depending on the panel variant. updateUI is then called. * * - Parameters: - * - itemTemplate: The widget defining the DUXToolbarPanelItemTemplate object to insert. + * - itemTemplate: The widget defining the DUXBetaToolbarPanelItemTemplate object to insert. * - atIndex: The index at which to insert into the toolbar array. */ - public func insert(itemTemplate: DUXToolbarPanelItemTemplate, atIndex: Int) { + public func insert(itemTemplate: DUXBetaToolbarPanelItemTemplate, atIndex: Int) { if usingTemplates { if (currentSelectedIndex != NSNotFound) && (atIndex <= currentSelectedIndex) { // Inserting before out selected index. We need to massage the selection. @@ -294,7 +297,6 @@ fileprivate let kMinPanelHeight: CGFloat = 144.0 self.view.addSubview(toolView) } - /** * The method layoutInternals is a lazy instantiation of the scrollbar location. It should normally be called only once, * when panel tools are finally added. If the setupUI runs before configuration happens, the default is a top toolbar @@ -363,14 +365,14 @@ fileprivate let kMinPanelHeight: CGFloat = 144.0 toolHeaderViews.removeAll() } - func insertToolHeaderFromTemplate(_ toolItemTemplate: DUXToolbarPanelItemTemplate, at: Int) { + func insertToolHeaderFromTemplate(_ toolItemTemplate: DUXBetaToolbarPanelItemTemplate, at: Int) { let toolIcon = self.constructToolIcon(image: toolItemTemplate.barIcon, title: toolItemTemplate.barName) toolHeaderViews.insert(toolIcon, at: at) recognizerList.insert(ToolRecognizer(toolIconView: toolIcon, self), at: at) } func insertToolHeaderFromWidget(_ widget: DUXBetaBaseWidget, at: Int) { - if let x = widget as? DUXToolbarPanelSupportProtocol { + if let x = widget as? DUXBetaToolbarPanelSupportProtocol { let toolIcon = self.constructIconFrom(widget: x) toolHeaderViews.insert(toolIcon, at: at) recognizerList.insert(ToolRecognizer(toolIconView: toolIcon, self), at: at) @@ -523,7 +525,7 @@ fileprivate let kMinPanelHeight: CGFloat = 144.0 return toolIconView } - func constructIconFrom(widget: T) -> UIView where T: DUXToolbarPanelSupportProtocol { + func constructIconFrom(widget: T) -> UIView where T: DUXBetaToolbarPanelSupportProtocol { let image = widget.toolbarItemIcon?() let title = widget.toolbarItemTitle?() @@ -598,6 +600,7 @@ fileprivate let kMinPanelHeight: CGFloat = 144.0 return NSNotFound } + // TODO: Update with better hilighting code func drawHilighting(selected: Bool, at: Int) { if selected { toolHeaderViews[at].backgroundColor = self.panelSelectionColor @@ -685,7 +688,7 @@ fileprivate let kMinPanelHeight: CGFloat = 144.0 // Now we have a special case of a panel nesting inside another panel. We need to maximize that entire area // The good news is that we know the toolView is the maximized area already, so just pin to the edges - if let _ = widget as? DUXPanelWidget { + if let _ = widget as? DUXBetaPanelWidget { widgetView.topAnchor.constraint(equalTo:toolView.topAnchor).isActive = true widgetView.leadingAnchor.constraint(equalTo:toolView.leadingAnchor).isActive = true widgetView.bottomAnchor.constraint(equalTo:toolView.bottomAnchor).isActive = true @@ -702,6 +705,8 @@ fileprivate let kMinPanelHeight: CGFloat = 144.0 let widthRatio: CGFloat = widgetView.bounds.size.width / toolView.bounds.size.width let heightRatio: CGFloat = widgetView.bounds.size.height / toolView.bounds.size.height + // TODO: This code still needs comprehensive testing and a complete test plan. Develomental testing appears to show + // it working correctly. This probably could probably be refactored into a smaller routines if (widthRatio <= 1.0) && (heightRatio <= 1.0) { // widget is entirely smaller than the tool area. Already centered but height and width need to be set because // we just added it to the subview. It may have been added before and removed, so it's proportions are correct, @@ -730,7 +735,26 @@ fileprivate let kMinPanelHeight: CGFloat = 144.0 widgetView.widthAnchor.constraint(equalTo: toolView.widthAnchor, multiplier: widthHeightRatio).isActive = true } } + + } + + + //MARK: KVO support for hiding/showing of toolbar items and/or widgets + // DUXBetaToobarPanelWidget does nto current support hiding/showing tools. There are technical questions about + // what is shown/hidden to control this using KVO. Do we need to have the template objects be shown/hidden? + /* + @objc public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if let change = change, let keyPath = keyPath { + if (keyPath == "hidden") && (change[.newKey] != nil) { + // Now figure out if this is a toolbar item of an actual widget item which is live and possibly shown + if let testView = object as? UIView { + + self.updateUI() + } + } + } } + */ //MARK: Support for including toolbar panels within toolbar panels @@ -741,5 +765,6 @@ fileprivate let kMinPanelHeight: CGFloat = 144.0 public func toolbarItemIcon() -> UIImage? { return toolbarImage } + } diff --git a/DJIUXSDKWidgets/Panels/DUXTopBarWidget.swift b/DJIUXSDKWidgets/Panels/DUXBetaTopBarWidget.swift similarity index 92% rename from DJIUXSDKWidgets/Panels/DUXTopBarWidget.swift rename to DJIUXSDKWidgets/Panels/DUXBetaTopBarWidget.swift index ea482c4..b56cbce 100644 --- a/DJIUXSDKWidgets/Panels/DUXTopBarWidget.swift +++ b/DJIUXSDKWidgets/Panels/DUXBetaTopBarWidget.swift @@ -1,9 +1,11 @@ // -// DUXTopBarWidget.swift +// DUXBetaTopBarWidget.swift // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -26,7 +28,7 @@ import Foundation /** - * DUXTopBarWidget is the container for the top bar widgets. + * DUXBetaTopBarWidget is the container for the top bar widgets. * * - DUXBetaSystemStatusWidget * - DUXBetaFlightModeWidget @@ -38,9 +40,9 @@ import Foundation * - DUXBetaVideoSignalWidget * - DUXBetaBatteryWidget * - DUXBetaConnectionWidget - * + * */ -@objcMembers public class DUXTopBarWidget: DUXBarPanelWidget { +@objcMembers public class DUXBetaTopBarWidget: DUXBetaBarPanelWidget { let systemStatusWidget = DUXBetaSystemStatusWidget() let flightModeWidget = DUXBetaFlightModeWidget() @@ -81,7 +83,7 @@ import Foundation */ public override func defaultConfigurationSetup() { // Enforce default configuration as an horizontal bar - let config = DUXPanelWidgetConfiguration(type:.bar, variant:.horizontal) + let config = DUXBetaPanelWidgetConfiguration(type:.bar, variant:.horizontal) let _ = configure(config) } @@ -89,7 +91,7 @@ import Foundation * Override of the parent method where we create * a specific constraint for the leftbar stackview. */ - public override func spacingConstraint(forOrientation orientation: DUXPanelVariant, + public override func spacingConstraint(forOrientation orientation: DUXBetaPanelVariant, andWorkBar workBar: UIView, withSize size: CGSize, andConstant constant: CGFloat) -> NSLayoutConstraint { diff --git a/DJIUXSDKWidgets/Panels/DUXSmartListModel.swift b/DJIUXSDKWidgets/Panels/DUXSmartListModel.swift deleted file mode 100644 index 8d96835..0000000 --- a/DJIUXSDKWidgets/Panels/DUXSmartListModel.swift +++ /dev/null @@ -1,260 +0,0 @@ -// -// DUXSmartListModel.swift -// DJIUXSDKWidgets -// -// Copyright © 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. -// - -import Foundation - -public typealias widgetID = String - -@objc open class DUXSmartListModel : NSObject { - open var modelClassnameList: [String] = [String]() - - fileprivate var containerModel: DUXListPanelWidgetBaseModel? = nil - fileprivate var activeWidgetCount = 0 - fileprivate var totalWidgetCount = 0 - fileprivate var totalWidgetArray: [DUXBetaBaseWidget] = [] - fileprivate var activeWidgetArray: [DUXBetaBaseWidget] = [] - - fileprivate var blacklist: [widgetID] = [widgetID]() - - public override init() { - // This method sets up anything needed by the SmartModel and is invoked during creation, before injection happens - super.init() - // Set up all the default models here and install key listeners to - // monitor for changes - buildModelLists() - } - - @objc public init(blacklist: [widgetID]) { - // This method sets up anything needed by the SmartModel and is invoked during creation, before injection happens - super.init() - // Set up all the default models here and install key listeners to - // monitor for changes - self.blacklist = blacklist - buildModelLists() - } - - - deinit { - for widget in totalWidgetArray { - widget.removeObserver(self, forKeyPath: "view.hidden") - } - unbindRKVOModel(self) - } - - @objc public func setListPanelModel(_ lpwm : DUXListPanelWidgetBaseModel) -> Void { - // This method should ideally be private and protected if possible, allowing only - // the ListPanelWidget to access it as a friend (in C++ terminology) - containerModel = lpwm - - // Now that we have the model to store and display our widgets, we need to instantiate and populate them. - buildAndInstallWidgets() - containerModel!.setWidgetsArray(activeWidgetArray) - } - - // MARK: - Widget and count finding for visible and full lists - @objc open func activeCount() -> Int { // How many widgets are actually being shown in the list - return activeWidgetCount - } - - @objc open func totalCount() -> Int { - // How many widgets are alive, even any hidden widgets - return totalWidgetCount - } - - @objc open func activeWidgetAtIndex(index: Int) -> DUXBetaBaseWidget? { - // Fetch the widget from the list of displayed widgets. This will throw an exception if the - // index is out of range - return activeWidgetArray[index] - } - - @objc open func widgetAtIndex(index: Int) -> DUXBetaBaseWidget? { - // Fetch the widget from the list of all widgets, even hidden. This will throw an exception - // if the index is out of range - return totalWidgetArray[index] - } - - @objc open func findActiveWidgetWithID(widgetID: String) -> DUXBetaBaseWidget? { - // Search for a particular widget and return it if it exists and is shown or nil - let (found, index) = findIndexByWidgetID(widgetID: widgetID, list: activeWidgetArray) - if found { - return activeWidgetArray[index] - } - return nil - } - - @objc open func findWidgetWithID(widgetID: String) -> DUXBetaBaseWidget? { - // Search for a particular widget and return it or nil - let (found, index) = findIndexByWidgetID(widgetID: widgetID, list: totalWidgetArray) - if found { - return totalWidgetArray[index] - } - return nil - } - - @objc open func findActiveWidgetIndexWithID(widgetID: String) -> Int { - // Search for a particular widget and return it if it exists and is shown or nil - let (found, index) = findIndexByWidgetID(widgetID: widgetID, list: activeWidgetArray) - if found { - return index - } - return NSNotFound - } - - @objc open func findWidgetIndexWithID(widgetID: String) -> Int { - // Search for a particular widget and return it or nil - let (found, index) = findIndexByWidgetID(widgetID: widgetID, list: totalWidgetArray) - if found { - return index - } - return NSNotFound - } - - /** - IMPORTANT: The move functionality only works on the full widget list - */ - @objc open func move(identifier: widgetID, toFullIndex: Int) { - // This moves actual widgets in the display list order. Hidden widgets are included in the ordering - let indexTuple = findIndexByWidgetID(widgetID: identifier, list: totalWidgetArray) - if indexTuple.0 { - let widget = totalWidgetArray.remove(at:indexTuple.1) - totalWidgetArray.insert(widget, at:toFullIndex) - } - } - - @objc open func move(fromFullIndex: Int, toFullIndex: Int) { - let widget = totalWidgetArray.remove(at:fromFullIndex) - totalWidgetArray.insert(widget, at:toFullIndex) - } - - // Implment the key binding code needed specifically for the smart model here - // These are called by the DUXListPanelWidgetBaseModel from its setup/cleanup methods - @objc open func setup() { - - } - - // Teardown the key bindings. - @objc open func cleanup() { - - } - - // MARK: - List Crafting and Construction - @objc open func buildModelLists() { - // In concrete class, use the KeyManager to monitor which product you are connected to and adjust this list as needed, then call buildAndInstallWidgets to replace the currently list of widgets - modelClassnameList = [ - DUXSDCardRemainingCapacityListItemWidget.duxbeta_className(), - DUXRCStickModeListItemWidget.duxbeta_className(), - DUXMaxAltitudeListItemWidget.duxbeta_className(), - DUXFlightModeListItemWidget.duxbeta_className(), -// self.className(DUXPreflightMaxDistanceWidget.self) -// DUXPreflightCompassWidget, -// DUXPreflightIMUWidget, -// DUXPreflightESCWidget, -// DUXPreflightVisionWidget -// DUXRadioQualityChecklistItem -// DUXAircraftBatteryChecklistItem -// DUXAircraftBatteryTemperatureChecklistItem -// DUXGimbalChecklistItem -// DUXStorageCapacityChecklistItem -// DUXStorageCapacityChecklistItem -// DUXTravelModeChecklistItem - ] - } - - // MARK: - Widget Custom Initialization - @objc open func customizeWidgetSetup(widget: DUXBetaBaseWidget) { - // For custom customizations of differnt widgets, this method can be overridden - // and any additional customization for select widgets can be done after they are created - } - - // MARK: - Internal functions which may be overridden - @objc open func buildAndInstallWidgets() { - // Decide which list to use. Base class just uses the modelClassnameList. Specialized - // ListSmartModels may overide this method to select different lists based on connected - // aircraft or other customizations - for className in modelClassnameList { - let classInst = NSClassFromString(className) as? NSObject.Type - // See if in blacklist and prevent adding if it is in there - if !self.blacklist.contains(className) { - if let widget = classInst!.init() as? DUXBetaBaseWidget { - customizeWidgetSetup(widget: widget) - totalWidgetArray.append(widget) - // Need to do standard KVO for this since we aren't holding the widget in - // a path accessible manner for this model class so there is no valid - // key path from this object to the widget property - widget.addObserver(self, forKeyPath: "view.hidden", options: .new, context: nil) - - activeWidgetArray.append(widget) - } - } - } - - } - - @objc open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { - if let _ = object as? DUXBetaBaseWidget { - if keyPath == "view.hidden" { - // Visibility changed. Update the visible widget list - self.buildVisibleWidgetList() - if let cm = containerModel { - cm.setWidgetsArray(activeWidgetArray) - } - } else { - print("Bad keyPath observed (\(String(describing: keyPath))) in ListSmartModel") - } - } else { - print("Bad observing for non-widget (\(String(describing:object)) in ListSmartModel") - } - } - - // This is a shorter rebuild version of the buildAndInstallWidgets which only rebuilds - // the visible widdet list. - @objc open func buildVisibleWidgetList() { - var newVisibleList: [DUXBetaBaseWidget] = [DUXBetaBaseWidget]() - for widget in totalWidgetArray { - if !widget.view.isHidden { - newVisibleList.append(widget) - } - } - activeWidgetArray = newVisibleList - } - - // MARK: - Internal functions to not be overridden - internal func findIndexByWidgetID(widgetID : String, list: [DUXBetaBaseWidget]) -> (Bool, Int) { - for (index, aWidget) in list.enumerated() { - let fullString = aWidget.duxbeta_className() - let className = String(fullString.split(separator: ".").last!) - if className == widgetID { - return (true, index) - } - } - return (false, NSNotFound) - } - - // This method returns the class name with no module attached to the name - internal func className(_ some: Any) -> String { - return (some is Any.Type) ? "\(some)" : "\(type(of: some))" - } - -} diff --git a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemEditTextButtonWidget.h b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemEditTextButtonWidget.h similarity index 68% rename from DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemEditTextButtonWidget.h rename to DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemEditTextButtonWidget.h index 5050657..4e03f08 100644 --- a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemEditTextButtonWidget.h +++ b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemEditTextButtonWidget.h @@ -1,9 +1,11 @@ // -// DUXListItemEditTextButtonWidget.h +// DUXBetaListItemEditTextButtonWidget.h // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -23,45 +25,45 @@ // SOFTWARE. // -#import +#import NS_ASSUME_NONNULL_BEGIN /** - * @brief DUXTextInputChangeBlock is an executable block prototype for a block which will be called when the text in this widget's + * @brief DUXBetaTextInputChangeBlock is an executable block prototype for a block which will be called when the text in this widget's * text edit field changes. * @param newText The updated text from the editText field */ -typedef void(^DUXTextInputChangeBlock)(NSString *newText); +typedef void(^DUXBetaTextInputChangeBlock)(NSString *newText); /** -* @brief DUXKeyBoardChangedState is an executable block prototype for a block which will be called when iOS keyboard changes +* @brief DUXBetaKeyBoardChangedState is an executable block prototype for a block which will be called when iOS keyboard changes * state (is shown or hidden). * @param isActive If the keyboard is now visible. */ -typedef void(^DUXKeyBoardChangedState)(BOOL isActive); +typedef void(^DUXBetaKeyBoardChangedState)(BOOL isActive); /** -* @enum DUXListItemLabelWidgetType -* @brief DUXListItemLabelWidgetType is used to define the layout and elements of the DUXListItemEditTextButtonWidget. +* @enum DUXBetaListItemLabelWidgetType +* @brief DUXBetaListItemLabelWidgetType is used to define the layout and elements of the DUXBetaListItemEditTextButtonWidget. * -* @field DUXListItemOnlyEdit - An edit field with a hint label at the trailing edge of the widget. -* @field DUXListItemEditAndButton - An action button centered and an edit field and hint label at the trailing edge of the widget. +* @field DUXBetaListItemOnlyEdit - An edit field with a hint label at the trailing edge of the widget. +* @field DUXBetaListItemEditAndButton - An action button centered and an edit field and hint label at the trailing edge of the widget. */ -typedef NS_ENUM(NSInteger, DUXListItemEditTextButtonWidgetType) { - DUXListItemOnlyEdit, - DUXListItemEditAndButton +typedef NS_ENUM(NSInteger, DUXBetaListItemEditTextButtonWidgetType) { + DUXBetaListItemOnlyEdit, + DUXBetaListItemEditAndButton }; /** - * @class DUXListItemEditTextButtonWidget is a list item widget which supplies an edit text field at the trailing edge of + * @class DUXBetaListItemEditTextButtonWidget is a list item widget which supplies an edit text field at the trailing edge of * the widget, with a hint field to the left of the edit field. It allows an optional action button centered in the widget. * The options for creating the widget are: * - * DUXListItemOnlyEdit - An edit field with a hint label at the trailing edge of the widget. - * DUXListItemEditAndButton - An action button centered and an edit field and hint label at the trailing edge of the widget. + * DUXBetaListItemOnlyEdit - An edit field with a hint label at the trailing edge of the widget. + * DUXBetaListItemEditAndButton - An action button centered and an edit field and hint label at the trailing edge of the widget. */ -@interface DUXListItemEditTextButtonWidget : DUXListItemTitleWidget +@interface DUXBetaListItemEditTextButtonWidget : DUXBetaListItemTitleWidget /// Flag indicating if the edit field is enabled for editing @property (nonatomic, readwrite) BOOL enableEditField; // This should also be tied to isProductConnected in the attached model /// The actual text editing field @@ -73,12 +75,19 @@ typedef NS_ENUM(NSInteger, DUXListItemEditTextButtonWidgetType) { @property (nonatomic, strong) NSString *hintText; /// Flag indicating if the action button is enabled. @property (nonatomic, readwrite) BOOL buttonEnabled; +/// Flag indicating if the button should be moved to the edge if the text editing field is hidden +@property (nonatomic, readwrite) BOOL dynamicButtonAdjustment; /// Executable code block which will be called when the keyboard appears or disappears on screen -@property (nonatomic, strong) DUXKeyBoardChangedState keyboardChangedStatusBlock; +@property (nonatomic, strong) DUXBetaKeyBoardChangedState keyboardChangedStatusBlock; -/// The color to used for the text in the edit text field when enabled +/// The color to be used for the text in the edit text field when enabled @property (nonatomic, strong) UIColor *editTextColor; +/// The color to be used for the text in the edit text field when enabled and entry is valid +@property (nonatomic, strong) UIColor *editTextValidColor; +/// The color to be used for the text in the edit text field when enabled and entry is invalid +@property (nonatomic, strong) UIColor *editTextInvalidColor; + /// The border color for the edit text field when enabled @property (nonatomic, strong) UIColor *editTextBorderColor; /// The text color for the edit text field contents when editing is disabled @@ -99,10 +108,10 @@ typedef NS_ENUM(NSInteger, DUXListItemEditTextButtonWidgetType) { /** * @brief This init method for the class is used to define which layout is desired for the widget -* @param widgetStyle The DUXListItemEditTextButtonWidgetType to use for selecting the layout -* @return a DUXListItemEditTextButtonWidget instance +* @param widgetStyle The DUXBetaListItemEditTextButtonWidgetType to use for selecting the layout +* @return a DUXBetaListItemEditTextButtonWidget instance */ -- (instancetype)init:(DUXListItemEditTextButtonWidgetType)widgetStyle; +- (instancetype)init:(DUXBetaListItemEditTextButtonWidgetType)widgetStyle; /** * @brief The setButtonTitle method sets the title of the optional action button if available based on the layout settings. @@ -110,6 +119,30 @@ typedef NS_ENUM(NSInteger, DUXListItemEditTextButtonWidgetType) { */ - (void)setButtonTitle:(NSString*)newButtonTitle; +/** +* @brief The setButtonHidden method hides or shows the action button. +* @param isHidden A boolean indicating if the button should be hidden. +*/ +- (void)setButtonHidden:(BOOL)isHidden; + +/** +* @brief The hideInputAndHint method hides/shows both the input edit text field and the hint label. It also disables the edit text field. +* @param doHide The boolean indicating if hiding is YES or NO. +*/ +- (void)hideInputAndHint:(BOOL)doHide; + +/** +* @brief The hideInputField method hides/shows and enables/disables only input edit text field. +* @param doHide The boolean indicating if hiding is YES or NO. +*/ +- (void)hideInputField:(BOOL)doHide; + +/** +* @brief The hideHintLabel method hides/shows the hint label. +* @param doHide The boolean indicating if hiding is YES or NO. +*/ +- (void)hideHintLabel:(BOOL)doHide; + /** * @brief The setEditText method sets the text in the edit text field. * @param editFieldText The string to put into the edit text field. @@ -147,7 +180,7 @@ typedef NS_ENUM(NSInteger, DUXListItemEditTextButtonWidgetType) { * The executable block takes a single parameter, an NSString with the new edit text field value. * @param newBlock The next executable block to call when the text field changes. */ -- (void)setTextChangedBlock:(DUXTextInputChangeBlock)newBlock; +- (void)setTextChangedBlock:(DUXBetaTextInputChangeBlock)newBlock; /** * @brief Call setButtonAction to install a new action button code block that is executed when the action button is pressed. @@ -167,7 +200,7 @@ typedef NS_ENUM(NSInteger, DUXListItemEditTextButtonWidgetType) { #pragma mark - Hooks /** - * ListItemEditTextUIState contains the hooks for UI changes in the widget class DUXListItemEditTextButtonWidget. + * ListItemEditTextUIState contains the hooks for UI changes in the widget class DUXBetaListItemEditTextButtonWidget. * It inherits all UI hooks in ListItemTitleUIState and adds: * * Key: editBeginsUpdate Type: NSNumber - Always sends YES as an NSNumber. @@ -183,7 +216,6 @@ typedef NS_ENUM(NSInteger, DUXListItemEditTextButtonWidgetType) { + (instancetype)editBeginsUpdate; + (instancetype)editEndsUpdate; - + (instancetype)buttonTapped; + (instancetype)buttonStateChanged:(BOOL)newState; diff --git a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemEditTextButtonWidget.m b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemEditTextButtonWidget.m similarity index 71% rename from DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemEditTextButtonWidget.m rename to DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemEditTextButtonWidget.m index 62099e2..2725d33 100644 --- a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemEditTextButtonWidget.m +++ b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemEditTextButtonWidget.m @@ -1,9 +1,11 @@ // -// DUXListItemEditTextButtonWidget.m +// DUXBetaListItemEditTextButtonWidget.m // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -23,7 +25,7 @@ // SOFTWARE. // -#import "DUXListItemEditTextButtonWidget.h" +#import "DUXBetaListItemEditTextButtonWidget.h" @import DJIUXSDKCore; @@ -31,16 +33,16 @@ static const CGFloat kDesignHeightActionButtonMinimum = 28.0; static CGFloat listItemAtionButtonSideMargin = 8.0; -@interface DUXListItemEditTextButtonWidget () +@interface DUXBetaListItemEditTextButtonWidget () @property (nonatomic, assign) BOOL isReturnPress; @property (nonatomic, assign) NSInteger minLimit; @property (nonatomic, assign) NSInteger maxLimit; @property (nonatomic, assign) CGFloat editFieldWidthHint; -@property (nonatomic, strong) DUXTextInputChangeBlock changeBlock; +@property (nonatomic, strong) DUXBetaTextInputChangeBlock changeBlock; @property (nonatomic, strong) GenericButtonActionBlock buttonActionBlock; -@property (nonatomic, strong) DUXTextInputChangeBlock internalTextChangeBlockWrapper; +@property (nonatomic, strong) DUXBetaTextInputChangeBlock internalTextChangeBlockWrapper; @property (nonatomic, strong) NSString *editTextString; @property (nonatomic, strong) NSString *originalFieldString; @@ -52,13 +54,21 @@ @interface DUXListItemEditTextButtonWidget () @property (nonatomic, strong) NSLayoutConstraint *buttonWidthConstraint; +// This may be eiter centerXAnchor or rightAnchor depending on what else is currently visible +@property (nonatomic, strong) NSLayoutConstraint *buttonPositionConstraint; + +@property (nonatomic, assign) DUXBetaListItemEditTextButtonWidgetType widgetStyle; + +@property (nonatomic, readwrite) BOOL editTextIsValid; + @end -@implementation DUXListItemEditTextButtonWidget +@implementation DUXBetaListItemEditTextButtonWidget // If somebody doesn't call the specialized setter version, just give the edit without button version - (instancetype)init { if (self = [super init]) { + _widgetStyle = DUXBetaListItemOnlyEdit; _hasButton = NO; } return self; @@ -67,14 +77,16 @@ - (instancetype)init { - (instancetype)initWithCoder:(NSCoder *)coder { self = [super initWithCoder:coder]; if (self) { + _widgetStyle = DUXBetaListItemOnlyEdit; _hasButton = NO; } return self; } -- (instancetype)init:(DUXListItemEditTextButtonWidgetType)widgetStyle { +- (instancetype)init:(DUXBetaListItemEditTextButtonWidgetType)widgetStyle { if (self = [super init]) { - _hasButton = (widgetStyle == DUXListItemEditAndButton); + _widgetStyle = widgetStyle; + _hasButton = (widgetStyle == DUXBetaListItemEditAndButton); } return self; } @@ -82,6 +94,9 @@ - (instancetype)init:(DUXListItemEditTextButtonWidgetType)widgetStyle { - (void)setupCustomizableSettings { [super setupCustomizableSettings]; _editTextColor = [UIColor duxbeta_linkBlueColor]; + + _editTextValidColor = [UIColor duxbeta_linkBlueColor]; + _editTextInvalidColor = [UIColor duxbeta_dangerColor]; _editTextBorderColor = [UIColor duxbeta_whiteColor]; _editTextDisabledColor = [UIColor duxbeta_lightGrayColor]; _editTextFont = [UIFont systemFontOfSize:14.0]; @@ -102,18 +117,32 @@ - (void)viewDidLoad { _editFieldWidthHint = 0; self.enableEditField = YES; + self.editTextIsValid = YES; + self.dynamicButtonAdjustment = YES; + } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self updateUI]; - BindRKVOModel(self, @selector(editEnabledChanged), enableEditField); - BindRKVOModel(self, @selector(updateButtonUI), buttonColors, buttonFont, - buttonCornerRadius, buttonBorderWidth, buttonBorderColors); + buttonCornerRadius, buttonBorderWidth, buttonBorderColors); BindRKVOModel(self, @selector(updateHintUI), hintBackgroundColor, hintTextColor, hintTextFont); - BindRKVOModel(self, @selector(updateEditTextUI), editTextColor, editTextBorderColor, editTextDisabledColor, editTextFont, editTextCornerRadius, editTextBorderWidth); + BindRKVOModel(self, @selector(updateEditTextUI), editTextBorderColor, editTextDisabledColor, + editTextFont, editTextCornerRadius, editTextBorderWidth); + BindRKVOModel(self, @selector(editTextColorUpdated), editTextColor); + // Due to KVO calls happening as soon as the model is is bound, the base editTextColor must be set/bound before + // Any of the optional valid/invali colors are bound, or even the validity check is bound. + BindRKVOModel(self, @selector(updateEditTextConditionalColorsUI), editTextValidColor, editTextInvalidColor); + BindRKVOModel(self, @selector(textValidityChanged), editTextIsValid); + BindRKVOModel(self, @selector(editEnabledChanged), enableEditField); + + + if (_widgetStyle == DUXBetaListItemEditAndButton) { + BindRKVOModel(self, @selector(updateButtonPositioning), inputField.isHidden); + + } } - (void)viewWillDisappear:(BOOL)animated { @@ -122,9 +151,11 @@ - (void)viewWillDisappear:(BOOL)animated { } - (void)editEnabledChanged { - if (self.inputField.enabled != self.enableEditField) { - [self updateEditTextUI]; - } + [self updateEditTextUI]; +} + +- (void)textValidityChanged { + self.editTextColor = _editTextIsValid ? self.editTextValidColor : self.editTextInvalidColor; } - (instancetype)setButtonAction:(GenericButtonActionBlock)actionBlock { @@ -137,14 +168,29 @@ - (GenericButtonActionBlock)getButtonAction { } - (IBAction)buttonPush { - [DUXStateChangeBroadcaster send:[ListItemEditTextUIState buttonTapped]]; + [DUXBetaStateChangeBroadcaster send:[ListItemEditTextUIState buttonTapped]]; if (self.buttonActionBlock) { self.buttonActionBlock(self); } } -- (void)setTextChangedBlock:(DUXTextInputChangeBlock)newBlock { +- (void)hideInputAndHint:(BOOL)doHide { + self.inputField.hidden = doHide; + self.hintTextLabel.hidden = doHide; + [self updateButtonPositioning]; +} + +- (void)hideInputField:(BOOL)doHide { + self.inputField.hidden = doHide; + [self updateButtonPositioning]; +} + +- (void)hideHintLabel:(BOOL)doHide { + self.hintTextLabel.hidden = doHide; +} + +- (void)setTextChangedBlock:(DUXBetaTextInputChangeBlock)newBlock { _changeBlock = newBlock; } @@ -234,7 +280,15 @@ - (void)setupUI { self.inputField.delegate = self; if (_hasButton) { - [_actionButton.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor].active = YES; + if (_buttonPositionConstraint) { + _buttonPositionConstraint.active = NO; + } + if (_widgetStyle == DUXBetaListItemEditAndButton) { + _buttonPositionConstraint = [_actionButton.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor]; + } else { + _buttonPositionConstraint = [_actionButton.trailingAnchor constraintEqualToAnchor:self.trailingMarginGuide.trailingAnchor constant:0.0]; + } + _buttonPositionConstraint.active = YES; [_actionButton.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor].active = YES; [_actionButton.heightAnchor constraintEqualToConstant:kDesignHeightActionButtonMinimum].active = YES; _buttonWidthConstraint = [_actionButton.widthAnchor constraintEqualToConstant:[self.actionButton intrinsicContentSize].width + listItemAtionButtonSideMargin * 2]; @@ -260,11 +314,11 @@ - (void)setupUI { self.inputField.text = self.editTextString; } - __weak DUXListItemEditTextButtonWidget *weakSelf = self; + __weak DUXBetaListItemEditTextButtonWidget *weakSelf = self; self.internalTextChangeBlockWrapper = ^(NSString *newText) { NSInteger newHeight = [newText intValue]; if (newHeight > 0) { - __strong DUXListItemEditTextButtonWidget *strongSelf = weakSelf; + __strong DUXBetaListItemEditTextButtonWidget *strongSelf = weakSelf; if (strongSelf) { if (strongSelf.changeBlock) { strongSelf.changeBlock(newText); @@ -274,24 +328,51 @@ - (void)setupUI { }; } +- (void)updateButtonPositioning { + if ((_widgetStyle == DUXBetaListItemEditAndButton) && _dynamicButtonAdjustment) { + _buttonPositionConstraint.active = NO; + if (self.inputField.isHidden) { + // Button needs to move to the right edge + _buttonPositionConstraint = [_actionButton.trailingAnchor constraintEqualToAnchor:self.trailingMarginGuide.trailingAnchor constant:0.0]; + } else { + // Button needs to be centered + _buttonPositionConstraint = [_actionButton.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor]; + } + _buttonPositionConstraint.active = YES; + } +} + +- (void)updateEditTextConditionalColorsUI { + // One of the contitional colors changed and those are used for the edit text color. + [self textValidityChanged]; + [self updateEditTextUI]; +} + +- (void)editTextColorUpdated { + // The standard edit text color was changed but may be overwritten by validation code + // when validating/invalidating the edit field. + [self updateEditTextUI]; +} + - (void)updateEditTextUI { [super updateUI]; - + self.inputField.font = self.editTextFont; self.inputField.layer.cornerRadius = self.editTextCornerRadius; - self.inputField.layer.borderWidth = self.editTextBorderWidth; if (self.inputField.enabled != self.enableEditField) { self.inputField.enabled = self.enableEditField; - if (self.enableEditField) { - self.inputField.layer.borderColor = [self.editTextBorderColor CGColor]; - self.inputField.textColor = self.editTextColor; - [self.inputField reloadInputViews]; - } else { - self.inputField.layer.borderColor = [self.editTextDisabledColor CGColor]; - self.inputField.textColor = self.editTextDisabledColor; - [self.inputField reloadInputViews]; - } + } + if (self.enableEditField) { + self.inputField.layer.borderColor = [self.editTextBorderColor CGColor]; + self.inputField.layer.borderWidth = self.editTextBorderWidth; + self.inputField.textColor = self.editTextColor; + [self.inputField reloadInputViews]; + + } else { + self.inputField.layer.borderColor = [self.editTextDisabledColor CGColor]; + self.inputField.textColor = self.editTextDisabledColor; + [self.inputField reloadInputViews]; } } @@ -316,6 +397,17 @@ - (void)updateButtonUI { } } +- (void)setButtonEnabled:(BOOL)makeEnabled { + _buttonEnabled = makeEnabled; + _actionButton.enabled = makeEnabled; + [self updateButtonUI]; +} + +- (void)setButtonHidden:(BOOL)isHidden { + _actionButton.hidden = isHidden; + [self updateButtonUI]; +} + - (void)setButtonTitle:(NSString*)newButtonTitle { _buttonTitle = newButtonTitle; if (self.actionButton) { @@ -333,6 +425,7 @@ - (void)setEditText:(NSString*)editFieldText { _editTextString = editFieldText; if (self.inputField) { self.inputField.text = _editTextString; + self.editTextIsValid = [self validateInputString:_editTextString]; } } @@ -354,6 +447,10 @@ - (void)setEditFieldWidth:(CGFloat)editWidth { - (void)setEditFieldValuesMin:(NSInteger)minValue maxValue:(NSInteger)maxValue { _minLimit = minValue; _maxLimit = maxValue; + + if ((self.enableEditField) && (self.inputField.text.length > 0)) { + [self validateInputString:self.inputField.text]; + } } - (DUXBetaWidgetSizeHint)widgetSizeHint { @@ -363,7 +460,7 @@ - (DUXBetaWidgetSizeHint)widgetSizeHint { // MARK: Support for text field. - (void)textFieldDidBeginEditing:(UITextField *)textField { - [DUXStateChangeBroadcaster send:[ListItemEditTextUIState editBeginsUpdate]]; + [DUXBetaStateChangeBroadcaster send:[ListItemEditTextUIState editBeginsUpdate]]; self.isReturnPress = NO; self.originalFieldString = [textField.text copy]; // Keep this in case they end editing with an invalid value. @@ -374,13 +471,16 @@ - (void)textFieldDidBeginEditing:(UITextField *)textField { - (void)textFieldDidEndEditing:(UITextField *)textField{ textField.layer.borderWidth = self.editTextBorderWidth; // This is to fix the borderWidth getting reset on a double tap - [DUXStateChangeBroadcaster send:[ListItemEditTextUIState editEndsUpdate]]; + [DUXBetaStateChangeBroadcaster send:[ListItemEditTextUIState editEndsUpdate]]; + if ([self validateString:textField.text]) { textField.text = [NSString stringWithFormat:@"%d",textField.text.intValue]; self.internalTextChangeBlockWrapper(textField.text); } else { textField.text = _originalFieldString; } + + self.editTextIsValid = YES; // The edit field guarantees to have put a valid value back in the edit field } - (BOOL)textFieldShouldReturn:(UITextField *)textField { @@ -395,14 +495,15 @@ - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRang NSMutableString *inputString = [NSMutableString stringWithString:textField.text]; [inputString replaceCharactersInRange:range withString:string]; if (inputString.length > 0) { - return [self validateInputString:inputString]; + self.editTextIsValid = [self validateInputString:inputString]; } return YES; } - (BOOL)validateInputString:(NSString *)string{ if ([self isTextFieldUnsignedIntValue:string]) { - if (self.maxLimit && (string.intValue > self.maxLimit)) { + // A min limit of 0 is a valid min limit unless explicitly changed, so don't check to see if it has a non-zero value + if ((self.maxLimit && (string.intValue > self.maxLimit)) || (string.intValue < self.minLimit)) { return NO; } } else { @@ -434,9 +535,9 @@ - (void)doneClicked:(UIButton *) button { // the flag for the action button. Override this in the custom model class for a specific // widget. - (GenericButtonActionBlock)sampleActionBlock { - __weak DUXListItemEditTextButtonWidget *weakSelf = self; - return ^(DUXListItemEditTextButtonWidget* classInstance) { - __strong DUXListItemEditTextButtonWidget *strongSelf = weakSelf; + __weak DUXBetaListItemEditTextButtonWidget *weakSelf = self; + return ^(DUXBetaListItemEditTextButtonWidget* classInstance) { + __strong DUXBetaListItemEditTextButtonWidget *strongSelf = weakSelf; if (strongSelf) { strongSelf.buttonEnabled = NO; diff --git a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemEditTextButtonWidgetModel.h b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemEditTextButtonWidgetModel.h new file mode 100644 index 0000000..588cab4 --- /dev/null +++ b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemEditTextButtonWidgetModel.h @@ -0,0 +1,43 @@ +// +// DUXBetaListItemEditTextButtonWidgetModel.h +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// + +#import +#import +#import "DUXBetaBaseWidgetModel+Protected.h" +@import DJIUXSDKCore; + +NS_ASSUME_NONNULL_BEGIN + +/** + * @class DUXBetaListItemEditTextButtonWidgetModel is a simple class descending from DUXBetaBaseWidgetModel. It is a placeholder for subclasses for + * UXListItemEditTextButtonWidgets to create model objects from. + */ +@interface DUXBetaListItemEditTextButtonWidgetModel : DUXBetaBaseWidgetModel + +@end + +NS_ASSUME_NONNULL_END diff --git a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemEditTextButtonWidgetModel.m b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemEditTextButtonWidgetModel.m similarity index 89% rename from DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemEditTextButtonWidgetModel.m rename to DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemEditTextButtonWidgetModel.m index 5f35380..ae5a501 100644 --- a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemEditTextButtonWidgetModel.m +++ b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemEditTextButtonWidgetModel.m @@ -1,9 +1,11 @@ // -// DUXListItemEditTextButtonWidgetModel.m +// DUXBetaListItemEditTextButtonWidgetModel.m // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -23,11 +25,11 @@ // SOFTWARE. // -#import +#import #import "DUXBetaBaseWidgetModel+Protected.h" @import DJIUXSDKCore; -@interface DUXListItemEditTextButtonWidgetModel () +@interface DUXBetaListItemEditTextButtonWidgetModel () @property (nonatomic, strong) DJIKey* keyForField; @property (nonatomic, strong) DJIKey *rangeKey; @property (nonatomic, strong) DJIKey *enabledKey; // If nil, don't show enabled button @@ -36,7 +38,7 @@ @interface DUXListItemEditTextButtonWidgetModel () @end -@implementation DUXListItemEditTextButtonWidgetModel +@implementation DUXBetaListItemEditTextButtonWidgetModel // TODO: Flesh this out to handle more than just nemeric fields eventually // Right now this code in the model is unused. diff --git a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemLabelButtonWidget.h b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemLabelButtonWidget.h similarity index 70% rename from DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemLabelButtonWidget.h rename to DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemLabelButtonWidget.h index e817a38..2eb9151 100644 --- a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemLabelButtonWidget.h +++ b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemLabelButtonWidget.h @@ -1,9 +1,11 @@ // -// DUXListItemLabelButtonWidget.h +// DUXBetaListItemLabelButtonWidget.h // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -23,46 +25,46 @@ // SOFTWARE. // -#import +#import NS_ASSUME_NONNULL_BEGIN /** - * @enum DUXListItemLabelWidgetType - * @brief DUXListItemLabelWidgetType is used to define the layout and elements of the DUXListItemLabelButtonWidget. + * @enum DUXBetaListItemLabelWidgetType + * @brief DUXBetaListItemLabelWidgetType is used to define the layout and elements of the DUXBetaListItemLabelButtonWidget. * - * @field DUXListItemLabelOnly - A label for information at the trailing edge of the widget. - * @field DUXListItemButtonOnly - An action button at the trailing edge of the widget. - * @field DUXListITemLabelAndButton - A label for information at the trailing edge of the widget and an action button centered in the widget. + * @field DUXBetaListItemLabelOnly - A label for information at the trailing edge of the widget. + * @field DUXBetaListItemButtonOnly - An action button at the trailing edge of the widget. + * @field DUXBetaListITemLabelAndButton - A label for information at the trailing edge of the widget and an action button centered in the widget. */ -typedef NS_ENUM(NSInteger, DUXListItemLabelWidgetType) { - DUXListItemLabelOnly, - DUXListItemButtonOnly, - DUXListItemLabelAndButton +typedef NS_ENUM(NSInteger, DUXBetaListItemLabelWidgetType) { + DUXBetaListItemLabelOnly, + DUXBetaListItemButtonOnly, + DUXBetaListItemLabelAndButton }; /** -* @protocol: All subclasses of DUXListItemLabelButtonWidget must conform to this protocol! +* @protocol: All subclasses of DUXBetaListItemLabelButtonWidget must conform to this protocol! * -* Preferred method is to create a DUXBaseWidgetModel property called widgetModel. +* Preferred method is to create a DUXBetaBaseWidgetModel property called widgetModel. */ -@protocol DUXListItemModelProducer +@protocol DUXBetaListItemModelProducer - (DUXBetaBaseWidgetModel *)widgetModel; @end /** - * @class: DUXListItemLabelButtonWidget is a list item widget descending from DUXListItemTitleWidget. It supports three - * visual layouts, defined by DUXListItemLabelWidgetType enum. + * @class: DUXBetaListItemLabelButtonWidget is a list item widget descending from DUXBetaListItemTitleWidget. It supports three + * visual layouts, defined by DUXBetaListItemLabelWidgetType enum. * - * DUXListItemLabelOnly - A label for information at the trailing edge of the widget. - * DUXListItemButtonOnly - An action button at the trailing edge of the widget. - * DUXListITemLabelAndButton - A label for information at the trailing edge of the widget and an action button centered in the widget. + * DUXBetaListItemLabelOnly - A label for information at the trailing edge of the widget. + * DUXBetaListItemButtonOnly - An action button at the trailing edge of the widget. + * DUXBetaListITemLabelAndButton - A label for information at the trailing edge of the widget and an action button centered in the widget. * * The action button uses an executable block to perform an action when the button is pressed. */ -@interface DUXListItemLabelButtonWidget : DUXListItemTitleWidget +@interface DUXBetaListItemLabelButtonWidget : DUXBetaListItemTitleWidget /// Flag indicating of the button is currently enabled @property (nonatomic, readwrite) BOOL buttonEnabled; /// The button which is used in the widget if the layout supplies a button @@ -78,10 +80,10 @@ typedef NS_ENUM(NSInteger, DUXListItemLabelWidgetType) { /** * @brief This init method for the class is used to define which layout is desired for the widget - * @param widgetStyle The DUXListItemLabelWidgetType to use for selecting the layout - * @return a DUXListItemLabelButtonWidget instance + * @param widgetStyle The DUXBetaListItemLabelWidgetType to use for selecting the layout + * @return a DUXBetaListItemLabelButtonWidget instance */ -- (instancetype)init:(DUXListItemLabelWidgetType)widgetStyle; +- (instancetype)init:(DUXBetaListItemLabelWidgetType)widgetStyle; /** * @brief The setButtonTitle method sets the title of the optional action button if available based on the layout settings. @@ -108,6 +110,8 @@ typedef NS_ENUM(NSInteger, DUXListItemLabelWidgetType) { - (GenericButtonActionBlock)getButtonAction; @end +#pragma mark - Hooks + /** * ListItemLabelButtonModelState contains the hooks for model for descendents of this base class. * It inherits all model hooks in ListItemTitleModelState and adds no hooks. @@ -117,7 +121,7 @@ typedef NS_ENUM(NSInteger, DUXListItemLabelWidgetType) { @end /** - * ListItemLabelButtonUIState contains common hooks for UI changes in the list item base class DUXListItemLabelButtonWidget. + * ListItemLabelButtonUIState contains common hooks for UI changes in the list item base class DUXBetaListItemLabelButtonWidget. * It inherits all UI hooks in ListItemTitleUIState and adds: * * Key: buttonTapped Type: NSNumber - Always sends YES as an NSNumber when the actionButton is tapped diff --git a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemLabelButtonWidget.m b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemLabelButtonWidget.m similarity index 88% rename from DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemLabelButtonWidget.m rename to DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemLabelButtonWidget.m index 7a6965d..6510b71 100644 --- a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemLabelButtonWidget.m +++ b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemLabelButtonWidget.m @@ -1,9 +1,11 @@ // -// DUXListItemLabelButtonWidget.m +// DUXBetaListItemLabelButtonWidget.m // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -23,13 +25,13 @@ // SOFTWARE. // -#import "DUXListItemLabelButtonWidget.h" +#import "DUXBetaListItemLabelButtonWidget.h" @import DJIUXSDKCore; static CGFloat listItemAtionButtonSideMargin = 8.0; static const CGFloat kDesignHeightActionButtonMinimum = 28.0; -@interface DUXListItemLabelButtonWidget () +@interface DUXBetaListItemLabelButtonWidget () @property (nonatomic, strong) UIButton *actionButton; @property (nonatomic, strong) UILabel *displayTextLabel; @@ -44,9 +46,9 @@ @interface DUXListItemLabelButtonWidget () @property (nonatomic, strong) NSLayoutConstraint *buttonWidthConstraint; @end -@implementation DUXListItemLabelButtonWidget +@implementation DUXBetaListItemLabelButtonWidget -- (instancetype)init:(DUXListItemLabelWidgetType)widgetStyle { +- (instancetype)init:(DUXBetaListItemLabelWidgetType)widgetStyle { if (self = [super init]) { [self setupWidgetStyle: widgetStyle]; } @@ -58,14 +60,14 @@ - (instancetype)init:(DUXListItemLabelWidgetType)widgetStyle { - (instancetype)initWithCoder:(NSCoder *)coder { self = [super initWithCoder:coder]; if (self) { - [self setupWidgetStyle: DUXListItemLabelAndButton]; + [self setupWidgetStyle: DUXBetaListItemLabelAndButton]; } return self; } -- (void)setupWidgetStyle:(DUXListItemLabelWidgetType)widgetStyle { - _hasButton = (widgetStyle == DUXListItemButtonOnly) || (widgetStyle == DUXListItemLabelAndButton); - _hasLabel = (widgetStyle == DUXListItemLabelOnly) || (widgetStyle == DUXListItemLabelAndButton); +- (void)setupWidgetStyle:(DUXBetaListItemLabelWidgetType)widgetStyle { + _hasButton = (widgetStyle == DUXBetaListItemButtonOnly) || (widgetStyle == DUXBetaListItemLabelAndButton); + _hasLabel = (widgetStyle == DUXBetaListItemLabelOnly) || (widgetStyle == DUXBetaListItemLabelAndButton); if (_hasButton) { self.buttonEnabled = YES; } @@ -73,16 +75,11 @@ - (void)setupWidgetStyle:(DUXListItemLabelWidgetType)widgetStyle { - (void)setupCustomizableSettings { [super setupCustomizableSettings]; - _labelFont = [UIFont systemFontOfSize:[UIFont smallSystemFontSize]]; + _labelFont = [UIFont systemFontOfSize:14.0]; _labelTextColorNormal = [self normalColor]; _labelTextColorDisconnected = [self disabledColor]; } -- (DUXBetaBaseWidgetModel *)widgetModel { - [[[NSException alloc] initWithName:NSGenericException reason:@"Derived classes must have a widget model" userInfo:nil] raise]; - return [[DUXBetaBaseWidgetModel alloc] init]; -} - - (void)viewDidLoad { [super viewDidLoad]; self.view.translatesAutoresizingMaskIntoConstraints = NO; @@ -122,7 +119,7 @@ - (void)setLabelText:(NSString *)labelText { _labelText = labelText; if (self.displayTextLabel) { self.displayTextLabel.text = labelText; - [DUXStateChangeBroadcaster send:[ListItemLabelButtonUIState displayStringUpdated:self.displayString]]; + [DUXBetaStateChangeBroadcaster send:[ListItemLabelButtonUIState displayStringUpdated:self.displayString]]; } } } @@ -130,7 +127,7 @@ - (void)setLabelText:(NSString *)labelText { #pragma mark - Action Button Support - (void)buttonEnabledChanged { - [DUXStateChangeBroadcaster send:[ListItemLabelButtonUIState enabledButtonStateChanged:self.buttonEnabled]]; + [DUXBetaStateChangeBroadcaster send:[ListItemLabelButtonUIState enabledButtonStateChanged:self.buttonEnabled]]; [self updateUI]; } @@ -144,7 +141,7 @@ - (GenericButtonActionBlock)getButtonAction { } - (IBAction)buttonPush { - [DUXStateChangeBroadcaster send:[ListItemLabelButtonUIState buttonTapped]]; + [DUXBetaStateChangeBroadcaster send:[ListItemLabelButtonUIState buttonTapped]]; if (self.buttonActionBlock) { self.buttonActionBlock(self); @@ -240,6 +237,11 @@ - (void)updateButton { } } +- (DUXBetaBaseWidgetModel *)widgetModel { + [[[NSException alloc] initWithName:NSGenericException reason:@"Derived classes must have a widget model" userInfo:nil] raise]; + return [[DUXBetaBaseWidgetModel alloc] init]; +} + - (void)updateLabel { if (!self.hasLabel) { return; @@ -256,9 +258,9 @@ - (void)updateLabel { // the flag for the action button. Override this in the custom model class for a specific // widget. - (GenericButtonActionBlock)sampleActionBlock { - __weak DUXListItemLabelButtonWidget *weakSelf = self; - return ^(DUXListItemLabelButtonWidget* classInstance) { - __strong DUXListItemLabelButtonWidget *strongSelf = weakSelf; + __weak DUXBetaListItemLabelButtonWidget *weakSelf = self; + return ^(DUXBetaListItemLabelButtonWidget* classInstance) { + __strong DUXBetaListItemLabelButtonWidget *strongSelf = weakSelf; if (strongSelf) { strongSelf.displayTextLabel.text = NSLocalizedString(@"Ready", @"Ready"); @@ -271,21 +273,19 @@ - (GenericButtonActionBlock)sampleActionBlock { /* This method must be overridden by the actual concrete class */ -- (NSString *)displayString { +- (NSString*)displayString { return @"Needs Override"; } #pragma mark - Update Helpers - (void)displayTextUpdated { - [DUXStateChangeBroadcaster send:[ListItemLabelButtonUIState displayStringUpdated:self.displayString]]; + [DUXBetaStateChangeBroadcaster send:[ListItemLabelButtonUIState displayStringUpdated:self.displayString]]; [self updateUI]; } @end -#pragma mark - Hooks - @implementation ListItemLabelButtonUIState + (instancetype)buttonTapped { diff --git a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemRadioButtonWidget.h b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemRadioButtonWidget.h similarity index 90% rename from DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemRadioButtonWidget.h rename to DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemRadioButtonWidget.h index 0c09f9b..dc87232 100644 --- a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemRadioButtonWidget.h +++ b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemRadioButtonWidget.h @@ -1,9 +1,11 @@ // -// DUXListItemRadioButtonWidget.h +// DUXBetaListItemRadioButtonWidget.h // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -23,13 +25,13 @@ // SOFTWARE. // -#import +#import NS_ASSUME_NONNULL_BEGIN typedef void(^RadioButtonOptionChanged)(NSInteger oldSelectedIndex, NSInteger newSelectedIndex); /** - * @class DUXListItemRadioButtonWidget implements a list item with a radio button option (UISegmentedControl). + * @class DUXBetaListItemRadioButtonWidget implements a list item with a radio button option (UISegmentedControl). * The radio button will call an action block when the selection changes. The radio buttons are also color customizable * and supports the same appearance under iOS 12, and iOS 13+. * When subclassing this widget, implement a custom RadioButtonOptionChanged and install it, then use the setOptionTitles or @@ -37,11 +39,14 @@ typedef void(^RadioButtonOptionChanged)(NSInteger oldSelectedIndex, NSInteger ne * * Subclasses will also need to implement a model class to process the changes to the radio options. */ -@interface DUXListItemRadioButtonWidget : DUXListItemTitleWidget +@interface DUXBetaListItemRadioButtonWidget : DUXBetaListItemTitleWidget /// The current value of the radio button selection (0...N) @property (nonatomic, readwrite) NSInteger selection; +/// The color of the text to use for the selected item in the radio control. Non-selected text uses the normal customization color +@property (nonatomic, strong) UIColor *selectedRadioTextColor; + /** * @brief Use the method setOptionTitles to replace all existing options with the strings from the passed in array. * @param newTitles An array of NSStrings to use as titles for the radio groups. @@ -52,7 +57,7 @@ typedef void(^RadioButtonOptionChanged)(NSInteger oldSelectedIndex, NSInteger ne * @brief The method addOptionToGroup will append a new option to the displayed options already in the control. * @param optionName The new option name to add. */ -- (NSInteger)addOptionToGroup:(NSString *)optionName; +- (NSInteger)addOptionToGroup:(NSString*)optionName; /** * @brief Use removeOptionFromGroup to remove an option from the radio group displayed. @@ -112,6 +117,7 @@ typedef void(^RadioButtonOptionChanged)(NSInteger oldSelectedIndex, NSInteger ne @end +#pragma mark - Hooks /** * ListItemRadioButtonModelState is an empty hook class because this base class doesn't send any UI updates. * Inherit from this class for subclassing to include all parent model hooks. diff --git a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemRadioButtonWidget.m b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemRadioButtonWidget.m similarity index 80% rename from DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemRadioButtonWidget.m rename to DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemRadioButtonWidget.m index fa0597d..4da1140 100644 --- a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemRadioButtonWidget.m +++ b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemRadioButtonWidget.m @@ -1,9 +1,11 @@ // -// DUXListItemRadioButtonWidget.m +// DUXBetaListItemRadioButtonWidget.m // DJIUXSDKWidgets -/// -// Copyright © 2018-2020 DJI // +// MIT License +// +// Copyright © 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 @@ -23,12 +25,12 @@ // SOFTWARE. // -#import "DUXListItemRadioButtonWidget.h" +#import "DUXBetaListItemRadioButtonWidget.h" #import "UIImage+DUXBetaAssets.h" #import "NSObject+DUXBetaRKVOExtension.h" @import DJIUXSDKCore; -@interface DUXListItemRadioButtonWidget () +@interface DUXBetaListItemRadioButtonWidget () @property (nonatomic, strong) UISegmentedControl *radioControl; @property (nonatomic, strong) NSLayoutAnchor *radioLeadingAnchor; @@ -40,7 +42,7 @@ @interface DUXListItemRadioButtonWidget () @property (nonatomic, strong) UIColor *tabsBackgroundColor; @property (nonatomic, strong) NSMutableDictionary *customSelectionTints; -@property (nonatomic, strong) NSMutableDictionary *customBackgroundColora; +@property (nonatomic, strong) NSMutableDictionary *customBackgroundColors; @property (nonatomic, strong) NSMutableDictionary *customDisabledColora; @property (nonatomic, strong) UIImage *baseDividerImage; // This is the raw image to be colorized into colorizedDividerImage @@ -48,7 +50,7 @@ @interface DUXListItemRadioButtonWidget () @property (nonatomic, strong) UIImage *ios13BackgroundImage; @end -@implementation DUXListItemRadioButtonWidget +@implementation DUXBetaListItemRadioButtonWidget - (instancetype)init { if (self = [super init]) { @@ -77,17 +79,27 @@ - (void)setupCustomizableSettings { [super setupCustomizableSettings]; _customSelectionTints = [NSMutableDictionary new]; - _customBackgroundColora = [NSMutableDictionary new]; - _customBackgroundColora = [NSMutableDictionary new]; + _customBackgroundColors = [NSMutableDictionary new]; _selectionTint = [UIColor duxbeta_whiteColor]; _tabsBackgroundColor = [UIColor colorWithWhite:0.0f alpha:.2f]; // Black with slight transparency + + // Some special color handling due to the way UISegementControl handles disabling the control. The selected color + // needs to be set based on the highlight color. But then the selection disappears during a disable + // so the disabled state then needs to be the normal color. And we have to adjust selected state + // during control enable disable. + // TL;DR version: these colors need to be set unsually due to the differing background color of the selected state. + _selectedRadioTextColor = [UIColor duxbeta_blackColor]; + self.buttonColors[@(UIControlStateSelected)] = [self selectedRadioTextColor]; + self.buttonColors[@(UIControlStateDisabled)] = [self normalColor]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - BindRKVOModel(self, @selector(tintUpdated), selection, radioControl.enabled); + BindRKVOModel(self, @selector(tintUpdated), selection); + BindRKVOModel(self, @selector(selectedRadioTextColorChanged), selectedRadioTextColor); + BindRKVOModel(self, @selector(enableStateChanged), radioControl.enabled); BindRKVOModel(self, @selector(backgroundTintUpdated), tabsBackgroundColor, selectionTint); BindRKVOModel(self, @selector(editingEnableChanged), isControlEnabled); } @@ -175,6 +187,11 @@ - (void)radioControlChanged:(UISegmentedControl *)theControl { } } +- (void)selectedRadioTextColorChanged { + self.buttonColors[@(UIControlStateSelected)] = [self selectedRadioTextColor]; + [self enableStateChanged]; // Juggle the selection and tint colors using the new color as needed. +} + #pragma mark - UI - (void)setupUI { if (_radioControl != nil) { @@ -196,7 +213,7 @@ - (void)setupUI { _radioControl.tintColor = _selectionTint; } - [self tintUpdated]; // The KVO won't have been installed when setupUI is first called. + [self enableStateChanged]; // The KVO won't have been installed when setupUI is first called. This also calls tintUpdated internally _radioControl.layer.borderColor = [self.buttonBorderColors[@(UIControlStateNormal)] CGColor]; _radioControl.layer.borderWidth = self.buttonBorderWidth; @@ -245,6 +262,31 @@ - (void)tintUpdated { [_radioControl setDividerImage:_colorizedDividerImage forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault]; } +- (void)enableStateChanged { + // This routine is to force changes in color for enable/disable states. When the UISegmentControl is disabled, the highlighting goes away. + // That means the selected text color probably doesn't match the unselected text color. So we have to juggle the selected color state depending + // on if the control is enabled or not. + [self tintUpdated]; + + if (_radioControl.enabled) { + [_radioControl setTitleTextAttributes:@{ NSForegroundColorAttributeName:self.buttonColors[@(UIControlStateSelected)] } + forState:UIControlStateSelected]; + } else { + [_radioControl setTitleTextAttributes:@{ NSForegroundColorAttributeName:self.buttonColors[@(UIControlStateNormal)] } + forState:UIControlStateSelected]; + } + + if (@available(iOS 13.0, *)) { + } else { + // For pre-iOS 13, remove the selection highlight + if (_radioControl.enabled) { + _radioControl.tintColor = _selectionTint; + } else { + _radioControl.tintColor = [UIColor clearColor]; + } + } +} + - (void)backgroundTintUpdated { if (@available(iOS 13.0, *)) { @@ -280,7 +322,7 @@ - (void)updateButtonTextColors { [_radioControl setTitleTextAttributes:@{ NSForegroundColorAttributeName:self.buttonColors[@(UIControlStateSelected)] } forState:UIControlStateSelected]; [_radioControl setTitleTextAttributes:@{ NSForegroundColorAttributeName: self.buttonColors[@(UIControlStateDisabled)] } - forState:UIControlStateSelected]; + forState:UIControlStateDisabled]; } - (void)editingEnableChanged { diff --git a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemSwitchWidget.h b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemSwitchWidget.h similarity index 86% rename from DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemSwitchWidget.h rename to DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemSwitchWidget.h index 0fa5eb4..a717646 100644 --- a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemSwitchWidget.h +++ b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemSwitchWidget.h @@ -1,9 +1,11 @@ // -// DUXListItemSwitchWidget.h +// DUXBetaListItemSwitchWidget.h // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -26,24 +28,25 @@ #import #import -#import "DUXListItemSwitchWidgetModel.h" +#import "DUXBetaListItemSwitchWidgetModel.h" NS_ASSUME_NONNULL_BEGIN typedef void (^SwitchChangedActionBlock)(BOOL newSwitchValue); -@interface DUXListItemSwitchWidget : DUXListItemTitleWidget +@interface DUXBetaListItemSwitchWidget : DUXBetaListItemTitleWidget @property (nonatomic, strong) IBOutlet UISwitch *onOffSwitch; @property (nonatomic, strong) UIColor *switchTintColor; +- (void)setSwitchAction:(SwitchChangedActionBlock)newBlock; - (void)switchEnabledStateChanged; @end -#pragma mark - Hooks +#pragma mark - UIState Hooks /** - * ListItemSwitchUIState contains the hooks for UI changes in the widget class DUXListItemEditTextButtonWidget. + * ListItemSwitchUIState contains the hooks for UI changes in the widget class DUXBetaListItemEditTextButtonWidget. * It inherits all UI hooks in ListItemTitleUIState and adds: * * Key: switchValueChanged Type: NSNumber - Sends a boolean value as an NSNumber to indicate the new state of the option switch. @@ -52,10 +55,8 @@ typedef void (^SwitchChangedActionBlock)(BOOL newSwitchValue); */ @interface ListItemSwitchUIState : ListItemTitleUIState - + (instancetype)switchValueChanged:(BOOL)isOn; + (instancetype)switchEnabled:(BOOL)isEnabled; - @end NS_ASSUME_NONNULL_END diff --git a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemSwitchWidget.m b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemSwitchWidget.m similarity index 69% rename from DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemSwitchWidget.m rename to DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemSwitchWidget.m index 9e93abc..9f4c70e 100644 --- a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemSwitchWidget.m +++ b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemSwitchWidget.m @@ -1,9 +1,11 @@ // -// DUXListItemSwitchWidget.m +// DUXBetaListItemSwitchWidget.m // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -23,14 +25,15 @@ // SOFTWARE. // -#import "DUXListItemSwitchWidget.h" +#import "DUXBetaListItemSwitchWidget.h" @import DJIUXSDKCore; -@interface DUXListItemSwitchWidget () -@property (nonatomic, weak) SwitchChangedActionBlock actionBlock; +@interface DUXBetaListItemSwitchWidget () +@property (nonatomic, strong) SwitchChangedActionBlock actionBlock; +@property (nonatomic, assign) BOOL uiAlreadySetup; @end -@implementation DUXListItemSwitchWidget +@implementation DUXBetaListItemSwitchWidget - (void)setupCustomizableSettings { [super setupCustomizableSettings]; @@ -62,18 +65,37 @@ - (void)setSwitchAction:(SwitchChangedActionBlock)newBlock { } - (void)switchEnabledStateChanged { - [DUXStateChangeBroadcaster send:[ListItemSwitchUIState switchEnabled:self.onOffSwitch.enabled]]; + [DUXBetaStateChangeBroadcaster send:[ListItemSwitchUIState switchEnabled:self.onOffSwitch.enabled]]; [self updateUI]; } // This setupUI does not set a hard width. That should probably be imposed externally. - (void)setupUI { + if (_uiAlreadySetup) { return; } + _uiAlreadySetup = YES; [super setupUI]; self.onOffSwitch = [[UISwitch alloc] init]; self.onOffSwitch.translatesAutoresizingMaskIntoConstraints = NO; [self.view addSubview:self.onOffSwitch]; + if (@available(iOS 13, *)) { + UIView *switchBackingView = [[UIView alloc] initWithFrame:_onOffSwitch.frame]; + [_onOffSwitch.superview insertSubview:switchBackingView belowSubview:_onOffSwitch]; + switchBackingView.userInteractionEnabled = NO; + switchBackingView.translatesAutoresizingMaskIntoConstraints = NO; + + switchBackingView.layer.cornerRadius = switchBackingView.bounds.size.height / 2; + switchBackingView.layer.borderColor = [[UIColor whiteColor] CGColor]; + switchBackingView.layer.borderWidth = 2.0; + switchBackingView.backgroundColor = [UIColor clearColor]; + + [switchBackingView.leadingAnchor constraintEqualToAnchor:_onOffSwitch.leadingAnchor].active = YES;; + [switchBackingView.trailingAnchor constraintEqualToAnchor:_onOffSwitch.trailingAnchor].active = YES;; + [switchBackingView.topAnchor constraintEqualToAnchor:_onOffSwitch.topAnchor].active = YES;; + [switchBackingView.bottomAnchor constraintEqualToAnchor:_onOffSwitch.bottomAnchor].active = YES;; + } + // These are indded magic number for Apple switches. They aren't actually documented, but are the internal sizes always used // for a switch. Using different numbers is non-optimal. [self.onOffSwitch.widthAnchor constraintEqualToConstant:51.0].active = YES; @@ -92,16 +114,10 @@ - (void)updateSwitchTint { } - (IBAction)switchChanged:(id)sender { - [DUXStateChangeBroadcaster send:[ListItemSwitchUIState switchValueChanged:self.onOffSwitch.on]]; + [DUXBetaStateChangeBroadcaster send:[ListItemSwitchUIState switchValueChanged:self.onOffSwitch.on]]; if (self.actionBlock) { self.actionBlock(self.onOffSwitch.on); } - -// [self.widgetModel toggleSettingWithCompletionBlock:^(NSError * _Nullable error) { -// if (error != nil) { -// NSLog(@"error setting option '%@': %@", self.titleString, error); -// } -// }]; } - (DUXBetaWidgetSizeHint)widgetSizeHint { diff --git a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemSwitchWidgetModel.h b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemSwitchWidgetModel.h new file mode 100644 index 0000000..34f7685 --- /dev/null +++ b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemSwitchWidgetModel.h @@ -0,0 +1,63 @@ +// +// DUXBetaListItemSwitchWidgetModel.h +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// + +#import + +/** + * @brief DUXBetaWidgetModelActionCompletionBlock defines the action block to be called when the widget switch changes state. + * @param error Any NSError which was generated trying to change the switch state. + */ +typedef void (^DUXBetaWidgetModelActionCompletionBlock)(NSError * _Nullable error); + +NS_ASSUME_NONNULL_BEGIN + +/** + * @class DUXBetaListItemSwitchWidgetModel is a Widget class descending from DUXBetaBaseWidgetModel, meant to be used in DUXBetaListPanelWidgets. It implements + * a simple switch using a DJIKey which references a boolean value. The switch widget updates the actual key when a value changes and calls + * the specified DUXBetaWidgetModelActionCompletionBlock to allow additonal action to be taken on the state change. + */ +@interface DUXBetaListItemSwitchWidgetModel : DUXBetaBaseWidgetModel +// A generic boolean value to hold the current key state for easy reading. +@property (nonatomic, readwrite) BOOL genericBool; + +/** + * @brief Call initWithKey when allocating the widget, passing in the DJIKey which will be monitored. + * @param theKey DJIKey which as been created and is associated with a Boolean value. + * @return The newly initialzied instance of DUXBetaListItemSwitchWidgetModel. + */ +- (instancetype)initWithKey:(DJIKey*)theKey; + +/** + * @brief The method toggleSettingsWithCompletionBlock is used to set the completion callback block when the switch setting changes. This allows additional + * processing to be done on boolean state change. + * @param completionBlock A block of type DUXBetaWidgetModelActionCompletionBlock. + */ +- (void)toggleSettingWithCompletionBlock:(DUXBetaWidgetModelActionCompletionBlock)completionBlock; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemSwitchWidgetModel.m b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemSwitchWidgetModel.m similarity index 80% rename from DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemSwitchWidgetModel.m rename to DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemSwitchWidgetModel.m index 995e30d..e6afde5 100644 --- a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemSwitchWidgetModel.m +++ b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemSwitchWidgetModel.m @@ -1,9 +1,11 @@ // -// DUXGenericOptionSwitchWidgetModel.m +// DUXBetaGenericOptionSwitchWidgetModel.m // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -23,17 +25,17 @@ // SOFTWARE. // -#import +#import #import "DUXBetaBaseWidgetModel+Protected.h" @import DJIUXSDKCore; -@interface DUXListItemSwitchWidgetModel () +@interface DUXBetaListItemSwitchWidgetModel () @property (nonatomic, strong) DJIKey* observationKey; @end -@implementation DUXListItemSwitchWidgetModel +@implementation DUXBetaListItemSwitchWidgetModel - (instancetype)initWithKey:(DJIKey*)theKey { if (self = [super init]) { @@ -44,12 +46,12 @@ - (instancetype)initWithKey:(DJIKey*)theKey { return self; } -- (void)setup { +- (void)inSetup { BindSDKKey(_observationKey, genericBool); BindRKVOModel(self, @selector(updateStates), genericBool); } -- (void)cleanup { +- (void)inCleanup { UnBindSDK; UnBindRKVOModel(self); } @@ -59,10 +61,10 @@ - (void)dealloc { } - (void)updateStates { - // Don't need to do any transormation because it is a direct connecton to the key value + // Don't need to do any transformation because it is a direct connecton to the key value } -- (void)toggleSettingWithCompletionBlock:(DUXWidgetModelActionCompletionBlock)completionBlock { +- (void)toggleSettingWithCompletionBlock:(DUXBetaWidgetModelActionCompletionBlock)completionBlock { [[DJISDKManager keyManager] setValue:@(!self.genericBool) forKey:_observationKey withCompletion:^(NSError *error) { if (completionBlock != nil) { completionBlock(error); diff --git a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemTitleWidget.h b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemTitleWidget.h similarity index 89% rename from DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemTitleWidget.h rename to DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemTitleWidget.h index 43c08f8..fa2f15b 100644 --- a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemTitleWidget.h +++ b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemTitleWidget.h @@ -1,9 +1,11 @@ // -// DUXGenericLabelBaseWidget.h +// DUXBetaGenericLabelBaseWidget.h // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -22,24 +24,23 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // - #ifndef _H_DUXGenericLabelBaseWidget #define _H_DUXGenericLabelBaseWidget #import #import -#import -#import +#import +#import NS_ASSUME_NONNULL_BEGIN // Define this here for the list items which need it. This may be moved once the full base class hierarchy is refactored // for ListItemButtonWiget and ListItemLabelButtonWidget -@class DUXListItemButtonWidget; +@class DUXBetaListItemButtonWidget; typedef void (^GenericButtonActionBlock)(id senderWidget); /** - * @class: DUXListItemTitleWidget is the base class for list item widgets most often found in DUXListPanelWidget lists. + * @class: DUXBetaListItemTitleWidget is the base class for list item widgets most often found in DUXBetaListPanelWidget lists. * These base widgets support a single icon and title. It also supports the customizations for colors and fonts for all the * descendent widgets. * @@ -47,7 +48,7 @@ typedef void (^GenericButtonActionBlock)(id senderWidget); * * Concrete final classes will need to implement their own property model. */ -@interface DUXListItemTitleWidget : DUXBetaBaseWidget +@interface DUXBetaListItemTitleWidget : DUXBetaBaseWidget /// The font/size to use for the title @property (nonatomic, strong) UIFont *titleFont; /// The color to draw the title with @@ -62,7 +63,7 @@ typedef void (^GenericButtonActionBlock)(id senderWidget); /** * The following customizations do not directly apply to the base widget, but are relevant to sub-classed widgets */ -/// The Font/Color to draw the button label for sub-classes with buttons +/// The font/size to draw the button label for sub-classes with buttons @property (nonatomic, strong) UIFont *buttonFont; /// The width of the button border for sub-classes with buttons @property (nonatomic, assign) float buttonBorderWidth; @@ -180,26 +181,26 @@ typedef void (^GenericButtonActionBlock)(id senderWidget); - (BOOL)forceAspectRatio; /** - * @brief Method setButtonColor:forUIControlState: sets the color for drawing any subclass button for the given controlsState. + * @brief Method setButtonColor:forUIControlState: sets the color for drawing any subclass button for the given controlState. * It manages updating the button colors dictionary. */ - (void)setButtonColor:(UIColor *)buttonColor forUIControlState:(UIControlState)controlState; /** - * @brief Method getButtonColorForUIControlState retrieves the color for drawing any subclass button for the given controlsState. + * @brief Method getButtonColorForUIControlState retrieves the color for drawing any subclass button for the given controlState. * @return UIColor* to use for drawing with the given UIControlState */ - (UIColor *)getButtonColorForUIControlState:(UIControlState)controlState; /** * @brief Method setButtonBorderColor:forUIControlState: sets the color for drawing any subclass button border for the given - * controlsState. It manages updating the border colors dictionary. + * controlState. It manages updating the border colors dictionary. */ - (void)setButtonBorderColor:(UIColor *)buttonColor forUIControlState:(UIControlState)controlState; /** * @brief Method getButtonBorderColorForUIControlState retrieves the color for drawing the border of any subclass button - * for the given controlsState. + * for the given controlState. * @return UIColor* to use for drawing with the given UIControlState */ - (UIColor *)getButtonBorderColorForUIControlState:(UIControlState)controlState; @@ -208,20 +209,20 @@ typedef void (^GenericButtonActionBlock)(id senderWidget); #pragma mark - Hooks /** - * ListItemTitleUIState is the base class for all UI hooks descended from DUXListenItemTitleWidget. - * It inherits from DUXStateChangeBaseData which is the root hook data class and impements common UIHooks which may + * ListItemTitleUIState is the base class for all UI hooks descended from DUXBetaListenItemTitleWidget. + * It inherits from DUXBetaStateChangeBaseData which is the root hook data class and impements common UIHooks which may * be used by and of the UI subclases for ListItems * * Key: dialogDisplayed Type: id - Sends a string identifier for a dialog being displayed (usually in response to a * button tap or edit value update). * - * Key: dialogActionConfirm Type: id - Sends the string identifier for a dialog after the OK/Confirmation button was + * Key: dialogActionConfirm Type: id - Sends the string identifier for a dialog after the OK/Confirmation btutton was * tapped in the displayed dialog. * * Key: dialogActionDismiss Type: id - Sends the string identifier for a dialog after the Cancel/Dismiss button was * tapped in the displayed dialog. */ -@interface ListItemTitleUIState : DUXStateChangeBaseData +@interface ListItemTitleUIState : DUXBetaStateChangeBaseData + (instancetype)dialogDisplayed:(id)info; + (instancetype)dialogActionConfirm:(id)info; @@ -231,14 +232,17 @@ typedef void (^GenericButtonActionBlock)(id senderWidget); /** * ListItemTitleModelState contains the hooks for the widget models for classes desceneded from the ListItemTitleModelState - * It inherits from DUXStateChangeBaseData which is the root hook data class. + * It inherits from DUXBetaStateChangeBaseData which is the root hook data class. * * Key: productConnected Type: NSNumber - Sends a boolean value as an NSNumber when the product connection state changes. Boolean indicates if product is connected. */ -@interface ListItemTitleModelState : DUXStateChangeBaseData +@interface ListItemTitleModelState : DUXBetaStateChangeBaseData + + (instancetype)productConnected:(BOOL)isConnected; + @end + NS_ASSUME_NONNULL_END #endif diff --git a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemTitleWidget.m b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemTitleWidget.m similarity index 91% rename from DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemTitleWidget.m rename to DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemTitleWidget.m index 303de50..7fd11ad 100644 --- a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemTitleWidget.m +++ b/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXBetaListItemTitleWidget.m @@ -1,9 +1,11 @@ // -// DUXListItemTitleWidget.m +// DUXBetaListItemTitleWidget.m // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -23,7 +25,7 @@ // SOFTWARE. // -#import "DUXListItemTitleWidget.h" +#import "DUXBetaListItemTitleWidget.h" #import "UIImage+DUXBetaAssets.h" @import DJIUXSDKCore; @@ -33,9 +35,9 @@ static const CGFloat listWidgetDefaultMinSizeHeight = 54.0; /** - * Internal properties used by DUXListItemTitleWidget + * Internal properties used by DUXBetaListItemTitleWidget */ -@interface DUXListItemTitleWidget () +@interface DUXBetaListItemTitleWidget () @property (nonatomic, strong) NSString *titleString; @property (nonatomic, strong) NSString *iconName; @@ -43,7 +45,7 @@ @interface DUXListItemTitleWidget () @end -@implementation DUXListItemTitleWidget +@implementation DUXBetaListItemTitleWidget - (instancetype)init { self = [super init]; @@ -76,10 +78,12 @@ - (instancetype)initWithCoder:(NSCoder *)coder { - (instancetype)setTitle:(NSString*)titleString andIconName:(NSString* _Nullable)iconName { // This tears down the old model if it exists and builds a fresh one _titleString = titleString; - _iconImage = [[UIImage duxbeta_imageWithAssetNamed:iconName] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + if (iconName) { + _iconImage = [[UIImage duxbeta_imageWithAssetNamed:iconName] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + } // Subclasses will need to implement this. This widget doesn't need a model, it is just the label -// self.widgetModel = [[DUXGenericOptionSwitchWidgetModel alloc] initWithKey:self.widgetModelKey]; +// self.widgetModel = [[DUXBetaGenericOptionSwitchWidgetModel alloc] initWithKey:self.widgetModelKey]; [self setupUI]; return self; } @@ -148,6 +152,8 @@ - (void)setupUI { self.iconView.translatesAutoresizingMaskIntoConstraints = NO; self.iconView.tintColor = self.iconTintColor; [self.view addSubview:self.iconView]; + [self.iconView.heightAnchor constraintLessThanOrEqualToAnchor:self.view.heightAnchor multiplier:0.95].active = YES; + [self.iconView.heightAnchor constraintLessThanOrEqualToConstant:30.0].active = YES; [self.iconView.widthAnchor constraintEqualToAnchor:self.iconView.heightAnchor].active = YES; [self.iconView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor].active = YES; [self.iconView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:20.0].active = YES; @@ -205,7 +211,7 @@ - (void)setButtonColor:(UIColor *)buttonColor forUIControlState:(UIControlState) [self.buttonColors setObject:buttonColor forKey:@(controlState)]; } -- (UIColor *)getButtonColorForUIControlState:(UIControlState)controlState { +- (UIColor*)getButtonColorForUIControlState:(UIControlState)controlState { return [self.buttonColors objectForKey:@(controlState)]; } @@ -217,27 +223,27 @@ - (UIColor *)getButtonBorderColorForUIControlState:(UIControlState)controlState return [self.buttonBorderColors objectForKey:@(controlState)]; } -- (UIColor *)normalColor { +- (UIColor*)normalColor { return [UIColor duxbeta_whiteColor]; } -- (UIColor *)disabledColor { +- (UIColor*)disabledColor { return [UIColor duxbeta_disabledGrayColor]; } -- (UIColor *)warningColor { +- (UIColor*)warningColor { return [UIColor duxbeta_whiteColor]; } -- (UIColor *)buttonBorderNormalColor { +- (UIColor*)buttonBorderNormalColor { return [UIColor duxbeta_whiteColor]; } -- (UIColor *)buttonBorderDisabledColor { +- (UIColor*)buttonBorderDisabledColor { return [UIColor duxbeta_disabledGrayColor]; } -- (UIColor *)buttonBorderSelectedColor { +- (UIColor*)buttonBorderSelectedColor { return [UIColor duxbeta_whiteColor]; } diff --git a/DJIUXSDKWidgets/RCDistanceWidget/DUXBetaRCDistanceWidgetModel.m b/DJIUXSDKWidgets/RCDistanceWidget/DUXBetaRCDistanceWidgetModel.m index f732075..d4b88d5 100644 --- a/DJIUXSDKWidgets/RCDistanceWidget/DUXBetaRCDistanceWidgetModel.m +++ b/DJIUXSDKWidgets/RCDistanceWidget/DUXBetaRCDistanceWidgetModel.m @@ -32,6 +32,7 @@ @interface DUXBetaRCDistanceWidgetModel() @property (strong, nonatomic) CLLocationManager *locationManager; +@property (nonatomic) BOOL locationAccessGranted; @property (assign, nonatomic) double distanceInMeters; @property (nonatomic) NSMeasurement *distance; @@ -49,16 +50,16 @@ @implementation DUXBetaRCDistanceWidgetModel - (instancetype)init { self = [super init]; if (self) { - _locationManager = [[CLLocationManager alloc] init]; - _locationManager.delegate = self; _distanceInMeters = 0; _aircraftLocation = nil; _deviceLocation = nil; + _locationAccessGranted = NO; if (self.unitSystem == DUXBetaUnitSystemMetric) { _distance = [[NSMeasurement alloc] initWithDoubleValue:0 unit: NSUnitLength.meters]; } else { _distance = [[NSMeasurement alloc] initWithDoubleValue:0 unit: NSUnitLength.feet]; } + [self setupLocationManager]; } return self; } @@ -69,7 +70,7 @@ - (void)inSetup { BindSDKKey([DJIFlightControllerKey keyWithParam:DJIFlightControllerParamAircraftLocation], aircraftLocation); BindSDKKey([DJIRemoteControllerKey keyWithParam:DJIRemoteControllerParamGPSData], rcGPSData); - BindRKVOModel(self, @selector(updateDistanceInMeters), rcGPSData, aircraftLocation); + BindRKVOModel(self, @selector(updateDistanceInMeters), rcGPSData, aircraftLocation, deviceLocation); BindRKVOModel(self, @selector(updateDistance), distanceInMeters); } @@ -78,20 +79,42 @@ - (void)inCleanup { UnBindRKVOModel(self); } +- (void)setupLocationManager { + self.locationManager = [[CLLocationManager alloc] init]; + self.locationManager.delegate = self; + + CLAuthorizationStatus authStatus = [CLLocationManager authorizationStatus]; + + if (!(authStatus == kCLAuthorizationStatusRestricted || + authStatus == kCLAuthorizationStatusDenied)) { + + if (authStatus == kCLAuthorizationStatusNotDetermined) { + [self.locationManager requestWhenInUseAuthorization]; + } + } + self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation; + [self.locationManager startUpdatingLocation]; +} + - (void)updateDistanceInMeters { CLLocationCoordinate2D coordinate; - if (CLLocationCoordinate2DIsValid(self.rcGPSData.location)) { + if (CLLocationCoordinate2DIsValid(self.rcGPSData.location) && self.rcGPSData.location.latitude != 0.0 && self.rcGPSData.location.longitude != 0.0) { coordinate = self.rcGPSData.location; } else { coordinate = self.deviceLocation.coordinate; } - + if (self.aircraftLocation == nil || !CLLocationCoordinate2DIsValid(self.aircraftLocation.coordinate) || !CLLocationCoordinate2DIsValid(coordinate)) { self.distanceInMeters = 0; return; } + + if (self.locationAccessGranted == NO && self.rcGPSData.location.latitude == 0.0 && self.rcGPSData.location.longitude == 0.0) { + self.distanceInMeters = 0; + return; + } CLLocation *location = [[CLLocation alloc] initWithLatitude:coordinate.latitude longitude:coordinate.longitude]; self.distanceInMeters = [location distanceFromLocation: self.aircraftLocation]; @@ -137,7 +160,14 @@ - (void)setDistanceUnits:(NSUnitLength *)distanceUnits { - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { if ([locations count] > 0) { self.deviceLocation = [locations lastObject]; + } else { + self.deviceLocation = manager.location; } + self.locationAccessGranted = YES; +} + +- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { + self.locationAccessGranted = NO; } @end diff --git a/DJIUXSDKWidgets/RTKEnabledWidget/DUXBetaRTKEnabledWidget.h b/DJIUXSDKWidgets/RTKEnabledWidget/DUXBetaRTKEnabledWidget.h new file mode 100644 index 0000000..9595e0d --- /dev/null +++ b/DJIUXSDKWidgets/RTKEnabledWidget/DUXBetaRTKEnabledWidget.h @@ -0,0 +1,90 @@ +// +// DUXBetaRTKEnabledWidget.h +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// + +#import +#import +@class DUXBetaRTKEnabledWidgetModel; + +NS_ASSUME_NONNULL_BEGIN + +/** + * Display: + * This widget displays a switch that will enable or disable RTK. + * + * Usage: + * Preferred Aspect Ratio 400:120 + */ +@interface DUXBetaRTKEnabledWidget : DUXBetaBaseWidget + +/** + * The widget model that the battery widget receives its information from. +*/ +@property (nonatomic, strong) DUXBetaRTKEnabledWidgetModel *widgetModel; + +/** + * The font of the title (RTK Positioning). Default point size = 20. +*/ +@property (nonatomic, strong) UIFont *titleFont; + +/** + * The color of the title (RTK Positioning). +*/ +@property (nonatomic, strong) UIColor *titleTextColor; + +/** + * The background color of the title (RTK Positioning). +*/ +@property (nonatomic, strong) UIColor *titleBackgroundColor; + +/** + * The font of the description: (When RTK module malfunctions, manually disable...). Default point size = 14. +*/ +@property (nonatomic, strong) UIFont *descriptionFont; + +/** + * The color of the description text: (When RTK module malfunctions, manually disable...). +*/ +@property (nonatomic, strong) UIColor *descriptionTextColor; + +/** + * The background color of the description label: (When RTK module malfunctions, manually disable...). +*/ +@property (nonatomic, strong) UIColor *descriptionBackgroundColor; + +/** + * The color the enable switch shows when enabled. +*/ +@property (nonatomic, strong) UIColor *enabledSwitchTintColor; + +/** + * The widget's background color. +*/ +@property (nonatomic, strong) UIColor *widgetBackgroundColor; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DJIUXSDKWidgets/RTKEnabledWidget/DUXBetaRTKEnabledWidget.m b/DJIUXSDKWidgets/RTKEnabledWidget/DUXBetaRTKEnabledWidget.m new file mode 100644 index 0000000..a8454dc --- /dev/null +++ b/DJIUXSDKWidgets/RTKEnabledWidget/DUXBetaRTKEnabledWidget.m @@ -0,0 +1,277 @@ +// +// DUXBetaRTKEnabledWidget.m +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// + +#import "DUXBetaRTKEnabledWidget.h" +#import "DUXBetaRTKEnabledWidgetModel.h" +#import +@import DJIUXSDKCore; +@import DJIUXSDKCommunication; + +static NSString *kRTKEnabledDescription = @"When RTK module malfunctions, manually disable RTK and switch back to GPS mode.\n(If you enable RTK after takeoff, the GPS will continually be used.)"; + +static NSString *kWarningMessageReason = @"Failed to enable RTK"; +static NSString *kWarningMessageSolution = @"Motors are running. Stop them and try again."; + +static CGSize const kDesignSize = {500.0, 100.0}; +static const CGFloat kMarginWidth = 15.0; +static const CGFloat kTitleInitialSize = 16.0; +static const CGFloat kDescriptionInitialSize = 11.0; + +@interface DUXBetaRTKEnabledWidget() + +@property (strong, nonatomic) UILabel *titleLabel; +@property (strong, nonatomic) UILabel *descriptionLabel; +@property (strong, nonatomic) UISwitch *enableSwitch; + +@end + +/** + * RTKEnabledWidgetUIState contains the hooks for UI changes in the widget class DUXBetaRTKEnabledWidget. + * It implements the hook: + * + * Key: switchChanged Type: NSNumber - Sends a boolean value as an NSNumber indicating the state of the switch + * when it changes. +*/ +@interface RTKEnabledWidgetUIState : DUXBetaStateChangeBaseData + ++ (instancetype)switchChanged:(BOOL)isEnabled; + +@end + +/** + * RTKEnabledWidgetModelState contains the model hooks for the DUXBetaRTKEnabledWidget. + * It implements the hooks: + * + * Key: productConnected Type: NSNumber - Sends a boolean value as an NSNumber indicating the connected state of + * the device when it changes. + * + * Key: rtkEnabledUpdate Type: NSNumber - Sends a boolean value as an NSNumber indicating the rtk enabled status + * when it changes. +*/ +@interface RTKEnabledWidgetModelState : DUXBetaStateChangeBaseData + ++ (instancetype)productConnected:(BOOL)isConnected; + ++ (instancetype)rtkEnabledUpdate:(BOOL)isRTKEnabled; + +@end + +@implementation DUXBetaRTKEnabledWidget + +- (instancetype)init { + self = [super init]; + if (self) { + [self setupInstanceVariables]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + self = [super init]; + if (self) { + [self setupInstanceVariables]; + } + return self; +} + +- (void)setupInstanceVariables { + _titleFont = [UIFont systemFontOfSize:kTitleInitialSize]; + _descriptionFont = [UIFont systemFontOfSize:kDescriptionInitialSize]; + _titleTextColor = [UIColor duxbeta_whiteColor]; + _descriptionTextColor = [UIColor lightGrayColor]; + _widgetBackgroundColor = [UIColor duxbeta_clearColor]; + _titleBackgroundColor = [UIColor clearColor]; + _descriptionBackgroundColor = [UIColor clearColor]; + _enabledSwitchTintColor = [UIColor systemGreenColor]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.widgetModel = [[DUXBetaRTKEnabledWidgetModel alloc] init]; + [self.widgetModel setup]; + [self setupUI]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + BindRKVOModel(self.widgetModel, @selector(updateUI), rtkEnabled, isProductConnected); + BindRKVOModel(self, @selector(updateCustomizations), titleFont, titleTextColor, descriptionFont, descriptionTextColor, widgetBackgroundColor, titleBackgroundColor, descriptionBackgroundColor); + + BindRKVOModel(self.widgetModel, @selector(sendIsProductConnected), isProductConnected); + BindRKVOModel(self.widgetModel, @selector(sendRTKEnabledUpdate), rtkEnabled); +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + + UnBindRKVOModel(self); + UnBindRKVOModel(self.widgetModel); +} + +- (void)dealloc { + [self.widgetModel cleanup]; +} + +- (void)setupUI { + UIStackView *topRowStackView = [[UIStackView alloc] init]; + topRowStackView.translatesAutoresizingMaskIntoConstraints = NO; + topRowStackView.axis = UILayoutConstraintAxisHorizontal; + topRowStackView.distribution = UIStackViewDistributionEqualSpacing; + topRowStackView.alignment = UIStackViewAlignmentCenter; + topRowStackView.spacing = 30; + + [self.view addSubview:topRowStackView]; + [topRowStackView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:kMarginWidth].active = YES; + [topRowStackView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:(-1 * kMarginWidth)].active = YES; + [topRowStackView.topAnchor constraintEqualToAnchor:self.view.topAnchor constant:kMarginWidth].active = YES; + [topRowStackView.bottomAnchor constraintEqualToAnchor:self.view.centerYAnchor constant:(-1 * kMarginWidth)].active = YES; + [topRowStackView.heightAnchor constraintEqualToConstant:20.0].active = YES; + + self.titleLabel = [[UILabel alloc] init]; + self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + self.titleLabel.textAlignment = NSTextAlignmentCenter; + self.titleLabel.font = self.titleFont; + self.titleLabel.textColor = self.titleTextColor; + self.titleLabel.text = NSLocalizedString(@"RTK Positioning", @"Title Label"); + + self.enableSwitch = [[UISwitch alloc] init]; + [self.enableSwitch setOn:self.widgetModel.rtkEnabled]; + self.enableSwitch.translatesAutoresizingMaskIntoConstraints = NO; + [self.enableSwitch addTarget:self + action:@selector(onSwitchTapped) + forControlEvents:UIControlEventTouchUpInside]; + + [topRowStackView addArrangedSubview:self.titleLabel]; + [topRowStackView addArrangedSubview:self.enableSwitch]; + + self.descriptionLabel = [[UILabel alloc] init]; + self.descriptionLabel.translatesAutoresizingMaskIntoConstraints = NO; + self.descriptionLabel.textAlignment = NSTextAlignmentCenter; + self.descriptionLabel.numberOfLines = 2; + self.descriptionLabel.font = self.descriptionFont; + self.descriptionLabel.textColor = self.descriptionTextColor; + self.descriptionLabel.text = NSLocalizedString(kRTKEnabledDescription, @"Title Label"); + self.descriptionLabel.textAlignment = NSTextAlignmentLeft; + + [self.view addSubview:self.descriptionLabel]; + + [self.descriptionLabel.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:kMarginWidth].active = YES; + [self.descriptionLabel.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:(-1 * kMarginWidth)].active = YES; + [self.descriptionLabel.topAnchor constraintEqualToAnchor:self.titleLabel.bottomAnchor constant:kMarginWidth].active = YES; + [self.descriptionLabel.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES; + + self.enableSwitch.onTintColor = self.enabledSwitchTintColor; + self.view.backgroundColor = self.widgetBackgroundColor; +} + +- (void)sendIsProductConnected { + [[DUXBetaStateChangeBroadcaster instance] send:[RTKEnabledWidgetModelState productConnected:self.widgetModel.isProductConnected]]; +} + +- (void)sendRTKEnabledUpdate { + [[DUXBetaStateChangeBroadcaster instance] send:[RTKEnabledWidgetModelState rtkEnabledUpdate:self.widgetModel.rtkEnabled]]; +} + +- (void)sendSwitchChanged { + [[DUXBetaStateChangeBroadcaster instance] send:[RTKEnabledWidgetUIState switchChanged:self.enableSwitch.on]]; +} + +- (void)onSwitchTapped { + if (self.enableSwitch.isOn) { + if (self.widgetModel.canEnableRTK) { + [self.widgetModel sendRtkEnabled:YES]; + } else { + [self.enableSwitch setOn:NO]; + if (self.widgetModel.areMotorsOn) { + [self sendMotorsOnWarningMessage]; + } + } + } else { + [self.widgetModel sendRtkEnabled:NO]; + } +} + +- (void)updateCustomizations { + self.titleLabel.textColor = self.titleTextColor; + self.titleLabel.backgroundColor = self.titleBackgroundColor; + self.titleLabel.font = self.titleFont; + self.descriptionLabel.textColor = self.descriptionTextColor; + self.descriptionLabel.backgroundColor = self.descriptionBackgroundColor; + self.descriptionLabel.font = self.descriptionFont; + self.enableSwitch.onTintColor = self.enabledSwitchTintColor; + self.view.backgroundColor = self.widgetBackgroundColor; +} + +- (void)updateUI { + [self.enableSwitch setOn:self.widgetModel.rtkEnabled && self.widgetModel.isProductConnected]; + [self sendSwitchChanged]; +} + +- (void)sendMotorsOnWarningMessage { + DUXBetaWarningMessageKey *warningMessageKey = [[DUXBetaWarningMessageKey alloc] initWithIndex:0 + parameter:DUXBetaWarningMessageParameterSendWarningMessage]; + + DUXBetaWarningMessage *warningMessage = [[DUXBetaWarningMessage alloc] init]; + warningMessage.reason = kWarningMessageReason; + warningMessage.solution = kWarningMessageSolution; + warningMessage.level = DUXBetaWarningMessageLevelNotify; + warningMessage.type = DUXBetaWarningMessageTypePinned; + + ModelValue *modelWithWarningMessage = [[ModelValue alloc] initWithValue:[warningMessage copy]]; + + [[DUXBetaSingleton sharedObservableInMemoryKeyedStore] setModelValue:modelWithWarningMessage + forKey:warningMessageKey]; +} + +- (DUXBetaWidgetSizeHint)widgetSizeHint { + DUXBetaWidgetSizeHint hint = {kDesignSize.width / kDesignSize.height, kDesignSize.width, kDesignSize.height}; + return hint; +} + +@end + +@implementation RTKEnabledWidgetUIState + ++ (instancetype)switchChanged:(BOOL)isEnabled { + return [[RTKEnabledWidgetUIState alloc] initWithKey:@"switchChanged" number:[NSNumber numberWithBool:isEnabled]]; +} + +@end + +@implementation RTKEnabledWidgetModelState + ++ (instancetype)productConnected:(BOOL)isConnected { + return [[RTKEnabledWidgetModelState alloc] initWithKey:@"productConnected" number:[NSNumber numberWithBool:isConnected]]; +} + ++ (instancetype)rtkEnabledUpdate:(BOOL)isRTKEnabled { + return [[RTKEnabledWidgetModelState alloc] initWithKey:@"rtkEnabledUpdate" number:[NSNumber numberWithBool:isRTKEnabled]]; +} + +@end diff --git a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemSwitchWidgetModel.h b/DJIUXSDKWidgets/RTKEnabledWidget/DUXBetaRTKEnabledWidgetModel.h similarity index 77% rename from DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemSwitchWidgetModel.h rename to DJIUXSDKWidgets/RTKEnabledWidget/DUXBetaRTKEnabledWidgetModel.h index afd8f1b..2e735a6 100644 --- a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemSwitchWidgetModel.h +++ b/DJIUXSDKWidgets/RTKEnabledWidget/DUXBetaRTKEnabledWidgetModel.h @@ -1,7 +1,9 @@ // -// DUXListItemSwitchWidgetModel.h +// DUXBetaRTKEnabledWidgetModel.h // DJIUXSDKWidgets // +// MIT License +// // Copyright © 2018-2020 DJI // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -23,17 +25,19 @@ // SOFTWARE. // +#import #import -typedef void (^DUXWidgetModelActionCompletionBlock)(NSError * _Nullable error); - NS_ASSUME_NONNULL_BEGIN -@interface DUXListItemSwitchWidgetModel : DUXBetaBaseWidgetModel -@property (nonatomic, readwrite) BOOL genericBool; +@interface DUXBetaRTKEnabledWidgetModel : DUXBetaBaseWidgetModel + +@property (assign, nonatomic, readonly) BOOL canEnableRTK; +@property (assign, nonatomic, readonly) BOOL areMotorsOn; + +@property (assign, nonatomic) BOOL rtkEnabled; -- (instancetype)initWithKey:(DJIKey*)theKey; -- (void)toggleSettingWithCompletionBlock:(DUXWidgetModelActionCompletionBlock)completionBlock; +- (void)sendRtkEnabled:(BOOL)rtkEnabled; @end diff --git a/DJIUXSDKWidgets/RTKEnabledWidget/DUXBetaRTKEnabledWidgetModel.m b/DJIUXSDKWidgets/RTKEnabledWidget/DUXBetaRTKEnabledWidgetModel.m new file mode 100644 index 0000000..8bcc305 --- /dev/null +++ b/DJIUXSDKWidgets/RTKEnabledWidget/DUXBetaRTKEnabledWidgetModel.m @@ -0,0 +1,96 @@ +// +// DUXBetaRTKEnabledWidgetModel.m +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// + +#import +#import "DUXBetaRTKEnabledWidgetModel.h" +@import DJIUXSDKCore; + +@interface DUXBetaRTKEnabledWidgetModel() + +@property (assign, nonatomic, readwrite) BOOL canEnableRTK; +@property (assign, nonatomic, readwrite) BOOL areMotorsOn; +@property (assign, nonatomic) BOOL isRTKTakeoffHeightSet; +@property (assign, nonatomic) DJIRTKDataSource rtkDataSource; +@property (strong, nonatomic) DJIFlightControllerKey *rtkEnabledKey; + +@end + +@implementation DUXBetaRTKEnabledWidgetModel + +- (instancetype)init { + self = [super init]; + if (self) { + _isRTKTakeoffHeightSet = NO; + _rtkDataSource = DJIRTKDataSourceUnknown; + } + return self; +} + +- (void)inSetup { + self.rtkEnabledKey = [DJIFlightControllerKey keyWithIndex:0 + subComponent:DJIFlightControllerRTKSubComponent + subComponentIndex:0 + andParam:DJIRTKParamEnabled]; + _rtkEnabled = [[DJISDKManager keyManager] getValueForKey:self.rtkEnabledKey]; + BindSDKKey(self.rtkEnabledKey, rtkEnabled); + BindSDKKey([DJIFlightControllerKey keyWithParam:DJIFlightControllerParamAreMotorsOn], areMotorsOn); + BindRKVOModel(self, @selector(updateCanEnableRTK), areMotorsOn); + + if ([[DJISDKManager product] isKindOfClass:DJIAircraft.class]) { + DJIAircraft *djiAircraft = (DJIAircraft *)[DJISDKManager product]; + djiAircraft.flightController.RTK.delegate = self; + } +} + +- (void)inCleanup { + UnBindSDK; + UnBindRKVOModel(self); +} + +- (void)rtk:(DJIRTK *_Nonnull)rtk didUpdateState:(DJIRTKState *_Nonnull)state { + self.isRTKTakeoffHeightSet = state.isTakeoffAltitudeRecorded; + self.rtkDataSource = state.homePointDataSource; + [self updateCanEnableRTK]; +} + +- (void)updateCanEnableRTK { + BOOL isHomePointTypeRTK = (self.rtkDataSource == DJIRTKDataSourceRTK); + self.canEnableRTK = !self.areMotorsOn || (self.isRTKTakeoffHeightSet && isHomePointTypeRTK); +} + +- (void)sendRtkEnabled:(BOOL)rtkEnabled { + [[DJISDKManager keyManager] setValue:@(rtkEnabled) forKey:self.rtkEnabledKey withCompletion:^(NSError * _Nullable error) { + if (error == nil) { + self.rtkEnabled = rtkEnabled; + } else { + NSLog(@"Error setting rtkEnabled to %@:%@", rtkEnabled ? @"YES" : @"NO", error.description); + } + }]; + +} + +@end diff --git a/DJIUXSDKWidgets/RTKSatelliteStatusWidget/DUXBetaRTKSatelliteStatusWidget.h b/DJIUXSDKWidgets/RTKSatelliteStatusWidget/DUXBetaRTKSatelliteStatusWidget.h new file mode 100644 index 0000000..87fbb4d --- /dev/null +++ b/DJIUXSDKWidgets/RTKSatelliteStatusWidget/DUXBetaRTKSatelliteStatusWidget.h @@ -0,0 +1,168 @@ +// +// DUXBetaRTKSatelliteStatusWidget.h +// DJIUXSDK +// +// MIT License +// +// Copyright © 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. +// + +#import +#import "DUXBetaRTKSatelliteStatusWidgetModel.h" + +typedef NS_ENUM(NSUInteger, DUXBetaRTKLocationState) { + DUXBetaRTKLocationStateNone = 0, + DUXBetaRTKLocationStateSinglePoint = 16, + DUXBetaRTKLocationStateFloat = 32, + DUXBetaRTKLocationStateFloatIono = 33, + DUXBetaRTKLocationStateFloatNarrow = 34, + DUXBetaRTKLocationStateFixedPoint = 50, +}; + +NS_ASSUME_NONNULL_BEGIN + +/** +* Display: +* This widget displays the state of RTK base station state and a table of RTK positioning values. +* +* Usage: +* Preferred Aspect Ratio 500:500 +*/ +@interface DUXBetaRTKSatelliteStatusWidget : DUXBetaBaseWidget + +/** + * The widget model that the battery widget receives its information from. +*/ +@property (nonatomic, strong) DUXBetaRTKSatelliteStatusWidgetModel *widgetModel; + +/** + * The font of the status title label (Base Station Status:). Default point size = 12. +*/ +@property (nonatomic, strong) UIFont *statusTitleLabelFont; + +/** + * The color of the status title label. +*/ +@property (nonatomic, strong) UIColor *statusTitleLabelTextColor; + +/** + * The background color of the status title label. +*/ +@property (nonatomic, strong) UIColor *statusTitleLabelBackgroundColor; + +/** + * The font of the status label (follows the status title label). Default point size = 12. +*/ +@property (nonatomic, strong) UIFont *statusLabelFont; + +/** + * The background color of the status label. +*/ +@property (nonatomic, strong) UIColor *statusLabelBackgroundColor; + +/** + * The font of the title labels (labels in the first row or column of the table). Default point size = 12. +*/ +@property (nonatomic, strong) UIFont *titleLabelFont; + +/** + * The text color of the title labels. +*/ +@property (nonatomic, strong) UIColor *titleLabelTextColor; + +/** + * The background color of the title labels. +*/ +@property (nonatomic, strong) UIColor *titleLabelBackgroundColor; + +/** + * The font of the value labels (labels in the first row or column of the table). Default point size = 12. +*/ +@property (nonatomic, strong) UIFont *valueLabelFont; + +/** + * The text color of the value labels. +*/ +@property (nonatomic, strong) UIColor *valueLabelTextColor; + +/** + * The background color of the value labels. +*/ +@property (nonatomic, strong) UIColor *valueLabelBackgroundColor; + +/** + * The table line color (default gray). +*/ +@property (nonatomic, strong) UIColor *tableColor; + +/** + * The visibility of the Beidou satellite counts row of the table. +*/ +@property (nonatomic, assign) BOOL isBeidouCountVisible; + +/** + * The visibility of the Glonass satellite counts row of the table. +*/ +@property (nonatomic, assign) BOOL isGlonassCountVisible; + +/** + * The visibility of the Galileo satellite counts row of the table. +*/ +@property (nonatomic, assign) BOOL isGalileoCountVisible; + +/** + * The image asset for the valid orientaion icon (only shown for M210RTK). +*/ +@property (nonatomic, strong) UIImage *orientationValidImage; + +/** + * The image asset for the invalid orientaion icon (only shown for M210RTK). +*/ +@property (nonatomic, strong) UIImage *orientationInvalidImage; + +/** + * The image tint for the valid orientaion icon (only shown for M210RTK). +*/ +@property (nonatomic, strong) UIColor *orientationValidTint; + +/** + * The image tint for the invalid orientaion icon (only shown for M210RTK). +*/ +@property (nonatomic, strong) UIColor *orientationInvalidTint; + +/** + * The background color of the widget. +*/ +@property (nonatomic, strong) UIColor *backgroundColor; + +/** + * Set the color of the connection status text for the specified connection status state. +*/ +- (void)setStatusTextColor:(UIColor *)fontColor forConnectionStatus:(DUXBetaRTKConnectionStatus)status; + +/** + * Get the color of the connection status text for the specified connection status state. +*/ +- (UIColor *)statusTextColorForConnectionStatus:(DUXBetaRTKConnectionStatus)status; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DJIUXSDKWidgets/RTKSatelliteStatusWidget/DUXBetaRTKSatelliteStatusWidget.m b/DJIUXSDKWidgets/RTKSatelliteStatusWidget/DUXBetaRTKSatelliteStatusWidget.m new file mode 100644 index 0000000..ca0fae5 --- /dev/null +++ b/DJIUXSDKWidgets/RTKSatelliteStatusWidget/DUXBetaRTKSatelliteStatusWidget.m @@ -0,0 +1,1063 @@ +// +// DUXBetaRTKSatelliteStatusWidget.m +// DJIUXSDK +// +// MIT License +// +// Copyright © 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. +// + +#import "DUXBetaRTKSatelliteStatusWidget.h" +#import "DUXBetaRTKSatelliteStatusWidgetModel.h" +#import "UIImage+DUXBetaAssets.h" +#import "UIFont+DUXBetaFonts.h" +@import DJIUXSDKCore; +#import +#import + +static CGSize const kDesignSize = {500.0, 500.0}; +static NSString *const kConnectionStatusTitleEnding = @" Status: "; + +static const float kMarginForTitles = 15.0; +static const float kMarginThin = 10.0; +static const float kTableLineWidth = 1.0; +static const float kDefaultFontSize = 12.0; + +@interface DUXBetaRTKSatelliteStatusWidget () + +@property (strong, nonatomic) UILabel *statusTitleLabel; +@property (strong, nonatomic) UILabel *statusLabel; + +@property (strong, nonatomic) UIView *tableBorderView; +@property (strong, nonatomic) UIView *verticalDivider1; +@property (strong, nonatomic) UIView *verticalDivider2; +@property (strong, nonatomic) UIView *horizontalDivider1; +@property (strong, nonatomic) UIView *horizontalDivider2; +@property (strong, nonatomic) UIView *horizontalDivider3; +@property (strong, nonatomic) UIView *horizontalDivider4; + +@property (strong, nonatomic) UILabel *aircraftColumnLabel; +@property (strong, nonatomic) UILabel *baseStationColumnLabel; + +@property (strong, nonatomic) UILabel *orientationRowLabel; +@property (strong, nonatomic) UILabel *positioningRowLabel; + +@property (strong, nonatomic) UILabel *aircraftOrientationLabel; + +@property (strong, nonatomic) UIImage *aircraftHeadingValidImage; +@property (strong, nonatomic) UIImage *aircraftHeadingInvalidImage; + +@property (strong, nonatomic) UILabel *aircraftPositioningLabel; + +@property (strong, nonatomic) UILabel *latitudeTitleLabel; +@property (strong, nonatomic) UILabel *longitudeTitleLabel; +@property (strong, nonatomic) UILabel *altitudeTitleLabel; +@property (strong, nonatomic) UILabel *courseAngleTitleLabel; + +@property (strong, nonatomic) UILabel *aircraftLatitudeLabel; +@property (strong, nonatomic) UILabel *aircraftLongitudeLabel; +@property (strong, nonatomic) UILabel *aircraftAltitudeLabel; +@property (strong, nonatomic) UILabel *aircraftCourseAngleLabel; + +@property (strong, nonatomic) UILabel *baseStationLatitudeLabel; +@property (strong, nonatomic) UILabel *baseStationLongitudeLabel; +@property (strong, nonatomic) UILabel *baseStationAltitudeLabel; + +@property (strong, nonatomic) UILabel *gpsTitleLabel; +@property (strong, nonatomic) UILabel *beidouTitleLabel; +@property (strong, nonatomic) UILabel *glonassTitleLabel; +@property (strong, nonatomic) UILabel *galileoTitleLabel; + +@property (strong, nonatomic) UILabel *antenna1TitleLabel; +@property (strong, nonatomic) UILabel *antenna1GPSCountLabel; +@property (strong, nonatomic) UILabel *antenna1BeidouCountLabel; +@property (strong, nonatomic) UILabel *antenna1GlonassCountLabel; +@property (strong, nonatomic) UILabel *antenna1GalileoCountLabel; + +@property (strong, nonatomic) UILabel *antenna2TitleLabel; +@property (strong, nonatomic) UILabel *antenna2GPSCountLabel; +@property (strong, nonatomic) UILabel *antenna2BeidouCountLabel; +@property (strong, nonatomic) UILabel *antenna2GlonassCountLabel; +@property (strong, nonatomic) UILabel *antenna2GalileoCountLabel; + +@property (strong, nonatomic) UILabel *baseStationGPSCountLabel; +@property (strong, nonatomic) UILabel *baseStationBeidouCountLabel; +@property (strong, nonatomic) UILabel *baseStationGlonassCountLabel; +@property (strong, nonatomic) UILabel *baseStationGalileoCountLabel; + +@property (strong, nonatomic) UILabel *standardDeviationTitleLabel; + +@property (strong, nonatomic) UILabel *latitudeStandardDeviationLabel; +@property (strong, nonatomic) UILabel *longitudeStandardDeviationLabel; +@property (strong, nonatomic) UILabel *altitudeStandardDeviationLabel; + +@property (strong, nonatomic) NSLayoutConstraint *row4ConstraintToRow5; +@property (strong, nonatomic) NSLayoutConstraint *row4ConstraintToBorder; +@property (strong, nonatomic) NSLayoutConstraint *row5ConstraintToBorder; + +@property (strong, nonatomic) NSLayoutConstraint *antennaRowTopConstraint; +@property (strong, nonatomic) NSLayoutConstraint *row2ToRow3Constraint; + +@property (strong, nonatomic) NSMutableDictionary *statusLabelColors; + +@property (strong, nonatomic) UIImageView *aircraftOrientationImageView; + +@property (strong, nonatomic) UIStackView *standardDeviationStackView; + +@property (strong, nonatomic) NSMutableArray *titleLabels; +@property (strong, nonatomic) NSMutableArray *valueLabels; +@property (assign, nonatomic) uint8_t visibleConstellationCount; + +@property (strong, nonatomic) NSMutableSet *countStackViewTopConstraints; + +@end + + +/** + * RTKSatelliteStatusWidgetModelState contains the model hooks for the DUXBetaRTKSatelliteStatusWidget. + * It implements the hooks: + * + * Key: productConnected Type: NSNumber - Sends a boolean value as an NSNumber indicating the connected state of + * the device when it changes. + * + * Key: rtkConnectedUpdate Type: NSNumber - Sends a boolean value as an NSNumber indicating if the current device + * supports RTK. + * Key: rtkStateUpdate Type: DJIRTKState - Sends a DJIRTKState when received from the widget model. + * Key: modelUpdate Type: NSString - Sends the model name of the product when updated. + * Key: rtkSignalUpdate Type: DJIRTKReferenceStationSource - Sends the type of base station reference source used. + * Key: standardDeviationUpdate Type: DJILocationStandardDeviation - Sends the standard deviation of the aircraft's location when updated. + * Key: baseStationStatusUpdate Type: DUXBetaRTKConnectionStatus - Sends the status of the base station when updated. + * Key: rtkNetRTCMStatusUpdate Type: DJIRTKNetworkServiceState - Sends the RTK network service state when updated. +*/ +@interface RTKSatelliteStatusWidgetModelState : DUXBetaStateChangeBaseData + ++ (instancetype)productConnected:(BOOL)isConnected; + ++ (instancetype)rtkConnectedUpdate:(BOOL)isConnected; + ++ (instancetype)rtkStateUpdate:(DJIRTKState *)rtkState; + ++ (instancetype)modelUpdate:(NSString *)modelName; + ++ (instancetype)rtkSignalUpdate:(DJIRTKReferenceStationSource)rtkSignal; + ++ (instancetype)standardDeviationUpdate:(DJILocationStandardDeviation *)locationStandardDeviation; + ++ (instancetype)baseStationStatusUpdate:(DUXBetaRTKConnectionStatus)connectionStatus; + ++ (instancetype)rtkNetRTCMStatusUpdate:(DJIRTKNetworkServiceState *)networkServiceState; + + +@end + +@implementation DUXBetaRTKSatelliteStatusWidget + +- (instancetype)init { + self = [super init]; + if (self) { + _titleLabelTextColor = [UIColor duxbeta_whiteColor]; + _titleLabelFont = [UIFont systemFontOfSize:kDefaultFontSize]; + _titleLabelBackgroundColor = [UIColor duxbeta_clearColor]; + _valueLabelTextColor = [UIColor duxbeta_whiteColor]; + _valueLabelFont = [UIFont systemFontOfSize:kDefaultFontSize]; + _valueLabelBackgroundColor = [UIColor duxbeta_clearColor]; + + _statusTitleLabelFont = [UIFont systemFontOfSize:kDefaultFontSize]; + _statusTitleLabelTextColor = [UIColor duxbeta_whiteColor]; + _statusTitleLabelBackgroundColor = [UIColor duxbeta_clearColor]; + + _statusLabelFont = [UIFont systemFontOfSize:kDefaultFontSize]; + _statusLabelBackgroundColor = [UIColor duxbeta_clearColor]; + + _isBeidouCountVisible = YES; + _isGlonassCountVisible = YES; + _isGalileoCountVisible = YES; + + _visibleConstellationCount = 4; + + _aircraftHeadingValidImage = [UIImage duxbeta_imageWithAssetNamed:@"OrientationValid"]; + _aircraftHeadingInvalidImage = [UIImage duxbeta_imageWithAssetNamed:@"OrientationInvalid"]; + + _statusLabelColors = [[NSMutableDictionary alloc] initWithDictionary:@{ + @(DUXBetaRTKConnectionStatusInUse) : [UIColor duxbeta_systemStatusWidgetGreenColor], + @(DUXBetaRTKConnectionStatusNotInUse) : [UIColor duxbeta_yellowColor], + @(DUXBetaRTKConnectionStatusDisconnected) : [UIColor duxbeta_systemStatusWidgetRedColor] + }]; + + _tableColor = [UIColor duxbeta_rtkTableBorderColor]; + _countStackViewTopConstraints = [[NSMutableSet alloc] init]; + _backgroundColor = [UIColor duxbeta_clearColor]; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.widgetModel = [[DUXBetaRTKSatelliteStatusWidgetModel alloc] init]; + [self.widgetModel setup]; + + [self setupUI]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + // Update Values + BindRKVOModel(self.widgetModel, @selector(updateConnectionStatus), isProductConnected, rtkEnabled, rtkInUse, networkServiceState.channelState, rtkConnectionStatus); + BindRKVOModel(self.widgetModel, @selector(updatePrecisionValues), modelName, isHeadingValid, locationState, rtkState); + BindRKVOModel(self.widgetModel, @selector(updateLocationAndCountValues), rtkState, modelName); + BindRKVOModel(self.widgetModel, @selector(updateReferenceStationSource), rtkSignal); + BindRKVOModel(self.widgetModel, @selector(updateFieldVisibilities), modelName, isProductConnected); + BindRKVOModel(self.widgetModel, @selector(updateStandardDeviations), rtkState.mobileStationStandardDeviation); + + // Update Customizations + BindRKVOModel(self, @selector(customizeConnectionStatus), statusTitleLabelFont, statusTitleLabelTextColor, statusTitleLabelBackgroundColor); + BindRKVOModel(self, @selector(updatePrecisionValues), orientationValidImage, orientationInvalidImage, orientationValidTint, orientationInvalidTint); + BindRKVOModel(self, @selector(customizeTitles), titleLabelFont, titleLabelTextColor, titleLabelBackgroundColor); + BindRKVOModel(self, @selector(customizeValues), valueLabelFont, valueLabelTextColor, valueLabelBackgroundColor); + BindRKVOModel(self, @selector(customizeBackground), tableColor, backgroundColor); + BindRKVOModel(self, @selector(updateFieldVisibilities), isBeidouCountVisible, isGlonassCountVisible, isGalileoCountVisible); + + // Send Hooks + BindRKVOModel(self.widgetModel, @selector(sendIsProductConnected), isProductConnected); + BindRKVOModel(self.widgetModel, @selector(sendRTKConnectedUpdate), rtkSupported); + BindRKVOModel(self.widgetModel, @selector(sendRTKStateUpdate), rtkState); + BindRKVOModel(self.widgetModel, @selector(sendModelUpdate), rtkSupported); + BindRKVOModel(self.widgetModel, @selector(sendRTKSignalUpdate), rtkSupported); + BindRKVOModel(self.widgetModel, @selector(sendRTKStandardDeviationUpdate), rtkState.mobileStationStandardDeviation); + BindRKVOModel(self.widgetModel, @selector(sendBaseStationStatusUpdate), rtkSupported); + BindRKVOModel(self.widgetModel, @selector(sendNetRTCMStatusUpdate), rtkSupported); +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + + UnBindRKVOModel(self.widgetModel); +} + +- (void)dealloc { + [self.widgetModel cleanup]; +} + +- (void)setupUI { + // Setup Elements Above Table: + self.view.translatesAutoresizingMaskIntoConstraints = NO; + [self.view.widthAnchor constraintEqualToConstant:kDesignSize.width].active = YES; + + self.view.backgroundColor = [UIColor duxbeta_blackColor]; + self.view.translatesAutoresizingMaskIntoConstraints = NO; + + NSString *connectionStatus = [[self stringForReferenceStationSource:self.widgetModel.rtkSignal] stringByAppendingString:kConnectionStatusTitleEnding]; + self.statusTitleLabel = [self tableLabelWithTitle:connectionStatus textColor:[UIColor duxbeta_whiteColor] andFont:[UIFont systemFontOfSize:kDefaultFontSize]]; + + [self.view addSubview:self.statusTitleLabel]; + + [self.statusTitleLabel.leftAnchor constraintEqualToAnchor:self.view.leftAnchor constant:kMarginForTitles].active = YES; + [self.statusTitleLabel.topAnchor constraintEqualToAnchor:self.view.topAnchor constant:kMarginThin].active = YES; + [self.statusTitleLabel.heightAnchor constraintEqualToConstant:20.0].active = YES; + + UIColor *statusInitialColor = [self.statusLabelColors objectForKey:@(DUXBetaRTKConnectionStatusDisconnected)]; + self.statusLabel = [self tableLabelWithTitle:@"Disconnected" textColor:statusInitialColor andFont:[UIFont systemFontOfSize:kDefaultFontSize]]; + [self.view addSubview:self.statusLabel]; + + [self.statusLabel.centerYAnchor constraintEqualToAnchor:self.statusTitleLabel.centerYAnchor].active = YES; + [self.statusLabel.leftAnchor constraintEqualToAnchor:self.statusTitleLabel.rightAnchor].active = YES; + [self.statusLabel.heightAnchor constraintEqualToAnchor:self.statusTitleLabel.heightAnchor].active = YES; + + // Setup RTK Satellite Status Table + self.tableBorderView = [[UIView alloc] init]; + self.tableBorderView.translatesAutoresizingMaskIntoConstraints = NO; + self.tableBorderView.layer.borderWidth = kTableLineWidth; + self.tableBorderView.layer.cornerRadius = 15.0; + [self.view addSubview:self.tableBorderView]; + + [self.tableBorderView.leftAnchor constraintEqualToAnchor:self.view.leftAnchor constant:kMarginForTitles].active = YES; + [self.tableBorderView.rightAnchor constraintEqualToAnchor:self.view.rightAnchor constant:-kMarginForTitles].active = YES; + [self.tableBorderView.topAnchor constraintEqualToAnchor:self.statusTitleLabel.bottomAnchor constant:kMarginThin].active = YES; + [self.tableBorderView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES; + + UILayoutGuide *column1Guide = [[UILayoutGuide alloc] init]; + [self.view addLayoutGuide:column1Guide]; + [column1Guide.widthAnchor constraintEqualToAnchor:self.tableBorderView.widthAnchor multiplier:0.25].active = YES; + [column1Guide.leftAnchor constraintEqualToAnchor:self.tableBorderView.leftAnchor].active = YES; + + UILayoutGuide *column2Guide = [[UILayoutGuide alloc] init]; + [self.view addLayoutGuide:column2Guide]; + [column2Guide.leftAnchor constraintEqualToAnchor:column1Guide.rightAnchor].active = YES; + + UILayoutGuide *column3Guide = [[UILayoutGuide alloc] init]; + [self.view addLayoutGuide:column3Guide]; + [column3Guide.leftAnchor constraintEqualToAnchor:column2Guide.rightAnchor].active = YES; + [column3Guide.rightAnchor constraintEqualToAnchor:self.tableBorderView.rightAnchor].active = YES; + [column3Guide.widthAnchor constraintEqualToAnchor:column2Guide.widthAnchor].active = YES; + + UILayoutGuide *row1Guide = [[UILayoutGuide alloc] init]; + [self.view addLayoutGuide:row1Guide]; + [row1Guide.topAnchor constraintEqualToAnchor:self.tableBorderView.topAnchor].active = YES; + [row1Guide.heightAnchor constraintEqualToConstant:30.0].active = YES; + + UILayoutGuide *row2Guide = [[UILayoutGuide alloc] init]; + [self.view addLayoutGuide:row2Guide]; + [row2Guide.topAnchor constraintEqualToAnchor:row1Guide.bottomAnchor].active = YES; + + UILayoutGuide *row3Guide = [[UILayoutGuide alloc] init]; + [self.view addLayoutGuide:row3Guide]; + [row3Guide.topAnchor constraintEqualToAnchor:row2Guide.bottomAnchor].active = YES; + + // The antenna row guide sits between rows 3 and 4 of the table. + // It shows the titles Antenna 1 and Antenna 2 and is dynamically hidden when the drone has one rtk antenna. + UILayoutGuide *antennaLabelRowGuide = [[UILayoutGuide alloc] init]; + [self.view addLayoutGuide:antennaLabelRowGuide]; + self.antennaRowTopConstraint = [antennaLabelRowGuide.topAnchor constraintEqualToAnchor:row3Guide.bottomAnchor]; + self.antennaRowTopConstraint.active = YES; + [antennaLabelRowGuide.heightAnchor constraintEqualToAnchor:row1Guide.heightAnchor multiplier:1].active = YES; + + + UILayoutGuide *row4Guide = [[UILayoutGuide alloc] init]; + [self.view addLayoutGuide:row4Guide]; + [row4Guide.topAnchor constraintEqualToAnchor:antennaLabelRowGuide.bottomAnchor].active = YES; + self.row2ToRow3Constraint = [row4Guide.topAnchor constraintEqualToAnchor:row3Guide.bottomAnchor]; + self.row2ToRow3Constraint.active = NO; + + self.row4ConstraintToBorder = [row4Guide.bottomAnchor constraintEqualToAnchor:self.tableBorderView.bottomAnchor]; + self.row4ConstraintToBorder.active = NO; + + UILayoutGuide *row5Guide = [[UILayoutGuide alloc] init]; + [self.view addLayoutGuide:row5Guide]; + + self.row4ConstraintToRow5 = [row5Guide.topAnchor constraintEqualToAnchor:row4Guide.bottomAnchor]; + self.row4ConstraintToRow5.active = YES; + + self.row5ConstraintToBorder = [row5Guide.bottomAnchor constraintEqualToAnchor:self.tableBorderView.bottomAnchor]; + self.row5ConstraintToBorder.active = YES; + + self.horizontalDivider1 = [self createDivider]; + [self.horizontalDivider1.leftAnchor constraintEqualToAnchor:self.tableBorderView.leftAnchor].active = YES; + [self.horizontalDivider1.heightAnchor constraintEqualToConstant:kTableLineWidth].active = YES; + [self.horizontalDivider1.rightAnchor constraintEqualToAnchor:self.tableBorderView.rightAnchor].active = YES; + [self.horizontalDivider1.centerYAnchor constraintEqualToAnchor:row1Guide.bottomAnchor].active = YES; + + self.horizontalDivider2 = [self createDivider]; + [self.horizontalDivider2.leadingAnchor constraintEqualToAnchor:self.tableBorderView.leadingAnchor].active = YES; + [self.horizontalDivider2.heightAnchor constraintEqualToConstant:kTableLineWidth].active = YES; + [self.horizontalDivider2.trailingAnchor constraintEqualToAnchor:self.tableBorderView.trailingAnchor].active = YES; + [self.horizontalDivider2.centerYAnchor constraintEqualToAnchor:row2Guide.bottomAnchor].active = YES; + + self.horizontalDivider3 = [self createDivider]; + [self.horizontalDivider3.leadingAnchor constraintEqualToAnchor:self.tableBorderView.leadingAnchor].active = YES; + [self.horizontalDivider3.heightAnchor constraintEqualToConstant:kTableLineWidth].active = YES; + [self.horizontalDivider3.trailingAnchor constraintEqualToAnchor:self.tableBorderView.trailingAnchor].active = YES; + [self.horizontalDivider3.centerYAnchor constraintEqualToAnchor:row3Guide.bottomAnchor].active = YES; + + self.horizontalDivider4 = [self createDivider]; + [self.horizontalDivider4.leadingAnchor constraintEqualToAnchor:self.tableBorderView.leadingAnchor].active = YES; + [self.horizontalDivider4.heightAnchor constraintEqualToConstant:kTableLineWidth].active = YES; + [self.horizontalDivider4.trailingAnchor constraintEqualToAnchor:self.tableBorderView.trailingAnchor].active = YES; + [self.horizontalDivider4.centerYAnchor constraintEqualToAnchor:row4Guide.bottomAnchor].active = YES; + + self.verticalDivider1 = [self createDivider]; + [self.verticalDivider1.topAnchor constraintEqualToAnchor:self.tableBorderView.topAnchor].active = YES; + [self.verticalDivider1.widthAnchor constraintEqualToConstant:kTableLineWidth].active = YES; + [self.verticalDivider1.bottomAnchor constraintEqualToAnchor:self.tableBorderView.bottomAnchor].active = YES; + [self.verticalDivider1.centerXAnchor constraintEqualToAnchor:column1Guide.rightAnchor].active = YES; + + self.verticalDivider2 = [self createDivider]; + [self.verticalDivider2.topAnchor constraintEqualToAnchor:self.tableBorderView.topAnchor].active = YES; + [self.verticalDivider2.widthAnchor constraintEqualToConstant:kTableLineWidth].active = YES; + [self.verticalDivider2.bottomAnchor constraintEqualToAnchor:self.tableBorderView.bottomAnchor].active = YES; + [self.verticalDivider2.centerXAnchor constraintEqualToAnchor:column2Guide.rightAnchor].active = YES; + + self.aircraftColumnLabel = [self titleLabelWithTitle:@"Aircraft"]; + [self.view addSubview:self.aircraftColumnLabel]; + [self.aircraftColumnLabel.leftAnchor constraintEqualToAnchor:column1Guide.rightAnchor constant:kMarginForTitles].active = YES; + [self.aircraftColumnLabel.rightAnchor constraintEqualToAnchor:column2Guide.rightAnchor constant:-kMarginForTitles].active = YES; + [self.aircraftColumnLabel.centerYAnchor constraintEqualToAnchor:row1Guide.centerYAnchor].active = YES; + + self.baseStationColumnLabel = [self titleLabelWithTitle:@"Base Station"]; + [self.view addSubview:self.baseStationColumnLabel]; + [self.baseStationColumnLabel.leftAnchor constraintEqualToAnchor:column2Guide.rightAnchor constant:kMarginForTitles].active = YES; + [self.baseStationColumnLabel.rightAnchor constraintEqualToAnchor:column3Guide.rightAnchor constant:-kMarginForTitles].active = YES; + [self.baseStationColumnLabel.centerYAnchor constraintEqualToAnchor:row1Guide.centerYAnchor].active = YES; + + UIStackView *precisionTitlesStackView = [self stackViewWithRowGuide:row2Guide columnGuide:column1Guide]; + + self.orientationRowLabel = [self titleLabelWithTitle:@"Orientation:"]; + self.positioningRowLabel = [self titleLabelWithTitle:@"Positioning:"]; + + [precisionTitlesStackView addArrangedSubview:self.orientationRowLabel]; + [precisionTitlesStackView addArrangedSubview:self.positioningRowLabel]; + + UIStackView *locationTitlesStackView = [self stackViewWithRowGuide:row3Guide columnGuide:column1Guide]; + + self.latitudeTitleLabel = [self titleLabelWithTitle:@"Latitude:"]; + self.longitudeTitleLabel = [self titleLabelWithTitle:@"Longitude:"]; + self.altitudeTitleLabel = [self titleLabelWithTitle:@"Altitude:"]; + self.courseAngleTitleLabel = [self titleLabelWithTitle:@"Course Angle:"]; + + [locationTitlesStackView addArrangedSubview:self.latitudeTitleLabel]; + [locationTitlesStackView addArrangedSubview:self.longitudeTitleLabel]; + [locationTitlesStackView addArrangedSubview:self.altitudeTitleLabel]; + [locationTitlesStackView addArrangedSubview:self.courseAngleTitleLabel]; + + self.antenna1TitleLabel = [self titleLabelWithTitle:@"Antenna 1"]; + self.antenna2TitleLabel = [self titleLabelWithTitle:@"Antenna 2"]; + [self.view addSubview:self.antenna1TitleLabel]; + [self.view addSubview:self.antenna2TitleLabel]; + + [self.antenna1TitleLabel.widthAnchor constraintEqualToAnchor:self.antenna2TitleLabel.widthAnchor].active = YES; + [self.antenna1TitleLabel.leftAnchor constraintEqualToAnchor:column2Guide.leftAnchor constant:kMarginForTitles].active = YES; + [self.antenna1TitleLabel.rightAnchor constraintEqualToAnchor:self.antenna2TitleLabel.leftAnchor constant:-kMarginForTitles].active = YES; + [self.antenna2TitleLabel.rightAnchor constraintEqualToAnchor:column2Guide.rightAnchor constant:-kMarginForTitles].active = YES; + [self.antenna1TitleLabel.centerYAnchor constraintEqualToAnchor:antennaLabelRowGuide.centerYAnchor].active = YES; + [self.antenna2TitleLabel.centerYAnchor constraintEqualToAnchor:antennaLabelRowGuide.centerYAnchor].active = YES; + + UIStackView *antenna1StackView = [self countStackViewWithRowGuide:row4Guide columnGuide:nil]; + [antenna1StackView.leftAnchor constraintEqualToAnchor:column2Guide.leftAnchor constant:kMarginForTitles].active = YES; + + self.antenna1GPSCountLabel = [self valueLabelWithTitle:@"N/A"]; + self.antenna1BeidouCountLabel = [self valueLabelWithTitle:@"N/A"]; + self.antenna1GlonassCountLabel = [self valueLabelWithTitle:@"N/A"]; + self.antenna1GalileoCountLabel = [self valueLabelWithTitle:@"N/A"]; + + [antenna1StackView addArrangedSubview:self.antenna1GPSCountLabel]; + [antenna1StackView addArrangedSubview:self.antenna1BeidouCountLabel]; + [antenna1StackView addArrangedSubview:self.antenna1GlonassCountLabel]; + [antenna1StackView addArrangedSubview:self.antenna1GalileoCountLabel]; + + UIStackView *antenna2StackView = [self countStackViewWithRowGuide:row4Guide columnGuide:nil]; + + [antenna2StackView.leftAnchor constraintEqualToAnchor:antenna1StackView.rightAnchor constant:kMarginForTitles].active = YES; + [antenna2StackView.rightAnchor constraintEqualToAnchor:column2Guide.rightAnchor constant:-kMarginForTitles].active = YES; + [antenna2StackView.widthAnchor constraintEqualToAnchor:antenna1StackView.widthAnchor].active = YES; + + + self.antenna2GPSCountLabel = [self valueLabelWithTitle:@"N/A"]; + self.antenna2BeidouCountLabel = [self valueLabelWithTitle:@"N/A"]; + self.antenna2GlonassCountLabel = [self valueLabelWithTitle:@"N/A"]; + self.antenna2GalileoCountLabel = [self valueLabelWithTitle:@"N/A"]; + + [antenna2StackView addArrangedSubview:self.antenna2GPSCountLabel]; + [antenna2StackView addArrangedSubview:self.antenna2BeidouCountLabel]; + [antenna2StackView addArrangedSubview:self.antenna2GlonassCountLabel]; + [antenna2StackView addArrangedSubview:self.antenna2GalileoCountLabel]; + + UIStackView *constellationNameStackView = [self countStackViewWithRowGuide:row4Guide columnGuide:column1Guide]; + + self.gpsTitleLabel = [self titleLabelWithTitle:@"GPS:"]; + self.beidouTitleLabel = [self titleLabelWithTitle:@"BeiDou:"]; + self.glonassTitleLabel = [self titleLabelWithTitle:@"GLONASS:"]; + self.galileoTitleLabel = [self titleLabelWithTitle:@"Galileo:"]; + [constellationNameStackView addArrangedSubview:self.gpsTitleLabel]; + [constellationNameStackView addArrangedSubview:self.beidouTitleLabel]; + [constellationNameStackView addArrangedSubview:self.glonassTitleLabel]; + [constellationNameStackView addArrangedSubview:self.galileoTitleLabel]; + + UIStackView *aircraftPrecisionStackView = [self stackViewWithRowGuide:row2Guide columnGuide:column2Guide]; + self.aircraftOrientationLabel = [self valueLabelWithTitle:@"N/A"]; + self.aircraftOrientationImageView = [[UIImageView alloc] initWithImage:self.orientationInvalidImage]; + self.aircraftPositioningLabel = [self valueLabelWithTitle:@"N/A"]; + self.aircraftOrientationLabel.hidden = YES; + self.aircraftOrientationImageView.hidden = YES; + + [aircraftPrecisionStackView addArrangedSubview:self.aircraftOrientationLabel]; + [aircraftPrecisionStackView addArrangedSubview:self.aircraftOrientationImageView]; + [aircraftPrecisionStackView addArrangedSubview:self.aircraftPositioningLabel]; + + UIStackView *aircraftLocationStackView = [self stackViewWithRowGuide:row3Guide columnGuide:column2Guide]; + self.aircraftLatitudeLabel = [self valueLabelWithTitle:@"N/A"]; + self.aircraftLongitudeLabel = [self valueLabelWithTitle:@"N/A"]; + self.aircraftAltitudeLabel = [self valueLabelWithTitle:@"N/A"]; + self.aircraftCourseAngleLabel = [self valueLabelWithTitle:@"N/A"]; + [aircraftLocationStackView addArrangedSubview:self.aircraftLatitudeLabel]; + [aircraftLocationStackView addArrangedSubview:self.aircraftLongitudeLabel]; + [aircraftLocationStackView addArrangedSubview:self.aircraftAltitudeLabel]; + [aircraftLocationStackView addArrangedSubview:self.aircraftCourseAngleLabel]; + + UIStackView *baseStationLocationStackView = [self stackViewWithRowGuide:nil columnGuide:column3Guide]; + [baseStationLocationStackView.topAnchor constraintEqualToAnchor:row3Guide.topAnchor constant:kMarginForTitles].active = YES; + + self.baseStationLatitudeLabel = [self valueLabelWithTitle:@"N/A"]; + self.baseStationLongitudeLabel = [self valueLabelWithTitle:@"N/A"]; + self.baseStationAltitudeLabel = [self valueLabelWithTitle:@"N/A"]; + [baseStationLocationStackView addArrangedSubview:self.baseStationLatitudeLabel]; + [baseStationLocationStackView addArrangedSubview:self.baseStationLongitudeLabel]; + [baseStationLocationStackView addArrangedSubview:self.baseStationAltitudeLabel]; + + UIStackView *baseStationCountStackView = [self countStackViewWithRowGuide:row4Guide columnGuide:column3Guide]; + self.baseStationGPSCountLabel = [self valueLabelWithTitle:@"N/A"]; + self.baseStationBeidouCountLabel = [self valueLabelWithTitle:@"N/A"]; + self.baseStationGlonassCountLabel = [self valueLabelWithTitle:@"N/A"]; + self.baseStationGalileoCountLabel = [self valueLabelWithTitle:@"N/A"]; + [baseStationCountStackView addArrangedSubview:self.baseStationGPSCountLabel]; + [baseStationCountStackView addArrangedSubview:self.baseStationBeidouCountLabel]; + [baseStationCountStackView addArrangedSubview:self.baseStationGlonassCountLabel]; + [baseStationCountStackView addArrangedSubview:self.baseStationGalileoCountLabel]; + + self.standardDeviationTitleLabel = [self titleLabelWithTitle:@"Standard Deviation:"]; + self.standardDeviationTitleLabel.numberOfLines = 2; + [self.view addSubview:self.standardDeviationTitleLabel]; + [self.standardDeviationTitleLabel.topAnchor constraintEqualToAnchor:row5Guide.topAnchor constant:kMarginForTitles].active = YES; + [self.standardDeviationTitleLabel.leftAnchor constraintEqualToAnchor:column1Guide.leftAnchor constant:kMarginForTitles].active = YES; + [self.standardDeviationTitleLabel.rightAnchor constraintEqualToAnchor:column1Guide.rightAnchor constant:-kMarginForTitles].active = YES; + + self.standardDeviationStackView = [self stackViewWithRowGuide:nil columnGuide:column2Guide]; + [self.standardDeviationStackView.topAnchor constraintEqualToAnchor:row5Guide.topAnchor constant:kMarginForTitles].active = YES; + [self.standardDeviationStackView.bottomAnchor constraintEqualToAnchor:row5Guide.bottomAnchor constant:-kMarginForTitles].active = YES; + self.latitudeStandardDeviationLabel = [self valueLabelWithTitle:@"N/A"]; + self.longitudeStandardDeviationLabel = [self valueLabelWithTitle:@"N/A"]; + self.altitudeStandardDeviationLabel = [self valueLabelWithTitle:@"N/A"]; + [self.standardDeviationStackView addArrangedSubview:self.latitudeStandardDeviationLabel]; + [self.standardDeviationStackView addArrangedSubview:self.longitudeStandardDeviationLabel]; + [self.standardDeviationStackView addArrangedSubview:self.altitudeStandardDeviationLabel]; +} + +// Helper method to create divider +- (UIView *)createDivider { + UIView *divider = [[UIView alloc] init]; + divider.translatesAutoresizingMaskIntoConstraints = NO; + divider.backgroundColor = [UIColor duxbeta_grayColor]; + [self.view addSubview:divider]; + return divider; +} + +// Helper method to create a title label +- (UILabel *)titleLabelWithTitle:(NSString *)labelTitle { + if (self.titleLabels == nil) { + self.titleLabels = [[NSMutableArray alloc] init]; + } + UILabel *titleLabel = [self tableLabelWithTitle:labelTitle textColor:self.titleLabelTextColor andFont:self.titleLabelFont]; + [self.titleLabels addObject:titleLabel]; + return titleLabel; +} + +// Helper method to create a value label +- (UILabel *)valueLabelWithTitle:(NSString *)labelTitle { + if (self.valueLabels == nil) { + self.valueLabels = [[NSMutableArray alloc] init]; + } + UILabel *valueLabel = [self tableLabelWithTitle:labelTitle textColor:self.valueLabelTextColor andFont:self.valueLabelFont]; + valueLabel.opaque = NO; + [self.valueLabels addObject:valueLabel]; + return valueLabel; +} + +// Helper method to create a label for the rtk satellite status table +- (UILabel *)tableLabelWithTitle:(NSString *)labelTitle textColor:(UIColor *)textColor andFont:(UIFont *)font { + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.textAlignment = NSTextAlignmentLeft; + titleLabel.textColor = [UIColor duxbeta_whiteColor]; + titleLabel.font = self.titleLabelFont; + titleLabel.text = NSLocalizedString(labelTitle, @"Table Label Title"); + return titleLabel; +} + +// Helper method to create a stack view constrained by the layout guides. +// Pass nil to layout guide to avoid constraining that dimension. +- (UIStackView *)stackViewWithRowGuide:(UILayoutGuide *)rowGuide columnGuide:(UILayoutGuide *)columnGuide { + UIStackView *stackView = [[UIStackView alloc] init]; + stackView.translatesAutoresizingMaskIntoConstraints = NO; + stackView.axis = UILayoutConstraintAxisVertical; + stackView.distribution = UIStackViewDistributionEqualSpacing; + stackView.alignment = UIStackViewAlignmentLeading; + stackView.spacing = kMarginThin; + [self.view addSubview:stackView]; + if (rowGuide != nil) { + [stackView.topAnchor constraintEqualToAnchor:rowGuide.topAnchor constant:kMarginForTitles].active = YES; + [stackView.bottomAnchor constraintEqualToAnchor:rowGuide.bottomAnchor constant:-kMarginForTitles].active = YES; + } + if (columnGuide != nil) { + [stackView.leftAnchor constraintEqualToAnchor:columnGuide.leftAnchor constant:kMarginForTitles].active = YES; + [stackView.rightAnchor constraintEqualToAnchor:columnGuide.rightAnchor constant:-kMarginForTitles].active = YES; + } + return stackView; +} + +- (UIStackView *)countStackViewWithRowGuide:(UILayoutGuide *)rowGuide columnGuide:(UILayoutGuide *)columnGuide { + UIStackView *countStackView = [self stackViewWithRowGuide:nil columnGuide:columnGuide]; + NSLayoutConstraint *stackViewTopConstraint = [countStackView.topAnchor constraintEqualToAnchor:rowGuide.topAnchor constant:0.0]; + stackViewTopConstraint.active = YES; + [self.countStackViewTopConstraints addObject:stackViewTopConstraint]; + [countStackView.bottomAnchor constraintEqualToAnchor:rowGuide.bottomAnchor constant:-kMarginForTitles].active = YES; + return countStackView; +} + +#pragma mark - Update Methods + +- (void)updateReferenceStationSource { + NSString *sourceString = [self stringForReferenceStationSource:self.widgetModel.rtkSignal]; + self.statusTitleLabel.text = [sourceString stringByAppendingString:kConnectionStatusTitleEnding]; + self.baseStationColumnLabel.text = sourceString; +} + +- (NSString *)stringForReferenceStationSource:(DUXBetaRTKSignal)signal { + switch (signal) { + case DUXBetaRTKSignalDRTK2: + return @"D-RTK 2 Mobile Station"; + case DUXBetaRTKSignalBaseStation: + return @"Base Station"; + case DUXBetaRTKSignalNetworkRTK: + return @"Network RTK"; + case DUXBetaRTKSignalCustomNetwork: + return @"Custom Network RTK"; + } +} + +- (void)updateConnectionStatus { + self.statusLabel.textColor = [self.statusLabelColors objectForKey:@(self.widgetModel.rtkConnectionStatus)]; + + switch (self.widgetModel.rtkSignal) { + case DUXBetaRTKSignalBaseStation: + case DUXBetaRTKSignalDRTK2: + [self updateBaseStationStatus]; + break; + case DUXBetaRTKSignalNetworkRTK: + case DUXBetaRTKSignalCustomNetwork: + [self updateNetworkStatus]; + break; + } +} + +- (void)updateBaseStationStatus { + switch (self.widgetModel.rtkConnectionStatus) { + case DUXBetaRTKConnectionStatusInUse: + self.statusLabel.text = NSLocalizedString(@"RTK connected. RTK data in use", @"Value label"); + break; + case DUXBetaRTKConnectionStatusNotInUse: + self.statusLabel.text = NSLocalizedString(@"RTK connected. RTK data not in use", @"Value label"); + break; + case DUXBetaRTKConnectionStatusDisconnected: + self.statusLabel.text = NSLocalizedString(@"Not Connected", @"Value label"); + break; + } +} + +- (void)updateNetworkStatus { + NSString *sourceString = [self stringForReferenceStationSource:self.widgetModel.rtkSignal]; + NSString *localizedInternalError = NSLocalizedString(@"Internal error", @"Value label"); + NSString *internalErrorMessage = [sourceString stringByAppendingFormat:@" %@", localizedInternalError]; + switch (self.widgetModel.networkServiceState.channelState) { + case DJIRTKNetworkServiceChannelStateTransmitting: + if (self.widgetModel.rtkInUse) { + self.statusLabel.text = NSLocalizedString(@"RTK connected. RTK data in use", @"Value label"); + } else { + self.statusLabel.text = NSLocalizedString(@"RTK connected. RTK data not in use", @"Value label"); + } + break; + case DJIRTKNetworkServiceChannelStateLoginFailure: + self.statusLabel.text = NSLocalizedString(@"Verification Failed", @"Value label"); + break; + case DJIRTKNetworkServiceChannelStateServiceSuspension: + self.statusLabel.text = NSLocalizedString(@"Suspending account...", @"Value label"); + break; + //case DJIRTKNetworkServiceChannelStateAccountExpired:// == ACCOUNT_EXPIRED missing? + //self.statusLabel.text = @"Time Verification Failed"; + //break; + case DJIRTKNetworkServiceChannelStateNetworkNotReachable: + self.statusLabel.text = NSLocalizedString(@"Network Unavailable", @"Value label"); + break; + case DJIRTKNetworkServiceChannelStateUnknown: + self.statusLabel.text = internalErrorMessage; + break; + case DJIRTKNetworkServiceChannelStateAccountError: + self.statusLabel.text = NSLocalizedString(@"Account error", @"Value label"); + break; + case DJIRTKNetworkServiceChannelStateConnecting: + self.statusLabel.text = NSLocalizedString(@"Connecting to server...", @"Value label"); + break; + case DJIRTKNetworkServiceChannelStateInvalidRequest: + self.statusLabel.text = NSLocalizedString(@"Request rejected by server", @"Value label"); + break; + case DJIRTKNetworkServiceChannelStateServerNotReachable: + self.statusLabel.text = NSLocalizedString(@"Connecting to server...", @"Value label"); + break; + default: + self.statusLabel.text = NSLocalizedString(@"Not Connected", @"Value label"); + break; + } +} + +- (void)updatePrecisionValues { + // Orientation + if (self.widgetModel.modelName == DJIAircraftModelNameMatrice210RTK) { + self.aircraftOrientationLabel.hidden = YES; + self.aircraftOrientationImageView.hidden = NO; + if (self.widgetModel.isHeadingValid) { + if (self.orientationValidTint) { + self.aircraftOrientationImageView.image = [self.orientationValidImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + } else { + self.aircraftOrientationImageView.image = self.aircraftHeadingValidImage; + } + } else { + if (self.orientationInvalidTint) { + self.aircraftOrientationImageView.image = [self.orientationInvalidImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + } else { + self.aircraftOrientationImageView.image = self.aircraftHeadingInvalidImage; + } + } + } else if (self.widgetModel.modelName == DJIAircraftModelNameMatrice210RTKV2) { + self.aircraftOrientationLabel.hidden = NO; + self.aircraftOrientationImageView.hidden = YES; + switch (self.widgetModel.rtkState.headingSolution) { + case DJIRTKHeadingSolutionNone: + self.aircraftOrientationLabel.text = @"N/A"; + break; + case DJIRTKHeadingSolutionSinglePoint: + self.aircraftOrientationLabel.text = NSLocalizedString(@"SINGLE", @"Value label"); + break; + case DJIRTKHeadingSolutionFloat: + self.aircraftOrientationLabel.text = NSLocalizedString(@"FLOAT", @"Value label"); + break; + case DJIRTKHeadingSolutionFixedPoint: + self.aircraftOrientationLabel.text = NSLocalizedString(@"FIXED", @"Value label"); + break; + case DJIRTKHeadingSolutionUnknown: + self.aircraftOrientationLabel.text = NSLocalizedString(@"UNKNOWN", @"Value label"); + break; + default: + break; + } + } + + //Position + switch (self.widgetModel.locationState) { + case DUXBetaRTKLocationStateNone: + self.aircraftPositioningLabel.text = @"N/A"; + break; + case DUXBetaRTKLocationStateSinglePoint: + self.aircraftPositioningLabel.text = NSLocalizedString(@"SINGLE", @"Value label"); + break; + case DUXBetaRTKLocationStateFloat: + case DUXBetaRTKLocationStateFloatIono: + case DUXBetaRTKLocationStateFloatNarrow: + self.aircraftPositioningLabel.text = NSLocalizedString(@"FLOAT", @"Value label"); + break; + case DUXBetaRTKLocationStateFixedPoint: + self.aircraftPositioningLabel.text = NSLocalizedString(@"FIXED", @"Value label"); + break; + default: + self.aircraftPositioningLabel.text = NSLocalizedString(@"UNKNOWN", @"Value label"); + break; + } +} + +- (void)updateLocationAndCountValues { + // Update Aircraft Location Values + if (self.widgetModel.locationState != DUXBetaRTKLocationStateNone) { + self.aircraftLatitudeLabel.text = [NSString stringWithFormat:@"%.9f", self.widgetModel.rtkState.mobileStationLocation.latitude]; + self.aircraftLongitudeLabel.text = [NSString stringWithFormat:@"%.9f", self.widgetModel.rtkState.mobileStationLocation.longitude]; + self.aircraftAltitudeLabel.text = [NSString stringWithFormat:@"%.3f", self.widgetModel.rtkState.mobileStationAltitude]; + self.baseStationLatitudeLabel.text = [NSString stringWithFormat:@"%.9f", self.widgetModel.rtkState.baseStationLocation.latitude]; + self.baseStationLongitudeLabel.text = [NSString stringWithFormat:@"%.9f", self.widgetModel.rtkState.baseStationLocation.longitude]; + self.baseStationAltitudeLabel.text = [NSString stringWithFormat:@"%.3f", self.widgetModel.rtkState.baseStationAltitude]; + self.aircraftCourseAngleLabel.text = self.widgetModel.isHeadingValid ? [NSString stringWithFormat:@"%.2f", self.widgetModel.rtkState.mobileStationFusionHeading] : @"N/A"; + } else { + self.aircraftLatitudeLabel.text = @"N/A"; + self.aircraftLongitudeLabel.text = @"N/A"; + self.aircraftAltitudeLabel.text = @"N/A"; + self.aircraftCourseAngleLabel.text = @"N/A"; + + self.baseStationLatitudeLabel.text = @"N/A"; + self.baseStationLongitudeLabel.text = @"N/A"; + self.baseStationAltitudeLabel.text = @"N/A"; + } + + if (self.widgetModel.rtkEnabled) { + self.antenna1GPSCountLabel.text = [NSString stringWithFormat:@"%lu", (unsigned long)self.widgetModel.rtkState.mobileStationReceiver1GPSInfo.satelliteCount]; + self.antenna1BeidouCountLabel.text = [NSString stringWithFormat:@"%lu", (unsigned long)self.widgetModel.rtkState.mobileStationReceiver1BeiDouInfo.satelliteCount]; + self.antenna1GlonassCountLabel.text = [NSString stringWithFormat:@"%lu", (unsigned long)self.widgetModel.rtkState.mobileStationReceiver1GalileoInfo.satelliteCount]; + self.antenna1GalileoCountLabel.text = [NSString stringWithFormat:@"%lu", (unsigned long)self.widgetModel.rtkState.mobileStationReceiver1GalileoInfo.satelliteCount]; + + self.antenna2GPSCountLabel.text = [NSString stringWithFormat:@"%lu", (unsigned long)self.widgetModel.rtkState.mobileStationReceiver2GPSInfo.satelliteCount]; + self.antenna2BeidouCountLabel.text = [NSString stringWithFormat:@"%lu", (unsigned long)self.widgetModel.rtkState.mobileStationReceiver2BeiDouInfo.satelliteCount]; + self.antenna2GlonassCountLabel.text = [NSString stringWithFormat:@"%lu", (unsigned long)self.widgetModel.rtkState.mobileStationReceiver2GLONASSInfo.satelliteCount]; + self.antenna2GalileoCountLabel.text = [NSString stringWithFormat:@"%lu", (unsigned long)self.widgetModel.rtkState.mobileStationReceiver2GalileoInfo.satelliteCount]; + + self.baseStationGPSCountLabel.text = [NSString stringWithFormat:@"%lu", (unsigned long)self.widgetModel.rtkState.baseStationReceiverGPSInfo.satelliteCount]; + self.baseStationBeidouCountLabel.text = [NSString stringWithFormat:@"%lu", (unsigned long)self.widgetModel.rtkState.baseStationReceiverBeiDouInfo.satelliteCount]; + self.baseStationGlonassCountLabel.text = [NSString stringWithFormat:@"%lu", (unsigned long)self.widgetModel.rtkState.baseStationReceiverGLONASSInfo.satelliteCount]; + self.baseStationGalileoCountLabel.text = [NSString stringWithFormat:@"%lu", (unsigned long)self.widgetModel.rtkState.baseStationReceiverGalileoInfo.satelliteCount]; + } else { + self.antenna1GPSCountLabel.text = @"N/A"; + self.antenna1BeidouCountLabel.text = @"N/A"; + self.antenna1GlonassCountLabel.text = @"N/A"; + self.antenna1GalileoCountLabel.text = @"N/A"; + + self.antenna2GPSCountLabel.text = @"N/A"; + self.antenna2BeidouCountLabel.text = @"N/A"; + self.antenna2GlonassCountLabel.text = @"N/A"; + self.antenna2GalileoCountLabel.text = @"N/A"; + + self.baseStationGPSCountLabel.text = @"N/A"; + self.baseStationBeidouCountLabel.text = @"N/A"; + self.baseStationGlonassCountLabel.text = @"N/A"; + self.baseStationGalileoCountLabel.text = @"N/A"; + } + [self.view setNeedsDisplay]; +} + +- (void)updateStandardDeviations { + self.latitudeStandardDeviationLabel.text = [NSString stringWithFormat:@"%.7f m", self.widgetModel.rtkState.mobileStationStandardDeviation.latitude]; + self.longitudeStandardDeviationLabel.text = [NSString stringWithFormat:@"%.7f m", self.widgetModel.rtkState.mobileStationStandardDeviation.longtitude]; + self.altitudeStandardDeviationLabel.text = [NSString stringWithFormat:@"%.7f m", self.widgetModel.rtkState.mobileStationStandardDeviation.altitude]; +} + +- (void)updateFieldVisibilities { + // Orientation + NSArray *aircraftWithOrientation = @[DJIAircraftModelNameMatrice210RTK, DJIAircraftModelNameMatrice210RTKV2];//TODO: M300 too? + CGFloat shouldShowOrientation = NO; + for (NSString *name in aircraftWithOrientation) { + if ([self.widgetModel.modelName isEqualToString:name]) { + shouldShowOrientation = YES; + } + } + if (shouldShowOrientation) { + self.orientationRowLabel.hidden = NO; + if ([self.widgetModel.modelName isEqualToString: DJIAircraftModelNameMatrice210RTK]) { + self.aircraftOrientationImageView.hidden = NO; + self.aircraftOrientationLabel.hidden = YES; + } else { + self.aircraftOrientationImageView.hidden = YES; + self.aircraftOrientationLabel.hidden = NO; + } + } else { + self.orientationRowLabel.hidden = YES; + self.aircraftOrientationImageView.hidden = YES; + self.aircraftOrientationLabel.hidden = YES; + } + + // Customize Satellite Constellation Count Visibility + self.beidouTitleLabel.hidden = !self.isBeidouCountVisible; + self.antenna1BeidouCountLabel.hidden = !self.isBeidouCountVisible; + self.baseStationBeidouCountLabel.hidden = !self.isBeidouCountVisible; + + self.glonassTitleLabel.hidden = !self.isGlonassCountVisible; + self.antenna1GlonassCountLabel.hidden = !self.isGlonassCountVisible; + self.baseStationGlonassCountLabel.hidden = !self.isGlonassCountVisible; + + self.galileoTitleLabel.hidden = !self.isGalileoCountVisible; + self.antenna1GalileoCountLabel.hidden = !self.isGalileoCountVisible; + self.baseStationGalileoCountLabel.hidden = !self.isGalileoCountVisible; + + // Scale Row Height Appropriately + self.visibleConstellationCount = 4; + if (!self.isBeidouCountVisible) { + self.visibleConstellationCount--; + } + if (!self.isGlonassCountVisible) { + self.visibleConstellationCount--; + } + if (!self.isGalileoCountVisible) { + self.visibleConstellationCount--; + } + + // Antenna 2 Values and Antenna 1 Header should be hidden for P4R + if ([self.widgetModel.modelName isEqualToString:DJIAircraftModelNamePhantom4RTK]) { + self.antenna1TitleLabel.hidden = YES; + self.antenna2TitleLabel.hidden = YES; + self.antenna2GPSCountLabel.hidden = YES; + self.antenna2BeidouCountLabel.hidden = YES; + self.antenna2GlonassCountLabel.hidden = YES; + self.antenna2GalileoCountLabel.hidden = YES; + + self.antennaRowTopConstraint.active = NO; + self.row2ToRow3Constraint.active = YES; + + [self toggleRow4TopAnchorMargins:YES]; + } else { + self.antenna1TitleLabel.hidden = NO; + self.antenna2TitleLabel.hidden = NO; + self.antenna2GPSCountLabel.hidden = NO; + self.antenna2BeidouCountLabel.hidden = !self.isBeidouCountVisible; + self.antenna2GlonassCountLabel.hidden = !self.isGlonassCountVisible; + self.antenna2GalileoCountLabel.hidden = !self.isGalileoCountVisible; + + self.antennaRowTopConstraint.active = YES; + self.row2ToRow3Constraint.active = NO; + + [self toggleRow4TopAnchorMargins:NO]; + } + + // Standard Deviation + if ([self.widgetModel.modelName isEqualToString:DJIAircraftModelNamePhantom4RTK]) { + self.row4ConstraintToBorder.active = NO; + self.row4ConstraintToRow5.active = YES; + self.horizontalDivider4.hidden = NO; + self.standardDeviationStackView.hidden = NO; + self.standardDeviationTitleLabel.hidden = NO; + } else { + self.row4ConstraintToBorder.active = YES; + self.row4ConstraintToRow5.active = NO; + self.horizontalDivider4.hidden = YES; + self.standardDeviationStackView.hidden = YES; + self.standardDeviationTitleLabel.hidden = YES; + } +} + +- (void)toggleRow4TopAnchorMargins:(BOOL)marginsOn { + for (NSLayoutConstraint *constraint in self.countStackViewTopConstraints) { + constraint.constant = marginsOn ? kMarginForTitles : 0.0; + } +} +#pragma mark - Customization Methods +- (void)customizeConnectionStatus { + self.statusTitleLabel.font = self.statusTitleLabelFont; + self.statusTitleLabel.textColor = self.statusTitleLabelTextColor; + self.statusTitleLabel.backgroundColor = self.statusTitleLabelBackgroundColor; + + self.statusLabel.font = self.statusLabelFont; + self.statusLabel.backgroundColor = self.statusLabelBackgroundColor; +} + +- (void)customizeBackground { + // Customize Widget Background + self.view.backgroundColor = self.backgroundColor; + + // Customize Table Color + self.tableBorderView.layer.borderColor = self.tableColor.CGColor; + self.horizontalDivider1.backgroundColor = self.tableColor; + self.horizontalDivider2.backgroundColor = self.tableColor; + self.horizontalDivider3.backgroundColor = self.tableColor; + self.horizontalDivider4.backgroundColor = self.tableColor; + + self.verticalDivider1.backgroundColor = self.tableColor; + self.verticalDivider2.backgroundColor = self.tableColor; +} + +- (void)customizeTitles { + for (UILabel *label in self.titleLabels) { + label.font = self.titleLabelFont; + label.textColor = self.titleLabelTextColor; + label.backgroundColor = self.titleLabelBackgroundColor; + } +} + +- (void)customizeValues { + for (UILabel *label in self.valueLabels) { + label.font = self.valueLabelFont; + label.textColor = self.valueLabelTextColor; + label.backgroundColor = self.valueLabelBackgroundColor; + } +} + +- (void)setStatusTextColor:(UIColor *)fontColor forConnectionStatus:(DUXBetaRTKConnectionStatus)status { + [self.statusLabelColors setObject:fontColor forKey:@(status)]; +} + +- (UIColor *)statusTextColorForConnectionStatus:(DUXBetaRTKConnectionStatus)status { + return self.statusLabelColors[@(status)]; +} + +#pragma mark - Miscellanious +- (DUXBetaWidgetSizeHint)widgetSizeHint { + DUXBetaWidgetSizeHint hint = {kDesignSize.width / kDesignSize.height, kDesignSize.width, kDesignSize.height}; + return hint; +} + +- (NSString *)toolbarItemTitle { + return @"RTK"; +} + +- (UIImage *)toolbarItemIcon { + return [UIImage duxbeta_imageWithAssetNamed:@"GPSSignalIcon"]; +} + +#pragma mark - Send Updates +- (void)sendIsProductConnected { + [[DUXBetaStateChangeBroadcaster instance] send:[RTKSatelliteStatusWidgetModelState productConnected:self.widgetModel.isProductConnected]]; +} + +- (void)sendRTKConnectedUpdate { + [[DUXBetaStateChangeBroadcaster instance] send:[RTKSatelliteStatusWidgetModelState rtkConnectedUpdate:self.widgetModel.rtkSupported]]; +} + +- (void)sendRTKStateUpdate { + if (self.widgetModel.rtkState != nil) { + [[DUXBetaStateChangeBroadcaster instance] send:[RTKSatelliteStatusWidgetModelState rtkStateUpdate:self.widgetModel.rtkState]]; + } +} + +- (void)sendModelUpdate { + if (self.widgetModel.modelName != nil) { + [[DUXBetaStateChangeBroadcaster instance] send:[RTKSatelliteStatusWidgetModelState modelUpdate:self.widgetModel.modelName]]; + } +} + +- (void)sendRTKSignalUpdate { + [[DUXBetaStateChangeBroadcaster instance] send:[RTKSatelliteStatusWidgetModelState rtkSignalUpdate:self.widgetModel.rtkSignal]]; +} + +- (void)sendRTKStandardDeviationUpdate { + if (self.widgetModel.rtkState.mobileStationStandardDeviation != nil) { + [[DUXBetaStateChangeBroadcaster instance] send:[RTKSatelliteStatusWidgetModelState standardDeviationUpdate:self.widgetModel.rtkState.mobileStationStandardDeviation]]; + } +} + +- (void)sendBaseStationStatusUpdate { + [[DUXBetaStateChangeBroadcaster instance] send:[RTKSatelliteStatusWidgetModelState baseStationStatusUpdate:self.widgetModel.rtkConnectionStatus]]; +} + +- (void)sendNetRTCMStatusUpdate { + if (self.widgetModel.networkServiceState != nil) { + [[DUXBetaStateChangeBroadcaster instance] send:[RTKSatelliteStatusWidgetModelState rtkNetRTCMStatusUpdate:self.widgetModel.networkServiceState]]; + } +} + + +@end + +@implementation RTKSatelliteStatusWidgetModelState + ++ (instancetype)productConnected:(BOOL)isConnected { + return [[RTKSatelliteStatusWidgetModelState alloc] initWithKey:@"productConnected" number:[NSNumber numberWithBool:isConnected]]; +} + ++ (instancetype)rtkConnectedUpdate:(BOOL)isRTKConnected { + return [[RTKSatelliteStatusWidgetModelState alloc] initWithKey:@"rtkConnectedUpdate" number:[NSNumber numberWithBool:isRTKConnected]]; +} + ++ (instancetype)rtkStateUpdate:(DJIRTKState *)rtkState { + return [[RTKSatelliteStatusWidgetModelState alloc] initWithKey:@"rtkStateUpdate" object:rtkState]; +} + ++ (instancetype)modelUpdate:(NSString *)modelName { + return [[RTKSatelliteStatusWidgetModelState alloc] initWithKey:@"modelUpdate" string:modelName]; +} + ++ (instancetype)rtkSignalUpdate:(DJIRTKReferenceStationSource)rtkSignal { + return [[RTKSatelliteStatusWidgetModelState alloc] initWithKey:@"modelUpdate" number:[NSNumber numberWithUnsignedChar:rtkSignal]]; +} + ++ (instancetype)standardDeviationUpdate:(DJILocationStandardDeviation *)locationStandardDeviation { + return [[RTKSatelliteStatusWidgetModelState alloc] initWithKey:@"standardDeviationUpdate" object:locationStandardDeviation]; +} + ++ (instancetype)baseStationStatusUpdate:(DUXBetaRTKConnectionStatus)connectionStatus { + return [[RTKSatelliteStatusWidgetModelState alloc] initWithKey:@"baseStationStatusUpdate" number:[NSNumber numberWithUnsignedLong:connectionStatus]]; +} + ++ (instancetype)rtkNetRTCMStatusUpdate:(DJIRTKNetworkServiceState *)networkServiceState { + return [[RTKSatelliteStatusWidgetModelState alloc] initWithKey:@"rtkNetRTCMStatusUpdate" object:networkServiceState]; +} + +@end + diff --git a/DJIUXSDKWidgets/RTKSatelliteStatusWidget/DUXBetaRTKSatelliteStatusWidgetModel.h b/DJIUXSDKWidgets/RTKSatelliteStatusWidget/DUXBetaRTKSatelliteStatusWidgetModel.h new file mode 100644 index 0000000..6539f3b --- /dev/null +++ b/DJIUXSDKWidgets/RTKSatelliteStatusWidget/DUXBetaRTKSatelliteStatusWidgetModel.h @@ -0,0 +1,69 @@ +// +// DUXBetaRTKSatelliteStatusWidgetModel.h +// DJIUXSDK +// +// MIT License +// +// Copyright © 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. +// + +#import + +typedef NS_ENUM(NSUInteger, DUXBetaRTKConnectionStatus) { + DUXBetaRTKConnectionStatusInUse = 0, + DUXBetaRTKConnectionStatusNotInUse = 1, + DUXBetaRTKConnectionStatusDisconnected = 2, +}; + +typedef NS_ENUM(NSUInteger, DUXBetaRTKSignal) { + DUXBetaRTKSignalBaseStation = 0, + DUXBetaRTKSignalDRTK2 = 1, + DUXBetaRTKSignalNetworkRTK = 2, + DUXBetaRTKSignalCustomNetwork = 3, +}; + +NS_ASSUME_NONNULL_BEGIN + +@interface DUXBetaRTKSatelliteStatusWidgetModel : DUXBetaBaseWidgetModel + +@property (assign, nonatomic, readonly) BOOL rtkSupported; + +@property (assign, nonatomic, readonly) BOOL rtkInUse; + +@property (assign, nonatomic, readonly) BOOL rtkEnabled; + +@property (assign, nonatomic, readonly) DUXBetaRTKConnectionStatus rtkConnectionStatus; + +@property (assign, nonatomic, readonly) DUXBetaRTKSignal rtkSignal; + +@property (assign, nonatomic, readonly) DJIRTKNetworkServiceState *networkServiceState; + +@property (assign, nonatomic, readonly) BOOL isHeadingValid; + +@property (assign, nonatomic, readonly) NSUInteger locationState; + +@property (strong, nonatomic, readonly) NSString *modelName; + +@property (strong, nonatomic, readonly) DJIRTKState *rtkState; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DJIUXSDKWidgets/RTKSatelliteStatusWidget/DUXBetaRTKSatelliteStatusWidgetModel.m b/DJIUXSDKWidgets/RTKSatelliteStatusWidget/DUXBetaRTKSatelliteStatusWidgetModel.m new file mode 100644 index 0000000..718f1ed --- /dev/null +++ b/DJIUXSDKWidgets/RTKSatelliteStatusWidget/DUXBetaRTKSatelliteStatusWidgetModel.m @@ -0,0 +1,168 @@ +// +// DUXBetaRTKSatelliteStatusWidgetModel.m +// DJIUXSDK +// +// MIT License +// +// Copyright © 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. +// + +#import "DUXBetaRTKSatelliteStatusWidgetModel.h" +#import "DUXBetaBaseWidgetModel+Protected.h" +@import DJIUXSDKCore; + +@interface DUXBetaRTKSatelliteStatusWidgetModel() + +@property (assign, nonatomic, readwrite) BOOL rtkSupported; + +@property (assign, nonatomic, readwrite) BOOL rtkEnabled; +@property (assign, nonatomic, readwrite) BOOL rtkInUse; + +@property (assign, nonatomic, readwrite) DUXBetaRTKConnectionStatus rtkConnectionStatus; + +@property (assign, nonatomic, readwrite) BOOL isHeadingValid; + +@property (assign, nonatomic) DJIRTKReferenceStationSource rtkReferenceSource; + +@property (assign, nonatomic, readwrite) DUXBetaRTKSignal rtkSignal; + +@property (assign, nonatomic) DJIRTKNetworkServiceState *networkServiceState; + +@property (assign, nonatomic, readwrite) DJIRTKHeadingSolution headingSolution; + +@property (strong, nonatomic, readwrite) NSString *modelName; + +@property (strong, nonatomic, readwrite, nullable) NSError *rtkError; + +@property (assign, nonatomic, readwrite) NSUInteger locationState; + +@property (strong, nonatomic, readwrite) DJIRTKState *rtkState; + +@end + +@implementation DUXBetaRTKSatelliteStatusWidgetModel + +- (instancetype)init { + self = [super init]; + if (self) { + _modelName = DJIAircraftModelNameUnknownAircraft; + _rtkSupported = NO; + _rtkEnabled = YES; + _locationState = 0; + _rtkError = nil; + _rtkConnectionStatus = DUXBetaRTKConnectionStatusDisconnected; + } + return self; +} + +- (void)inSetup { + //Model Name + BindSDKKey([DJIProductKey keyWithParam:DJIProductParamModelName], modelName); + + //RTK Reference Source + BindSDKKey([self rtkKeyWithParam:DJIRTKParamReferenceStationSource], rtkReferenceSource); + + //Supported + BindSDKKey([self rtkKeyWithParam:DJIFlightControllerParamRTKSupported], rtkSupported); + + //Using rtk? + BindSDKKey([self rtkKeyWithParam:DJIRTKParamIsRTKBeingUsed], rtkInUse); + + //Enabled + BindSDKKey([self rtkKeyWithParam:DJIRTKParamEnabled], rtkEnabled); + + //Status + BindSDKKey([self rtkKeyWithParam:DJIRTKParamStatus], locationState); + + //Error + BindSDKKey([self rtkKeyWithParam:DJIRTKParamError], rtkError); + + BindSDKKey([self rtkKeyWithParam:DJIRTKParamIsHeadingValid], isHeadingValid); + BindSDKKey([self rtkKeyWithParam:DJIRTKParamHeadingSolution], headingSolution); + + + [DJISDKManager.rtkNetworkServiceProvider addNetworkServiceStateListener:self queue:nil block:^(DJIRTKNetworkServiceState * _Nonnull state) { + self.networkServiceState = state; + }]; + + if ([[DJISDKManager product] isKindOfClass:DJIAircraft.class]) { + DJIAircraft *djiAircraft = (DJIAircraft *)[DJISDKManager product]; + djiAircraft.flightController.RTK.delegate = self; + } + + BindRKVOModel(self, @selector(updateRTKSignal), modelName, rtkReferenceSource); + BindRKVOModel(self, @selector(updateRTKConnectionStatus), rtkEnabled, rtkInUse, isProductConnected); +} + +// Helper method to create an RTK Sub-Component Key +- (DJIFlightControllerKey *)rtkKeyWithParam:(NSString *)paramIn { + return [DJIFlightControllerKey keyWithIndex:0 + subComponent:DJIFlightControllerRTKSubComponent + subComponentIndex:0 + andParam:paramIn]; +} + +- (void)inCleanup { + UnBindSDK; + UnBindRKVOModel(self); +} + +// DJIRTKDelegate method to receive RTK State updates +- (void)rtk:(DJIRTK *_Nonnull)rtk didUpdateState:(DJIRTKState *_Nonnull)state { + self.rtkState = state; +} + +// Update RTK Signal Type +- (void)updateRTKSignal { + if ([self.modelName isEqualToString:DJIAircraftModelNameMatrice210RTK]) { + self.rtkSignal = DUXBetaRTKSignalBaseStation; + } else { + switch (self.rtkReferenceSource) { + case DJIRTKReferenceStationSourceBaseStation: + self.rtkSignal = DUXBetaRTKSignalDRTK2; + break; + case DJIRTKReferenceStationSourceUnknown: + self.rtkSignal = DUXBetaRTKSignalBaseStation; + break; + case DJIRTKReferenceStationSourceCustomNetworkService: + self.rtkSignal = DUXBetaRTKSignalCustomNetwork; + break; + case DJIRTKReferenceStationSourceNetworkRTK: + self.rtkSignal = DUXBetaRTKSignalNetworkRTK; + break; + } + } +} + +// Calculate Connection Status +- (void)updateRTKConnectionStatus { + if (self.rtkEnabled && self.isProductConnected) { + if (self.rtkInUse) { + self.rtkConnectionStatus = DUXBetaRTKConnectionStatusInUse; + } else { + self.rtkConnectionStatus = DUXBetaRTKConnectionStatusNotInUse; + } + } else { + self.rtkConnectionStatus = DUXBetaRTKConnectionStatusDisconnected; + } +} + +@end diff --git a/DJIUXSDKWidgets/RTKWidget/DUXBetaRTKWidget.h b/DJIUXSDKWidgets/RTKWidget/DUXBetaRTKWidget.h new file mode 100644 index 0000000..1f41bf8 --- /dev/null +++ b/DJIUXSDKWidgets/RTKWidget/DUXBetaRTKWidget.h @@ -0,0 +1,88 @@ +// +// DUXBetaRTKSatelliteStatusWidget.h +// DJIUXSDK +// +// MIT License +// +// Copyright © 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. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface DUXBetaRTKWidget : DUXBetaBaseWidget + +/** + * The DUXBetaRTKEnabledWidget positioned at the top of the widget. +*/ +@property (nonatomic, strong) DUXBetaRTKEnabledWidget *rtkEnabledWidget; + +/** + * The DUXBetaRTKSatelliteStatusWidget positioned near the bottom of the widget. +*/ +@property (nonatomic, strong) DUXBetaRTKSatelliteStatusWidget *rtkSatelliteStatusWidget; + +/** + * The font of the RTK description label. Default point size = 11.0. +*/ +@property (nonatomic, strong) UIFont *rtkDescriptionFont; + +/** + * The text color of the RTK description label. +*/ +@property (nonatomic, strong) UIColor *rtkDescriptionTextColor; + +/** + * The background color of the RTK description label. +*/ +@property (nonatomic, strong) UIColor *rtkDescriptionBackgroundColor; + +/** + * The font of the OK button. Default point size = 16.0. +*/ +@property (nonatomic, strong) UIFont *okFont; + +/** + * The text color of the OK button. +*/ +@property (nonatomic, strong) UIColor *okTextColor; + +/** + * The background color of the OK button. +*/ +@property (nonatomic, strong) UIColor *okBackgroundColor; + +/** + * The color of the separator lines for this widget and all sub-widgets. +*/ +@property (nonatomic, strong) UIColor *separatorColor; + +/** + * The background color of the widget. +*/ +@property (nonatomic, strong) UIColor *backgroundColor; + + + +@end + +NS_ASSUME_NONNULL_END diff --git a/DJIUXSDKWidgets/RTKWidget/DUXBetaRTKWidget.m b/DJIUXSDKWidgets/RTKWidget/DUXBetaRTKWidget.m new file mode 100644 index 0000000..4b2a42c --- /dev/null +++ b/DJIUXSDKWidgets/RTKWidget/DUXBetaRTKWidget.m @@ -0,0 +1,297 @@ +// +// DUXBetaRTKSatelliteStatusWidget.h +// DJIUXSDK +// +// MIT License +// +// Copyright © 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. +// + +#import "DUXBetaRTKWidget.h" +@import DJIUXSDKCore; + +static const float kOKButtonLineWidth = 1.0; +static const float kOKButtonDefaultFontSize = 16.0; +static const float kOKButtonHeight = 35.0; +static const float kDisabledLabelHeight = 160.0; +static const float kDisabledLabelWidth = 420.0; +static const float kMaxHeightProportion = 0.75; +static const float kDisabledLabelDefaultFontSize = 11.0; +static const float kOkButtonTopSpace = 15.0; + +static NSString *const kDisabledRTKMessage = @"Real-Time Kinematic positioning is based on the phase of the signal's carrier phase and outputs centimeter-level 3D positioning on particular coordinates. Heading is determined by the two antennas of the rover"; + + +@interface DUXBetaRTKWidget () + +@property (strong, nonatomic) UIButton *okButton; +@property (strong, nonatomic) UILabel *rtkDisabledLabel; +@property (strong, nonatomic) UIView *rtkDisabledView; +@property (strong, nonatomic) UIView *buttonDividerView; +@property (strong, nonatomic) NSLayoutConstraint *scrollViewHeightConstraintLarge; +@property (strong, nonatomic) NSLayoutConstraint *scrollViewHeightConstraintSmall; +@property (strong, nonatomic) UIScrollView *scrollView; +@property (strong, nonatomic) UIStackView *stackView; + +@end + +/** + * RTKWidgetUIState contains the hooks for UI changes in the widget class DUXBetaRTKWidget. + * It implements the hook: + * + * Key: okTap Type: NSNumber - Sends an NSNumber(0) when ok is tapped by the user. +*/ +@interface RTKWidgetUIState : DUXBetaStateChangeBaseData + ++ (instancetype)okTap; + +@end + +/** + * RTKWidgetModelState contains the model hooks for the DUXBetaRTKWidget. + * It implements the hooks: + * + * Key: productConnected Type: NSNumber - Sends a boolean value as an NSNumber indicating the connected state of + * the device when it changes. + * + * Key: rtkEnabledUpdate Type: NSNumber - Sends a boolean value as an NSNumber indicating if RTK is Enabled. + * Key: visibilityUpdate Type: NSNumber - Sends a boolean value as an NSNumber indicating if the widget is visible. +*/ +@interface RTKWidgetModelState : DUXBetaStateChangeBaseData + ++ (instancetype)productConnected:(BOOL)isConnected; + ++ (instancetype)rtkEnabledUpdate:(BOOL)isEnabled; + ++ (instancetype)visibilityUpdate:(BOOL)isVisible; + +@end + + +@implementation DUXBetaRTKWidget + +- (instancetype)init { + self = [super init]; + if (self) { + _rtkEnabledWidget = [[DUXBetaRTKEnabledWidget alloc] init]; + _rtkSatelliteStatusWidget = [[DUXBetaRTKSatelliteStatusWidget alloc] init]; + _rtkDisabledLabel = [[UILabel alloc] init]; + + _rtkDescriptionFont = [UIFont systemFontOfSize:kDisabledLabelDefaultFontSize]; + _rtkDescriptionTextColor = [UIColor duxbeta_whiteColor]; + _rtkDescriptionBackgroundColor = [UIColor duxbeta_clearColor]; + + _okFont = [UIFont systemFontOfSize:kOKButtonDefaultFontSize]; + _okTextColor = [UIColor duxbeta_whiteColor]; + + _okBackgroundColor = [UIColor duxbeta_clearColor]; + + _separatorColor = [UIColor duxbeta_rtkTableBorderColor]; + _backgroundColor = [UIColor duxbeta_blackColor]; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + [self setupUI]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + CGFloat widgetHeights = self.stackView.bounds.size.height; + if ([self.view superview].bounds.size.height * kMaxHeightProportion > widgetHeights) { + self.scrollViewHeightConstraintLarge = [self.scrollView.heightAnchor constraintEqualToAnchor:self.stackView.heightAnchor]; + } else { + self.scrollViewHeightConstraintLarge = [self.scrollView.heightAnchor constraintEqualToConstant:[self.view superview].bounds.size.height * kMaxHeightProportion]; + } + + BindRKVOModel(self.rtkSatelliteStatusWidget.widgetModel, @selector(updateSatelliteStatusVisibility), rtkEnabled, isProductConnected); + [[DUXBetaStateChangeBroadcaster instance] registerListener:self analyticsClassName:@"GPSSignalWidgetUIState" handler:^(DUXBetaStateChangeBaseData * _Nonnull analyticsData) { + [self showWidgetIfSupported]; + [[DUXBetaStateChangeBroadcaster instance] send:[RTKWidgetModelState visibilityUpdate:YES]]; + }]; + + // Customizations + BindRKVOModel(self, @selector(customizeDisabledLabel), rtkDescriptionTextColor, rtkDescriptionFont, rtkDescriptionBackgroundColor); + BindRKVOModel(self, @selector(customizeOkButton), okTextColor, okFont, okBackgroundColor); + BindRKVOModel(self, @selector(customizeBackground), backgroundColor, separatorColor); +} + +- (void)setupUI { + self.view.hidden = YES; + + self.scrollView = [[UIScrollView alloc] init]; + self.scrollView.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:self.scrollView]; + [self.scrollView.leftAnchor constraintEqualToAnchor:self.view.leftAnchor].active = YES; + [self.scrollView.rightAnchor constraintEqualToAnchor:self.view.rightAnchor].active = YES; + [self.scrollView.topAnchor constraintEqualToAnchor:self.view.topAnchor].active = YES; + + [self.scrollView.widthAnchor constraintEqualToConstant:self.rtkEnabledWidget.widgetSizeHint.minimumWidth].active = YES; + + self.stackView = [[UIStackView alloc] init]; + self.stackView.translatesAutoresizingMaskIntoConstraints = NO; + self.stackView.axis = UILayoutConstraintAxisVertical; + self.stackView.alignment = UIStackViewAlignmentLeading; + [self.scrollView addSubview:self.stackView]; + + [self.stackView.topAnchor constraintEqualToAnchor:self.scrollView.topAnchor].active = YES; + [self.stackView.leftAnchor constraintEqualToAnchor:self.scrollView.leftAnchor].active = YES; + [self.stackView.rightAnchor constraintEqualToAnchor:self.scrollView.rightAnchor].active = YES; + [self.stackView.bottomAnchor constraintEqualToAnchor:self.scrollView.bottomAnchor].active = YES; + + self.scrollViewHeightConstraintSmall = [self.scrollView.heightAnchor constraintEqualToAnchor:self.stackView.heightAnchor];//TODO: move to setupUI? + + // RTK Enabled Widget + self.rtkEnabledWidget.view.translatesAutoresizingMaskIntoConstraints = NO; + [self.stackView addArrangedSubview:self.rtkEnabledWidget.view]; + + // RTK Satellite Status Widget + //[self.scrollView addSubview:self.rtkSatelliteStatusWidget.view]; + self.rtkSatelliteStatusWidget.view.translatesAutoresizingMaskIntoConstraints = NO; + [self.stackView addArrangedSubview:self.rtkSatelliteStatusWidget.view]; + + // RTK Disabled Label View + self.rtkDisabledView = [[UIView alloc] init]; + self.rtkDisabledView.translatesAutoresizingMaskIntoConstraints = NO; + [self.rtkDisabledView.widthAnchor constraintEqualToConstant:500.0].active = YES; + [self.rtkDisabledView.heightAnchor constraintEqualToConstant:kDisabledLabelHeight].active = YES; + + self.rtkDisabledLabel.translatesAutoresizingMaskIntoConstraints = NO; + [self.rtkDisabledView addSubview:self.rtkDisabledLabel]; + [self.rtkDisabledLabel.widthAnchor constraintEqualToConstant:kDisabledLabelWidth].active = YES; + [self.rtkDisabledLabel.heightAnchor constraintEqualToConstant:kDisabledLabelHeight].active = YES; + [self.rtkDisabledLabel.centerYAnchor constraintEqualToAnchor:self.rtkDisabledView.centerYAnchor].active = YES; + [self.rtkDisabledLabel.centerXAnchor constraintEqualToAnchor:self.rtkDisabledView.centerXAnchor].active = YES; + self.rtkDisabledLabel.text = NSLocalizedString(kDisabledRTKMessage, @"RTKWidgetDisabledMessage"); + self.rtkDisabledLabel.numberOfLines = 3; + + [self.stackView addArrangedSubview:self.rtkDisabledView]; + + // OK Button + self.okButton = [[UIButton alloc] init]; + self.okButton.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:self.okButton]; + [self.okButton.leftAnchor constraintEqualToAnchor:self.view.leftAnchor].active = YES; + [self.okButton.rightAnchor constraintEqualToAnchor:self.view.rightAnchor].active = YES; + [self.okButton.topAnchor constraintEqualToAnchor:self.scrollView.bottomAnchor constant:kOkButtonTopSpace].active = YES; + [self.okButton.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES; + [self.okButton.heightAnchor constraintEqualToConstant:kOKButtonHeight].active = YES; + self.okButton.titleLabel.textAlignment = NSTextAlignmentCenter; + [self.okButton setTitle:NSLocalizedString(@"OK", @"RTKWidgetOK") forState:UIControlStateNormal]; + self.okButton.hidden = NO; + + [self.okButton addTarget:self action:@selector(hideWidget) forControlEvents:UIControlEventTouchUpInside]; + + self.buttonDividerView = [[UIView alloc] init]; + self.buttonDividerView.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:self.buttonDividerView]; + [self.buttonDividerView.heightAnchor constraintEqualToConstant:kOKButtonLineWidth].active = YES; + [self.buttonDividerView.leftAnchor constraintEqualToAnchor:self.view.leftAnchor].active = YES; + [self.buttonDividerView.rightAnchor constraintEqualToAnchor:self.view.rightAnchor].active = YES; + [self.buttonDividerView.topAnchor constraintEqualToAnchor:self.okButton.topAnchor].active = YES; +} + +- (void)hideWidget { + [self.view setHidden:YES]; + [[DUXBetaStateChangeBroadcaster instance] send:[RTKWidgetUIState okTap]]; + [[DUXBetaStateChangeBroadcaster instance] send:[RTKWidgetModelState visibilityUpdate:NO]]; +} + +- (void)showWidgetIfSupported { + dispatch_async(dispatch_get_main_queue(), ^{ + if ([self.rtkSatelliteStatusWidget.widgetModel.modelName isEqualToString:DJIAircraftModelNamePhantom4RTK] || + [self.rtkSatelliteStatusWidget.widgetModel.modelName isEqualToString:DJIAircraftModelNameMatrice210RTK] || + [self.rtkSatelliteStatusWidget.widgetModel.modelName isEqualToString:DJIAircraftModelNameMatrice210RTKV2] ) { + [self.view setHidden:NO]; + } + }); +} + +- (void)updateSatelliteStatusVisibility { + BOOL shouldHideSatelliteStatus = !(self.rtkEnabledWidget.widgetModel.rtkEnabled && self.rtkEnabledWidget.widgetModel.isProductConnected); + [self.rtkSatelliteStatusWidget.view setHidden:shouldHideSatelliteStatus]; + [self.rtkDisabledView setHidden:self.rtkEnabledWidget.widgetModel.rtkEnabled]; + self.scrollViewHeightConstraintLarge.active = !shouldHideSatelliteStatus; + self.scrollViewHeightConstraintSmall.active = shouldHideSatelliteStatus; + + [[DUXBetaStateChangeBroadcaster instance] send:[RTKWidgetModelState rtkEnabledUpdate:self.rtkEnabledWidget.widgetModel.rtkEnabled]]; + [[DUXBetaStateChangeBroadcaster instance] send:[RTKWidgetModelState productConnected:self.rtkEnabledWidget.widgetModel.isProductConnected]]; +} + +#pragma mark - Customizations + +- (void)customizeDisabledLabel { + self.rtkDisabledLabel.textColor = self.rtkDescriptionTextColor; + self.rtkDisabledLabel.font = self.rtkDescriptionFont; + self.rtkDisabledLabel.backgroundColor = self.rtkDescriptionBackgroundColor; +} + +- (void)customizeOkButton { + [self.okButton setTitleColor:self.okTextColor forState:UIControlStateNormal]; + self.okButton.titleLabel.font = self.okFont; + self.okButton.backgroundColor = self.okBackgroundColor; + [self.okButton setNeedsDisplay]; +} + +- (void)customizeBackground { + self.view.backgroundColor = self.backgroundColor; + self.buttonDividerView.backgroundColor = self.separatorColor; + if (self.rtkSatelliteStatusWidget != nil) { + self.rtkSatelliteStatusWidget.tableColor = self.separatorColor; + } +} + +#pragma mark - DUXBetaBaseWidget + +- (DUXBetaWidgetSizeHint)widgetSizeHint { + DUXBetaWidgetSizeHint hint = {self.view.bounds.size.width/self.view.bounds.size.height, self.view.bounds.size.width, self.view.bounds.size.height}; + return hint; +} + +@end + +@implementation RTKWidgetUIState : DUXBetaStateChangeBaseData + ++ (instancetype)okTap { + return [[RTKWidgetUIState alloc] initWithKey:@"okTap" number:@0]; +} + +@end + +@implementation RTKWidgetModelState : DUXBetaStateChangeBaseData + ++ (instancetype)productConnected:(BOOL)isConnected { + return [[RTKWidgetModelState alloc] initWithKey:@"productConnected" number:[NSNumber numberWithBool:isConnected]]; +} + ++ (instancetype)rtkEnabledUpdate:(BOOL)isEnabled { + return [[RTKWidgetModelState alloc] initWithKey:@"rtkEnabledUpdate" number:[NSNumber numberWithBool:isEnabled]]; +} + ++ (instancetype)visibilityUpdate:(BOOL)isVisible { + return [[RTKWidgetModelState alloc] initWithKey:@"visibilityUpdate" number:[NSNumber numberWithBool:isVisible]]; +} + +@end diff --git a/DJIUXSDKWidgets/RemainingFlightTimeWidget/DUXBetaRemainingFlightTimeData.h b/DJIUXSDKWidgets/RemainingFlightTimeWidget/DUXBetaRemainingFlightTimeData.h index e3e2bd3..a620268 100644 --- a/DJIUXSDKWidgets/RemainingFlightTimeWidget/DUXBetaRemainingFlightTimeData.h +++ b/DJIUXSDKWidgets/RemainingFlightTimeWidget/DUXBetaRemainingFlightTimeData.h @@ -2,6 +2,8 @@ // DUXBetaRemainingFlightTimeData.h // DJIUXSDKWidgets // +// MIT License +// // Copyright © 2018-2020 DJI // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -30,7 +32,7 @@ NS_ASSUME_NONNULL_BEGIN @interface DUXBetaRemainingFlightTimeData : NSObject /** - * Create a new DUXRemainingFlightTimeData object. + * Create a new DUXBetaRemainingFlightTimeData object. * * @param charge Battery charge remaining in percent. * @param batteryToLand Battery charge required to land. diff --git a/DJIUXSDKWidgets/RemainingFlightTimeWidget/DUXBetaRemainingFlightTimeData.m b/DJIUXSDKWidgets/RemainingFlightTimeWidget/DUXBetaRemainingFlightTimeData.m index c2f7467..5e1af3d 100644 --- a/DJIUXSDKWidgets/RemainingFlightTimeWidget/DUXBetaRemainingFlightTimeData.m +++ b/DJIUXSDKWidgets/RemainingFlightTimeWidget/DUXBetaRemainingFlightTimeData.m @@ -2,6 +2,8 @@ // DUXBetaRemainingFlightTimeData.m // DJIUXSDKWidgets // +// MIT License +// // Copyright © 2018-2020 DJI // // Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/DJIUXSDKWidgets/RemainingFlightTimeWidget/DUXBetaRemainingFlightTimeWidget.h b/DJIUXSDKWidgets/RemainingFlightTimeWidget/DUXBetaRemainingFlightTimeWidget.h index 3fcd9cc..8768d91 100644 --- a/DJIUXSDKWidgets/RemainingFlightTimeWidget/DUXBetaRemainingFlightTimeWidget.h +++ b/DJIUXSDKWidgets/RemainingFlightTimeWidget/DUXBetaRemainingFlightTimeWidget.h @@ -33,6 +33,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Widget that shows the remaining flight time information for the aircraft. */ + @interface DUXBetaRemainingFlightTimeWidget : DUXBetaBaseWidget /** diff --git a/DJIUXSDKWidgets/RemainingFlightTimeWidget/DUXBetaRemainingFlightTimeWidget.m b/DJIUXSDKWidgets/RemainingFlightTimeWidget/DUXBetaRemainingFlightTimeWidget.m index 9ae2bd2..778a63b 100644 --- a/DJIUXSDKWidgets/RemainingFlightTimeWidget/DUXBetaRemainingFlightTimeWidget.m +++ b/DJIUXSDKWidgets/RemainingFlightTimeWidget/DUXBetaRemainingFlightTimeWidget.m @@ -62,18 +62,18 @@ @interface DUXBetaRemainingFlightTimeWidget () @end /** - * DUXRemainingFlightTimeWidgetModelState contains the model hooks for the DUXRemainingFlightTimeWidget. + * DUXBetaRemainingFlightTimeWidgetModelState contains the model hooks for the DUXBetaRemainingFlightTimeWidget. * It implements the hooks: * * Key: productConnected Type: NSNumber - Sends a boolean value as an NSNumber indicating if an aircraft is connected. * - * Key: flightTimeDataUpdate Type: id - Sends a DUXRemainingFlightTimeData object whenever the remaining flight time updates. + * Key: flightTimeDataUpdate Type: id - Sends a DUXBetaRemainingFlightTimeData object whenever the remaining flight time updates. * This will send approximately once a second when the aircraft is active. * * Key: isAircraftFlyingUpdate Type: NSNumber - Sends a boolean value as an NSNumber indicating if the aircraft is currrently * flying. YES indicates in flight, no indicates landed. */ -@interface DUXRemainingFlightTimeWidgetModelState : DUXStateChangeBaseData +@interface DUXBetaRemainingFlightTimeWidgetModelState : DUXBetaStateChangeBaseData + (instancetype)productConnected:(BOOL)isConnected; + (instancetype)flightTimeDataUpdate:(DUXBetaRemainingFlightTimeData *)flightTimeData; @@ -456,18 +456,18 @@ - (DUXBetaWidgetSizeHint)widgetSizeHint { @end -@implementation DUXRemainingFlightTimeWidgetModelState +@implementation DUXBetaRemainingFlightTimeWidgetModelState + (instancetype)productConnected:(BOOL)isConnected { - return [[DUXRemainingFlightTimeWidgetModelState alloc] initWithKey:@"productConnected" number:@(isConnected)]; + return [[DUXBetaRemainingFlightTimeWidgetModelState alloc] initWithKey:@"productConnected" number:@(isConnected)]; } + (instancetype)flightTimeDataUpdate:(DUXBetaRemainingFlightTimeData *)flightTimeData { - return [[DUXRemainingFlightTimeWidgetModelState alloc] initWithKey:@"flightTimeDataUpdate" object:flightTimeData]; + return [[DUXBetaRemainingFlightTimeWidgetModelState alloc] initWithKey:@"flightTimeDataUpdate" object:flightTimeData]; } + (instancetype)isAircraftFlyingUpdate:(BOOL)isAircraftFlying { - return [[DUXRemainingFlightTimeWidgetModelState alloc] initWithKey:@"isAircraftFlyingUpdate" number:@(isAircraftFlying)]; + return [[DUXBetaRemainingFlightTimeWidgetModelState alloc] initWithKey:@"isAircraftFlyingUpdate" number:@(isAircraftFlying)]; } @end diff --git a/DJIUXSDKWidgets/RemoteControlSignalWidget/DUXBetaRemoteControllerSignalWidget.m b/DJIUXSDKWidgets/RemoteControlSignalWidget/DUXBetaRemoteControllerSignalWidget.m index 7e6b421..c6f9ba6 100644 --- a/DJIUXSDKWidgets/RemoteControlSignalWidget/DUXBetaRemoteControllerSignalWidget.m +++ b/DJIUXSDKWidgets/RemoteControlSignalWidget/DUXBetaRemoteControllerSignalWidget.m @@ -29,7 +29,7 @@ #import "UIImage+DUXBetaAssets.h" #import "UIFont+DUXBetaFonts.h" #import "UIColor+DUXBetaColors.h" -#import "DUXStateChangeBroadcaster.h" +#import "DUXBetaStateChangeBroadcaster.h" @import DJIUXSDKCore; @@ -53,7 +53,7 @@ @interface DUXBetaRemoteControllerSignalWidget () @end /** - * RemoteControllerSignalWidgetModelState contains the model hooks for the DUXRemoteControllerSignalWidget. + * RemoteControllerSignalWidgetModelState contains the model hooks for the DUXBetaRemoteControllerSignalWidget. * It implements the hooks: * * Key: productConnected Type: NSNumber - Sends a boolean value as an NSNumber indicating if an aircraft @@ -61,7 +61,7 @@ @interface DUXBetaRemoteControllerSignalWidget () * * Key: remoteControlSignalQualityUpdate Type: NSNumber - Sends changes in the remote controller signal controller as an NSNumber */ -@interface RemoteControllerSignalWidgetModelState : DUXStateChangeBaseData +@interface RemoteControllerSignalWidgetModelState : DUXBetaStateChangeBaseData + (instancetype)productConnected:(BOOL)isConnected; + (instancetype)remoteControllerSignalQualityUpdate:(NSInteger)signalQuality; @end @@ -166,11 +166,11 @@ - (void)setupUI { // Model Hooks - (void)sendIsProductConnected { - [[DUXStateChangeBroadcaster instance] send:[RemoteControllerSignalWidgetModelState productConnected:self.widgetModel.isProductConnected]]; + [[DUXBetaStateChangeBroadcaster instance] send:[RemoteControllerSignalWidgetModelState productConnected:self.widgetModel.isProductConnected]]; } - (void)sendRCSignalQualityUpdate { - [[DUXStateChangeBroadcaster instance] send:[RemoteControllerSignalWidgetModelState remoteControllerSignalQualityUpdate:self.widgetModel.barsLevel]]; + [[DUXBetaStateChangeBroadcaster instance] send:[RemoteControllerSignalWidgetModelState remoteControllerSignalQualityUpdate:self.widgetModel.barsLevel]]; } - (UIImage *)currentSignalIndicatorImage { @@ -222,4 +222,3 @@ + (instancetype)remoteControllerSignalQualityUpdate:(NSInteger)signalQuality { } @end - diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/Checkbox.imageset/Checkbox.pdf b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/Checkbox.imageset/Checkbox.pdf new file mode 100644 index 0000000..7a0a890 Binary files /dev/null and b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/Checkbox.imageset/Checkbox.pdf differ diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointNarrowedCross.imageset/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/Checkbox.imageset/Contents.json similarity index 79% rename from DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointNarrowedCross.imageset/Contents.json rename to DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/Checkbox.imageset/Contents.json index 40e7e71..7e26153 100644 --- a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointNarrowedCross.imageset/Contents.json +++ b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/Checkbox.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "CenterPointNarrowedCross.pdf", + "filename" : "Checkbox.pdf", "idiom" : "universal" } ], diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/CheckboxSelected.imageset/CheckboxSelected.pdf b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/CheckboxSelected.imageset/CheckboxSelected.pdf new file mode 100644 index 0000000..929a713 Binary files /dev/null and b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/CheckboxSelected.imageset/CheckboxSelected.pdf differ diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/CheckboxSelected.imageset/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/CheckboxSelected.imageset/Contents.json new file mode 100644 index 0000000..4590015 --- /dev/null +++ b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/CheckboxSelected.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "CheckboxSelected.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/Contents.json similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Contents.json rename to DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/Contents.json diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/ErrorIcon.imageset/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/ErrorIcon.imageset/Contents.json new file mode 100644 index 0000000..401d395 --- /dev/null +++ b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/ErrorIcon.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "ErrorIcon.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/ErrorIcon.imageset/ErrorIcon.pdf b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/ErrorIcon.imageset/ErrorIcon.pdf new file mode 100644 index 0000000..28d7fee Binary files /dev/null and b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/ErrorIcon.imageset/ErrorIcon.pdf differ diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SlideOff.imageset/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SlideOff.imageset/Contents.json new file mode 100644 index 0000000..501a77a --- /dev/null +++ b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SlideOff.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "SlideOn.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SlideOff.imageset/SlideOn.pdf b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SlideOff.imageset/SlideOn.pdf new file mode 100644 index 0000000..e561569 Binary files /dev/null and b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SlideOff.imageset/SlideOn.pdf differ diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SlideOn.imageset/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SlideOn.imageset/Contents.json new file mode 100644 index 0000000..169a390 --- /dev/null +++ b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SlideOn.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "SlideOn.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SlideOn.imageset/SlideOn.pdf b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SlideOn.imageset/SlideOn.pdf new file mode 100644 index 0000000..e561569 Binary files /dev/null and b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SlideOn.imageset/SlideOn.pdf differ diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/Slider.imageset/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/Slider.imageset/Contents.json new file mode 100644 index 0000000..8885d14 --- /dev/null +++ b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/Slider.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Slider.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/Slider.imageset/Slider.pdf b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/Slider.imageset/Slider.pdf new file mode 100644 index 0000000..0f66265 Binary files /dev/null and b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/Slider.imageset/Slider.pdf differ diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SliderChevron.imageset/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SliderChevron.imageset/Contents.json new file mode 100644 index 0000000..89b93b4 --- /dev/null +++ b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SliderChevron.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "SliderChevron.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SliderChevron.imageset/SliderChevron.pdf b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SliderChevron.imageset/SliderChevron.pdf new file mode 100644 index 0000000..0d1764d Binary files /dev/null and b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SliderChevron.imageset/SliderChevron.pdf differ diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SuccessIcon.imageset/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SuccessIcon.imageset/Contents.json new file mode 100644 index 0000000..1d98053 --- /dev/null +++ b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SuccessIcon.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "SuccessIcon.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SuccessIcon.imageset/SuccessIcon.pdf b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SuccessIcon.imageset/SuccessIcon.pdf new file mode 100644 index 0000000..e0b05e7 Binary files /dev/null and b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/SuccessIcon.imageset/SuccessIcon.pdf differ diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/WarningIcon.imageset/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/WarningIcon.imageset/Contents.json new file mode 100644 index 0000000..ac867ae --- /dev/null +++ b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/WarningIcon.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "WarningIcon.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/WarningIcon.imageset/WarningIcon.pdf b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/WarningIcon.imageset/WarningIcon.pdf new file mode 100644 index 0000000..6448c28 Binary files /dev/null and b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/Dialog/WarningIcon.imageset/WarningIcon.pdf differ diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointCircle.imageset/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointCircle.imageset/Contents.json index 303c92a..6770c86 100644 --- a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointCircle.imageset/Contents.json +++ b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointCircle.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "CenterPointCircle.pdf", + "filename" : "ic_fpv_centerpoint_circle.pdf", "idiom" : "universal" } ], diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointCircle.imageset/CenterPointCircle.pdf b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointCircle.imageset/ic_fpv_centerpoint_circle.pdf similarity index 100% rename from DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointCircle.imageset/CenterPointCircle.pdf rename to DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointCircle.imageset/ic_fpv_centerpoint_circle.pdf diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointCross.imageset/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointCross.imageset/Contents.json index 192a790..62bedc3 100644 --- a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointCross.imageset/Contents.json +++ b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointCross.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "CenterPointCross.pdf", + "filename" : "ic_fpv_centerpoint_cross.pdf", "idiom" : "universal" } ], diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointCross.imageset/CenterPointCross.pdf b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointCross.imageset/ic_fpv_centerpoint_cross.pdf similarity index 100% rename from DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointCross.imageset/CenterPointCross.pdf rename to DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointCross.imageset/ic_fpv_centerpoint_cross.pdf diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointFrame.imageset/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointFrame.imageset/Contents.json index cd83250..702bd0f 100644 --- a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointFrame.imageset/Contents.json +++ b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointFrame.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "CenterPointFrame.pdf", + "filename" : "ic_fpv_centerpoint_frame.pdf", "idiom" : "universal" } ], diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointFrame.imageset/CenterPointFrame.pdf b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointFrame.imageset/ic_fpv_centerpoint_frame.pdf similarity index 100% rename from DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointFrame.imageset/CenterPointFrame.pdf rename to DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointFrame.imageset/ic_fpv_centerpoint_frame.pdf diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointFrameAndCross.imageset/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointFrameAndCross.imageset/Contents.json index f584e98..38c742d 100644 --- a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointFrameAndCross.imageset/Contents.json +++ b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointFrameAndCross.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "CenterPointFrameAndCross.pdf", + "filename" : "ic_fpv_centerpoint_frame_and_cross.pdf", "idiom" : "universal" } ], diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointFrameAndCross.imageset/CenterPointFrameAndCross.pdf b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointFrameAndCross.imageset/ic_fpv_centerpoint_frame_and_cross.pdf similarity index 100% rename from DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointFrameAndCross.imageset/CenterPointFrameAndCross.pdf rename to DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointFrameAndCross.imageset/ic_fpv_centerpoint_frame_and_cross.pdf diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointNarrowCross.imageset/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointNarrowCross.imageset/Contents.json new file mode 100644 index 0000000..934b1bd --- /dev/null +++ b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointNarrowCross.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "ic_fpv_centerpoint_narrowcross.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointNarrowedCross.imageset/CenterPointNarrowedCross.pdf b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointNarrowCross.imageset/ic_fpv_centerpoint_narrowcross.pdf similarity index 100% rename from DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointNarrowedCross.imageset/CenterPointNarrowedCross.pdf rename to DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointNarrowCross.imageset/ic_fpv_centerpoint_narrowcross.pdf diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointSquare.imageset/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointSquare.imageset/Contents.json index b2ccbc3..f4fb4e3 100644 --- a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointSquare.imageset/Contents.json +++ b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointSquare.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "CenterPointSquare.pdf", + "filename" : "ic_fpv_centerpoint_square.pdf", "idiom" : "universal" } ], diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointSquare.imageset/CenterPointSquare.pdf b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointSquare.imageset/ic_fpv_centerpoint_square.pdf similarity index 100% rename from DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointSquare.imageset/CenterPointSquare.pdf rename to DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointSquare.imageset/ic_fpv_centerpoint_square.pdf diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointSquareAndCross.imageset/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointSquareAndCross.imageset/Contents.json index 2d9da8c..386fb2a 100644 --- a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointSquareAndCross.imageset/Contents.json +++ b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointSquareAndCross.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "CenterPointSquareAndCross.pdf", + "filename" : "ic_fpv_centerpoint_square_and_cross.pdf", "idiom" : "universal" } ], diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointSquareAndCross.imageset/CenterPointSquareAndCross.pdf b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointSquareAndCross.imageset/ic_fpv_centerpoint_square_and_cross.pdf similarity index 100% rename from DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointSquareAndCross.imageset/CenterPointSquareAndCross.pdf rename to DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/FPVWidget/CenterPoint/CenterPointSquareAndCross.imageset/ic_fpv_centerpoint_square_and_cross.pdf diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelBackArrow.imageset/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelBackArrow.imageset/Contents.json index 8b6ecb2..3648a9c 100644 --- a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelBackArrow.imageset/Contents.json +++ b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelBackArrow.imageset/Contents.json @@ -1,22 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "Slice@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "Slice@3x.png", - "scale" : "3x" + "filename" : "PanelBackArrow.pdf", + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelBackArrow.imageset/PanelBackArrow.pdf b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelBackArrow.imageset/PanelBackArrow.pdf new file mode 100644 index 0000000..80d3b57 Binary files /dev/null and b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelBackArrow.imageset/PanelBackArrow.pdf differ diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelBackArrow.imageset/Slice@2x.png b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelBackArrow.imageset/Slice@2x.png deleted file mode 100644 index d38f45a..0000000 Binary files a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelBackArrow.imageset/Slice@2x.png and /dev/null differ diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelBackArrow.imageset/Slice@3x.png b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelBackArrow.imageset/Slice@3x.png deleted file mode 100644 index 9eece93..0000000 Binary files a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelBackArrow.imageset/Slice@3x.png and /dev/null differ diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelNextArrow.imageset/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelNextArrow.imageset/Contents.json new file mode 100644 index 0000000..a5cf137 --- /dev/null +++ b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelNextArrow.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "PanelNextArrow.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelNextArrow.imageset/PanelNextArrow.pdf b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelNextArrow.imageset/PanelNextArrow.pdf new file mode 100644 index 0000000..39f9076 Binary files /dev/null and b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelNextArrow.imageset/PanelNextArrow.pdf differ diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelToolbarClose.imageset/Close@2x.png b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelToolbarClose.imageset/Close@2x.png deleted file mode 100644 index 57256e6..0000000 Binary files a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelToolbarClose.imageset/Close@2x.png and /dev/null differ diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelToolbarClose.imageset/Close@3x.png b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelToolbarClose.imageset/Close@3x.png deleted file mode 100644 index 05ca744..0000000 Binary files a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelToolbarClose.imageset/Close@3x.png and /dev/null differ diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelToolbarClose.imageset/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelToolbarClose.imageset/Contents.json index 1cca01b..d8ab249 100644 --- a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelToolbarClose.imageset/Contents.json +++ b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelToolbarClose.imageset/Contents.json @@ -1,22 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "Close@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "Close@3x.png", - "scale" : "3x" + "filename" : "PanelToolbarClose.pdf", + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelToolbarClose.imageset/PanelToolbarClose.pdf b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelToolbarClose.imageset/PanelToolbarClose.pdf new file mode 100644 index 0000000..585c902 Binary files /dev/null and b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/PanelSupport/PanelToolbarClose.imageset/PanelToolbarClose.pdf differ diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/RTKSatelliteStatusWIdget/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/RTKSatelliteStatusWIdget/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/RTKSatelliteStatusWIdget/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/RTKSatelliteStatusWIdget/OrientationInvalid.imageset/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/RTKSatelliteStatusWIdget/OrientationInvalid.imageset/Contents.json new file mode 100644 index 0000000..ca8020d --- /dev/null +++ b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/RTKSatelliteStatusWIdget/OrientationInvalid.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "RTKFixBad.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/RTKSatelliteStatusWIdget/OrientationInvalid.imageset/RTKFixBad.pdf b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/RTKSatelliteStatusWIdget/OrientationInvalid.imageset/RTKFixBad.pdf new file mode 100644 index 0000000..5ebfceb Binary files /dev/null and b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/RTKSatelliteStatusWIdget/OrientationInvalid.imageset/RTKFixBad.pdf differ diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/RTKSatelliteStatusWIdget/OrientationValid.imageset/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/RTKSatelliteStatusWIdget/OrientationValid.imageset/Contents.json new file mode 100644 index 0000000..0ad1cb5 --- /dev/null +++ b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/RTKSatelliteStatusWIdget/OrientationValid.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "RTKFixGood.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/RTKSatelliteStatusWIdget/OrientationValid.imageset/RTKFixGood.pdf b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/RTKSatelliteStatusWIdget/OrientationValid.imageset/RTKFixGood.pdf new file mode 100644 index 0000000..e2dc840 Binary files /dev/null and b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/RTKSatelliteStatusWIdget/OrientationValid.imageset/RTKFixGood.pdf differ diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/SimulatorIndicatorWidget/SimulatorActive.imageset/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/SimulatorIndicatorWidget/SimulatorActiveImage.imageset/Contents.json similarity index 100% rename from DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/SimulatorIndicatorWidget/SimulatorActive.imageset/Contents.json rename to DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/SimulatorIndicatorWidget/SimulatorActiveImage.imageset/Contents.json diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/SimulatorIndicatorWidget/SimulatorActive.imageset/SimulatorActiveImage.pdf b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/SimulatorIndicatorWidget/SimulatorActiveImage.imageset/SimulatorActiveImage.pdf similarity index 100% rename from DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/SimulatorIndicatorWidget/SimulatorActive.imageset/SimulatorActiveImage.pdf rename to DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/SimulatorIndicatorWidget/SimulatorActiveImage.imageset/SimulatorActiveImage.pdf diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/SimulatorIndicatorWidget/SimulatorInactive.imageset/Contents.json b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/SimulatorIndicatorWidget/SimulatorInactiveImage.imageset/Contents.json similarity index 100% rename from DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/SimulatorIndicatorWidget/SimulatorInactive.imageset/Contents.json rename to DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/SimulatorIndicatorWidget/SimulatorInactiveImage.imageset/Contents.json diff --git a/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/SimulatorIndicatorWidget/SimulatorInactive.imageset/SimulatorInactiveImage.pdf b/DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/SimulatorIndicatorWidget/SimulatorInactiveImage.imageset/SimulatorInactiveImage.pdf similarity index 100% rename from DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/SimulatorIndicatorWidget/SimulatorInactive.imageset/SimulatorInactiveImage.pdf rename to DJIUXSDKWidgets/Resources/DUXWidgets.xcassets/SimulatorIndicatorWidget/SimulatorInactiveImage.imageset/SimulatorInactiveImage.pdf diff --git a/DJIUXSDKWidgets/Resources/Fonts/pirulen.ttf b/DJIUXSDKWidgets/Resources/Fonts/pirulen.ttf new file mode 100644 index 0000000..ce6c71a Binary files /dev/null and b/DJIUXSDKWidgets/Resources/Fonts/pirulen.ttf differ diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton.imageset/CancelButton.png b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton.imageset/CancelButton.png new file mode 100644 index 0000000..dbead1d Binary files /dev/null and b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton.imageset/CancelButton.png differ diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton.imageset/CancelButton@2x.png b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton.imageset/CancelButton@2x.png new file mode 100644 index 0000000..3da3863 Binary files /dev/null and b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton.imageset/CancelButton@2x.png differ diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton.imageset/CancelButton@3x.png b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton.imageset/CancelButton@3x.png new file mode 100644 index 0000000..ff24292 Binary files /dev/null and b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton.imageset/CancelButton@3x.png differ diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton.imageset/Contents.json b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton.imageset/Contents.json new file mode 100644 index 0000000..81435f2 --- /dev/null +++ b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "CancelButton.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "CancelButton@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "CancelButton@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton_pressed.imageset/CancelButton_pressed.png b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton_pressed.imageset/CancelButton_pressed.png new file mode 100644 index 0000000..01bc271 Binary files /dev/null and b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton_pressed.imageset/CancelButton_pressed.png differ diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton_pressed.imageset/CancelButton_pressed@2x.png b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton_pressed.imageset/CancelButton_pressed@2x.png new file mode 100644 index 0000000..ef33ed0 Binary files /dev/null and b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton_pressed.imageset/CancelButton_pressed@2x.png differ diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton_pressed.imageset/CancelButton_pressed@3x.png b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton_pressed.imageset/CancelButton_pressed@3x.png new file mode 100644 index 0000000..f2846b3 Binary files /dev/null and b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton_pressed.imageset/CancelButton_pressed@3x.png differ diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton_pressed.imageset/Contents.json b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton_pressed.imageset/Contents.json new file mode 100644 index 0000000..1568472 --- /dev/null +++ b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/CancelButton_pressed.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "CancelButton_pressed.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "CancelButton_pressed@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "CancelButton_pressed@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusFlightMode.imageset/Contents.json b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusFlightMode.imageset/Contents.json index 21b18c1..f361420 100644 --- a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusFlightMode.imageset/Contents.json +++ b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusFlightMode.imageset/Contents.json @@ -1,23 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "filename" : "SystemStatusFlightMode.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "SystemStatusFlightMode@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "SystemStatusFlightMode@3x.png", - "scale" : "3x" + "filename" : "SystemStatusFlightMode.pdf", + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusFlightMode.imageset/SystemStatusFlightMode.pdf b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusFlightMode.imageset/SystemStatusFlightMode.pdf new file mode 100644 index 0000000..f7d69f8 Binary files /dev/null and b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusFlightMode.imageset/SystemStatusFlightMode.pdf differ diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusFlightMode.imageset/SystemStatusFlightMode.png b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusFlightMode.imageset/SystemStatusFlightMode.png deleted file mode 100644 index 0cc379d..0000000 Binary files a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusFlightMode.imageset/SystemStatusFlightMode.png and /dev/null differ diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusFlightMode.imageset/SystemStatusFlightMode@2x.png b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusFlightMode.imageset/SystemStatusFlightMode@2x.png deleted file mode 100644 index 07b7a00..0000000 Binary files a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusFlightMode.imageset/SystemStatusFlightMode@2x.png and /dev/null differ diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusFlightMode.imageset/SystemStatusFlightMode@3x.png b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusFlightMode.imageset/SystemStatusFlightMode@3x.png deleted file mode 100644 index cf9dbfe..0000000 Binary files a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusFlightMode.imageset/SystemStatusFlightMode@3x.png and /dev/null differ diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusMaxAltitudeLimit.imageset/Contents.json b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusMaxAltitudeLimit.imageset/Contents.json index 9638972..b6a4e17 100644 --- a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusMaxAltitudeLimit.imageset/Contents.json +++ b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusMaxAltitudeLimit.imageset/Contents.json @@ -1,22 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "SystemStatusMaxAltitudeLimit@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "SystemStatusMaxAltitudeLimit@3x.png", - "scale" : "3x" + "filename" : "SystemStatusMaxAltitudeLimit.pdf", + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusMaxAltitudeLimit.imageset/SystemStatusMaxAltitudeLimit.pdf b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusMaxAltitudeLimit.imageset/SystemStatusMaxAltitudeLimit.pdf new file mode 100644 index 0000000..d26352f Binary files /dev/null and b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusMaxAltitudeLimit.imageset/SystemStatusMaxAltitudeLimit.pdf differ diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusMaxAltitudeLimit.imageset/SystemStatusMaxAltitudeLimit@2x.png b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusMaxAltitudeLimit.imageset/SystemStatusMaxAltitudeLimit@2x.png deleted file mode 100644 index 8704d9c..0000000 Binary files a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusMaxAltitudeLimit.imageset/SystemStatusMaxAltitudeLimit@2x.png and /dev/null differ diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusMaxAltitudeLimit.imageset/SystemStatusMaxAltitudeLimit@3x.png b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusMaxAltitudeLimit.imageset/SystemStatusMaxAltitudeLimit@3x.png deleted file mode 100644 index 94d2a0a..0000000 Binary files a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusMaxAltitudeLimit.imageset/SystemStatusMaxAltitudeLimit@3x.png and /dev/null differ diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusMaxFlightDistance.imageset/Contents.json b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusMaxFlightDistance.imageset/Contents.json new file mode 100644 index 0000000..273d32f --- /dev/null +++ b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusMaxFlightDistance.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "SystemStatusMaxFlightDistance.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusMaxFlightDistance.imageset/SystemStatusMaxFlightDistance.pdf b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusMaxFlightDistance.imageset/SystemStatusMaxFlightDistance.pdf new file mode 100644 index 0000000..905c3b3 Binary files /dev/null and b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusMaxFlightDistance.imageset/SystemStatusMaxFlightDistance.pdf differ diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusRC.imageset/Contents.json b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusRC.imageset/Contents.json index b57c6d1..3f66cff 100644 --- a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusRC.imageset/Contents.json +++ b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusRC.imageset/Contents.json @@ -1,23 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "filename" : "SystemStatusRC.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "SystemStatusRC@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "SystemStatusRC@3x.png", - "scale" : "3x" + "filename" : "SystemStatusRC.pdf", + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusRC.imageset/SystemStatusRC.pdf b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusRC.imageset/SystemStatusRC.pdf new file mode 100644 index 0000000..09a830f Binary files /dev/null and b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusRC.imageset/SystemStatusRC.pdf differ diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusRC.imageset/SystemStatusRC.png b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusRC.imageset/SystemStatusRC.png deleted file mode 100644 index 622752e..0000000 Binary files a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusRC.imageset/SystemStatusRC.png and /dev/null differ diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusRC.imageset/SystemStatusRC@2x.png b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusRC.imageset/SystemStatusRC@2x.png deleted file mode 100644 index 49e69dc..0000000 Binary files a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusRC.imageset/SystemStatusRC@2x.png and /dev/null differ diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusRC.imageset/SystemStatusRC@3x.png b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusRC.imageset/SystemStatusRC@3x.png deleted file mode 100644 index 904ea69..0000000 Binary files a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusRC.imageset/SystemStatusRC@3x.png and /dev/null differ diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusRTHAltitude.imageset/Contents.json b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusRTHAltitude.imageset/Contents.json new file mode 100644 index 0000000..45b115a --- /dev/null +++ b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusRTHAltitude.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "SystemStatusRTHAltitude.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusRTHAltitude.imageset/SystemStatusRTHAltitude.pdf b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusRTHAltitude.imageset/SystemStatusRTHAltitude.pdf new file mode 100644 index 0000000..f4c646f Binary files /dev/null and b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusRTHAltitude.imageset/SystemStatusRTHAltitude.pdf differ diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusStorageSDCard.imageset/Contents.json b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusStorageSDCard.imageset/Contents.json index 978037a..2a810e8 100644 --- a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusStorageSDCard.imageset/Contents.json +++ b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusStorageSDCard.imageset/Contents.json @@ -1,23 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "filename" : "SystemStatusStorageSDCard.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "SystemStatusStorageSDCard@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "SystemStatusStorageSDCard@3x.png", - "scale" : "3x" + "filename" : "SystemStatusSDCard.pdf", + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusStorageSDCard.imageset/SystemStatusSDCard.pdf b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusStorageSDCard.imageset/SystemStatusSDCard.pdf new file mode 100644 index 0000000..0e2c3dc Binary files /dev/null and b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusStorageSDCard.imageset/SystemStatusSDCard.pdf differ diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusStorageSDCard.imageset/SystemStatusStorageSDCard.png b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusStorageSDCard.imageset/SystemStatusStorageSDCard.png deleted file mode 100644 index b5d707e..0000000 Binary files a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusStorageSDCard.imageset/SystemStatusStorageSDCard.png and /dev/null differ diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusStorageSDCard.imageset/SystemStatusStorageSDCard@2x.png b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusStorageSDCard.imageset/SystemStatusStorageSDCard@2x.png deleted file mode 100644 index 3da2cf0..0000000 Binary files a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusStorageSDCard.imageset/SystemStatusStorageSDCard@2x.png and /dev/null differ diff --git a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusStorageSDCard.imageset/SystemStatusStorageSDCard@3x.png b/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusStorageSDCard.imageset/SystemStatusStorageSDCard@3x.png deleted file mode 100644 index daac027..0000000 Binary files a/DJIUXSDKWidgets/Resources/SystemStatus.xcassets/Items/SystemStatusStorageSDCard.imageset/SystemStatusStorageSDCard@3x.png and /dev/null differ diff --git a/DJIUXSDKWidgets/SimulatorIndicatorWidget/DUXBetaSimulatorIndicatorWidget.h b/DJIUXSDKWidgets/SimulatorIndicatorWidget/DUXBetaSimulatorIndicatorWidget.h index 2db74fa..76cb242 100644 --- a/DJIUXSDKWidgets/SimulatorIndicatorWidget/DUXBetaSimulatorIndicatorWidget.h +++ b/DJIUXSDKWidgets/SimulatorIndicatorWidget/DUXBetaSimulatorIndicatorWidget.h @@ -2,8 +2,10 @@ // DUXBetaSimulatorIndicatorWidget.h // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -29,7 +31,7 @@ NS_ASSUME_NONNULL_BEGIN /** - * This widget displays the state of the simulator indicator. + * The state of the simulator indicator widget */ typedef NS_ENUM(NSUInteger, DUXBetaSimulatorIndicatorState) { DUXBetaSimulatorIndicatorStateDisconnected, // no aircraft connected diff --git a/DJIUXSDKWidgets/SimulatorIndicatorWidget/DUXBetaSimulatorIndicatorWidget.m b/DJIUXSDKWidgets/SimulatorIndicatorWidget/DUXBetaSimulatorIndicatorWidget.m index 2fc1791..1a6b84e 100644 --- a/DJIUXSDKWidgets/SimulatorIndicatorWidget/DUXBetaSimulatorIndicatorWidget.m +++ b/DJIUXSDKWidgets/SimulatorIndicatorWidget/DUXBetaSimulatorIndicatorWidget.m @@ -2,8 +2,10 @@ // DUXBetaSimulatorIndicatorWidget.m // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -27,7 +29,7 @@ #import "DUXBetaSimulatorIndicatorWidget.h" #import "UIImage+DUXBetaAssets.h" -#import "DUXStateChangeBroadcaster.h" +#import "DUXBetaStateChangeBroadcaster.h" #import "NSLayoutConstraint+DUXBetaMultiplier.h" /** @@ -38,7 +40,7 @@ * * Key: simpulatorStateUpdated Type: NSNumber - Sends a boolean value as an NSNumber when the simulator state changes. */ -@interface DUXBetaSimulatorIndicatorWidgetModelState : DUXStateChangeBaseData +@interface DUXBetaSimulatorIndicatorWidgetModelState : DUXBetaStateChangeBaseData + (instancetype)productConnected:(BOOL)isConnected; + (instancetype)simulatorStateUpdated:(BOOL)isActive; @@ -51,7 +53,7 @@ + (instancetype)simulatorStateUpdated:(BOOL)isActive; * * Key: onWidgetTap Type: NSNumber - Sends a boolean YES value as an NSNumber indicating the widget was tapped. */ -@interface DUXBetaSimulatorIndicatorWidgetUIState : DUXStateChangeBaseData +@interface DUXBetaSimulatorIndicatorWidgetUIState : DUXBetaStateChangeBaseData + (instancetype)onWidgetTap; @@ -90,9 +92,9 @@ - (instancetype)initWithCoder:(NSCoder *)coder { - (void)setupInstanceVariables { self.imageMapping = [[NSMutableDictionary alloc] initWithDictionary:@{ - @(DUXBetaSimulatorIndicatorStateActive): [UIImage duxbeta_imageWithAssetNamed:@"SimulatorActive"], - @(DUXBetaSimulatorIndicatorStateInactive): [UIImage duxbeta_imageWithAssetNamed:@"SimulatorInactive"], - @(DUXBetaSimulatorIndicatorStateDisconnected): [UIImage duxbeta_imageWithAssetNamed:@"SimulatorInactive"] + @(DUXBetaSimulatorIndicatorStateActive): [UIImage duxbeta_imageWithAssetNamed:@"SimulatorActiveImage"], + @(DUXBetaSimulatorIndicatorStateInactive): [UIImage duxbeta_imageWithAssetNamed:@"SimulatorInactiveImage"], + @(DUXBetaSimulatorIndicatorStateDisconnected): [UIImage duxbeta_imageWithAssetNamed:@"SimulatorInactiveImage"] }]; self.tintColorMapping = [[NSMutableDictionary alloc] initWithDictionary:@{ @@ -207,15 +209,15 @@ - (void)updateUI { } - (void)sendProductConnected { - [[DUXStateChangeBroadcaster instance] send:[DUXBetaSimulatorIndicatorWidgetModelState productConnected:self.widgetModel.isProductConnected]]; + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaSimulatorIndicatorWidgetModelState productConnected:self.widgetModel.isProductConnected]]; } - (void)sendSimulatorStatus { - [[DUXStateChangeBroadcaster instance] send:[DUXBetaSimulatorIndicatorWidgetModelState productConnected:self.widgetModel.isSimulatorActive]]; + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaSimulatorIndicatorWidgetModelState productConnected:self.widgetModel.isSimulatorActive]]; } - (void)handleTap { - [[DUXStateChangeBroadcaster instance] send:[DUXBetaSimulatorIndicatorWidgetUIState onWidgetTap]]; + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaSimulatorIndicatorWidgetUIState onWidgetTap]]; } - (void)updateMinImageDimensions { diff --git a/DJIUXSDKWidgets/SimulatorIndicatorWidget/DUXBetaSimulatorIndicatorWidgetModel.h b/DJIUXSDKWidgets/SimulatorIndicatorWidget/DUXBetaSimulatorIndicatorWidgetModel.h index 904881c..f531bc0 100644 --- a/DJIUXSDKWidgets/SimulatorIndicatorWidget/DUXBetaSimulatorIndicatorWidgetModel.h +++ b/DJIUXSDKWidgets/SimulatorIndicatorWidget/DUXBetaSimulatorIndicatorWidgetModel.h @@ -2,8 +2,10 @@ // DUXBetaSimulatorIndicatorWidgetModel.h // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 diff --git a/DJIUXSDKWidgets/SimulatorIndicatorWidget/DUXBetaSimulatorIndicatorWidgetModel.m b/DJIUXSDKWidgets/SimulatorIndicatorWidget/DUXBetaSimulatorIndicatorWidgetModel.m index f9ea4ab..832677d 100644 --- a/DJIUXSDKWidgets/SimulatorIndicatorWidget/DUXBetaSimulatorIndicatorWidgetModel.m +++ b/DJIUXSDKWidgets/SimulatorIndicatorWidget/DUXBetaSimulatorIndicatorWidgetModel.m @@ -2,8 +2,10 @@ // DUXBetaSimulatorIndicatorWidgetModel.m // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaAlertView+SystemStatus.swift b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaAlertView+SystemStatus.swift new file mode 100644 index 0000000..a44d4f7 --- /dev/null +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaAlertView+SystemStatus.swift @@ -0,0 +1,106 @@ +// +// DUXBetaAlertView+SystemStatus.swift +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// + +import Foundation + +/** + * Extension that adds convenience methods for creating alerts + * used to display messages in the System Status List Item Widgets. + */ +public extension DUXBetaAlertView { + + /// The alert appearance specific to the system status list item widgets. + @objc static var systemAlertAppearance: DUXBetaAlertViewAppearance { + let appearance = DUXBetaAlertViewAppearance() + appearance.backgroundColor = .duxbeta_black() + appearance.headerLayoutType = .horizontal + appearance.headerLayoutSpacing = 10.0 + appearance.alertLayoutSpacing = 10.0 + appearance.shouldDismissOnTap = true + return appearance + } + + /** + * This method is creating an alert having a warning icon. + * + * - Parameters: + * - title: The title of the alert. + * - message: The message displayed by the alert. + * - heightScale: The height scale factor used to compute the actual height of the alert. + * + * - Returns: A custom alert view instance displaying the warning icon. + */ + @objc static func warningAlert(title: String, message: String, heightScale: DUXBetaAlertViewHeightScale = .medium) -> DUXBetaAlertView { + let alert = DUXBetaAlertView.defaultAlert(title: title, message: message, heightScale: heightScale) + alert.image = UIImage.duxbeta_image(withAssetNamed: "WarningIcon") + alert.appearance.imageTintColor = .duxbeta_alertWarning() + return alert + } + + /** + * This method is creating an alert having a success icon. + * + * - Parameters: + * - title: The title of the alert. + * - message: The message displayed by the alert. + * - heightScale: The height scale factor used to compute the actual height of the alert. + * + * - Returns: A custom alert view instance displaying the warning icon. + */ + @objc static func successAlert(title: String, message: String, heightScale: DUXBetaAlertViewHeightScale = .medium) -> DUXBetaAlertView { + let alert = DUXBetaAlertView.defaultAlert(title: title, message: message, heightScale: heightScale) + alert.image = UIImage.duxbeta_image(withAssetNamed: "SuccessIcon") + alert.appearance.imageTintColor = .duxbeta_success() + return alert + } + /** + * This method is creating an alert having an error icon. + * + * - Parameters: + * - title: The title of the alert. + * - message: The message displayed by the alert. + * - heightScale: The height scale factor used to compute the actual height of the alert. + * + * - Returns: A custom alert view instance displaying the warning icon. + */ + @objc static func failAlert(title: String, message: String, heightScale: DUXBetaAlertViewHeightScale = .medium) -> DUXBetaAlertView { + let alert = DUXBetaAlertView.defaultAlert(title: title, message: message, heightScale: heightScale) + alert.image = UIImage.duxbeta_image(withAssetNamed: "ErrorIcon") + alert.appearance.imageTintColor = .duxbeta_danger() + return alert + } + + // MARK: - Private Methods + + fileprivate static func defaultAlert(title: String, message: String, heightScale: DUXBetaAlertViewHeightScale = .medium) -> DUXBetaAlertView { + let alert = DUXBetaAlertView() + alert.appearance.heightScale = heightScale + alert.titleText = title + alert.message = message + return alert + } +} diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXFlightModeListItemWidget.h b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaFlightModeListItemWidget.h similarity index 84% rename from DJIUXSDKWidgets/SystemStatusListWidgets/DUXFlightModeListItemWidget.h rename to DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaFlightModeListItemWidget.h index 8a4cd9c..e89c170 100644 --- a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXFlightModeListItemWidget.h +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaFlightModeListItemWidget.h @@ -1,9 +1,11 @@ // -// DUXFlightModeListItemWidget.h +// DUXBetaFlightModeListItemWidget.h // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -23,19 +25,19 @@ // SOFTWARE. // -#import +#import NS_ASSUME_NONNULL_BEGIN /** * Widget for display in the SystemStatusList which shows the current flight mode setting for the aircraft. */ -@interface DUXFlightModeListItemWidget : DUXListItemLabelButtonWidget +@interface DUXBetaFlightModeListItemWidget : DUXBetaListItemLabelButtonWidget @end /** - * FlightModeListItemUIState contains the hooks for UI changes in the widget class DUXFlightModeListItemWidget. + * FlightModeListItemUIState contains the hooks for UI changes in the widget class DUXBetaFlightModeListItemWidget. * It inherits all UI hooks in ListItemLabelButtonUIState. * It has no hooks because there are no UI interactions in this widget. */ diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXFlightModeListItemWidget.m b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaFlightModeListItemWidget.m similarity index 79% rename from DJIUXSDKWidgets/SystemStatusListWidgets/DUXFlightModeListItemWidget.m rename to DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaFlightModeListItemWidget.m index 775dfba..0331d01 100644 --- a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXFlightModeListItemWidget.m +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaFlightModeListItemWidget.m @@ -1,9 +1,11 @@ // -// DUXFlightModeListItemWidget.m +// DUXBetaFlightModeListItemWidget.m // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -23,27 +25,27 @@ // SOFTWARE. // -#import "DUXFlightModeListItemWidget.h" -#import "DUXFlightModeListItemWidgetModel.h" +#import "DUXBetaFlightModeListItemWidget.h" +#import "DUXBetaFlightModeListItemWidgetModel.h" #import "NSObject+DUXBetaRKVOExtension.h" @import DJIUXSDKCore; -@interface DUXFlightModeListItemWidget () -@property (nonatomic, strong) DUXFlightModeListItemWidgetModel *widgetModel; +@interface DUXBetaFlightModeListItemWidget () +@property (nonatomic, strong) DUXBetaFlightModeListItemWidgetModel *widgetModel; @end -@implementation DUXFlightModeListItemWidget +@implementation DUXBetaFlightModeListItemWidget - (instancetype)init { - if (self = [super init:DUXListItemLabelOnly]) { + if (self = [super init:DUXBetaListItemLabelOnly]) { } return self; } - (instancetype)initWithCoder:(NSCoder*)coder { - if (self = [super init:DUXListItemLabelOnly]) { + if (self = [super init:DUXBetaListItemLabelOnly]) { } return self; @@ -53,7 +55,7 @@ - (void)viewDidLoad { [super viewDidLoad]; self.view.translatesAutoresizingMaskIntoConstraints = NO; - self.widgetModel = [[DUXFlightModeListItemWidgetModel alloc] init]; + self.widgetModel = [[DUXBetaFlightModeListItemWidgetModel alloc] init]; [self.widgetModel setup]; [self setTitle:NSLocalizedString(@"Flight Mode", @"System Status Checklist Item Title") andIconName:@"SystemStatusFlightMode"]; @@ -84,7 +86,8 @@ - (void)flightModeUpdate { newString = NSLocalizedString(@"N/A", @"N/A"); } [self setLabelText:newString]; - [DUXStateChangeBroadcaster send:[FlightModeListItemModelState flightModeUpdated:newString]]; + [DUXBetaStateChangeBroadcaster send:[FlightModeListItemModelState flightModeUpdated:newString]]; + } @end diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaFlightModeListItemWidgetModel.h b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaFlightModeListItemWidgetModel.h new file mode 100644 index 0000000..8f4e8f7 --- /dev/null +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaFlightModeListItemWidgetModel.h @@ -0,0 +1,56 @@ +// +// DUXBetaFlightModeListItemWidgetModel.h +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * @class DUXBetaFlightModeListItemWidgetModel is a subclass of DUXBetaBaseWidgetModel used to implement the model + * for the DUXBetaFlightModeListItemWigetModel. + * It exposes a single property for the flight mode string. + */ + @interface DUXBetaFlightModeListItemWidgetModel : DUXBetaBaseWidgetModel +/// The string for the name of the current flight mode +@property (nonatomic, strong, readonly) NSString *flightModeString; + +@end + +/** + * FlightModeListItemModelState contains the hooks for model changes in the widget class + * DUXBetaFlightModeListItemWidgetModel. + * It inherits all model hooks in ListItemLabelButtonModelState and adds: + * + * Key: flightModeUpdated Type: NSString - The new flight mode string when flight mode changes. +*/ +@interface FlightModeListItemModelState : ListItemLabelButtonModelState + ++ (instancetype)flightModeUpdated:(NSString*)newMode; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXFlightModeListItemWidgetModel.m b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaFlightModeListItemWidgetModel.m similarity index 88% rename from DJIUXSDKWidgets/SystemStatusListWidgets/DUXFlightModeListItemWidgetModel.m rename to DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaFlightModeListItemWidgetModel.m index 295a0a4..5e092d9 100644 --- a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXFlightModeListItemWidgetModel.m +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaFlightModeListItemWidgetModel.m @@ -1,9 +1,11 @@ // -// DUXFlightModeListItemWidgetModel.m +// DUXBetaFlightModeListItemWidgetModel.m // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -23,17 +25,17 @@ // SOFTWARE. // -#import "DUXFlightModeListItemWidgetModel.h" +#import "DUXBetaFlightModeListItemWidgetModel.h" #import "NSObject+DUXBetaRKVOExtension.h" @import DJIUXSDKCore; -@interface DUXFlightModeListItemWidgetModel () +@interface DUXBetaFlightModeListItemWidgetModel () @property (nonatomic, strong) NSString *flightModeString; @end -@implementation DUXFlightModeListItemWidgetModel +@implementation DUXBetaFlightModeListItemWidgetModel - (void)inSetup { BindSDKKey([DJIFlightControllerKey keyWithParam:DJIFlightControllerParamFlightModeString], flightModeString); diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxAltitudeListItemWidget.h b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxAltitudeListItemWidget.h new file mode 100644 index 0000000..ccb3d92 --- /dev/null +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxAltitudeListItemWidget.h @@ -0,0 +1,60 @@ +// +// DUXBetaMaxAltitudeListItemWidget.h +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// + +#import +#import "DUXBetaMaxAltitudeListItemWidgetModel.h" + + +@class DUXBetaAlertViewAppearance; + +NS_ASSUME_NONNULL_BEGIN +/** + * Widget for display in the SystemStatusList which shows the current max altitude setting for the aircraft + * and also allows editing the height within the limits shown in the hint text. + * + * The widget has 3 potential alerts which may show. Each can be configured by setting the appropriate setting + * properties of the appropriate DUXBetaAlertViewAppearance object below. +*/ +@interface DUXBetaMaxAltitudeListItemWidget : DUXBetaListItemEditTextButtonWidget + +/// AlertViewAppearance properties for alert when setting max altitutde above the current Return-To-Home Altitude +@property (nonatomic, strong) DUXBetaAlertViewAppearance *aboveReturnToHomeAlertAppearance; +/// AlertViewAppearance properties for alert when setting max altitutde above the local (FAA) 400 foot altitude limit +@property (nonatomic, strong) DUXBetaAlertViewAppearance *aboveLocalMaxAltitudeAlertAppearance; +/// AlertViewAppearance properties for alert when setting max altitutde encounters an error +@property (nonatomic, strong) DUXBetaAlertViewAppearance *maxAltitudeChangeFailedAlertAppearance; +@end + +/** + * MaxAltitudeListItemUIState contains no hooks specific to the MaxAltitude list widget UI. + * It inherits all UI hooks in ListItemEditTextUIState. + */ +@interface MaxAltitudeListItemUIState : ListItemEditTextUIState + +@end + +NS_ASSUME_NONNULL_END diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxAltitudeListItemWidget.m b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxAltitudeListItemWidget.m new file mode 100644 index 0000000..c674a20 --- /dev/null +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxAltitudeListItemWidget.m @@ -0,0 +1,339 @@ +// +// DUXBetaMaxAltitudeListItemWidget.m +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// + +#import "DUXBetaMaxAltitudeListItemWidget.h" +#import "DUXBetaMaxAltitudeListItemWidgetModel.h" +#import "NSObject+DUXBetaRKVOExtension.h" + +#import + +@import DJIUXSDKCore; + +@interface DUXBetaMaxAltitudeListItemWidget() + +@property (nonatomic) double fieldvalue; +@property (nonatomic, strong) DUXBetaMaxAltitudeListItemWidgetModel *widgetModel; +@property (nonatomic) DUXBetaAlertView *alert; + +@end + +@implementation DUXBetaMaxAltitudeListItemWidget + +- (instancetype)init { + if (self = [super init:DUXBetaListItemOnlyEdit]) { + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + if (self = [super init:DUXBetaListItemOnlyEdit]) { + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view. + self.view.translatesAutoresizingMaskIntoConstraints = NO; + self.widgetModel = [[DUXBetaMaxAltitudeListItemWidgetModel alloc] init]; + [self.widgetModel setup]; + + // Load standard appearances for the 3 alerts we may show so they can be customized before use. + [self loadCustomAlertsAppearance]; + + [self setTitle:NSLocalizedString(@"Max Flight Altitude", @"System Status Checklist Item Title") andIconName:@"SystemStatusMaxAltitudeLimit"]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + BindRKVOModel(self.widgetModel, @selector(metaDataChanged), isNoviceMode, rangeValue); + BindRKVOModel(self.widgetModel, @selector(maxAltitudeChanged), maxAltitude); + BindRKVOModel(self.widgetModel, @selector(productConnectedChanged), isProductConnected); +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + + UnBindRKVOModel(self); +} + +- (void)dealloc { + [_widgetModel cleanup]; +} + +- (void)setupUI { + if (self.widgetModel == nil) { + return; + } + + if (self.trailingTitleGuide == nil) { + [super setupUI]; + if (self.trailingTitleGuide == nil) { + return; + } + } + + [self setHintText:NSLocalizedString(@"20-500m", @"20-500m")]; + + __weak DUXBetaMaxAltitudeListItemWidget *weakSelf = self; + [self setTextChangedBlock: ^(NSString *newText) { + NSInteger newHeight = [newText intValue]; + // TODO: When supporting imperial units, need some slop on the maxAltitde comparison + if ((newHeight > 0) && (newHeight != (int)self.widgetModel.maxAltitude)) { + __strong DUXBetaMaxAltitudeListItemWidget *strongSelf = weakSelf; + if (strongSelf) { + [strongSelf handleMaxAltitudeChangeRequest:newHeight]; + } + } + }]; +} + +- (void)updateUI { + [super updateUI]; + + if (self.widgetModel.isProductConnected == NO) { + if (self.enableEditField == YES) { + self.enableEditField = NO; + } + [self setEditText:NSLocalizedString(@"N/A", @"N/A")]; + [self hideHintLabel:YES]; + [self hideInputField:NO]; + } else if (self.widgetModel.isNoviceMode) { + if (self.enableEditField == YES) { + self.enableEditField = NO; + } + [self setEditText:[self.widgetModel metersToUnitString:30.0]]; + [self hideHintLabel:YES]; + [self hideInputField:NO]; + } else { + if (self.enableEditField == NO) { + self.enableEditField = YES; + } + [self setEditText:[NSString stringWithFormat:@"%ld", (long) [self.widgetModel metersToMeasurementSystem:self.widgetModel.maxAltitude]]]; + [self hideInputAndHint:NO]; + [self setButtonHidden:NO]; + } + +} + +- (void)loadCustomAlertsAppearance { + self.aboveReturnToHomeAlertAppearance = [DUXBetaAlertView systemAlertAppearance]; + self.aboveReturnToHomeAlertAppearance.heightScale = DUXBetaAlertViewHeightScaleSmall; + self.aboveReturnToHomeAlertAppearance.imageTintColor = [UIColor duxbeta_warningColor]; + + self.aboveLocalMaxAltitudeAlertAppearance = [DUXBetaAlertView systemAlertAppearance]; + self.aboveLocalMaxAltitudeAlertAppearance.heightScale = DUXBetaAlertViewHeightScaleMedium; + self.aboveLocalMaxAltitudeAlertAppearance.imageTintColor = [UIColor duxbeta_warningColor]; + + self.maxAltitudeChangeFailedAlertAppearance = [DUXBetaAlertView systemAlertAppearance]; + self.maxAltitudeChangeFailedAlertAppearance.heightScale = DUXBetaAlertViewHeightScaleSmall; + self.maxAltitudeChangeFailedAlertAppearance.imageTintColor = [UIColor duxbeta_redColor]; +} + +- (void)handleMaxAltitudeChangeRequest:(NSInteger)newHeight { + if (!self.widgetModel.isNoviceMode) { + + // Quick sanity check + DUXBetaMaxAltitudeChange heightValidity = [self.widgetModel validateNewHeight:newHeight]; + + if ((heightValidity == DUXBetaMaxAltitudeChangeMaxAltitudeValid) || + (heightValidity == DUXBetaMaxAltitudeChangeAboveReturnHomeMaxAltitude) || + (heightValidity == DUXBetaMaxAltitudeChangeBelowReturnHomeAltitude)) { + + __weak DUXBetaMaxAltitudeListItemWidget *weakSelf = self; + [self.widgetModel updateMaxHeight:newHeight onCompletion:^(DUXBetaMaxAltitudeChange result) { + __strong DUXBetaMaxAltitudeListItemWidget *strongSelf = weakSelf; + if ((result == DUXBetaMaxAltitudeChangeUnableToChangeMaxAltitudeInBeginnerMode) || + (result == DUXBetaMaxAltitudeChangeUnknownError) || + (result == DUXBetaMaxAltitudeChangeUnableToChangeReturnHomeAltitude)) { + [strongSelf presentError]; + } + + if (heightValidity == DUXBetaMaxAltitudeChangeBelowReturnHomeAltitude) { + [strongSelf presentResetReturnHomeConfirmation:newHeight]; + } + }]; + } else if ((heightValidity == DUXBetaMaxAltitudeChangeAboveWarningHeightLimit) || + (heightValidity == DUXBetaMaxAltitudeChangeAboveWarningHeightLimitAndBelowReturnHomeAltitude)) { + [self presentWarning:heightValidity newHeight:newHeight]; + } + } +} + +- (void)presentResetReturnHomeConfirmation:(NSInteger)newHeight { + __weak DUXBetaMaxAltitudeListItemWidget *weakSelf = self; + + self.alert = [DUXBetaAlertView warningAlertWithTitle:NSLocalizedString(@"Max Flight Altitude", @"Max Flight Altitude") + message:[NSString stringWithFormat:NSLocalizedString(@"Failsafe RTH altitude cannot exceed maximum flight altitude. Failsafe RTH altitude will be set to %@.", @"Warning when setting max height above RTH limit"), [self.widgetModel metersToUnitString:newHeight]] + heightScale:DUXBetaAlertViewHeightScaleSmall]; + + DUXBetaAlertAction *defaultAction = [DUXBetaAlertAction actionWithActionTitle:NSLocalizedString(@"OK", @"OK") + style:UIAlertActionStyleDefault + actionType:DUXBetaAlertActionTypeClosure + target:nil + selector:nil + completionAction:^{ + [DUXBetaStateChangeBroadcaster send:[MaxAltitudeListItemUIState dialogActionConfirm:@"Confirmed must change return home altitude"]]; + + __strong DUXBetaMaxAltitudeListItemWidget *strongSelf = weakSelf; + [strongSelf.widgetModel updateMaxHeight:newHeight onCompletion:^(DUXBetaMaxAltitudeChange result) { + if ((result == DUXBetaMaxAltitudeChangeUnableToChangeMaxAltitudeInBeginnerMode) || + (result == DUXBetaMaxAltitudeChangeUnknownError) || + (result == DUXBetaMaxAltitudeChangeUnableToChangeReturnHomeAltitude)) { + [strongSelf presentError]; + } else { + [strongSelf.alert closeWithCompletion:nil]; + } + }]; + }]; + + void (^dismissCallback)(void) = ^{ + [DUXBetaStateChangeBroadcaster send:[MaxAltitudeListItemUIState dialogActionDismiss:@"Cancelled must change return home altitude"]]; + }; + DUXBetaAlertAction *cancelAction = [DUXBetaAlertAction actionWithActionTitle:NSLocalizedString(@"Cancel", @"Cancel") + style:UIAlertActionStyleCancel + actionType:DUXBetaAlertActionTypeClosure + target:nil + selector:nil + completionAction:dismissCallback]; + self.alert.dissmissCompletion = dismissCallback; + [self.alert add:cancelAction]; + [self.alert add:defaultAction]; + self.alert.appearance = self.aboveReturnToHomeAlertAppearance; + + [self.alert showWithCompletion: ^{ + [DUXBetaStateChangeBroadcaster send:[MaxAltitudeListItemUIState dialogDisplayed:@"Must change return home altitude"]]; + }]; +} + +- (void)presentWarning:(DUXBetaMaxAltitudeChange)heightValidity newHeight:(NSInteger)newHeight { + self.alert = [DUXBetaAlertView warningAlertWithTitle:NSLocalizedString(@"Max Flight Altitude", @"Max Flight Altitude") + message:NSLocalizedString(@"Altering the maximum altitude setting could violate local laws and regulations (a 400 ft / 120 m flight limit is set by the FAA).\nYou are solely responsible and liable for the operation of the aircraft after altering these settings.\nDJI and its affiliates shall not be liable for any damages, whether in contract, tort (including negligence), or any other legal or equitable theory.", @"Warning when setting max height above legal limit") + heightScale:DUXBetaAlertViewHeightScaleMedium]; + + __weak DUXBetaMaxAltitudeListItemWidget *weakSelf = self; + DUXBetaAlertAction *defaultAction = [DUXBetaAlertAction actionWithActionTitle:NSLocalizedString(@"OK", @"OK") + style:UIAlertActionStyleDefault + actionType:DUXBetaAlertActionTypeClosure + target:nil + selector:nil + completionAction:^{ + [DUXBetaStateChangeBroadcaster send:[MaxAltitudeListItemUIState dialogActionConfirm:@"Confirmed Max Altitude over 400ft warning"]]; + __strong DUXBetaMaxAltitudeListItemWidget *strongSelf = weakSelf; + if ((heightValidity == DUXBetaMaxAltitudeChangeAboveWarningHeightLimitAndBelowReturnHomeAltitude) || (heightValidity == DUXBetaMaxAltitudeChangeBelowReturnHomeAltitude)) { + [strongSelf.alert closeWithCompletion: ^{ + [strongSelf presentResetReturnHomeConfirmation:newHeight]; + }]; + } else { + [strongSelf.widgetModel updateMaxHeight:newHeight onCompletion:^(DUXBetaMaxAltitudeChange result) { + if ((result == DUXBetaMaxAltitudeChangeUnableToChangeMaxAltitudeInBeginnerMode) || + (result == DUXBetaMaxAltitudeChangeUnknownError) || + (result == DUXBetaMaxAltitudeChangeUnableToChangeReturnHomeAltitude)) { + [self presentError]; + } else { + [strongSelf.alert closeWithCompletion:nil]; + } + }]; + } + }]; + + void (^dismissCallback)(void) = ^{ + [DUXBetaStateChangeBroadcaster send:[MaxAltitudeListItemUIState dialogActionDismiss:@"Cancelled change from Max Altitude over 400ft warning"]]; + }; + DUXBetaAlertAction *cancelAction = [DUXBetaAlertAction actionWithActionTitle:NSLocalizedString(@"Cancel", @"Cancel") + style:UIAlertActionStyleCancel + actionType:DUXBetaAlertActionTypeClosure + target:nil + selector:nil + completionAction:dismissCallback]; + self.alert.dissmissCompletion = dismissCallback; + + [self.alert add:cancelAction]; + [self.alert add:defaultAction]; + self.alert.appearance = self.aboveLocalMaxAltitudeAlertAppearance; + + [self.alert showWithCompletion: ^{ + [DUXBetaStateChangeBroadcaster send:[MaxAltitudeListItemUIState dialogDisplayed:@"Max Altitude over 400ft warning"]]; + }]; +} + +- (void)presentError { + self.alert = [DUXBetaAlertView failAlertWithTitle:NSLocalizedString(@"Max Flight Altitude", @"Max Flight Altitude") + message:NSLocalizedString(@"Failed to set. Input number in range.", @"Failed to set. Input number in range.") + heightScale:DUXBetaAlertViewHeightScaleSmall]; + + void (^dismissCallback)(void) = ^{ + [DUXBetaStateChangeBroadcaster send:[MaxAltitudeListItemUIState dialogActionConfirm:@"Max Altitude change failed acknowledged."]]; + }; + DUXBetaAlertAction *defaultAction = [DUXBetaAlertAction actionWithActionTitle:NSLocalizedString(@"OK", @"OK") + style:UIAlertActionStyleCancel + actionType:DUXBetaAlertActionTypeClosure + target:nil selector:nil + completionAction:dismissCallback]; + self.alert.dissmissCompletion = dismissCallback; + [self.alert add:defaultAction]; + self.alert.appearance = self.maxAltitudeChangeFailedAlertAppearance; + [self.alert showWithCompletion: ^{ + [DUXBetaStateChangeBroadcaster send:[MaxAltitudeListItemUIState dialogDisplayed:@"Max Altitude change failed."]]; + }]; +} + +- (void)metaDataChanged { + if (self.widgetModel.rangeValue) { + DJIParamCapabilityMinMax* range = (DJIParamCapabilityMinMax*)self.widgetModel.rangeValue; + + NSInteger min = [range.min intValue]; + NSInteger max = [range.max intValue]; + NSString *hintRange = [NSString stringWithFormat:@"(%ld-%ld%@)", min, max, self.widgetModel.measurementUnitString]; + + [self setHintText:hintRange]; + [self setEditFieldValuesMin:min maxValue:max]; + } + [self updateUI]; + } + +- (void)productConnectedChanged { + self.enableEditField = (self.widgetModel.isNoviceMode == NO) && self.widgetModel.isProductConnected; + [MaxAltitudeListItemModelState productConnected:self.widgetModel.isProductConnected]; + // If there was an existing connection, we need to reload the altitude value in case it was in the middle of being + // changec during the disconnect. + [self maxAltitudeChanged]; +} + +- (void)maxAltitudeChanged { + NSString *heightString = [NSString stringWithFormat:@"%ld", (long)self.widgetModel.maxAltitude]; + [self setEditText:heightString]; + [self updateUI]; +} + +@end + +@implementation MaxAltitudeListItemUIState + +@end + diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXMaxAltitudeListItemWidgetModel.h b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxAltitudeListItemWidgetModel.h similarity index 68% rename from DJIUXSDKWidgets/SystemStatusListWidgets/DUXMaxAltitudeListItemWidgetModel.h rename to DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxAltitudeListItemWidgetModel.h index be95d26..e56d0c4 100644 --- a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXMaxAltitudeListItemWidgetModel.h +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxAltitudeListItemWidgetModel.h @@ -1,9 +1,11 @@ // -// DUXMaxAltitudeListItemWidgetModel.h +// DUXBetaMaxAltitudeListItemWidgetModel.h // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -24,26 +26,27 @@ // #import -#import +#import NS_ASSUME_NONNULL_BEGIN -typedef NS_ENUM(NSInteger, DUXMaxAltitudeChange) { - DUXMaxAltitudeChangeMaxAltitudeValid, - DUXMaxAltitudeChangeAboveWarningHeightLimit, - DUXMaxAltitudeChangeAboveReturnHomeMaxAltitude, - DUXMaxAltitudeChangeBelowMininimumAltitude, - DUXMaxAltitudeChangeAboveMaximumAltitude, - DUXMaxAltitudeChangeAboveWarningHeightLimitAndReturnHomeAltitude, - DUXMaxAltitudeChangeUnableToChangeMaxAltitudeInBeginnerMode, - DUXMaxAltitudeChangeUnableToChangeReturnHomeAltitude, - DUXMaxAltitudeChangeUnknownError +typedef NS_ENUM(NSInteger, DUXBetaMaxAltitudeChange) { + DUXBetaMaxAltitudeChangeMaxAltitudeValid, + DUXBetaMaxAltitudeChangeAboveWarningHeightLimit, + DUXBetaMaxAltitudeChangeAboveReturnHomeMaxAltitude, + DUXBetaMaxAltitudeChangeBelowMininimumAltitude, + DUXBetaMaxAltitudeChangeAboveMaximumAltitude, + DUXBetaMaxAltitudeChangeAboveWarningHeightLimitAndBelowReturnHomeAltitude, + DUXBetaMaxAltitudeChangeBelowReturnHomeAltitude, + DUXBetaMaxAltitudeChangeUnableToChangeMaxAltitudeInBeginnerMode, + DUXBetaMaxAltitudeChangeUnableToChangeReturnHomeAltitude, + DUXBetaMaxAltitudeChangeUnknownError }; /** * Model for the SystemStatusList widget to show and edit the max alitiude for the aircraft. */ -@interface DUXMaxAltitudeListItemWidgetModel : DUXBetaBaseWidgetModel +@interface DUXBetaMaxAltitudeListItemWidgetModel : DUXBetaBaseWidgetModel /** * Boolean indicating if the aircraft is in Beginner Mode. If so, changing the maximum altitude is not allowed. */ @@ -62,24 +65,24 @@ typedef NS_ENUM(NSInteger, DUXMaxAltitudeChange) { /** * Method used to check the new maximum altitude and return an enum indicating the suitability of the new max altitude. */ -- (DUXMaxAltitudeChange)validateNewHeight:(NSInteger)newHeight; +- (DUXBetaMaxAltitudeChange)validateNewHeight:(NSInteger)newHeight; /** * Method called to acutally change the max altitude of the aircraft. The completion block is called after setting with a result value indicating if the * change succeeded or if there was an error condition. */ -- (void)updateMaxHeight:(NSInteger)newHeight onCompletion:(void (^)(DUXMaxAltitudeChange))resultsBlock; +- (void)updateMaxHeight:(NSInteger)newHeight onCompletion:(void (^)(DUXBetaMaxAltitudeChange))resultsBlock; @end /** -* MaxAltitudeListItemModelState contains the hooks for the DUXMaxAltitudeListItemWidgetModel. +* MaxAltitudeListItemModelState contains the hooks for the DUXBetaMaxAltitudeListItemWidgetModel. * It inherits all model hooks in ListItemEditTextModelState and adds: * -* Key: maxAltitudedChange Type: NSNumber - The new value for maximum altitude when the user changes the altitude or when the drone connects and updats values. +* Key: maxAltitudeChanged Type: NSNumber - The new value for maximum altitude when the user changes the altitude or when the drone connects and updats values. */ @interface MaxAltitudeListItemModelState : ListItemEditTextModelState -+ (instancetype)maxAltitudedChange:(NSInteger)maxAltitude; ++ (instancetype)maxAltitudeChanged:(NSInteger)maxAltitude; @end NS_ASSUME_NONNULL_END diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXMaxAltitudeListItemWidgetModel.m b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxAltitudeListItemWidgetModel.m similarity index 69% rename from DJIUXSDKWidgets/SystemStatusListWidgets/DUXMaxAltitudeListItemWidgetModel.m rename to DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxAltitudeListItemWidgetModel.m index 9b8d370..844f7b9 100644 --- a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXMaxAltitudeListItemWidgetModel.m +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxAltitudeListItemWidgetModel.m @@ -1,9 +1,11 @@ // -// DUXMaxAltitudeListItemWidgetModel.m +// DUXBetaMaxAltitudeListItemWidgetModel.m // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -23,13 +25,13 @@ // SOFTWARE. // -#import "DUXMaxAltitudeListItemWidgetModel.h" +#import "DUXBetaMaxAltitudeListItemWidgetModel.h" #import "NSObject+DUXBetaRKVOExtension.h" @import DJIUXSDKCore; static const CGFloat maxLegalHeight = 120; -@interface DUXMaxAltitudeListItemWidgetModel () +@interface DUXBetaMaxAltitudeListItemWidgetModel () @property (nonatomic, strong) DJIFlightControllerKey *maxAltitudeKey; @property (nonatomic, strong) DJIFlightControllerKey *maxAltitudeRangeKey; @property (nonatomic, strong) DJIFlightControllerKey *noviceModeKey; @@ -39,7 +41,7 @@ @interface DUXMaxAltitudeListItemWidgetModel () @property (nonatomic, assign) float returnHomeHeight; @end -@implementation DUXMaxAltitudeListItemWidgetModel +@implementation DUXBetaMaxAltitudeListItemWidgetModel - (void)inSetup { self.limitMaxFlightHeight = maxLegalHeight; @@ -59,55 +61,58 @@ - (void)inCleanup { UnBindSDK; } -- (DUXMaxAltitudeChange)validateNewHeight:(NSInteger)newHeight { +- (DUXBetaMaxAltitudeChange)validateNewHeight:(NSInteger)newHeight { if (newHeight < self.rangeValue.min.intValue) { - return DUXMaxAltitudeChangeBelowMininimumAltitude; + return DUXBetaMaxAltitudeChangeBelowMininimumAltitude; } if (newHeight > self.rangeValue.max.intValue) { - return DUXMaxAltitudeChangeAboveMaximumAltitude; + return DUXBetaMaxAltitudeChangeAboveMaximumAltitude; } if (newHeight > _limitMaxFlightHeight) { - if (newHeight > _returnHomeHeight) { - return DUXMaxAltitudeChangeAboveWarningHeightLimitAndReturnHomeAltitude; + if (newHeight < _returnHomeHeight) { + return DUXBetaMaxAltitudeChangeAboveWarningHeightLimitAndBelowReturnHomeAltitude; } else { - return DUXMaxAltitudeChangeAboveWarningHeightLimit; + return DUXBetaMaxAltitudeChangeAboveWarningHeightLimit; } } + if (newHeight < _returnHomeHeight) { + return DUXBetaMaxAltitudeChangeBelowReturnHomeAltitude; + } if (newHeight > _returnHomeHeight) { - return DUXMaxAltitudeChangeAboveReturnHomeMaxAltitude; + return DUXBetaMaxAltitudeChangeAboveReturnHomeMaxAltitude; } - return DUXMaxAltitudeChangeMaxAltitudeValid; + return DUXBetaMaxAltitudeChangeMaxAltitudeValid; } -- (void)updateMaxHeight:(NSInteger)newHeight onCompletion:(void (^)(DUXMaxAltitudeChange))resultsBlock { +- (void)updateMaxHeight:(NSInteger)newHeight onCompletion:(void (^)(DUXBetaMaxAltitudeChange))resultsBlock { if (self.isNoviceMode) { - resultsBlock(DUXMaxAltitudeChangeUnableToChangeMaxAltitudeInBeginnerMode); + resultsBlock(DUXBetaMaxAltitudeChangeUnableToChangeMaxAltitudeInBeginnerMode); return; } [[DJISDKManager keyManager] setValue:@(newHeight) forKey:self.maxAltitudeKey withCompletion:^(NSError * _Nullable error) { - DUXMaxAltitudeChange returnValue = DUXMaxAltitudeChangeUnknownError; + DUXBetaMaxAltitudeChange returnValue = DUXBetaMaxAltitudeChangeUnknownError; if (error) { NSLog(@"error setting DJIFlightControllerParamMaxFlightHeight = %@", error); } else { - [DUXStateChangeBroadcaster send:[MaxAltitudeListItemModelState maxAltitudedChange:newHeight]]; - returnValue = DUXMaxAltitudeChangeMaxAltitudeValid; + [DUXBetaStateChangeBroadcaster send:[MaxAltitudeListItemModelState maxAltitudeChanged:newHeight]]; + returnValue = DUXBetaMaxAltitudeChangeMaxAltitudeValid; } resultsBlock(returnValue); }]; - + // Now we need to see what the current return home height is. If it is higher than the new max altidude, make sure it // is set to keep under that maximum - if (self.returnHomeHeight < newHeight) { + if (self.returnHomeHeight > newHeight) { [[DJISDKManager keyManager] setValue:@(newHeight) forKey:self.returnHomeHeightKey withCompletion:^(NSError * _Nullable error) { - DUXMaxAltitudeChange returnValue = DUXMaxAltitudeChangeMaxAltitudeValid; + DUXBetaMaxAltitudeChange returnValue = DUXBetaMaxAltitudeChangeMaxAltitudeValid; if (error) { - returnValue = DUXMaxAltitudeChangeUnableToChangeReturnHomeAltitude; + returnValue = DUXBetaMaxAltitudeChangeUnableToChangeReturnHomeAltitude; NSLog(@"error setting DJIFlightControllerParamGoHomeHeightInMeters = %@", error); } resultsBlock(returnValue); @@ -119,16 +124,16 @@ - (void)updateMaxHeight:(NSInteger)newHeight onCompletion:(void (^)(DUXMaxAltitu @implementation MaxAltitudeListItemModelState -+ (instancetype)maxAltitudedChange:(NSInteger)maxAltitude { - return [[self alloc] initWithKey:@"maxAltitudedChange" number:@(maxAltitude)]; ++ (instancetype)maxAltitudeChanged:(NSInteger)maxAltitude { + return [[self alloc] initWithKey:@"maxAltitudeChanged" number:@(maxAltitude)]; } + (instancetype)setMaxAltitudeSuccess { return [[self alloc] initWithKey:@"setMaxAltitudeSuccess" number:@(YES)]; } -+ (instancetype)setMaxAltitudeFailed:(NSInteger)maxAltitude { - return [[self alloc] initWithKey:@"setMaxAltitudeFailed" number:@(maxAltitude)]; ++ (instancetype)setMaxAltitudeFailed:(DUXBetaMaxAltitudeChange)error { + return [[self alloc] initWithKey:@"setMaxAltitudeFailed" number:@(error)]; } @end diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxFlightDistanceListItemWidget.h b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxFlightDistanceListItemWidget.h new file mode 100644 index 0000000..5d72b0c --- /dev/null +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxFlightDistanceListItemWidget.h @@ -0,0 +1,39 @@ +// +// DUXBetaMaxFlightDistanceListItemWidgetViewController.h +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface DUXBetaMaxFlightDistanceListItemWidget : DUXBetaListItemEditTextButtonWidget +/// The string to use as the button title for the Enable button to turn on the max flight distance limitation +@property (nonatomic, strong) NSString *enableButtonTitle; +/// The string to use as the button title for the Disable button to turn of the max flight distance limitation +@property (nonatomic, strong) NSString *disableButtonTitle; +@end + +NS_ASSUME_NONNULL_END diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxFlightDistanceListItemWidget.m b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxFlightDistanceListItemWidget.m new file mode 100644 index 0000000..f6d250f --- /dev/null +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxFlightDistanceListItemWidget.m @@ -0,0 +1,195 @@ +// +// DUXBetaMaxFlightDistanceListItemWidgetViewController.m +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// + +#import "DUXBetaMaxFlightDistanceListItemWidget.h" +#import "DUXBetaMaxFlightDistanceListItemWidgetModel.h" +#import "NSObject+DUXBetaRKVOExtension.h" +@import DJIUXSDKCore; + +@interface DUXBetaMaxFlightDistanceListItemWidget () +@property (nonatomic, strong) DUXBetaMaxFlightDistanceListItemWidgetModel *widgetModel; +@end + +@implementation DUXBetaMaxFlightDistanceListItemWidget +- (instancetype)init { + if (self = [super init:DUXBetaListItemEditAndButton]) { + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + if (self = [super init:DUXBetaListItemEditAndButton]) { + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.translatesAutoresizingMaskIntoConstraints = NO; + + // Test for existance because it is possible these were actually set before the view was loaded. + // (For instance, duing the customizeWidgetSetup in a SmartListModel.) + if (self.disableButtonTitle == nil) { + self.disableButtonTitle = NSLocalizedString(@"Disable", @"Max Flight Distance Enable/Disable button"); + } + if (self.enableButtonTitle == nil) { + self.enableButtonTitle = NSLocalizedString(@"Enable", @"Max Flight Distance Enable/Disable button"); + } + + + self.widgetModel = [[DUXBetaMaxFlightDistanceListItemWidgetModel alloc] init]; + [self.widgetModel setup]; + + [self setTitle:NSLocalizedString(@"Max Flight Distance", @"System Status Checklist Item Title") andIconName:@"SystemStatusMaxFlightDistance"]; + __weak DUXBetaMaxFlightDistanceListItemWidget *weakSelf = self; + [self setButtonAction:^(id senderWidget) { + __strong DUXBetaMaxFlightDistanceListItemWidget *strongSelf = weakSelf; + if (strongSelf) { + BOOL newEnableState = !strongSelf.widgetModel.isFlightRadiusEnabled; + [strongSelf.widgetModel flightRadiusEnable:newEnableState onCompletion:^(DUXBetaMaxFlightDistanceChange result) { + }]; + } + }]; + [self setupUI]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + BindRKVOModel(self.widgetModel, @selector(metaDataChanged), isNoviceMode, rangeValue, isFlightRadiusEnabled); + BindRKVOModel(self.widgetModel, @selector(maxDistanceChanged), maxFlightDistance); + BindRKVOModel(self.widgetModel, @selector(productConnectedChanged), isProductConnected); + BindRKVOModel(self, @selector(updateButtonTitle), disableButtonTitle, enableButtonTitle); +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + + UnBindRKVOModel(self); +} + +- (void)productConnectedChanged { + if (self.widgetModel.isProductConnected == NO) { + [self setEditText:NSLocalizedString(@"N/A", @"N/A")]; + } else { + // If product was just connected, the max distance doesn't change on update, so we need to manually set this. + [self maxDistanceChanged]; + } + + [self updateUI]; + +} + +- (void)metaDataChanged { + if (self.widgetModel.rangeValue) { + NSInteger min = [self.widgetModel.rangeValue.min intValue]; + NSInteger max = [self.widgetModel.rangeValue.max intValue]; + + NSString *rangeString = [NSString stringWithFormat:@"(%ld-%ld%@)", min, max, @"m"]; + [self setHintText:rangeString]; + [self setEditFieldValuesMin:min maxValue:max]; + } + + [self updateUI]; +} + +- (void)setupUI { + [super setupUI]; + + __weak DUXBetaMaxFlightDistanceListItemWidget *weakSelf = self; + [self setTextChangedBlock: ^(NSString *newText) { + NSInteger newMaxDistance = [newText intValue]; + if (newMaxDistance > 0) { + __strong DUXBetaMaxFlightDistanceListItemWidget *strongSelf = weakSelf; + if (strongSelf) { + [strongSelf.widgetModel updateMaxDistance:newMaxDistance onCompletion:^(DUXBetaMaxFlightDistanceChange resultInfo) { + + }]; + } + } + }]; +} + +- (void)updateUI { + [super updateUI]; + + BOOL isConnected = self.widgetModel.isProductConnected; + BOOL isFlightRadiusEnabled = self.widgetModel.isFlightRadiusEnabled; + BOOL isNoviceMode = self.widgetModel.isNoviceMode; + + if (isConnected == NO) { + if (self.enableEditField == YES) { + self.enableEditField = NO; + } + [self setEditText:NSLocalizedString(@"N/A", @"N/A")]; + [self hideHintLabel:YES]; + [self hideInputField:NO]; + [self setButtonHidden:YES]; + } else if (isNoviceMode) { + if (self.enableEditField == YES) { + self.enableEditField = NO; + } + [self setEditText:[self.widgetModel metersToUnitString:30.0]]; + [self hideHintLabel:YES]; + [self hideInputField:NO]; + [self setButtonHidden:YES]; + } else if (isFlightRadiusEnabled) { + if (self.enableEditField == NO) { + self.enableEditField = YES; + } + [self setEditText:[NSString stringWithFormat:@"%ld", (long) [self.widgetModel metersToMeasurementSystem:self.widgetModel.maxFlightDistance]]]; + self.inputField.layer.borderWidth = self.editTextBorderWidth; + [self hideInputAndHint:NO]; + [self updateButtonTitle]; + [self setButtonHidden:NO]; + } else { + if (self.enableEditField == YES) { + self.enableEditField = NO; + } + [self setEditText:[NSString stringWithFormat:@"%ld", (long) [self.widgetModel metersToMeasurementSystem:self.widgetModel.maxFlightDistance]]]; + [self hideInputAndHint:YES]; + [self updateButtonTitle]; + [self setButtonHidden:NO]; + } +} + +- (void)maxDistanceChanged { + if (!self.widgetModel.isNoviceMode) { + NSString *distanceString = [NSString stringWithFormat:@"%ld", (long)self.widgetModel.maxFlightDistance]; + [self setEditText:distanceString]; + [self updateUI]; + } +} + +- (void)updateButtonTitle { + if (self.widgetModel.isFlightRadiusEnabled) { + [self setButtonTitle:self.disableButtonTitle]; + } else { + [self setButtonTitle:self.enableButtonTitle]; + } +} +@end diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxFlightDistanceListItemWidgetModel.h b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxFlightDistanceListItemWidgetModel.h new file mode 100644 index 0000000..68c805e --- /dev/null +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxFlightDistanceListItemWidgetModel.h @@ -0,0 +1,87 @@ +// +// DUXBetaMaxFlightDistanceListItemWidgetModel.h +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, DUXBetaMaxFlightDistanceChange) { + DUXBetaMaxFlightDistanceChangeValid, + DUXBetaMaxFlightDistanceChangeOutOfMaximumRange, + DUXBetaMaxFlightDistanceChangeUnableToChangeInBeginnerMode, + DUXBetaMaxFlightDistanceChangeMaxDistanceEnableFailed, + DUXBetaMaxFlightDistanceRadiusEnabledChangeSuccessful, + DUXBetaMaxFlightDistanceRadiusEnabledChangeFailed, + DUXBetaMaxFlightDistanceChangeUnknownError +}; + +@interface DUXBetaMaxFlightDistanceListItemWidgetModel : DUXBetaBaseWidgetModel +/** +* Boolean indicating if the aircraft is in Beginner Mode. If so, changing the maximum flight distance is not allowed. +*/ +@property (nonatomic, readonly) BOOL isNoviceMode; + +/** + * The valid range of values (in meters) for the aircraft to fly within. + */ +@property (nonatomic, strong) DJIParamCapabilityMinMax *rangeValue; + +/** +* The current max distance allowed for the aircraft. +*/ +@property (nonatomic, assign) double maxFlightDistance; + +/** +* Boolean value indicating if the flight radius limit is enabled. +*/ +@property (nonatomic, readwrite) BOOL isFlightRadiusEnabled; + +- (void)flightRadiusEnable:(BOOL)enableFlightRadius onCompletion:(void (^ _Nullable) (DUXBetaMaxFlightDistanceChange))resultsBlock; +- (void)updateMaxDistance:(NSUInteger)newMaxDistance onCompletion:(void (^ _Nullable)(DUXBetaMaxFlightDistanceChange))resultsBlock; + +@end + +/** + * MaxAltitudeListItemModelState contains the hooks for the DUXBetaMaxAltitudeListItemWidgetModel. + * It inherits all model hooks in ListItemEditTextModelState and adds: + * + * Key: maxFlightDistanceEnabled Type: NSNumber - A boolean indicated the new state when the maxFlightDistance has been enabled or disabled. + * + * Key: updatedMaxFlightDistance Type: NSNumber - The new value for the maxFlightDistance after it has been successfully changed. + * + * Key: setMaxFlightDistanceSuccess Type: NSNumber - A constant number @(YES) indicating the max flight distance was successfully updated. + * + * Key: setMaxFlightDistanceFailed Type: NSError - A NSError indicating the reason the max flight distance failed to update. +*/ +@interface MaxFlightDistanceListItemModelState : ListItemEditTextModelState ++ (instancetype)maxFlightDistanceEnabled:(BOOL)isEnabled; ++ (instancetype)updatedMaxFlightDistance:(NSInteger)maxFlightDistance; ++ (instancetype)setMaxFlightDistanceSuccess; ++ (instancetype)setMaxFlightDistanceFailed:(NSError*)error; +@end + +NS_ASSUME_NONNULL_END diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxFlightDistanceListItemWidgetModel.m b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxFlightDistanceListItemWidgetModel.m new file mode 100644 index 0000000..c388909 --- /dev/null +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaMaxFlightDistanceListItemWidgetModel.m @@ -0,0 +1,122 @@ +// +// DUXBetaMaxFlightDistanceListItemWidgetModel.m +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// + +#import "DUXBetaMaxFlightDistanceListItemWidgetModel.h" +#import "NSObject+DUXBetaRKVOExtension.h" +@import DJIUXSDKCore; + +@interface DUXBetaMaxFlightDistanceListItemWidgetModel () +@property (nonatomic, readwrite) BOOL isNoviceMode; + +@property (nonatomic, strong) DJIFlightControllerKey *maxRangeKey; +@property (nonatomic, strong) DJIFlightControllerKey *flightRadiusEnabledKey; +@property (nonatomic, strong) DJIFlightControllerKey *maxFlightRadiusKey; +@end + +@implementation DUXBetaMaxFlightDistanceListItemWidgetModel + +- (void)inSetup { + self.maxRangeKey = [DJIFlightControllerKey keyWithParam:DJIFlightControllerParamMaxFlightRadiusRange]; + self.flightRadiusEnabledKey = [DJIFlightControllerKey keyWithParam:DJIFlightControllerParamMaxFlightRadiusEnabled]; + self.maxFlightRadiusKey = [DJIFlightControllerKey keyWithParam:DJIFlightControllerParamMaxFlightRadius]; + + BindSDKKey([DJIFlightControllerKey keyWithParam:DJIFlightControllerParamNoviceModeEnabled], isNoviceMode); + BindSDKKey(self.maxRangeKey, rangeValue); + BindSDKKey(self.maxFlightRadiusKey, maxFlightDistance); + BindSDKKey(self.flightRadiusEnabledKey, isFlightRadiusEnabled); +} + +- (void)inCleanup { + UnBindSDK; +} + +#pragma mark - Setters + +- (void)flightRadiusEnable:(BOOL)enableFlightRadius onCompletion:(void (^)(DUXBetaMaxFlightDistanceChange))resultsBlock { + if (self.isNoviceMode) { + return; + } + + [[DJISDKManager keyManager] setValue:@(enableFlightRadius) forKey:self.flightRadiusEnabledKey withCompletion:^(NSError * _Nullable error) { + if (resultsBlock) { + if (error) { + resultsBlock(DUXBetaMaxFlightDistanceRadiusEnabledChangeFailed); + } else { + [DUXBetaStateChangeBroadcaster send:[MaxFlightDistanceListItemModelState maxFlightDistanceEnabled:enableFlightRadius]]; + resultsBlock(DUXBetaMaxFlightDistanceRadiusEnabledChangeSuccessful); + } + } + }]; +} + +- (void)updateMaxDistance:(NSUInteger)newMaxDistance onCompletion:(void (^ _Nullable)(DUXBetaMaxFlightDistanceChange))resultsBlock { + if (self.isNoviceMode) { + if (resultsBlock) { + resultsBlock(DUXBetaMaxFlightDistanceChangeUnableToChangeInBeginnerMode); + } + return; + } + + [[DJISDKManager keyManager] setValue:@(newMaxDistance) forKey:self.maxFlightRadiusKey withCompletion:^(NSError * _Nullable error) { + DUXBetaMaxFlightDistanceChange returnValue = DUXBetaMaxFlightDistanceChangeUnknownError; + + if (error) { + [DUXBetaStateChangeBroadcaster send:[MaxFlightDistanceListItemModelState setMaxFlightDistanceFailed:error]]; + } else { + [DUXBetaStateChangeBroadcaster send:[MaxFlightDistanceListItemModelState setMaxFlightDistanceSuccess]]; + [DUXBetaStateChangeBroadcaster send:[MaxFlightDistanceListItemModelState updatedMaxFlightDistance:newMaxDistance]]; + + returnValue = DUXBetaMaxFlightDistanceChangeValid; + } + if (resultsBlock) { + resultsBlock(returnValue); + } + }]; + +} + +@end + +#pragma mark - Hooks +@implementation MaxFlightDistanceListItemModelState + ++ (instancetype)maxFlightDistanceEnabled:(BOOL)isEnabled { + return [[self alloc] initWithKey:@"maxFlightDistanceEnabled" number:@(isEnabled)]; +} + ++ (instancetype)updatedMaxFlightDistance:(NSInteger)maxFlightDistance { + return [[self alloc] initWithKey:@"updatedMaxFlightDistance" number:@(maxFlightDistance)]; +} + ++ (instancetype)setMaxFlightDistanceSuccess { + return [[self alloc] initWithKey:@"setMaxAltitudeSuccess" number:@(YES)]; +} + ++ (instancetype)setMaxFlightDistanceFailed:(NSError*)error { + return [[self alloc] initWithKey:@"setMaxAltitudeSuccess" object:error]; +} +@end diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXRCStickModeListItemWidget.h b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaRCStickModeListItemWidget.h similarity index 82% rename from DJIUXSDKWidgets/SystemStatusListWidgets/DUXRCStickModeListItemWidget.h rename to DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaRCStickModeListItemWidget.h index c776368..5a404a6 100644 --- a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXRCStickModeListItemWidget.h +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaRCStickModeListItemWidget.h @@ -1,9 +1,11 @@ // -// DUXRCStickModeListItemWidget.h +// DUXBetaRCStickModeListItemWidget.h // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -23,7 +25,7 @@ // SOFTWARE. // -#import +#import NS_ASSUME_NONNULL_BEGIN @@ -31,12 +33,12 @@ NS_ASSUME_NONNULL_BEGIN * Widget for displaying the RC stick mode in the SystemStatsList. This widget displays and allows the user to change * how the control sticks on the RC affect the aircraft from one of the preset values. */ -@interface DUXRCStickModeListItemWidget : DUXListItemRadioButtonWidget // DUXListItemRadioButtonWidget DUXListItemEditTextButtonWidget +@interface DUXBetaRCStickModeListItemWidget : DUXBetaListItemRadioButtonWidget // DUXBetaListItemRadioButtonWidget DUXBetaListItemEditTextButtonWidget @end /** - * RCModeListItemUIState contains the UI hooks for DUXRCStickModeListItemWidget + * RCModeListItemUIState contains the UI hooks for DUXBetaRCStickModeListItemWidget * It inherits from ListItemRadioButtonUIState and adds: * * Key: rcModeControlChange Type: NSNumber - Sends the integer value for the RC stick mode when the user selects a new RC stick mode. diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXRCStickModeListItemWidget.m b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaRCStickModeListItemWidget.m similarity index 87% rename from DJIUXSDKWidgets/SystemStatusListWidgets/DUXRCStickModeListItemWidget.m rename to DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaRCStickModeListItemWidget.m index 7c78c97..16e20c9 100644 --- a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXRCStickModeListItemWidget.m +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaRCStickModeListItemWidget.m @@ -1,9 +1,11 @@ // -// DUXRCStickModeListItemWidget.m +// DUXBetaRCStickModeListItemWidget.m // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -23,8 +25,8 @@ // SOFTWARE. // -#import "DUXRCStickModeListItemWidget.h" -#import "DUXRCStickModeListItemWidgetModel.h" +#import "DUXBetaRCStickModeListItemWidget.h" +#import "DUXBetaRCStickModeListItemWidgetModel.h" #import "NSObject+DUXBetaRKVOExtension.h" @import DJIUXSDKCore; @@ -37,12 +39,12 @@ typedef NS_ENUM(NSInteger, DUXBetaSupportedRCStickControlModes) { DUXBetaRCStickMode3 = 2 }; -@interface DUXRCStickModeListItemWidget () -@property (nonatomic, strong) DUXRCStickModeListItemWidgetModel *widgetModel; +@interface DUXBetaRCStickModeListItemWidget () +@property (nonatomic, strong) DUXBetaRCStickModeListItemWidgetModel *widgetModel; @end -@implementation DUXRCStickModeListItemWidget +@implementation DUXBetaRCStickModeListItemWidget - (void)viewDidLoad { self.view.translatesAutoresizingMaskIntoConstraints = NO; @@ -52,7 +54,7 @@ - (void)viewDidLoad { NSLocalizedString(@"Mode 3", @" RC Stick Mode 3")]]; [super setTitle:@"Control Stick Mode" andIconName: @"SystemStatusRC"]; - self.widgetModel = [DUXRCStickModeListItemWidgetModel new]; + self.widgetModel = [DUXBetaRCStickModeListItemWidgetModel new]; [_widgetModel setup]; [super viewDidLoad]; @@ -64,9 +66,9 @@ - (void)viewWillAppear:(BOOL)animated { BindRKVOModel(self.widgetModel, @selector(productConnected), isProductConnected); BindRKVOModel(self.widgetModel, @selector(rcStickModeChanged), remoteControllerMappingStyle); - __weak DUXRCStickModeListItemWidget *weakSelf = self; + __weak DUXBetaRCStickModeListItemWidget *weakSelf = self; [self setOptionSelectedAction:^(NSInteger oldSelectedIndex, NSInteger newSelectedIndex) { - __strong DUXRCStickModeListItemWidget *strongSelf = weakSelf; + __strong DUXBetaRCStickModeListItemWidget *strongSelf = weakSelf; if (!strongSelf) { return; } @@ -88,7 +90,7 @@ - (void)viewWillAppear:(BOOL)animated { // No other mode supported, shouldn't be allowd. Bail out return; } - [DUXStateChangeBroadcaster send:[RCModeListItemUIState rcModeControlChange:newSelectedIndex]]; + [DUXBetaStateChangeBroadcaster send:[RCModeListItemUIState rcModeControlChange:newSelectedIndex]]; [strongSelf.widgetModel setStickMode:newMode onCompletion:^(NSError *error) { }]; diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXRCStickModeListItemWidgetModel.h b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaRCStickModeListItemWidgetModel.h similarity index 91% rename from DJIUXSDKWidgets/SystemStatusListWidgets/DUXRCStickModeListItemWidgetModel.h rename to DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaRCStickModeListItemWidgetModel.h index 2cb0dbd..1a5b9a0 100644 --- a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXRCStickModeListItemWidgetModel.h +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaRCStickModeListItemWidgetModel.h @@ -1,9 +1,11 @@ // -// DUXRCStickModeListItemWidgetModel.h +// DUXBetaRCStickModeListItemWidgetModel.h // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -30,7 +32,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Model for the SystemStatusList widget to show and edit the RC stick mode for the controller/aircraft. */ -@interface DUXRCStickModeListItemWidgetModel : DUXBetaBaseWidgetModel +@interface DUXBetaRCStickModeListItemWidgetModel : DUXBetaBaseWidgetModel /** * The remote controller mapping style currently being applied between controller and aircraft. */ @@ -44,7 +46,7 @@ NS_ASSUME_NONNULL_BEGIN @end /** - * RCModeListItemModelState contains the model hooks for DUXRCStickModeListItemWidgetModel + * RCModeListItemModelState contains the model hooks for DUXBetaRCStickModeListItemWidgetModel * It inherits from ListItemTitleModelState and adds: * * Key: rcStickModeUpdated Type: NSNumber - Sends the new stick mode of type DJIRCAircraftMappingStyle as an NSNumber diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXRCStickModeListItemWidgetModel.m b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaRCStickModeListItemWidgetModel.m similarity index 86% rename from DJIUXSDKWidgets/SystemStatusListWidgets/DUXRCStickModeListItemWidgetModel.m rename to DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaRCStickModeListItemWidgetModel.m index 7ba9e6a..7f446fb 100644 --- a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXRCStickModeListItemWidgetModel.m +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaRCStickModeListItemWidgetModel.m @@ -1,9 +1,11 @@ // -// DUXRCStickModeListItemWidgetModel.m +// DUXBetaRCStickModeListItemWidgetModel.m // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -24,11 +26,11 @@ // #import -#import "DUXRCStickModeListItemWidgetModel.h" +#import "DUXBetaRCStickModeListItemWidgetModel.h" #import "NSObject+DUXBetaRKVOExtension.h" @import DJIUXSDKCore; -@implementation DUXRCStickModeListItemWidgetModel +@implementation DUXBetaRCStickModeListItemWidgetModel - (void)inSetup { DJIRemoteControllerKey *rcKey = [DJIRemoteControllerKey keyWithParam:DJIRemoteControllerParamAircraftMappingStyle]; @@ -43,9 +45,9 @@ - (void)inCleanup { - (void)setStickMode:(DJIRCAircraftMappingStyle)newMode onCompletion:(void (^ _Nullable)(NSError *) )resultsBlock { [[DJISDKManager keyManager] setValue:@(newMode) forKey:[DJIRemoteControllerKey keyWithParam:DJIRemoteControllerParamAircraftMappingStyle] withCompletion:^(NSError * _Nullable error) { if (error) { - [DUXStateChangeBroadcaster send:[RCModeListItemModelState rcStickModeUpdateFailed:error]]; + [DUXBetaStateChangeBroadcaster send:[RCModeListItemModelState rcStickModeUpdateFailed:error]]; } else { - [DUXStateChangeBroadcaster send:[RCModeListItemModelState rcStickModeUpdated:newMode]]; + [DUXBetaStateChangeBroadcaster send:[RCModeListItemModelState rcStickModeUpdated:newMode]]; } if (resultsBlock) { resultsBlock(error); diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaReturnToHomeAltitudeListItemWidget.h b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaReturnToHomeAltitudeListItemWidget.h new file mode 100644 index 0000000..b9af394 --- /dev/null +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaReturnToHomeAltitudeListItemWidget.h @@ -0,0 +1,81 @@ +// +// DUXBetaReturnToHomeAltitudeListItemWidget.h +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// +// 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. +// + +#import + +@class DUXBetaAlertViewApperance; + +NS_ASSUME_NONNULL_BEGIN + +/** + * Widget for display in the SystemStatusList which shows the current return to home altitude setting for the aircraft + * and also allows editing the height within the limits shown in the hint text. + * + * The widget has 2 potential alerts which may show. Each can be configured by setting the appropriate setting + * properties of the appropriate DUXBetaAlertViewAppearance object below. +*/ +@interface DUXBetaReturnToHomeAltitudeListItemWidget : DUXBetaListItemEditTextButtonWidget +/// AlertViewAppearance properties for alert after successfully changing the Return-To-Home Altitude +@property (nonatomic, strong) DUXBetaAlertViewAppearance *rthAltitudeChangeAlertAppearance; +/// AlertViewAppearance properties for alert after attempting to set Return-To-Home altitude above the max altitude setting. +@property (nonatomic, strong) DUXBetaAlertViewAppearance *rthAltitudeExceedsMaxAltitudeAlertAppearance; +@end + +/** + * ReturnToHomeAltitudeListItemUIState contains the hooks for the MaxAltitude list widget UI. + * It inherits all UI hooks in ListItemEditTextUIState and adds: + * + * Key: invalidHeightRejected Type: NSNumber - The entered height in meters which was rejected during validation in the return to home altitude model. + * + * Key: validHeightEntered Type: NSNumber - The entered maximum altitude after being validated by the return to home altitude model. + */ +@interface ReturnToHomeAltitudeListItemUIState : ListItemEditTextUIState + ++ (instancetype)invalidHeightRejected:(NSInteger)invalidHeightInMeters; ++ (instancetype)validHeightEntered:(NSInteger)validHeightInMeters; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaReturnToHomeAltitudeListItemWidget.m b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaReturnToHomeAltitudeListItemWidget.m new file mode 100644 index 0000000..02d3334 --- /dev/null +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaReturnToHomeAltitudeListItemWidget.m @@ -0,0 +1,257 @@ +// +// DUXBetaReturnToHomeAltitudeListItemWidget.m +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// + +#import "DUXBetaReturnToHomeAltitudeListItemWidget.h" +#import "DUXBetaReturnToHomeAltitudeListItemWidgetModel.h" +#import "NSObject+DUXBetaRKVOExtension.h" +#import + +@import DJIUXSDKCore; + +@interface DUXBetaReturnToHomeAltitudeListItemWidget () +@property (nonatomic, strong) DUXBetaReturnToHomeAltitudeListItemWidgetModel *widgetModel; +@end + +@implementation DUXBetaReturnToHomeAltitudeListItemWidget + +- (instancetype)init { + if (self = [super init:DUXBetaListItemOnlyEdit]) { + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + if (self = [super init:DUXBetaListItemOnlyEdit]) { + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.translatesAutoresizingMaskIntoConstraints = NO; + + _widgetModel = [DUXBetaReturnToHomeAltitudeListItemWidgetModel new]; + [self.widgetModel setup]; + + // Load standard appearances for the 2 alerts we may show so they can be customized before use. + [self loadCustomAlertsAppearance]; + + [self setTitle:NSLocalizedString(@"Return-To-Home Altitude", @"System Status Checklist Item Title") andIconName:@"SystemStatusRTHAltitude"]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + BindRKVOModel(self.widgetModel, @selector(metaDataChanged), isNoviceMode, rangeValue); + BindRKVOModel(self.widgetModel, @selector(maxAltitudeChanged), maxAltitude); + BindRKVOModel(self.widgetModel, @selector(rthAltitudeChanged), returnToHomeAltitude); + BindRKVOModel(self.widgetModel, @selector(productConnectedChanged), isProductConnected); +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + + UnBindRKVOModel(self); +} + +- (void)productConnectedChanged { + if (self.widgetModel.isProductConnected == NO) { + [self setEditText:NSLocalizedString(@"N/A", @"N/A")]; + } else { + // If product was just connected, the RTH height doesn't always change on update, so we need to manually set this. + [self maxAltitudeChanged]; + } + + [self updateUI]; +} + +- (void)metaDataChanged { + if (self.widgetModel.rangeValue) { + NSInteger min = [self.widgetModel.rangeValue.min intValue]; + NSInteger max = [self.widgetModel.rangeValue.max intValue]; + + NSString *rangeString = [NSString stringWithFormat:@"(%ld-%ld%@)", min, max, self.widgetModel.measurementUnitString]; + [self setHintText:rangeString]; + [self setEditFieldValuesMin:min maxValue:max]; + } + + [self updateUI]; +} + +- (void)maxAltitudeChanged { + [self metaDataChanged]; + // If we want to auto-update the RTH, do so here, but MaxAltitudeListItemWidget will adjust down as required. + [self updateUI]; +} + +- (void)rthAltitudeChanged { + if (!self.widgetModel.isNoviceMode) { + NSString *heightString = [NSString stringWithFormat:@"%ld", (long)self.widgetModel.returnToHomeAltitude]; + [self setEditText:heightString]; + } +} + +- (void)setupUI { + [super setupUI]; + + __weak DUXBetaReturnToHomeAltitudeListItemWidget *weakSelf = self; + [self setTextChangedBlock: ^(NSString *newText) { + NSInteger newHeight = [newText intValue]; + // TODO: Once we handle imperial units here, there needs to be some slop added to the altitude change comparison + if ((newHeight > 0) && (newHeight != (int)self.widgetModel.returnToHomeAltitude)) { + __strong DUXBetaReturnToHomeAltitudeListItemWidget *strongSelf = weakSelf; + if (strongSelf) { + [strongSelf handleRTHAltitudeChangeRequest:newHeight]; + } + } + }]; +} + +- (void)loadCustomAlertsAppearance { + self.rthAltitudeChangeAlertAppearance = [DUXBetaAlertView systemAlertAppearance]; + self.rthAltitudeChangeAlertAppearance.heightScale = DUXBetaAlertViewHeightScaleSmall; + self.rthAltitudeChangeAlertAppearance.imageTintColor = [UIColor duxbeta_warningColor]; + + self.rthAltitudeExceedsMaxAltitudeAlertAppearance = [DUXBetaAlertView systemAlertAppearance]; + self.rthAltitudeExceedsMaxAltitudeAlertAppearance.heightScale = DUXBetaAlertViewHeightScaleSmall; + self.rthAltitudeExceedsMaxAltitudeAlertAppearance.imageTintColor = [UIColor duxbeta_dangerColor]; +} + +- (void)updateUI { + [super updateUI]; + + BOOL isConnected = self.widgetModel.isProductConnected; + BOOL isNoviceMode = self.widgetModel.isNoviceMode; + + if (isConnected == NO) { + if (self.enableEditField == YES) { + self.enableEditField = NO; + } + [self setEditText:NSLocalizedString(@"N/A", @"N/A")]; + [self hideHintLabel:YES]; + [self hideInputField:NO]; + } else if (isNoviceMode) { + if (self.enableEditField == YES) { + self.enableEditField = NO; + } + [self setEditText:[self.widgetModel metersToUnitString:30.0]]; + [self hideHintLabel:YES]; + [self hideInputField:NO]; + } else { + if (self.enableEditField == NO) { + self.enableEditField = YES; + } + [self setEditText:[NSString stringWithFormat:@"%ld", (long) [self.widgetModel metersToMeasurementSystem:self.widgetModel.returnToHomeAltitude]]]; + [self hideInputAndHint:NO]; + } +} + +- (void)handleRTHAltitudeChangeRequest:(NSInteger) newHeight { + if (self.widgetModel.isNoviceMode) { return; } + + // Quick sanity check + DUXBetaReturnToHomeAltitudeChange heightValidity = [self.widgetModel validateNewHeight:newHeight]; + switch (heightValidity) { + case DUXBetaReturnToHomeAltitudeChangeValid: + case DUXBetaReturnToHomeAltitudeChangeAboveWarningHeightLimit: + [DUXBetaStateChangeBroadcaster send:[ReturnToHomeAltitudeListItemUIState validHeightEntered:newHeight]]; + break; + + case DUXBetaReturnToHomeAltitudeChangeAboveMaximumAltitude: + break; + + default: + [DUXBetaStateChangeBroadcaster send:[ReturnToHomeAltitudeListItemUIState invalidHeightRejected:newHeight]]; + break; + } + + if ((heightValidity == DUXBetaReturnToHomeAltitudeChangeValid) || (heightValidity == DUXBetaReturnToHomeAltitudeChangeAboveWarningHeightLimit)) { + [self.widgetModel updateReturnHomeHeight:newHeight onCompletion:^(DUXBetaReturnToHomeAltitudeChange result) { + if (result == DUXBetaReturnToHomeAltitudeChangeValid) { + [self presentWarning]; + } + }]; + } else if (heightValidity == DUXBetaReturnToHomeAltitudeChangeAboveMaximumAltitude) { + [self presentError]; + } +} + +- (void)presentWarning { + DUXBetaAlertView *alert = [DUXBetaAlertView warningAlertWithTitle:NSLocalizedString(@"Return to Home Altitude", @"Return to Home Altitude") + message:NSLocalizedString(@"The return altitude has been changed, please pay attention to flight safety.", @"The return altitude has been changed, please pay attention to flight safety.") + heightScale:DUXBetaAlertViewHeightScaleSmall]; + void (^dismissCallback)(void) = ^{ + [DUXBetaStateChangeBroadcaster send:[MaxAltitudeListItemUIState dialogActionDismiss:@"Return To Home Altitude change acknowledged."]]; + }; + DUXBetaAlertAction *cancelAction = [DUXBetaAlertAction actionWithActionTitle:NSLocalizedString(@"OK", @"OK") + style:UIAlertActionStyleCancel + actionType:DUXBetaAlertActionTypeClosure + target:nil + selector:nil + completionAction:dismissCallback]; + alert.dissmissCompletion = dismissCallback; + [alert add:cancelAction]; + alert.appearance = self.rthAltitudeChangeAlertAppearance; + [alert showWithCompletion: ^{ + [DUXBetaStateChangeBroadcaster send:[MaxAltitudeListItemUIState dialogDisplayed:@"Return To Home Altitude change acknowledged."]]; + }]; +} + +- (void)presentError { + DUXBetaAlertView *alert = [DUXBetaAlertView failAlertWithTitle:NSLocalizedString(@"Return to Home Altitude", @"Return to Home Altitude") + message:NSLocalizedString(@"Return to home altitude cannot exceed maximum flight altitude.", @"Return to home altitude cannot exceed maximum flight altitude.") + heightScale:DUXBetaAlertViewHeightScaleSmall]; + + void (^dismissCallback)(void) = ^{ + [DUXBetaStateChangeBroadcaster send:[ReturnToHomeAltitudeListItemUIState dialogDisplayed:@"Return To Home Altitude change failed acknowledged."]]; + }; + DUXBetaAlertAction *defaultAction = [DUXBetaAlertAction actionWithActionTitle:NSLocalizedString(@"OK", @"OK") + style:UIAlertActionStyleDefault + actionType:DUXBetaAlertActionTypeClosure + target:nil + selector:nil + completionAction:dismissCallback]; + alert.dissmissCompletion = dismissCallback; + [alert add:defaultAction]; + alert.appearance = self.rthAltitudeExceedsMaxAltitudeAlertAppearance; + [alert showWithCompletion: ^{ + [DUXBetaStateChangeBroadcaster send:[ReturnToHomeAltitudeListItemUIState dialogDisplayed:@"Return To Home Altitude change failed."]]; + }]; + [self rthAltitudeChanged]; // Revert display text to current RTH altitude value. +} + +@end + +@implementation ReturnToHomeAltitudeListItemUIState ++ (instancetype)invalidHeightRejected:(NSInteger)invalidHeightInMeters { + return [[self alloc] initWithKey:@"invalidHeightRejected" number:@(invalidHeightInMeters)]; +} + ++ (instancetype)validHeightEntered:(NSInteger)validHeightInMeters { + return [[self alloc] initWithKey:@"validHeightEntered" number:@(validHeightInMeters)]; +} +@end diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaReturnToHomeAltitudeListItemWidgetModel.h b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaReturnToHomeAltitudeListItemWidgetModel.h new file mode 100644 index 0000000..5a50359 --- /dev/null +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaReturnToHomeAltitudeListItemWidgetModel.h @@ -0,0 +1,121 @@ +// +// DUXBetaReturnToHomeAltitudeListItemWidgetModel.h +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * The DUXBetaReturnToHomeAltitudeChange defines the possible states related to validating and changing the ReturnToHomeAltitude + */ +typedef NS_ENUM(NSInteger, DUXBetaReturnToHomeAltitudeChange) { + /** + * The new altitude value is valid and/or is accepted by the aircraft during a change + */ + DUXBetaReturnToHomeAltitudeChangeValid, + + /** + * The new altitude is above the maximum height allowed by the FAA (400 feet, 122 meters) + */ + DUXBetaReturnToHomeAltitudeChangeAboveWarningHeightLimit, + + /** + * The new altitude is above the maximum height allowed by the aircraft + */ + DUXBetaReturnToHomeAltitudeChangeAboveMaximumAltitude, + + /** + * The new altitude is above the below the minimum height allowed by the aircraft + */ + DUXBetaReturnToHomeAltitudeChangeBelowMininimumAltitude, + + /** + * The aricraft is in beginner/novice mode and the RTH altitude may not be changed. + */ + DUXBetaReturnToHomeAltitudeChangeUnableToChangeInBeginnerMode, + + /** + * The RTH altitude was unable to be changed for an unknown reason. (Did aircraft disconnect?) + */ + DUXBetaReturnToHomeAltitudeChangeUnknownError +}; + + + +/** + * Model for the SystemStatusList widget to show and edit the return to home alitiude for the aircraft. +*/ +@interface DUXBetaReturnToHomeAltitudeListItemWidgetModel : DUXBetaBaseWidgetModel +/** +* Boolean indicating if the aircraft is in Beginner Mode. If so, changing the maximum altitude is not allowed. +*/ +@property (nonatomic, readonly) BOOL isNoviceMode; + +/** + * The valid range of values (in meters) for the aircraft to fly within. + */ +@property (nonatomic, strong) DJIParamCapabilityMinMax *rangeValue; + +/** + * The current maximum altitude (in meters) for the aircraft to fly within. +*/ +@property (nonatomic, readonly) double maxAltitude; + +/** +* The current altitude setting for Return-To-Home flight. +*/ +@property (nonatomic, assign) double returnToHomeAltitude; + +/** +* Method used to check the new return to home altitude and return an enum indicating the suitability of the new return to home altitude. +*/ +- (DUXBetaReturnToHomeAltitudeChange)validateNewHeight:(NSInteger)newHeight; +/** + * Method called to acutally change the return to home altitude of the aircraft. The completion block is called after setting with a + * result value indicating if the change succeeded or if there was an error condition. +*/ +- (void)updateReturnHomeHeight:(NSInteger)newHeight onCompletion:(void (^)(DUXBetaReturnToHomeAltitudeChange))resultsBlock; + +@end + +/** + * ReturnToHomeAltitudeListItemModelState contains the hooks for the DUXBetaReturnToHomeAltitudeListItemModel. + * It inherits all model hooks in ListItemEditTextModelState and adds: + * + * Key: returnHeightUpdated Type: NSNumber - The new value for the ReturnToHome altitude after it has been successfully changed. + * + * Key: setReturnToHomeAltitudeSuccess Type: NSNumber - A constant number @(YES) indicating the ReturnToHome altitude was successfully updated. + * + * Key: setReturnToHomeAltitudeFailed Type: NSError - A NSError indicating the reason the ReturnToHome altitude failed to update. +*/ +@interface ReturnToHomeAltitudeListItemModelState : ListItemEditTextModelState ++ (instancetype)returnHeightUpdated:(NSInteger)newReturnHeight; ++ (instancetype)setReturnToHomeAltitudeSuccess; ++ (instancetype)setReturnToHomeAltitudeFailed:(NSError*)error; +@end + +NS_ASSUME_NONNULL_END diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaReturnToHomeAltitudeListItemWidgetModel.m b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaReturnToHomeAltitudeListItemWidgetModel.m new file mode 100644 index 0000000..97319d9 --- /dev/null +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaReturnToHomeAltitudeListItemWidgetModel.m @@ -0,0 +1,111 @@ +// +// DUXBetaReturnToHomeAltitudeListItemWidgetModel.m +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// + +#import "DUXBetaReturnToHomeAltitudeListItemWidgetModel.h" +#import "NSObject+DUXBetaRKVOExtension.h" +@import DJIUXSDKCore; + +static const CGFloat maxLegalHeight = 120; + +@interface DUXBetaReturnToHomeAltitudeListItemWidgetModel () +@property (nonatomic, readwrite) BOOL isNoviceMode; + +@property (nonatomic, strong) DJIFlightControllerKey *maxAltitudeKey; +@property (nonatomic, strong) DJIFlightControllerKey *maxAltitudeRangeKey; +@property (nonatomic, strong) DJIFlightControllerKey *returnHomeHeightKey; + +@property (nonatomic, assign) NSUInteger limitRTHFlightHeight; +@end + +@implementation DUXBetaReturnToHomeAltitudeListItemWidgetModel + +- (void)inSetup { + self.limitRTHFlightHeight = maxLegalHeight; + + self.returnHomeHeightKey = [DJIFlightControllerKey keyWithParam:DJIFlightControllerParamGoHomeHeightInMeters]; + self.maxAltitudeKey = [DJIFlightControllerKey keyWithParam:DJIFlightControllerParamMaxFlightHeight]; + self.maxAltitudeRangeKey = [DJIFlightControllerKey keyWithParam:DJIFlightControllerParamMaxFlightHeightRange]; + + BindSDKKey([DJIFlightControllerKey keyWithParam:DJIFlightControllerParamNoviceModeEnabled], isNoviceMode); + BindSDKKey(self.maxAltitudeRangeKey, rangeValue); + BindSDKKey(self.maxAltitudeKey, maxAltitude); + BindSDKKey(self.returnHomeHeightKey, returnToHomeAltitude); +} + +- (void)inCleanup { + UnBindSDK; +} + +- (DUXBetaReturnToHomeAltitudeChange)validateNewHeight:(NSInteger)newHeight { + if (newHeight < self.rangeValue.min.intValue) { + return DUXBetaReturnToHomeAltitudeChangeBelowMininimumAltitude; + } + + if ((newHeight > self.rangeValue.max.intValue) || (newHeight > self.maxAltitude)) { + return DUXBetaReturnToHomeAltitudeChangeAboveMaximumAltitude; + } + + if (newHeight > _limitRTHFlightHeight) { + return DUXBetaReturnToHomeAltitudeChangeAboveWarningHeightLimit; + } + + return DUXBetaReturnToHomeAltitudeChangeValid; +} + +- (void)updateReturnHomeHeight:(NSInteger)newHeight onCompletion:(void (^)(DUXBetaReturnToHomeAltitudeChange))resultsBlock { + if (self.isNoviceMode) { + resultsBlock(DUXBetaReturnToHomeAltitudeChangeUnableToChangeInBeginnerMode); + return; + } + + [[DJISDKManager keyManager] setValue:@(newHeight) forKey:self.returnHomeHeightKey withCompletion:^(NSError * _Nullable error) { + DUXBetaReturnToHomeAltitudeChange returnValue = DUXBetaReturnToHomeAltitudeChangeUnknownError; + + if (error) { + [DUXBetaStateChangeBroadcaster send:[ReturnToHomeAltitudeListItemModelState setReturnToHomeAltitudeFailed:error]]; + } else { + [DUXBetaStateChangeBroadcaster send:[ReturnToHomeAltitudeListItemModelState returnHeightUpdated:newHeight]]; + [DUXBetaStateChangeBroadcaster send:[ReturnToHomeAltitudeListItemModelState setReturnToHomeAltitudeSuccess]]; + returnValue = DUXBetaReturnToHomeAltitudeChangeValid; + } + resultsBlock(returnValue); + }]; +} + +@end + +@implementation ReturnToHomeAltitudeListItemModelState ++ (instancetype)returnHeightUpdated:(NSInteger)newReturnHeight { + return [[self alloc] initWithKey:@"returnHeightUpdated" number:@(newReturnHeight)]; +} ++ (instancetype)setReturnToHomeAltitudeSuccess { + return [[self alloc] initWithKey:@"setReturnToHomeAltitudeSuccess" number:@(YES)]; +} ++ (instancetype)setReturnToHomeAltitudeFailed:(NSError*)error { + return [[self alloc] initWithKey:@"setReturnToHomeAltitudeFailed" object:error]; +} +@end diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaSDCardStatusListItemWidget.h b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaSDCardStatusListItemWidget.h new file mode 100644 index 0000000..7f0bf08 --- /dev/null +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaSDCardStatusListItemWidget.h @@ -0,0 +1,77 @@ +// +// DUXBetaSDCardStatusListItemWidget.h +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// + +#import + +@class DUXBetaAlertViewAppearance; + +NS_ASSUME_NONNULL_BEGIN + +/** + * Widget for display in the SystemStatusList which shows the current status/remaining storage on the SD Card in the aircraft + * and also allows formatting that SD Card. + * + * The widget has 3 potential alerts which may show. Each can be configured by setting the appropriate setting + * properties of the appropriate DUXBetaAlertViewAppearance object below. + */ +@interface DUXBetaSDCardStatusListItemWidget : DUXBetaListItemLabelButtonWidget + +/// AlertViewAppearance properties for alert confirm formatting the SD card +@property (nonatomic, strong) DUXBetaAlertViewAppearance *sdCardConfirmFormatAlertAppearance; +/// AlertViewAppearance properties for alert when the SD card has been formatted successfully +@property (nonatomic, strong) DUXBetaAlertViewAppearance *sdCardFormattingSuccessAlertAppearance; +/// AlertViewAppearance properties for alert when there was an error formattng the SD card +@property (nonatomic, strong) DUXBetaAlertViewAppearance *sdCardFormattingErrorAlertAppearance; + +@end + +/** + * SDCardStatusListWidgetModelState contains the model hooks for SDCardStatusListWidgetModelState + * It inherits from ListItemTitleModelState and adds: + * + * Key: sdCardStatusCapacityChanged Type: NSNumber - Sends the remaining free storage on the aircraft SD card when + * the values changes (due to photos or video recording). + * + * Key sdCardStateUpdated Type NSNumber - Sends the DJICameraSDCardOperationState for the SD card + * when it changes. +*/ +@interface SDCardStatusListWidgetModelState : ListItemTitleModelState + ++ (instancetype)sdCardStatusCapacityChanged:(NSInteger)freeStorageChanged; ++ (instancetype)sdCardStateUpdated:(NSNumber *)operationState; + +@end + +/** + * SDCardStatusListWidgetUIState contains the model hooks for DUXBetaSDCardRemainingCapacityListItemWidget + * It inherits from ListItemLabelButtonUIState and adds no hooks +*/ +@interface SDCardStatusListWidgetUIState : ListItemLabelButtonUIState + +@end + +NS_ASSUME_NONNULL_END diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaSDCardStatusListItemWidget.m b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaSDCardStatusListItemWidget.m new file mode 100644 index 0000000..0ddd8a1 --- /dev/null +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaSDCardStatusListItemWidget.m @@ -0,0 +1,401 @@ +// +// DUXBetaSDCardStatusListItemWidget.m +// DJIUXSDKWidgets +// +// MIT License +// +// Copyright © 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. +// + +#import "DUXBetaSDCardStatusListItemWidget.h" +#import "DUXBetaSDCardStatusListItemWidgetModel.h" +#import +@import DJIUXSDKCore; + +/** + * DUXBetaInternalSDCardFormattingStage is internal to SUXSDCardRemainingCapacityListItemWidget. + * It is used to track the current state of formatting an SD card to allow notification when + * an error occurs or successful completion is accomplished. + */ +NS_ENUM(NSInteger, DUXBetaInternalSDCardFormattingStage) { + DUXBetaInternalSDCardFormattingNO = 0, + DUXBetaInternalSDCardFormattingYES, + DUXBetaInternalSDCardFormattingERROR +}; + +@interface DUXBetaSDCardStatusListItemWidget () +@property (nonatomic, strong) DUXBetaSDCardStatusListItemWidgetModel *widgetModel; +@property (nonatomic, assign) enum DUXBetaInternalSDCardFormattingStage isFormatting; +@property (nonatomic, assign) enum DUXBetaInternalSDCardFormattingStage stillFormatting; +@end + +@implementation DUXBetaSDCardStatusListItemWidget + +- (instancetype)init { + if (self = [super init:DUXBetaListItemLabelAndButton]) { + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + if (self = [super init:DUXBetaListItemLabelAndButton]) { + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + self.minWidgetSize = CGSizeMake(820.0, 64.0); + + _widgetModel = [DUXBetaSDCardStatusListItemWidgetModel new]; + [_widgetModel setup]; + + // Load standard appearances for the 3 alerts we may show so they can be customized before use. + [self loadCustomAlertsAppearance]; + + // Do any additional setup after loading the view. + [super setButtonTitle:NSLocalizedString(@"Format", @"Format")]; + [super setTitle:NSLocalizedString(@"SD Card Storage", @"DUXSDCardStatusListItemWidget title") andIconName:@"SystemStatusStorageSDCard"]; + + __weak DUXBetaSDCardStatusListItemWidget *weakSelf = self; + [self setButtonAction:^(DUXBetaSDCardStatusListItemWidget* senderWidget) { + __strong DUXBetaSDCardStatusListItemWidget *strongSelf = weakSelf; + [strongSelf displayFormatSDCardDialog]; + }]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + BindRKVOModel(self.widgetModel, @selector(productConnected), isProductConnected); + + BindRKVOModel(self.widgetModel, @selector(storageUpdated), freeStorageInMB); + BindRKVOModel(self.widgetModel, @selector(sdOperationStateUpdated), sdOperationState); + BindRKVOModel(self.widgetModel, @selector(sdCardFormatErrorUpdated), sdCardFormatError); + BindRKVOModel(self, @selector(formattingStatusChanged), isFormatting); +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + UnBindRKVOModel(self.widgetModel); +} + +- (void)dealloc { + [_widgetModel cleanup]; +} + +- (void)loadCustomAlertsAppearance { + self.sdCardConfirmFormatAlertAppearance = [DUXBetaAlertView systemAlertAppearance]; + self.sdCardConfirmFormatAlertAppearance.heightScale = DUXBetaAlertViewHeightScaleSmall; + self.sdCardConfirmFormatAlertAppearance.imageTintColor = [UIColor duxbeta_warningColor]; + + self.sdCardFormattingSuccessAlertAppearance = [DUXBetaAlertView systemAlertAppearance]; + self.sdCardFormattingSuccessAlertAppearance.heightScale = DUXBetaAlertViewHeightScaleSmall; + self.sdCardFormattingSuccessAlertAppearance.imageTintColor = [UIColor duxbeta_successColor]; + + self.sdCardFormattingErrorAlertAppearance = [DUXBetaAlertView systemAlertAppearance]; + self.sdCardFormattingErrorAlertAppearance.heightScale = DUXBetaAlertViewHeightScaleSmall; + self.sdCardFormattingErrorAlertAppearance.imageTintColor = [UIColor duxbeta_dangerColor]; +} + +- (void)productConnected { + [SDCardStatusListWidgetModelState productConnected:self.widgetModel.isProductConnected]; + [self updatePresentationDisplay]; +} + +- (void)storageUpdated { + [SDCardStatusListWidgetModelState sdCardStatusCapacityChanged:self.widgetModel.freeStorageInMB]; + [self updatePresentationDisplay]; +} + +- (void)sdOperationStateUpdated { + [SDCardStatusListWidgetModelState sdCardStateUpdated:@(self.widgetModel.sdOperationState)]; + [self updatePresentationDisplay]; +} + +- (void)sdCardFormatErrorUpdated { + if (self.widgetModel.sdCardFormatError) { + _isFormatting = DUXBetaInternalSDCardFormattingERROR; + } + [self updatePresentationDisplay]; +} + +- (void)updatePresentationDisplay { + NSString *displayString = @""; + BOOL enableFormat = YES; + BOOL isDisabled = NO; + BOOL isNormal = NO; + BOOL formattingJustEnded = NO; + + if (!_widgetModel.isProductConnected) { + displayString = NSLocalizedString(@"N/A", @"N/A"); + enableFormat = NO; + isDisabled = YES; + if (_isFormatting == DUXBetaInternalSDCardFormattingYES) { + _stillFormatting = DUXBetaInternalSDCardFormattingERROR; + } + } else if (_widgetModel.isSDCardInserted && (_widgetModel.sdOperationState == DJICameraSDCardOperationStateNormal)) { + isNormal = YES; + if (_widgetModel.freeStorageInMB > 1024) { + displayString = [NSString stringWithFormat:@"%.02f GB", (_widgetModel.freeStorageInMB / 1024.0f)]; + } else { + displayString = [NSString stringWithFormat:@"%ld MB", (long) _widgetModel.freeStorageInMB]; + } + if (_stillFormatting && (_widgetModel.sdOperationState == DJICameraSDCardOperationStateNormal)) { + formattingJustEnded = YES; + } + } else { + if ((_widgetModel.isSDCardInserted == NO) || (_widgetModel.sdOperationState == DJICameraSDCardOperationStateNotInserted)) { + displayString = NSLocalizedString(@"Not Inserted", @"Not Inserted"); + enableFormat = NO; + } else { + switch (_widgetModel.sdOperationState) { + case DJICameraSDCardOperationStateNormal: + // This state can only be reached if isSDCardInserted == NO, but state is normal + isNormal = YES; + if (_widgetModel.freeStorageInMB > 1024) { + displayString = [NSString stringWithFormat:@"%.02f GB", (_widgetModel.freeStorageInMB / 1024.0f)]; + } else { + displayString = [NSString stringWithFormat:@"%ld MB", (long) _widgetModel.freeStorageInMB]; + } + break; + case DJICameraSDCardOperationStateNotInserted: + // This can only be reached if isSDCardInserted is YES, but the sdOperationState is not inserted. Should not be possible + displayString = NSLocalizedString(@"Not Inserted", @"Not Inserted"); + enableFormat = NO; + if (_isFormatting == DUXBetaInternalSDCardFormattingYES) { + _stillFormatting = DUXBetaInternalSDCardFormattingERROR; + } + break; + case DJICameraSDCardOperationStateInvalid: + displayString = NSLocalizedString(@"Invalid", @"Invalid"); + enableFormat = NO; + if (_isFormatting == DUXBetaInternalSDCardFormattingYES) { + _stillFormatting = DUXBetaInternalSDCardFormattingERROR; + } + break; + case DJICameraSDCardOperationStateReadOnly: + displayString = NSLocalizedString(@"Write Protected", @"Write Protected"); + enableFormat = NO; + if (_isFormatting == DUXBetaInternalSDCardFormattingYES) { + _stillFormatting = DUXBetaInternalSDCardFormattingERROR; + } + break; + case DJICameraSDCardOperationFormatNeeded: + displayString = NSLocalizedString(@"Format Required", @"Format Required"); + if (_isFormatting == DUXBetaInternalSDCardFormattingYES) { + _stillFormatting = DUXBetaInternalSDCardFormattingERROR; + } + break; + case DJICameraSDCardOperationStateFormatting: + displayString = NSLocalizedString(@"Formatting…", @"Formatting…"); + enableFormat = NO; + _stillFormatting = YES; + break; + case DJICameraSDCardOperationStateInvalidFileSystem: + displayString = NSLocalizedString(@"Format Required", @"Format Required"); + if (_isFormatting == DUXBetaInternalSDCardFormattingYES) { + _stillFormatting = DUXBetaInternalSDCardFormattingERROR; + } + break; + case DJICameraSDCardOperationStateBusy: + displayString = NSLocalizedString(@"Busy", @"Busy"); + enableFormat = NO; + break; + case DJICameraSDCardOperationStateFull: + displayString = NSLocalizedString(@"Full", @"Full"); + break; + case DJICameraSDCardOperationStateSlow: + displayString = NSLocalizedString(@"Slow", @"Slow"); + break; + case DJICameraSDCardOperationStateUnknownError: + displayString = NSLocalizedString(@"Unknown Error", @"Unknown Error"); + enableFormat = NO; + if (_isFormatting == DUXBetaInternalSDCardFormattingYES) { + _stillFormatting = DUXBetaInternalSDCardFormattingERROR; + } + break; + case DJICameraSDCardOperationStateNoRemainFileIndices: + displayString = NSLocalizedString(@"No file indices", @"No file indices"); + break; + case DJICameraSDCardOperationStateInitializing: + displayString = NSLocalizedString(@"Initializing", @"Initializing"); + enableFormat = NO; + break; + case DJICameraSDCardOperationStateFormatRecommended: + displayString = NSLocalizedString(@"Formatting Recommended", @"Formatting Recommended"); + break; + case DJICameraSDCardOperationStateRecoveringFiles: + displayString = NSLocalizedString(@"Repairing Video Files", @"Repairing Video Files"); + enableFormat = NO; + break; + case DJICameraSDCardOperationStateWritingSlowly: + displayString = NSLocalizedString(@"Slow Write Speed", @"Slow Write Speed"); + break; + default: + displayString = NSLocalizedString(@"Unknown Error", @"Unknown Error"); + enableFormat = NO; + if (_isFormatting == DUXBetaInternalSDCardFormattingYES) { + _stillFormatting = DUXBetaInternalSDCardFormattingERROR; + } + break; + } + } + } + + // Final step, set display text, colors and button enable state + self.buttonEnabled = enableFormat; + // These need to look at the final customizaton colors + [self.displayTextLabel setText:displayString]; + UIColor *textColor = [UIColor duxbeta_warningColor]; + if (isNormal) { + textColor = [UIColor duxbeta_whiteColor]; + } else if (isDisabled) { + textColor = [UIColor duxbeta_disabledGrayColor]; + } + [self.displayTextLabel setTextColor:textColor]; + + if ((_isFormatting == DUXBetaInternalSDCardFormattingYES) && ((_stillFormatting != DUXBetaInternalSDCardFormattingYES) || formattingJustEnded)) { + // Set the isFormatting flag. The KVO will then decide if the error dialog needs to be shown. + self.isFormatting = DUXBetaInternalSDCardFormattingNO; + } +} + +- (void)formattingStatusChanged { + if (_isFormatting == DUXBetaInternalSDCardFormattingNO) { + // Formatting has ended if _stillFormatting is not NO because we only set it to DUXBetaInternalSDCardFormattingNO in here when processing is done. + if (_stillFormatting != DUXBetaInternalSDCardFormattingNO) { + BOOL success = (_stillFormatting == DUXBetaInternalSDCardFormattingYES) && (self.widgetModel.sdCardFormatError == NO); + _isFormatting = DUXBetaInternalSDCardFormattingNO; + [self displayFormatCompletedDialog:success]; + _stillFormatting = DUXBetaInternalSDCardFormattingNO; + } + } else if (_isFormatting == DUXBetaInternalSDCardFormattingYES) { + // Just started formatting, only set the flag to know if we think it is still happening. + _stillFormatting = DUXBetaInternalSDCardFormattingYES; + } else { + // Either done normally or errored + BOOL success = !((_stillFormatting == DUXBetaInternalSDCardFormattingERROR) || (self.widgetModel.sdCardFormatError == YES)); + [self displayFormatCompletedDialog:success]; + } +} + +- (void)displayFormatSDCardDialog { + __weak DUXBetaSDCardStatusListItemWidget *weakSelf = self; + + DUXBetaAlertView *alert = [DUXBetaAlertView warningAlertWithTitle:NSLocalizedString(@"SD Card Format", @"SD Card Format") + message:NSLocalizedString(@"Are you sure you want to format the SD card?", "SD Formatting Workflow Action Description") + heightScale:DUXBetaAlertViewHeightScaleSmall]; + DUXBetaAlertAction *defaultAction = [DUXBetaAlertAction actionWithActionTitle:NSLocalizedString(@"OK", @"OK") + style:UIAlertActionStyleDefault + actionType:DUXBetaAlertActionTypeClosure + target:nil + selector:nil + completionAction:^{ + __strong DUXBetaSDCardStatusListItemWidget *strongSelf = weakSelf; + + [DUXBetaStateChangeBroadcaster send:[SDCardStatusListWidgetUIState dialogActionConfirm:@"sdCardFormatDialogConfirm"]]; + + strongSelf.isFormatting = YES; + [self.widgetModel formatSDCard]; + }]; + + DUXBetaAlertAction *cancelAction = [DUXBetaAlertAction actionWithActionTitle:NSLocalizedString(@"Cancel", @"Cancel") + style:UIAlertActionStyleCancel + actionType:DUXBetaAlertActionTypeClosure + target:nil + selector:nil + completionAction:^{ + [DUXBetaStateChangeBroadcaster send:[SDCardStatusListWidgetUIState dialogActionDismiss:@"sdCardFormatDialogConfirmCancel"]]; + __strong DUXBetaSDCardStatusListItemWidget *strongSelf = weakSelf; + strongSelf.isFormatting = NO; + }]; + [alert add:cancelAction]; + [alert add:defaultAction]; + alert.appearance = self.sdCardConfirmFormatAlertAppearance; + [alert showWithCompletion: ^{ + [DUXBetaStateChangeBroadcaster send:[SDCardStatusListWidgetUIState dialogDisplayed:@"sdCardFormatDialog"]]; + }]; +} + +- (void)displayFormatCompletedDialog:(BOOL)success { + DUXBetaAlertView *alert; + DUXBetaAlertAction *defaultAction; + void (^dismissCallback)(void); + if (success) { + alert = [DUXBetaAlertView successAlertWithTitle:NSLocalizedString(@"SD Card Format", @"SD Card Format") + message:NSLocalizedString(@"SD card formatting completed.", "Camera Formatting Workflow Success Description") + heightScale:DUXBetaAlertViewHeightScaleSmall]; + dismissCallback = ^{ + [DUXBetaStateChangeBroadcaster send:[SDCardStatusListWidgetUIState dialogActionConfirm:@"sdCardFormatDialogResultSuccess"]]; + }; + defaultAction = [DUXBetaAlertAction actionWithActionTitle:NSLocalizedString(@"OK", @"OK") + style:UIAlertActionStyleCancel + actionType:DUXBetaAlertActionTypeClosure + target:nil + selector:nil + completionAction:dismissCallback]; + + alert.appearance = self.sdCardFormattingSuccessAlertAppearance; + } else { + alert = [DUXBetaAlertView failAlertWithTitle:NSLocalizedString(@"SD Card Format", @"SD Card Format") + message:[NSString stringWithFormat:NSLocalizedString(@"Error formatting SD card.%@", "Camera Formatting Workflow Failure Description"), self.widgetModel.formatErrorDescription] + heightScale:DUXBetaAlertViewHeightScaleSmall]; + + dismissCallback = ^{ + [DUXBetaStateChangeBroadcaster send:[SDCardStatusListWidgetUIState dialogActionConfirm:@"sdCardFormatDialogResultFailed"]]; + }; + defaultAction = [DUXBetaAlertAction actionWithActionTitle:NSLocalizedString(@"OK", @"OK") + style:UIAlertActionStyleCancel + actionType:DUXBetaAlertActionTypeClosure + target:nil + selector:nil + completionAction:dismissCallback]; + + alert.appearance = self.sdCardFormattingErrorAlertAppearance; + } + + alert.dissmissCompletion = dismissCallback; + [alert add:defaultAction]; + [alert showWithCompletion: ^{ + [DUXBetaStateChangeBroadcaster send:[SDCardStatusListWidgetUIState dialogDisplayed:@"sdCardFormatDialogResult"]]; + }]; + + self.widgetModel.sdCardFormatError = NO; +} + +@end + +@implementation SDCardStatusListWidgetModelState + ++ (instancetype)sdCardStatusCapacityChanged:(NSInteger)freeStorageChanged { + return [[SDCardStatusListWidgetModelState alloc] initWithKey:@"sdCardStatusCapacityChanged" number:@(freeStorageChanged)]; +} + ++ (instancetype)sdCardStateUpdated:(NSNumber *)operationState { + return [[SDCardStatusListWidgetModelState alloc] initWithKey:@"sdCardStateUpdated" value:operationState]; +} + +@end + +@implementation SDCardStatusListWidgetUIState + +@end diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXSDCardRemainingCapacityListItemWidgetModel.h b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaSDCardStatusListItemWidgetModel.h similarity index 87% rename from DJIUXSDKWidgets/SystemStatusListWidgets/DUXSDCardRemainingCapacityListItemWidgetModel.h rename to DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaSDCardStatusListItemWidgetModel.h index a1354af..b9a2182 100644 --- a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXSDCardRemainingCapacityListItemWidgetModel.h +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaSDCardStatusListItemWidgetModel.h @@ -1,9 +1,11 @@ // -// DUXSDCardRemainingCapacityListWidgetModel.h +// DUXBetaSDCardStatusListItemWidgetModel.h // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -31,7 +33,7 @@ NS_ASSUME_NONNULL_BEGIN * Model for the SystemStatusList widget to show the current status/free space on the RC card for a camera. */ -@interface DUXSDCardRemainingCapacityListItemWidgetModel : DUXBetaBaseWidgetModel +@interface DUXBetaSDCardStatusListItemWidgetModel : DUXBetaBaseWidgetModel /** * The operationState for the SD Card installed in the drone SD card slot */ @@ -57,6 +59,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, readwrite) BOOL sdCardFormatError; +/** + * The error description value send by the SDK if formatting an SD Card had an immediate error. +*/ +@property (nonatomic, readonly) NSString *formatErrorDescription; + - (void)formatSDCard; diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXSDCardRemainingCapacityListItemWidgetModel.m b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaSDCardStatusListItemWidgetModel.m similarity index 81% rename from DJIUXSDKWidgets/SystemStatusListWidgets/DUXSDCardRemainingCapacityListItemWidgetModel.m rename to DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaSDCardStatusListItemWidgetModel.m index 2f0f3de..be35c89 100644 --- a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXSDCardRemainingCapacityListItemWidgetModel.m +++ b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXBetaSDCardStatusListItemWidgetModel.m @@ -1,9 +1,11 @@ // -// DUXSDCardRemainingCapacityListWidgetModel.m +// DUXBetaSDCardStatusListItemWidgetModel.m // DJIUXSDKWidgets // +// MIT License +// // Copyright © 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 @@ -23,16 +25,17 @@ // SOFTWARE. // -#import "DUXSDCardRemainingCapacityListItemWidgetModel.h" +#import "DUXBetaSDCardStatusListItemWidgetModel.h" @import DJIUXSDKCore; -@interface DUXSDCardRemainingCapacityListItemWidgetModel () +@interface DUXBetaSDCardStatusListItemWidgetModel () @property (nonatomic, readwrite) DJICameraSDCardOperationState sdOperationState; @property (nonatomic, readwrite) NSInteger freeStorageInMB; @property (nonatomic, readwrite) BOOL isSDCardInserted; +@property (nonatomic, readwrite) NSError *formatError; @end -@implementation DUXSDCardRemainingCapacityListItemWidgetModel +@implementation DUXBetaSDCardStatusListItemWidgetModel - (void)inSetup { [self bindKeys]; @@ -54,12 +57,20 @@ - (void)setPreferredCameraIndex:(NSUInteger)preferredCameraIndex { } } +- (NSString *)formatErrorDescription { + if (self.formatError != nil) { + return [NSString stringWithFormat:@"\n%@", self.formatError.localizedDescription]; + } + + return @""; +} + - (void)formatSDCard { //DJICameraParamFormatSDCard //DJICameraParamFormatStorage - __weak DUXSDCardRemainingCapacityListItemWidgetModel *weakSelf = self; + __weak DUXBetaSDCardStatusListItemWidgetModel *weakSelf = self; DJICameraKey *cameraKey = [DJICameraKey keyWithIndex:_preferredCameraIndex andParam:DJICameraParamFormatStorage]; [[DJISDKManager keyManager] performActionForKey:cameraKey @@ -67,8 +78,9 @@ - (void)formatSDCard { andCompletion:^(BOOL finished, DJIKeyedValue * _Nullable response, NSError * _Nullable error) { // This completion is only for the performAction method, not for the results of the format action. It will // not report when formating is completed, only if there are paraeter errors with this call. - __strong DUXSDCardRemainingCapacityListItemWidgetModel *strongSelf = weakSelf; + __strong DUXBetaSDCardStatusListItemWidgetModel *strongSelf = weakSelf; + strongSelf.formatError = error; if (error != nil) { strongSelf.sdCardFormatError = YES; } else { diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXMaxAltitudeListItemWidget.h b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXMaxAltitudeListItemWidget.h deleted file mode 100644 index 048605e..0000000 --- a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXMaxAltitudeListItemWidget.h +++ /dev/null @@ -1,53 +0,0 @@ -// -// DUXMaxAltitudeListItemWidget.h -// DJIUXSDKWidgets -// -// Copyright © 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. -// - -#import -#import "DUXMaxAltitudeListItemWidgetModel.h" - -NS_ASSUME_NONNULL_BEGIN -/** -* Widget for display in the SystemStatusList which shows the current max altitude setting for the aircraft -* and also allows editing the height within the limits shown in the hint text. -*/ -@interface DUXMaxAltitudeListItemWidget : DUXListItemEditTextButtonWidget - -@end - -/** - * MaxAltitudeListItemUIState contains the hooks for the MaxAltitude list widget UI. - * It inherits all UI hooks in ListItemEditTextUIState and adds: - * - * Key: invalidHeightRejected Type: NSNumber - The entered height in meters which was rejected during validation in the max altitude model. - * - * Key: validHeightEntered Type: NSNumber - The entered maximum altitude after being validated by the max altitude model. - */ -@interface MaxAltitudeListItemUIState : ListItemEditTextUIState - -+ (instancetype)invalidHeightRejected:(NSInteger)invalidHeightInMeters; -+ (instancetype)validHeightEntered:(NSInteger)invalidHeightInMeters; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXMaxAltitudeListItemWidget.m b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXMaxAltitudeListItemWidget.m deleted file mode 100644 index c46e460..0000000 --- a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXMaxAltitudeListItemWidget.m +++ /dev/null @@ -1,258 +0,0 @@ -// -// DUXMaxAltitudeListItemWidget.m -// DJIUXSDKWidgets -// -// Copyright © 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. -// - -#import "DUXMaxAltitudeListItemWidget.h" -#import "DUXMaxAltitudeListItemWidgetModel.h" -#import "NSObject+DUXBetaRKVOExtension.h" -@import DJIUXSDKCore; - -@interface DUXMaxAltitudeListItemWidget() - -@property (nonatomic) double fieldvalue; -@property (nonatomic, strong) DUXMaxAltitudeListItemWidgetModel *widgetModel; - -@end - -@implementation DUXMaxAltitudeListItemWidget - -- (instancetype)init { - if (self = [super init:DUXListItemOnlyEdit]) { - } - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)coder { - if (self = [super init:DUXListItemOnlyEdit]) { - } - return self; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - // Do any additional setup after loading the view. - self.view.translatesAutoresizingMaskIntoConstraints = NO; - self.widgetModel = [[DUXMaxAltitudeListItemWidgetModel alloc] init]; - [self.widgetModel setup]; - - [self setTitle:NSLocalizedString(@"Max Flight Altitude", @"System Status Checklist Item Title") andIconName:@"SystemStatusMaxAltitudeLimit"]; - [self setupUI]; -} - -- (void)viewWillAppear:(BOOL)animated { - [super viewWillAppear:animated]; - - BindRKVOModel(self.widgetModel, @selector(metaDataChanged), isNoviceMode, rangeValue); - BindRKVOModel(self.widgetModel, @selector(maxAltitudeChanged), maxAltitude); - BindRKVOModel(self.widgetModel, @selector(productConnectedChanged), isProductConnected); -} - -- (void)viewWillDisappear:(BOOL)animated { - [super viewWillDisappear:animated]; - - UnBindRKVOModel(self); -} - -- (void)dealloc { - [_widgetModel cleanup]; -} - -- (void)setupUI { - if (self.widgetModel == nil) { - return; - } - - if (self.trailingTitleGuide == nil) { - [super setupUI]; - if (self.trailingTitleGuide == nil) { - return; - } - } - - [self setHintText:NSLocalizedString(@"20-500m", @"20-500m")]; - - __weak DUXMaxAltitudeListItemWidget *weakSelf = self; - [self setTextChangedBlock: ^(NSString *newText) { - NSInteger newHeight = [newText intValue]; - if (newHeight > 0) { - __strong DUXMaxAltitudeListItemWidget *strongSelf = weakSelf; - if (strongSelf) { - [strongSelf handleMaxAltitudeChangeRequest:newHeight]; - } - } - }]; -} - -- (void)handleMaxAltitudeChangeRequest:(NSInteger) newHeight { - if (!self.widgetModel.isNoviceMode) { - // Quick sanity check - __weak DUXMaxAltitudeListItemWidget *weakSelf = self; - DUXMaxAltitudeChange heightValidity = [self.widgetModel validateNewHeight:newHeight]; - switch (heightValidity) { - case DUXMaxAltitudeChangeMaxAltitudeValid: - case DUXMaxAltitudeChangeAboveReturnHomeMaxAltitude: - case DUXMaxAltitudeChangeAboveWarningHeightLimit: - case DUXMaxAltitudeChangeAboveWarningHeightLimitAndReturnHomeAltitude: - [DUXStateChangeBroadcaster send:[MaxAltitudeListItemUIState validHeightEntered:newHeight]]; - break; - - default: - [DUXStateChangeBroadcaster send:[MaxAltitudeListItemUIState invalidHeightRejected:newHeight]]; - break; - } - - if ((heightValidity == DUXMaxAltitudeChangeMaxAltitudeValid) || (heightValidity == DUXMaxAltitudeChangeAboveReturnHomeMaxAltitude)) { - [self.widgetModel updateMaxHeight:newHeight onCompletion:^(DUXMaxAltitudeChange result) { - if ((result == DUXMaxAltitudeChangeUnableToChangeMaxAltitudeInBeginnerMode) || - (result == DUXMaxAltitudeChangeUnknownError) || - (result == DUXMaxAltitudeChangeUnableToChangeReturnHomeAltitude)) { - [self presentFailed]; - } - }]; - } else if ((heightValidity == DUXMaxAltitudeChangeAboveWarningHeightLimit) || (heightValidity == DUXMaxAltitudeChangeAboveWarningHeightLimitAndReturnHomeAltitude)) { - NSString *msg = NSLocalizedString(@"You Are Altering The Max Altitude Setting. This may violate local laws or regulations (for instance a 400ft (122m) height limit is set by the FAA in the USA). You are solely responsible and liable for the operation of the aircraft after altering these settings. DJI and its affiliates shall not be liable for any damages, whether in contract, tort (including negligence), or any other legal or equitable theory.", @"Warning when setting max height above legal limit"); - - UIAlertController* alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Flight Warning", @"Flight Warning") - message:msg - preferredStyle:UIAlertControllerStyleAlert]; - - UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK") style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - __strong DUXMaxAltitudeListItemWidget *strongSelf = weakSelf; - [DUXStateChangeBroadcaster send:[MaxAltitudeListItemUIState dialogActionConfirm:@"Confirmed Max Altitude over 400ft warning"]]; - if (heightValidity == DUXMaxAltitudeChangeAboveWarningHeightLimitAndReturnHomeAltitude) { - [strongSelf presentResetReturnHomeConfirmation:newHeight]; - } else { - [strongSelf.widgetModel updateMaxHeight:newHeight onCompletion:^(DUXMaxAltitudeChange result) { - if ((result == DUXMaxAltitudeChangeUnableToChangeMaxAltitudeInBeginnerMode) || - (result == DUXMaxAltitudeChangeUnknownError) || - (result == DUXMaxAltitudeChangeUnableToChangeReturnHomeAltitude)) { - [self presentFailed]; - } - }]; - } - }]; - - UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel") style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - [DUXStateChangeBroadcaster send:[MaxAltitudeListItemUIState dialogActionDismiss:@"Cancelled change from Max Altitude over 400ft warning"]]; - }]; - - [alert addAction:cancelAction]; - [alert addAction:defaultAction]; - [self presentViewController:alert animated:YES completion:nil]; - [DUXStateChangeBroadcaster send:[MaxAltitudeListItemUIState dialogDisplayed:@"Max Altitude over 400ft warning"]]; - } - } -} - -- (void)presentResetReturnHomeConfirmation:(NSInteger) newHeight { - __weak DUXMaxAltitudeListItemWidget *weakSelf = self; - - NSString *msg = NSLocalizedString(@"Failsafe RTH altitude cannot exceed maximum flight altitude. Failsafe RTH altitude will be set to entered maximum altitude value", @"Warning when setting max height above RTH limit"); - - UIAlertController* alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Flight Warning", @"Flight Warning") - message:msg - preferredStyle:UIAlertControllerStyleAlert]; - - UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK") style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - __strong DUXMaxAltitudeListItemWidget *strongSelf = weakSelf; - [DUXStateChangeBroadcaster send:[MaxAltitudeListItemUIState dialogActionConfirm:@"Confirmed must change return home altitude"]]; - [strongSelf.widgetModel updateMaxHeight:newHeight onCompletion:^(DUXMaxAltitudeChange result) { - if ((result == DUXMaxAltitudeChangeUnableToChangeMaxAltitudeInBeginnerMode) || - (result == DUXMaxAltitudeChangeUnknownError) || - (result == DUXMaxAltitudeChangeUnableToChangeReturnHomeAltitude)) { - [self presentFailed]; - } - }]; - }]; - - UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel") style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - [DUXStateChangeBroadcaster send:[MaxAltitudeListItemUIState dialogActionDismiss:@"Cancelled must change return home altitude"]]; - }]; - - [alert addAction:cancelAction]; - [alert addAction:defaultAction]; - [self presentViewController:alert animated:YES completion:nil]; - [DUXStateChangeBroadcaster send:[MaxAltitudeListItemUIState dialogDisplayed:@"Must change return home altitude"]]; -} - -- (void)presentFailed { - NSString *msg = NSLocalizedString(@"Failed to set maximum altitude", @"Failed to set maximum altitude"); - UIAlertController* alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Flight Warning", @"Flight Warning") - message:msg - preferredStyle:UIAlertControllerStyleAlert]; - - UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK") style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - [DUXStateChangeBroadcaster send:[MaxAltitudeListItemUIState dialogDisplayed:@"Max Altitude change failed acknowledged."]]; - }]; - - [alert addAction:defaultAction]; - [self presentViewController:alert animated:YES completion:nil]; - [DUXStateChangeBroadcaster send:[MaxAltitudeListItemUIState dialogDisplayed:@"Max Altitude change failed."]]; -} - -- (void)metaDataChanged { - if (self.widgetModel.rangeValue) { - DJIParamCapabilityMinMax* range = (DJIParamCapabilityMinMax*)self.widgetModel.rangeValue; - - NSInteger min = [range.min intValue]; - NSInteger max = [range.max intValue]; - NSString *hintRange = [NSString stringWithFormat:@"%ld-%ldm", min, max]; - [self setHintText:hintRange]; - [self setEditFieldValuesMin:min maxValue:max]; - } - - self.enableEditField = (self.widgetModel.isNoviceMode == NO) && self.widgetModel.isProductConnected; - } - -- (void)productConnectedChanged { - self.enableEditField = (self.widgetModel.isNoviceMode == NO) && self.widgetModel.isProductConnected; - [MaxAltitudeListItemModelState productConnected:self.widgetModel.isProductConnected]; - // If there was an existing connection, we need to reload the altitude value in case it was in the middle of being - // changec during the disconnect. - [self maxAltitudeChanged]; -} - -- (void)maxAltitudeChanged { - NSString *heightString = [NSString stringWithFormat:@"%ld", (long)self.widgetModel.maxAltitude]; - [self setEditText:heightString]; -} -@end - -@implementation MaxAltitudeListItemUIState - -+ (instancetype)invalidHeightRejected:(NSInteger)invalidHeightInMeters { - return [[self alloc] initWithKey:@"invalidHeightRejected" number:@(invalidHeightInMeters)]; -} - -+ (instancetype)validHeightEntered:(NSInteger)invalidHeightInMeters { - return [[self alloc] initWithKey:@"validHeightEntered" number:@(invalidHeightInMeters)]; -} - -@end - diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXSDCardRemainingCapacityListItemWidget.h b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXSDCardRemainingCapacityListItemWidget.h deleted file mode 100644 index 944d1d3..0000000 --- a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXSDCardRemainingCapacityListItemWidget.h +++ /dev/null @@ -1,68 +0,0 @@ -// -// DUXSDCardRemainingCapacityListItemWidget.h -// DJIUXSDKWidgets -// -// Copyright © 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. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * Widget for display in the SystemStatusList which shows the current status/remaining storage on the SD Card in the aircraft - * and also allows formatting that SD Card. - */ -@interface DUXSDCardRemainingCapacityListItemWidget : DUXListItemLabelButtonWidget - -@end - -/** - * SDCardRemainingCapacityListWidgetModelState contains the model hooks for DUXSDCardRemainingCapacityListItemWidgetModel - * It inherits from ListItemTitleModelState and adds: - * - * Key: sdCardRemainingCapacityChanged Type: NSNumber - Sends the remaining free storage on the aircraft SD card when - * the values changes (due to photos or video recording). - * - * Key sdCardRemainingCapacityCardInserted Type NSNumber - Sends a boolean value as an NSInteger when an SD card is - * inserted or removed. YES indicates insertion, NO indicates removal. - * Beware of crows removing SD cards mid-flight - * - * Key sdCardRemainingCapacityOperatingStatusChanged Type NSNumber - Sends the DJICameraSDCardOperationState for the SD card - * when it changes. -*/ -@interface SDCardRemainingCapacityListWidgetModelState : ListItemTitleModelState - -+ (instancetype)sdCardRemainingCapacityChanged:(NSInteger)freeStorageChanged; -+ (instancetype)sdCardRemainingCapacityCardInserted:(BOOL)cardInserted; -+ (instancetype)sdCardRemainingCapacityOperatingStatusChanged:(NSNumber *)operationState; - -@end - -/** - * SDCardRemainingCapacityListWidgetUIState contains the model hooks for DUXSDCardRemainingCapacityListItemWidget - * It inherits from ListItemLabelButtonUIState and adds no hooks -*/ -@interface SDCardRemainingCapacityListWidgetUIState : ListItemLabelButtonUIState - -@end - -NS_ASSUME_NONNULL_END diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXSDCardRemainingCapacityListItemWidget.m b/DJIUXSDKWidgets/SystemStatusListWidgets/DUXSDCardRemainingCapacityListItemWidget.m deleted file mode 100644 index 9300564..0000000 --- a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXSDCardRemainingCapacityListItemWidget.m +++ /dev/null @@ -1,351 +0,0 @@ -// -// DUXSDCardRemainingCapacityListItemWidget.m -// DJIUXSDKWidgets -// -// Copyright © 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. -// - -#import "DUXSDCardRemainingCapacityListItemWidget.h" -#import "DUXSDCardRemainingCapacityListItemWidgetModel.h" -@import DJIUXSDKCore; - -/** - * DUXInternalSDCardFormattingStage is internal to SUXSDCardRemainingCapacityListItemWidget. - * It is used to track the current state of formatting an SD card to allow notification when - * an error occurs or successful completion is accomplished. - */ -NS_ENUM(NSInteger, DUXInternalSDCardFormattingStage) { - DUXInternalSDCardFormattingNO = 0, - DUXInternalSDCardFormattingYES, - DUXInternalSDCardFormattingERROR -}; - -@interface DUXSDCardRemainingCapacityListItemWidget () -@property (nonatomic, strong) DUXSDCardRemainingCapacityListItemWidgetModel *widgetModel; -@property (nonatomic, assign) enum DUXInternalSDCardFormattingStage isFormatting; -@end - -@implementation DUXSDCardRemainingCapacityListItemWidget - -- (instancetype)init { - if (self = [super init:DUXListItemLabelAndButton]) { - } - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)coder { - if (self = [super init:DUXListItemLabelAndButton]) { - } - return self; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - self.minWidgetSize = CGSizeMake(820.0, 64.0); - - _widgetModel = [DUXSDCardRemainingCapacityListItemWidgetModel new]; - [_widgetModel setup]; - - // Do any additional setup after loading the view. - [super setButtonTitle:NSLocalizedString(@"Format", @"Format")]; - [super setTitle:NSLocalizedString(@"SD Card Remaining Capacity", @"DUXSDCardRemainingCapacityListItemWidget title") andIconName:@"SystemStatusStorageSDCard"]; - - __weak DUXSDCardRemainingCapacityListItemWidget *weakSelf = self; - [self setButtonAction:^(DUXSDCardRemainingCapacityListItemWidget* senderWidget) { - __strong DUXSDCardRemainingCapacityListItemWidget *strongSelf = weakSelf; - [strongSelf displayFormatSDCardDialog]; - }]; -} - -- (void)viewWillAppear:(BOOL)animated { - [super viewWillAppear:animated]; - - BindRKVOModel(self.widgetModel, @selector(productConnected), isProductConnected); - - BindRKVOModel(self.widgetModel, @selector(storageUpdated), freeStorageInMB); - BindRKVOModel(self.widgetModel, @selector(sdCardInsertedUpdated), isSDCardInserted); - BindRKVOModel(self.widgetModel, @selector(sdOperationStateUpdated), sdOperationState); - BindRKVOModel(self.widgetModel, @selector(sdCardFormatErrorUpdated), sdCardFormatError); -} - -- (void)viewWillDisappear:(BOOL)animated { - [super viewWillDisappear:animated]; - UnBindRKVOModel(self.widgetModel); -} - -- (void)dealloc { - [_widgetModel cleanup]; -} - -- (void)productConnected { - [SDCardRemainingCapacityListWidgetModelState productConnected:self.widgetModel.isProductConnected]; - [self updatePresentationDisplay]; -} - -- (void)storageUpdated { - [SDCardRemainingCapacityListWidgetModelState sdCardRemainingCapacityChanged:self.widgetModel.freeStorageInMB]; - [self updatePresentationDisplay]; -} - -- (void)sdCardInsertedUpdated { - [SDCardRemainingCapacityListWidgetModelState sdCardRemainingCapacityCardInserted:self.widgetModel.isSDCardInserted]; - [self updatePresentationDisplay]; -} - -- (void)sdOperationStateUpdated { - [SDCardRemainingCapacityListWidgetModelState sdCardRemainingCapacityOperatingStatusChanged:@(self.widgetModel.sdOperationState)]; - [self updatePresentationDisplay]; -} - -- (void)sdCardFormatErrorUpdated { - _isFormatting = DUXInternalSDCardFormattingERROR; - [self updatePresentationDisplay]; -} - -- (void)updatePresentationDisplay { - NSString *displayString = @""; - BOOL enableFormat = YES; - BOOL isDisabled = NO; - BOOL isNormal = NO; - enum DUXInternalSDCardFormattingStage stillFormatting = DUXInternalSDCardFormattingNO; - - if (!_widgetModel.isProductConnected) { - displayString = NSLocalizedString(@"N/A", @"N/A"); - enableFormat = NO; - isDisabled = YES; - if (_isFormatting == DUXInternalSDCardFormattingYES) { - stillFormatting = DUXInternalSDCardFormattingERROR; - } - } else if (_widgetModel.isSDCardInserted && (_widgetModel.sdOperationState == DJICameraSDCardOperationStateNormal)) { - isNormal = YES; - if (_widgetModel.freeStorageInMB > 1024) { - displayString = [NSString stringWithFormat:@"%.02f GB", (_widgetModel.freeStorageInMB / 1024.0f)]; - } else { - displayString = [NSString stringWithFormat:@"%ld MB", (long) _widgetModel.freeStorageInMB]; - } - } else { - if ((_widgetModel.isSDCardInserted == NO) || (_widgetModel.sdOperationState == DJICameraSDCardOperationStateNotInserted)) { - displayString = NSLocalizedString(@"Not Inserted", @"Not Inserted"); - enableFormat = NO; - } else { - switch (_widgetModel.sdOperationState) { - case DJICameraSDCardOperationStateNormal: - // This state can only be reached if isSDCardInserted == NO, but state is normal - isNormal = YES; - if (_widgetModel.freeStorageInMB > 1024) { - displayString = [NSString stringWithFormat:@"%.02f GB", (_widgetModel.freeStorageInMB / 1024.0f)]; - } else { - displayString = [NSString stringWithFormat:@"%ld MB", (long) _widgetModel.freeStorageInMB]; - } - break; - case DJICameraSDCardOperationStateNotInserted: - // This can only be reached if isSDCardInserted is YES, but the sdOperationState is not inserted. Should not be possible - displayString = NSLocalizedString(@"Not Inserted", @"Not Inserted"); - enableFormat = NO; - if (_isFormatting == DUXInternalSDCardFormattingYES) { - stillFormatting = DUXInternalSDCardFormattingERROR; - } - break; - case DJICameraSDCardOperationStateInvalid: - displayString = NSLocalizedString(@"Invalid", @"Invalid"); - enableFormat = NO; - if (_isFormatting == DUXInternalSDCardFormattingYES) { - stillFormatting = DUXInternalSDCardFormattingERROR; - } - break; - case DJICameraSDCardOperationStateReadOnly: - displayString = NSLocalizedString(@"Write Protected", @"Write Protected"); - enableFormat = NO; - if (_isFormatting == DUXInternalSDCardFormattingYES) { - stillFormatting = DUXInternalSDCardFormattingERROR; - } - break; - case DJICameraSDCardOperationFormatNeeded: - displayString = NSLocalizedString(@"Format Required", @"Format Required"); - if (_isFormatting == DUXInternalSDCardFormattingYES) { - stillFormatting = DUXInternalSDCardFormattingERROR; - } - break; - case DJICameraSDCardOperationStateFormatting: - displayString = NSLocalizedString(@"Formatting…", @"Formatting…"); - enableFormat = NO; - stillFormatting = YES; - break; - case DJICameraSDCardOperationStateInvalidFileSystem: - displayString = NSLocalizedString(@"Format Required", @"Format Required"); - if (_isFormatting == DUXInternalSDCardFormattingYES) { - stillFormatting = DUXInternalSDCardFormattingERROR; - } - break; - case DJICameraSDCardOperationStateBusy: - displayString = NSLocalizedString(@"Busy", @"Busy"); - enableFormat = NO; - break; - case DJICameraSDCardOperationStateFull: - displayString = NSLocalizedString(@"Full", @"Full"); - break; - case DJICameraSDCardOperationStateSlow: - displayString = NSLocalizedString(@"Slow", @"Slow"); - break; - case DJICameraSDCardOperationStateUnknownError: - displayString = NSLocalizedString(@"Unknown Error", @"Unknown Error"); - enableFormat = NO; - if (_isFormatting == DUXInternalSDCardFormattingYES) { - stillFormatting = DUXInternalSDCardFormattingERROR; - } - break; - case DJICameraSDCardOperationStateNoRemainFileIndices: - displayString = NSLocalizedString(@"No file indices", @"No file indices"); - break; - case DJICameraSDCardOperationStateInitializing: - displayString = NSLocalizedString(@"Initializing", @"Initializing"); - enableFormat = NO; - break; - case DJICameraSDCardOperationStateFormatRecommended: - displayString = NSLocalizedString(@"Formatting Recommended", @"Formatting Recommended"); - break; - case DJICameraSDCardOperationStateRecoveringFiles: - displayString = NSLocalizedString(@"Repairing Video Files", @"Repairing Video Files"); - enableFormat = NO; - break; - case DJICameraSDCardOperationStateWritingSlowly: - displayString = NSLocalizedString(@"Slow Write Speed", @"Slow Write Speed"); - break; - default: - displayString = NSLocalizedString(@"Unknown Error", @"Unknown Error"); - enableFormat = NO; - if (_isFormatting == DUXInternalSDCardFormattingYES) { - stillFormatting = DUXInternalSDCardFormattingERROR; - } - break; - } - } - } - - // Final step, set display text, colors and button enable state - self.buttonEnabled = enableFormat; - // These need to look at the final customizaton colors - [self.displayTextLabel setText:displayString]; - UIColor *textColor = [UIColor duxbeta_warningColor]; - if (isNormal) { - textColor = [UIColor duxbeta_whiteColor]; - } else if (isDisabled) { - textColor = [UIColor duxbeta_disabledGrayColor]; - } - [self.displayTextLabel setTextColor:textColor]; - - if ((_isFormatting != DUXInternalSDCardFormattingNO) && (stillFormatting != DUXInternalSDCardFormattingYES)) { - // Transitioned on the format state. Was it successful or not?? - BOOL success = (_isFormatting == DUXInternalSDCardFormattingYES) - && (stillFormatting != DUXInternalSDCardFormattingYES) - && (self.widgetModel.sdCardFormatError == NO); - _isFormatting = DUXInternalSDCardFormattingNO; - [self displayFormatCompletedDialog:success]; - } -} - -- (void)displayFormatSDCardDialog { - __weak DUXSDCardRemainingCapacityListItemWidget *weakSelf = self; - - NSString *msg = NSLocalizedString(@"Are you sure you want to format the SD card?", "Camera Formatting Workflow Action Description"); - UIAlertController* alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"⚠️ SD Card Format", @"⚠️ SD Card Format") - message:msg - preferredStyle:UIAlertControllerStyleAlert]; - - UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - __strong DUXSDCardRemainingCapacityListItemWidget *strongSelf = weakSelf; - - [DUXStateChangeBroadcaster send:[SDCardRemainingCapacityListWidgetUIState dialogActionConfirm:@"sdCardFormatDialogConfirm"]]; - - strongSelf.isFormatting = YES; - [self.widgetModel formatSDCard]; - }]; - - UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - [DUXStateChangeBroadcaster send:[SDCardRemainingCapacityListWidgetUIState dialogActionDismiss:@"sdCardFormatDialogConfirmCancel"]]; - __strong DUXSDCardRemainingCapacityListItemWidget *strongSelf = weakSelf; - strongSelf.isFormatting = NO; - }]; - - [alert addAction:cancelAction]; - [alert addAction:defaultAction]; - [self presentViewController:alert animated:YES completion:nil]; - [DUXStateChangeBroadcaster send:[SDCardRemainingCapacityListWidgetUIState dialogDisplayed:@"sdCardFormatDialog"]]; -} - -- (void)displayFormatCompletedDialog:(BOOL)success { - - NSString *msg; - NSString *title; - if (success) { - title = NSLocalizedString(@"✅ SD Card Format", @"✅ SD Card Format"); - msg = NSLocalizedString(@"SD card formatting completed.", "Camera Formatting Workflow Success Description"); - } else { - title = NSLocalizedString(@"🛑 SD Card Format", @"🛑 SD Card Format"); - msg = NSLocalizedString(@"Error formatting SD card.", "Camera Formatting Workflow Failure Description"); - } - UIAlertController* alert = [UIAlertController alertControllerWithTitle:title - message:msg - preferredStyle:UIAlertControllerStyleAlert]; - - UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (success) { - [DUXStateChangeBroadcaster send:[SDCardRemainingCapacityListWidgetUIState dialogActionDismiss:@"sdCardFormatDialogResultSuccess"]]; - } else { - [DUXStateChangeBroadcaster send:[SDCardRemainingCapacityListWidgetUIState dialogActionDismiss:@"sdCardFormatDialogResultFailed"]]; - } - }]; - - [alert addAction:defaultAction]; - [self presentViewController:alert animated:YES completion:nil]; - [DUXStateChangeBroadcaster send:[SDCardRemainingCapacityListWidgetUIState dialogDisplayed:@"sdCardFormatDialogResult"]]; - self.widgetModel.sdCardFormatError = NO; -} - - -@end - -@implementation SDCardRemainingCapacityListWidgetModelState - -+ (instancetype)sdCardRemainingCapacityChanged:(NSInteger)freeStorageChanged { - return [[SDCardRemainingCapacityListWidgetModelState alloc] initWithKey:@"sdCardRemainingCapacityChanged" number:@(freeStorageChanged)]; -} - -+ (instancetype)sdCardRemainingCapacityCardInserted:(BOOL)cardInserted { - return [[SDCardRemainingCapacityListWidgetModelState alloc] initWithKey:@"sdCardRemainingCapacityCardInserted" number:@(cardInserted)]; -} - -+ (instancetype)sdCardRemainingCapacityOperatingStatusChanged:(NSNumber *)operationState { - return [[SDCardRemainingCapacityListWidgetModelState alloc] initWithKey:@"sdCardRemainingCapacityOperatingStatusChanged" value:operationState]; -} - -@end - -@implementation SDCardRemainingCapacityListWidgetUIState - -@end diff --git a/DJIUXSDKWidgets/SystemStatusWidget/DUXBetaSystemStatusWidget.h b/DJIUXSDKWidgets/SystemStatusWidget/DUXBetaSystemStatusWidget.h index a71d8fe..a836026 100644 --- a/DJIUXSDKWidgets/SystemStatusWidget/DUXBetaSystemStatusWidget.h +++ b/DJIUXSDKWidgets/SystemStatusWidget/DUXBetaSystemStatusWidget.h @@ -30,9 +30,6 @@ NS_ASSUME_NONNULL_BEGIN -/** - * This widget displays a string message indicating the system status of the aircraft. -*/ @interface DUXBetaSystemStatusWidget : DUXBetaBaseWidget /** @@ -45,6 +42,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, strong) UIFont *messageFont; +/** + * Toggles the Red/Yellow/Green gradient backgrounds from DJI Go. +*/ +@property (nonatomic, assign) BOOL isGradientEnabled; + /** * Set the background color of the widget for the warning level. * @@ -63,7 +65,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Set the text color of messages with the warning level. * - * @param color Set the mesage text to this color + * @param color Set the message text to this color * @param systemStatusWarningLevel Set the text color for this warning level */ - (void)setTextColor:(UIColor *)color forSystemStatusWarningLevel:(DJIWarningStatusLevel)systemStatusWarningLevel; diff --git a/DJIUXSDKWidgets/SystemStatusWidget/DUXBetaSystemStatusWidget.m b/DJIUXSDKWidgets/SystemStatusWidget/DUXBetaSystemStatusWidget.m index 8f7b5b7..572229b 100644 --- a/DJIUXSDKWidgets/SystemStatusWidget/DUXBetaSystemStatusWidget.m +++ b/DJIUXSDKWidgets/SystemStatusWidget/DUXBetaSystemStatusWidget.m @@ -24,28 +24,26 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // - #import "DUXBetaSystemStatusWidget.h" #import "DUXMarqueeLabel.h" #import "UIImage+DUXBetaAssets.h" #import "UIFont+DUXBetaFonts.h" -#import "DUXStateChangeBroadcaster.h" - @import DJIUXSDKCore; +#import "DUXBetaStateChangeBroadcaster.h" static const CGSize kDesignSize = {297.0, 32.0}; static const CGFloat kDesignFontSize = 27.0; static NSString * const kInitialText = @"Disconnected"; /** - * DUXSystemStatusWidgetModelState contains the model hooks for the DUXSystemStatusWidget. + * DUXBetaSystemStatusWidgetModelState contains the model hooks for the DUXBetaSystemStatusWidget. * It implements the hooks: * * Key: productConnected Type: NSNumber - Sends a boolean value as an NSNumber indicating if an aircraft is connected. * * Key: systemStatusUpdated Type: NSString - The current system status message is sent whenever it changes. */ -@interface DUXSystemStatusWidgetModelState : DUXStateChangeBaseData +@interface DUXBetaSystemStatusWidgetModelState : DUXBetaStateChangeBaseData + (instancetype)productConnected:(BOOL)isConnected; + (instancetype)systemStatusUpdated:(NSString *)systemStatusMessage; @@ -53,12 +51,12 @@ + (instancetype)systemStatusUpdated:(NSString *)systemStatusMessage; @end /** - * DUXStatusWidgetUIState contains the hooks for UI changes in the widget class DUXSystemStatusWidget. + * DUXBetaStatusWidgetUIState contains the hooks for UI changes in the widget class DUXBetaSystemStatusWidget. * It implements the hook: * * Key: onWidgetTap Type: NSNumber - Sends a boolean YES value as an NSNumber indicating the widget was tapped. */ -@interface DUXSystemStatusWidgetUIState : DUXStateChangeBaseData +@interface DUXBetaSystemStatusWidgetUIState : DUXBetaStateChangeBaseData + (instancetype)onWidgetTap; @@ -93,6 +91,7 @@ - (instancetype)initWithCoder:(NSCoder *)coder { - (void)setupInstanceVariables { _messageFont = [UIFont duxbeta_dinMediumFontWithSize:kDesignFontSize]; + _isGradientEnabled = NO; _backgroundColorForWarningLevel = [@{ @(DJIWarningStatusLevelOffline) : [UIColor clearColor], @(DJIWarningStatusLevelNone) : [UIColor clearColor], @@ -105,7 +104,7 @@ - (void)setupInstanceVariables { @(DJIWarningStatusLevelOffline) : [UIColor duxbeta_disabledGrayColor], @(DJIWarningStatusLevelNone) : [UIColor duxbeta_systemStatusWidgetGreenColor], @(DJIWarningStatusLevelGood) : [UIColor duxbeta_systemStatusWidgetGreenColor], - @(DJIWarningStatusLevelWarning) : [UIColor duxbeta_systemStatusWidgetYellowColor], + @(DJIWarningStatusLevelWarning) : [UIColor duxbeta_yellowColor], @(DJIWarningStatusLevelError) : [UIColor duxbeta_systemStatusWidgetRedColor], } mutableCopy]; } @@ -129,7 +128,8 @@ - (void)viewWillAppear:(BOOL)animated { BindRKVOModel(self.widgetModel, @selector(updateUI), systemStatusWarningLevel); BindRKVOModel(self.widgetModel, @selector(updateIsCriticalWarning), isCriticalWarning); BindRKVOModel(self, @selector(updateLabelFont), messageFont); - + BindRKVOModel(self, @selector(setGradientBackground), isGradientEnabled, view.bounds); + // Bind hooks to model updates BindRKVOModel(self.widgetModel, @selector(sendProductConnected), isProductConnected); } @@ -145,6 +145,53 @@ - (void)dealloc { [self.widgetModel cleanup]; } +- (void)setGradientBackground { + if (self.isGradientEnabled) { + // Set gradient backgrounds + UIColor *gradientSafeColorPattern = [self gradientPatternWithBounds:self.view.bounds + colors:@[(__bridge id)[UIColor duxbeta_systemStatusWidgetGreenColor].CGColor, + (__bridge id)[UIColor duxbeta_clearColor].CGColor]]; + UIColor *gradientWarningColorPattern = [self gradientPatternWithBounds:self.view.bounds + colors:@[(__bridge id)[UIColor duxbeta_yellowColor].CGColor, + (__bridge id)[UIColor duxbeta_clearColor].CGColor]]; + UIColor *gradientErrorColorPattern = [self gradientPatternWithBounds:self.view.bounds + colors:@[(__bridge id)[UIColor duxbeta_systemStatusWidgetRedColor].CGColor, + (__bridge id)[UIColor duxbeta_clearColor].CGColor]]; + UIColor *gradientDisabledColorPattern = [self gradientPatternWithBounds:self.view.bounds + colors:@[(__bridge id)[UIColor duxbeta_disabledGrayColor].CGColor, + (__bridge id)[UIColor duxbeta_clearColor].CGColor]]; + + [self setBackgroundColor:gradientSafeColorPattern forSystemStatusWarningLevel:DJIWarningStatusLevelGood]; + [self setBackgroundColor:gradientDisabledColorPattern forSystemStatusWarningLevel:DJIWarningStatusLevelOffline]; + [self setBackgroundColor:gradientDisabledColorPattern forSystemStatusWarningLevel:DJIWarningStatusLevelNone]; + [self setBackgroundColor:gradientWarningColorPattern forSystemStatusWarningLevel:DJIWarningStatusLevelWarning]; + [self setBackgroundColor:gradientErrorColorPattern forSystemStatusWarningLevel:DJIWarningStatusLevelError]; + + // Set text color to white for all warning levels + self.textColorForWarningLevel = [@{ + @(DJIWarningStatusLevelOffline) : [UIColor duxbeta_whiteColor], + @(DJIWarningStatusLevelNone) : [UIColor duxbeta_whiteColor], + @(DJIWarningStatusLevelGood) : [UIColor duxbeta_whiteColor], + @(DJIWarningStatusLevelWarning) : [UIColor duxbeta_whiteColor], + @(DJIWarningStatusLevelError) : [UIColor duxbeta_whiteColor], + } mutableCopy]; + } +} + +- (UIColor *)gradientPatternWithBounds:(CGRect)bounds colors:(NSArray *)colors { + CAGradientLayer *gradientLayer = [CAGradientLayer layer]; + gradientLayer.frame = bounds; + gradientLayer.colors = colors; + gradientLayer.startPoint = CGPointMake(0.0, 0.5); + gradientLayer.endPoint = CGPointMake(1, 0.5); + + UIGraphicsBeginImageContext(gradientLayer.bounds.size); + [gradientLayer renderInContext:UIGraphicsGetCurrentContext()]; + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return [[UIColor alloc] initWithPatternImage:image]; +} + - (void)setupUI { self.backgroundView = [[UIImageView alloc] init]; self.backgroundView.translatesAutoresizingMaskIntoConstraints = NO; @@ -167,6 +214,7 @@ - (void)setupUI { [self.view addSubview:self.horizontalScrollingLabel]; + [self.view.heightAnchor constraintEqualToConstant:self.widgetSizeHint.minimumHeight].active = YES; [self.horizontalScrollingLabel.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor].active = YES; [self.horizontalScrollingLabel.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor].active = YES; [self.horizontalScrollingLabel.widthAnchor constraintEqualToAnchor:self.view.widthAnchor @@ -189,7 +237,7 @@ - (DUXBetaWidgetSizeHint)widgetSizeHint { /*********************************************************************************/ - (void)handleSuggestedMessageUpdate { self.horizontalScrollingLabel.text = self.widgetModel.suggestedWarningMessage; - [[DUXStateChangeBroadcaster instance] send:[DUXSystemStatusWidgetModelState systemStatusUpdated:self.widgetModel.suggestedWarningMessage]]; + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaSystemStatusWidgetModelState systemStatusUpdated:self.widgetModel.suggestedWarningMessage]]; } - (void)updateIsCriticalWarning { @@ -250,31 +298,31 @@ - (void)updateLabelFont { } - (void)sendProductConnected { - [[DUXStateChangeBroadcaster instance] send:[DUXSystemStatusWidgetModelState productConnected:self.widgetModel.isProductConnected]]; + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaSystemStatusWidgetModelState productConnected:self.widgetModel.isProductConnected]]; } - (void)handleTap { - [[DUXStateChangeBroadcaster instance] send:[DUXSystemStatusWidgetUIState onWidgetTap]]; + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaSystemStatusWidgetUIState onWidgetTap]]; } @end -@implementation DUXSystemStatusWidgetModelState +@implementation DUXBetaSystemStatusWidgetModelState + (instancetype)productConnected:(BOOL)isConnected { - return [[DUXSystemStatusWidgetModelState alloc] initWithKey:@"productConnected" number:@(isConnected)]; + return [[DUXBetaSystemStatusWidgetModelState alloc] initWithKey:@"productConnected" number:@(isConnected)]; } + (instancetype)systemStatusUpdated:(NSString *)systemStatusMessage { - return [[DUXSystemStatusWidgetModelState alloc] initWithKey:@"systemStatusMessage" string:systemStatusMessage]; + return [[DUXBetaSystemStatusWidgetModelState alloc] initWithKey:@"systemStatusMessage" string:systemStatusMessage]; } @end -@implementation DUXSystemStatusWidgetUIState +@implementation DUXBetaSystemStatusWidgetUIState + (instancetype)onWidgetTap { - return [[DUXSystemStatusWidgetUIState alloc] initWithKey:@"onWidgetTap" number:@(0)]; + return [[DUXBetaSystemStatusWidgetUIState alloc] initWithKey:@"onWidgetTap" number:@(0)]; } @end diff --git a/DJIUXSDKWidgets/SystemStatusWidget/DUXBetaSystemStatusWidgetModel.h b/DJIUXSDKWidgets/SystemStatusWidget/DUXBetaSystemStatusWidgetModel.h index e603ab2..a4c7cf2 100644 --- a/DJIUXSDKWidgets/SystemStatusWidget/DUXBetaSystemStatusWidgetModel.h +++ b/DJIUXSDKWidgets/SystemStatusWidget/DUXBetaSystemStatusWidgetModel.h @@ -27,6 +27,95 @@ #import +typedef NS_ENUM(NSInteger, DUXBetaSystemStatusWarningType) { + DUXBetaSystemStatusWarningTypeNone, + DUXBetaSystemStatusWarningTypeSingleBatteryNormalFlight, + DUXBetaSystemStatusWarningTypeNormalFlight, + DUXBetaSystemStatusWarningTypeNormalReady, + DUXBetaSystemStatusWarningTypeLimitSpace, + DUXBetaSystemStatusWarningTypeSingleBatteryNormalFlightNoGPS, + DUXBetaSystemStatusWarningTypeNormalFlightNoGPS, + DUXBetaSystemStatusWarningTypeNormalReadyNoGPS, + DUXBetaSystemStatusWarningTypeLimitSpaceNoGPS, + DUXBetaSystemStatusWarningTypeGoingHome, + DUXBetaSystemStatusWarningTypeGoingHomePreascending, + DUXBetaSystemStatusWarningTypeGoingHomeAlign, + DUXBetaSystemStatusWarningTypeGoingHomeAscending, + DUXBetaSystemStatusWarningTypeGoingHomeCruise, + DUXBetaSystemStatusWarningTypeLowRadioQuality, + DUXBetaSystemStatusWarningTypeSingleBatteryNormalFlightVision, + DUXBetaSystemStatusWarningTypeNormalFlightVision, + DUXBetaSystemStatusWarningTypeNormalReadyVision, + DUXBetaSystemStatusWarningTypeLimitSpaceVision, + DUXBetaSystemStatusWarningTypeSingleBatteryNormalFlightAtti, + DUXBetaSystemStatusWarningTypeNormalFlightAtti, + DUXBetaSystemStatusWarningTypeNormalReadyAtti, + DUXBetaSystemStatusWarningTypeLimitSpaceAtti, + DUXBetaSystemStatusWarningTypeGimbalStartupBlock, + DUXBetaSystemStatusWarningTypeGimbalWaitAutoRestart, + DUXBetaSystemStatusWarningTypeGimbalMotorOverload, + DUXBetaSystemStatusWarningTypeGimbalVibration, + DUXBetaSystemStatusWarningTypeGimbalBrokenState, + DUXBetaSystemStatusWarningTypeGimbalReachedMechanicalLimit, + DUXBetaSystemStatusWarningTypeGimbalRotationError, + DUXBetaSystemStatusWarningTypeGimbalMotorProtectionEnabled, + DUXBetaSystemStatusWarningTypeGaleWarning, + DUXBetaSystemStatusWarningTypeStrongRadioSignalNoise, + DUXBetaSystemStatusWarningTypeStrongRCRadioSignalNoise, + DUXBetaSystemStatusWarningTypeLowRadioSignal, + DUXBetaSystemStatusWarningTypeLowRCSignal, + DUXBetaSystemStatusWarningTypeRCDisturbLow, + DUXBetaSystemStatusWarningTypeRCDisturbMid, + DUXBetaSystemStatusWarningTypeRCDisturbHigh, + DUXBetaSystemStatusWarningTypeLowRCPower, + DUXBetaSystemStatusWarningTypeLowPowerGoHome, + DUXBetaSystemStatusWarningTypeLowPower, + DUXBetaSystemStatusWarningTypeOutOfControlGoHome, + DUXBetaSystemStatusWarningTypeOutOfControl, + DUXBetaSystemStatusWarningTypeGroundAirVersionNotMatch, + DUXBetaSystemStatusWarningTypeGoHomeError, + DUXBetaSystemStatusWarningTypeFirmwareNotMatch, + DUXBetaSystemStatusWarningTypeNotEnoughForce, + DUXBetaSystemStatusWarningTypeSmartSeriousLowPower, + DUXBetaSystemStatusWarningTypeSmartSeriousLowPowerLanding, + DUXBetaSystemStatusWarningTypeSeriousLowVoltage, + DUXBetaSystemStatusWarningTypeSeriousLowVoltageLanding, + DUXBetaSystemStatusWarningTypeCannotTakeoffNoAttiData, + DUXBetaSystemStatusWarningTypeCannotTakeoffIMUIniting, + DUXBetaSystemStatusWarningTypeCannotTakeoffNoviceProtected, + DUXBetaSystemStatusWarningTypeCannotTakeoff, + DUXBetaSystemStatusWarningTypeIMUHeating, + DUXBetaSystemStatusWarningTypeARHSCompassError, + DUXBetaSystemStatusWarningTypeIMUError, + DUXBetaSystemStatusWarningTypeSensorError, + DUXBetaSystemStatusWarningTypeIMUIniting, + DUXBetaSystemStatusWarningTypeBatteryLowTemperature, + DUXBetaSystemStatusWarningTypeBatteryOverTemperature, + DUXBetaSystemStatusWarningTypeBatteryOverCurrent, + DUXBetaSystemStatusWarningTypeBatteryBroken, + DUXBetaSystemStatusWarningTypeBatteryCommunicationError, + DUXBetaSystemStatusWarningTypeVisionCriticalError_TOF, + DUXBetaSystemStatusWarningTypeVisionCriticalError_UltraSonic, + DUXBetaSystemStatusWarningTypeVisionCriticalError_3D_TOF, + DUXBetaSystemStatusWarningTypeVisionCriticalError, + DUXBetaSystemStatusWarningTypeVisionDownCeliError, + DUXBetaSystemStatusWarningTypeVisionFrontCeliError, + DUXBetaSystemStatusWarningTypeVisionBackCeliError, + DUXBetaSystemStatusWarningTypeBarometerAirError, + DUXBetaSystemStatusWarningTypeESCError, + DUXBetaSystemStatusWarningTypeESCAirError, + DUXBetaSystemStatusWarningTypeCompassErrorToPAtti, + DUXBetaSystemStatusWarningTypeCompassError, + DUXBetaSystemStatusWarningTypeCameraEncryptError, + DUXBetaSystemStatusWarningTypeIMUNeedCalibrate, + DUXBetaSystemStatusWarningTypeMCError, + DUXBetaSystemStatusWarningTypeNoSignal, + DUXBetaSystemStatusWarningTypeNotConnectedAircraft, + DUXBetaSystemStatusWarningTypeMCInReadSDMode, + DUXBetaSystemStatusWarningTypeBatteryNotInPosition, + DUXBetaSystemStatusWarningTypeDisconnected, +}; + NS_ASSUME_NONNULL_BEGIN @interface DUXBetaSystemStatusWidgetModel : DUXBetaBaseWidgetModel diff --git a/DJIUXSDKWidgets/SystemStatusWidget/DUXBetaSystemStatusWidgetModel.m b/DJIUXSDKWidgets/SystemStatusWidget/DUXBetaSystemStatusWidgetModel.m index 3bf9963..3e1985d 100644 --- a/DJIUXSDKWidgets/SystemStatusWidget/DUXBetaSystemStatusWidgetModel.m +++ b/DJIUXSDKWidgets/SystemStatusWidget/DUXBetaSystemStatusWidgetModel.m @@ -81,8 +81,8 @@ - (void)updateStates { } if ([self.warningStatusItem.message containsString:@"Compass Error"]) { - DUXVoiceNotificationKey *voiceNotificationKey = [[DUXVoiceNotificationKey alloc] initWithIndex:0 - parameter:DUXVoiceNotificationParameterAttitude]; + DUXBetaVoiceNotificationKey *voiceNotificationKey = [[DUXBetaVoiceNotificationKey alloc] initWithIndex:0 + parameter:DUXBetaVoiceNotificationParameterAttitude]; DUXBetaVoiceNotification *voiceNotification = [[DUXBetaVoiceNotification alloc] init]; voiceNotification.audioPlayer = [self createAttiAudioPlayer]; diff --git a/DJIUXSDKWidgets/VPSWidget/DUXBetaVPSWidget.m b/DJIUXSDKWidgets/VPSWidget/DUXBetaVPSWidget.m index 97b48b4..71e8015 100644 --- a/DJIUXSDKWidgets/VPSWidget/DUXBetaVPSWidget.m +++ b/DJIUXSDKWidgets/VPSWidget/DUXBetaVPSWidget.m @@ -5,7 +5,7 @@ // MIT License // // Copyright © 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 diff --git a/DJIUXSDKWidgets/VPSWidget/DUXBetaVPSWidgetModel.m b/DJIUXSDKWidgets/VPSWidget/DUXBetaVPSWidgetModel.m index f270c1b..4b46666 100644 --- a/DJIUXSDKWidgets/VPSWidget/DUXBetaVPSWidgetModel.m +++ b/DJIUXSDKWidgets/VPSWidget/DUXBetaVPSWidgetModel.m @@ -5,7 +5,7 @@ // MIT License // // Copyright © 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 diff --git a/DJIUXSDKWidgets/VideoSignalWidget/DUXBetaVideoSignalWidget.h b/DJIUXSDKWidgets/VideoSignalWidget/DUXBetaVideoSignalWidget.h index 77ce626..405d311 100644 --- a/DJIUXSDKWidgets/VideoSignalWidget/DUXBetaVideoSignalWidget.h +++ b/DJIUXSDKWidgets/VideoSignalWidget/DUXBetaVideoSignalWidget.h @@ -1,9 +1,11 @@ // // DUXBetaVideoSignalWidget.h // DJIUXSDK -// +// +// MIT License +// // Copyright © 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 diff --git a/DJIUXSDKWidgets/VideoSignalWidget/DUXBetaVideoSignalWidget.m b/DJIUXSDKWidgets/VideoSignalWidget/DUXBetaVideoSignalWidget.m index 9bc5636..e078496 100644 --- a/DJIUXSDKWidgets/VideoSignalWidget/DUXBetaVideoSignalWidget.m +++ b/DJIUXSDKWidgets/VideoSignalWidget/DUXBetaVideoSignalWidget.m @@ -1,9 +1,11 @@ // // DUXBetaVideoSignalWidget.m // DJIUXSDK -// +// +// MIT License +// // Copyright © 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 @@ -28,7 +30,7 @@ #import "UIFont+DUXBetaFonts.h" #import "UIColor+DUXBetaColors.h" @import DJIUXSDKCore; -#import "DUXStateChangeBroadcaster.h" +#import "DUXBetaStateChangeBroadcaster.h" static NSString * const kVideoFeedLevel0ImageName = @"SignalLevel0"; static NSString * const kVideoFeedLevel1ImageName = @"SignalLevel1"; @@ -38,7 +40,7 @@ static NSString * const kVideoFeedLevel5ImageName = @"SignalLevel5"; static NSString * const kRemoteIconImageName = @"VideoSignal"; -static const CGFloat kFrequencyBandToWidgetWidthRatio = 0.38; +static const CGFloat kFrequencyBandToWidgetWidthRatio = 0.41; static const CGFloat kFrequencyBandToWidgetHeightRatio = 0.33; static const CGFloat kFrequencyBandFontSize = 30.0; @@ -60,13 +62,13 @@ @interface DUXBetaVideoSignalWidget () @end /** - * DUXVideoSignalWidgetModelState contains the model hooks for the DUXVideoSignalWidget. + * DUXBetaVideoSignalWidgetModelState contains the model hooks for the DUXBetaVideoSignalWidget. * It implements the hooks: * * Key: productConnected Type: NSNumber - Sends a boolean value as an NSNumber indicating if an aircraft * is connected. * - * Key: videoSignalQualityUpdate Type: NSNumber - Sends the DUXVideoSignalStrength as an NSNumber whenever the + * Key: videoSignalQualityUpdate Type: NSNumber - Sends the DUXBetaVideoSignalStrength as an NSNumber whenever the * video signal quality changes. * * Key: lightbridgeFrequncyBandUpdae Type: NSNumber - Sends the DJILightbridgeFrequencyBand enum value as an NSNumber whenever @@ -78,7 +80,7 @@ @interface DUXBetaVideoSignalWidget () * Key: ocusyncFrequencyBandUpdate Type: NSNumber - Sends the DJIOcuSyncFrequencyBand enum value as an NSNumber whenever * the frequency band changes when using an OcuSync connected aircraft. */ -@interface DUXVideoSignalWidgetModelState : DUXStateChangeBaseData +@interface DUXBetaVideoSignalWidgetModelState : DUXBetaStateChangeBaseData + (instancetype)productConnected:(BOOL)isConnected; + (instancetype)videoSignalQualityUpdate:(DUXBetaVideoSignalStrength)videoSignalQuality; @@ -258,28 +260,28 @@ - (void)setupUI { // Widget Model Hooks - (void)sendProductConnected { - [[DUXStateChangeBroadcaster instance] send:[DUXVideoSignalWidgetModelState productConnected:self.widgetModel.isProductConnected]]; + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaVideoSignalWidgetModelState productConnected:self.widgetModel.isProductConnected]]; } - (void)sendVideoSignalQualityUpdate { - [[DUXStateChangeBroadcaster instance] send:[DUXVideoSignalWidgetModelState videoSignalQualityUpdate:self.widgetModel.barsLevel]]; + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaVideoSignalWidgetModelState videoSignalQualityUpdate:self.widgetModel.barsLevel]]; } - (void)handleFrequencyBandUpdateLightbridge { self.frequencyBandLabel.text = self.lightbridgeBandToText[@(self.widgetModel.lightBridgeFrequencyBand)]; - [[DUXStateChangeBroadcaster instance] send:[DUXVideoSignalWidgetModelState lightbridgeFrequencyBandUpdate:self.widgetModel.lightBridgeFrequencyBand]]; + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaVideoSignalWidgetModelState lightbridgeFrequencyBandUpdate:self.widgetModel.lightBridgeFrequencyBand]]; self.currentAirLinkType = DUXBetaAirLinkTypeLightbridge; } - (void)handleFrequencyBandUpdateOcuSync { self.frequencyBandLabel.text = self.ocusyncBandToText[@(self.widgetModel.ocuSyncFrequencyBand)]; - [[DUXStateChangeBroadcaster instance] send:[DUXVideoSignalWidgetModelState ocusyncFrequencyBandUpdate:self.widgetModel.ocuSyncFrequencyBand]]; + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaVideoSignalWidgetModelState ocusyncFrequencyBandUpdate:self.widgetModel.ocuSyncFrequencyBand]]; self.currentAirLinkType = DUXBetaAirLinkTypeOcuSync; } - (void)handleFrequencyBandUpdateWiFi { self.frequencyBandLabel.text = self.wifiBandToText[@(self.widgetModel.wifiFrequencyBand)]; - [[DUXStateChangeBroadcaster instance] send:[DUXVideoSignalWidgetModelState wifiFrequencyBandUpdate:self.widgetModel.wifiFrequencyBand]]; + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaVideoSignalWidgetModelState wifiFrequencyBandUpdate:self.widgetModel.wifiFrequencyBand]]; self.currentAirLinkType = DUXBetaAirLinkTypeWiFi; } @@ -338,26 +340,26 @@ - (DUXBetaWidgetSizeHint)widgetSizeHint { @end -@implementation DUXVideoSignalWidgetModelState +@implementation DUXBetaVideoSignalWidgetModelState + (instancetype)productConnected:(BOOL)isConnected { - return [[DUXVideoSignalWidgetModelState alloc] initWithKey:@"productConnected" number:@(isConnected)]; + return [[DUXBetaVideoSignalWidgetModelState alloc] initWithKey:@"productConnected" number:@(isConnected)]; } + (instancetype)videoSignalQualityUpdate:(DUXBetaVideoSignalStrength)videoSignalQuality { - return [[DUXVideoSignalWidgetModelState alloc] initWithKey:@"videoSignalQualityUpdate" number:@(videoSignalQuality)]; + return [[DUXBetaVideoSignalWidgetModelState alloc] initWithKey:@"videoSignalQualityUpdate" number:@(videoSignalQuality)]; } + (instancetype)lightbridgeFrequencyBandUpdate:(DJILightbridgeFrequencyBand)frequencyBand { - return [[DUXVideoSignalWidgetModelState alloc] initWithKey:@"lightbridgeFrequencyBandUpdate" number:@(frequencyBand)]; + return [[DUXBetaVideoSignalWidgetModelState alloc] initWithKey:@"lightbridgeFrequencyBandUpdate" number:@(frequencyBand)]; } + (instancetype)wifiFrequencyBandUpdate:(DJIWiFiFrequencyBand)frequencyBand { - return [[DUXVideoSignalWidgetModelState alloc] initWithKey:@"wifiFrequencyBandUpdate" number:@(frequencyBand)]; + return [[DUXBetaVideoSignalWidgetModelState alloc] initWithKey:@"wifiFrequencyBandUpdate" number:@(frequencyBand)]; } + (instancetype)ocusyncFrequencyBandUpdate:(DJIOcuSyncFrequencyBand)frequencyBand { - return [[DUXVideoSignalWidgetModelState alloc] initWithKey:@"ocusyncFrequencyBandUpdate" number:@(frequencyBand)]; + return [[DUXBetaVideoSignalWidgetModelState alloc] initWithKey:@"ocusyncFrequencyBandUpdate" number:@(frequencyBand)]; } @end diff --git a/DJIUXSDKWidgets/VideoSignalWidget/DUXBetaVideoSignalWidgetModel.h b/DJIUXSDKWidgets/VideoSignalWidget/DUXBetaVideoSignalWidgetModel.h index 8aa3838..23c5146 100644 --- a/DJIUXSDKWidgets/VideoSignalWidget/DUXBetaVideoSignalWidgetModel.h +++ b/DJIUXSDKWidgets/VideoSignalWidget/DUXBetaVideoSignalWidgetModel.h @@ -1,9 +1,11 @@ // // DUXBetaVideoSignalWidgetModel.h // DJIUXSDK -// +// +// MIT License +// // Copyright © 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 diff --git a/DJIUXSDKWidgets/VideoSignalWidget/DUXBetaVideoSignalWidgetModel.m b/DJIUXSDKWidgets/VideoSignalWidget/DUXBetaVideoSignalWidgetModel.m index adf3853..538b613 100644 --- a/DJIUXSDKWidgets/VideoSignalWidget/DUXBetaVideoSignalWidgetModel.m +++ b/DJIUXSDKWidgets/VideoSignalWidget/DUXBetaVideoSignalWidgetModel.m @@ -1,9 +1,11 @@ // // DUXBetaVideoSignalWidgetModel.m // DJIUXSDK -// +// +// MIT License +// // Copyright © 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 @@ -101,6 +103,8 @@ - (void)ocuSyncBandChanged { } else { self.ocuSyncFrequencyBand = DJIOcuSyncFrequencyBandUnknown; } + } else { + self.ocuSyncFrequencyBand = self.rawOcuSyncFrequencyBand; } } diff --git a/DJIUXSDKWidgets/VisionWidget/DUXBetaVisionWidget.h b/DJIUXSDKWidgets/VisionWidget/DUXBetaVisionWidget.h index 1086711..5f2a466 100644 --- a/DJIUXSDKWidgets/VisionWidget/DUXBetaVisionWidget.h +++ b/DJIUXSDKWidgets/VisionWidget/DUXBetaVisionWidget.h @@ -1,9 +1,11 @@ // // DUXBetaVisionWidget.h // DJIUXSDK -// +// +// MIT License +// // Copyright © 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 @@ -32,6 +34,7 @@ NS_ASSUME_NONNULL_BEGIN * Widget to display the vision status/collision avoidance status, of the aircraft. * Depending on sensors availability, flight mode, and aircraft type. */ + @interface DUXBetaVisionWidget : DUXBetaBaseWidget /** diff --git a/DJIUXSDKWidgets/VisionWidget/DUXBetaVisionWidget.m b/DJIUXSDKWidgets/VisionWidget/DUXBetaVisionWidget.m index 883a3e8..fea5177 100644 --- a/DJIUXSDKWidgets/VisionWidget/DUXBetaVisionWidget.m +++ b/DJIUXSDKWidgets/VisionWidget/DUXBetaVisionWidget.m @@ -2,8 +2,10 @@ // DUXBetaVisionWidget.m // DJIUXSDK // +// MIT License +// // Copyright © 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 @@ -27,13 +29,13 @@ #import "UIImage+DUXBetaAssets.h" #import "UIFont+DUXBetaFonts.h" #import "UIColor+DUXBetaColors.h" -#import "DUXStateChangeBroadcaster.h" +#import "DUXBetaStateChangeBroadcaster.h" #import "NSLayoutConstraint+DUXBetaMultiplier.h" @import DJIUXSDKCore; -static NSString * const DUXVisionWidgetWarningMessageReason = @"Obstacle Avoidance Disabled."; -static NSString * const DUXVisionWidgetWarningMessageSolution = @"Fly with caution."; +static NSString * const DUXBetaVisionWidgetWarningMessageReason = @"Obstacle Avoidance Disabled."; +static NSString * const DUXBetaVisionWidgetWarningMessageSolution = @"Fly with caution."; @interface DUXBetaVisionWidget () @@ -45,19 +47,19 @@ @interface DUXBetaVisionWidget () @end /** - * DUXVisionSignalWidgetUIState contains the hooks for UI changes in the widget class DUXVisionWidget. + * DUXBetaVisionSignalWidgetUIState contains the hooks for UI changes in the widget class DUXBetaVisionWidget. * It implements the hook: * * Key: widgetTapp Type: NSNumber - Sends a boolean YES value as an NSNumber indicating the widget was tapped. */ -@interface DUXVisionSignalWidgetUIState : DUXStateChangeBaseData +@interface DUXBetaVisionSignalWidgetUIState : DUXBetaStateChangeBaseData + (instancetype)widgetTap; @end /** - * DUXVisionWidgetModelState contains the model hooks for the DUXVisionWidget. + * DUXBetaVisionWidgetModelState contains the model hooks for the DUXBetaVisionWidget. * It implements the hooks: * * Key: productConnected Type: NSNumber - Sends a boolean value as an NSNumber indicating if an aircraft is connected. @@ -70,7 +72,7 @@ + (instancetype)widgetTap; * Key: visibilityUpdate Type: NSNumber - Sends a boolean value as an NSNumber when the aircraft model changes (during * connection/disconnection) to indicate if aircraft supports vision. */ -@interface DUXVisionWidgetModelState : DUXStateChangeBaseData +@interface DUXBetaVisionWidgetModelState : DUXBetaStateChangeBaseData + (instancetype)productConnected:(BOOL)isConnected; + (instancetype)visionSystemStatusUpdate:(DUXBetaVisionStatus)visionStatus; @@ -190,22 +192,22 @@ - (void)updateUI { } - (void)sendIsProductConnected { - [[DUXStateChangeBroadcaster instance] send:[DUXVisionWidgetModelState productConnected:self.widgetModel.isProductConnected]]; + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaVisionWidgetModelState productConnected:self.widgetModel.isProductConnected]]; } - (void)sendVisionSystemStatusUpdate { - [[DUXStateChangeBroadcaster instance] send:[DUXVisionWidgetModelState visionSystemStatusUpdate:self.widgetModel.visionSystemStatus]]; + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaVisionWidgetModelState visionSystemStatusUpdate:self.widgetModel.visionSystemStatus]]; } - (void)sendVisibilityUpdate { - [[DUXStateChangeBroadcaster instance] send:[DUXVisionWidgetModelState visibilityUpdate:self.widgetModel.currentAircraftSupportVision]]; + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaVisionWidgetModelState visibilityUpdate:self.widgetModel.currentAircraftSupportVision]]; } - (void)sendVisionDisabledWarning { // Send Hook - [[DUXStateChangeBroadcaster instance] send:[DUXVisionWidgetModelState userAvoidanceEnabledUpdate:self.widgetModel.isCollisionAvoidanceEnabled]]; + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaVisionWidgetModelState userAvoidanceEnabledUpdate:self.widgetModel.isCollisionAvoidanceEnabled]]; // Send Warning Message - [self.widgetModel sendWarningMessageWithReason:DUXVisionWidgetWarningMessageReason andSolution:DUXVisionWidgetWarningMessageSolution]; + [self.widgetModel sendWarningMessageWithReason:DUXBetaVisionWidgetWarningMessageReason andSolution:DUXBetaVisionWidgetWarningMessageSolution]; } - (void)setImage:(UIImage *)image forVisionStatus:(DUXBetaVisionStatus)status { @@ -219,7 +221,7 @@ - (UIImage *)imageForVisionStatus:(DUXBetaVisionStatus)status { } - (void)widgetTapped { - [[DUXStateChangeBroadcaster instance] send:[DUXVisionSignalWidgetUIState widgetTap]]; + [[DUXBetaStateChangeBroadcaster instance] send:[DUXBetaVisionSignalWidgetUIState widgetTap]]; } - (void)updateMinImageDimensions { @@ -234,30 +236,30 @@ - (DUXBetaWidgetSizeHint)widgetSizeHint { @end -@implementation DUXVisionSignalWidgetUIState +@implementation DUXBetaVisionSignalWidgetUIState + (instancetype)widgetTap { - return [[DUXVisionSignalWidgetUIState alloc] initWithKey:@"widgetTap" number:@(0)]; + return [[DUXBetaVisionSignalWidgetUIState alloc] initWithKey:@"widgetTap" number:@(0)]; } @end -@implementation DUXVisionWidgetModelState +@implementation DUXBetaVisionWidgetModelState + (instancetype)productConnected:(BOOL)isConnected { - return [[DUXVisionWidgetModelState alloc] initWithKey:@"productConnected" number:@(isConnected)]; + return [[DUXBetaVisionWidgetModelState alloc] initWithKey:@"productConnected" number:@(isConnected)]; } + (instancetype)visionSystemStatusUpdate:(DUXBetaVisionStatus)visionStatus { - return [[DUXVisionWidgetModelState alloc] initWithKey:@"visionSystemStatusUpdate" number:@(visionStatus)]; + return [[DUXBetaVisionWidgetModelState alloc] initWithKey:@"visionSystemStatusUpdate" number:@(visionStatus)]; } + (instancetype)userAvoidanceEnabledUpdate:(BOOL)isUserAvoidanceEnabled { - return [[DUXVisionWidgetModelState alloc] initWithKey:@"userAvoidanceEnabledUpdate" number:@(isUserAvoidanceEnabled)]; + return [[DUXBetaVisionWidgetModelState alloc] initWithKey:@"userAvoidanceEnabledUpdate" number:@(isUserAvoidanceEnabled)]; } + (instancetype)visibilityUpdate:(BOOL)isVisible { - return [[DUXVisionWidgetModelState alloc] initWithKey:@"visibilityUpdate" number:@(isVisible)]; + return [[DUXBetaVisionWidgetModelState alloc] initWithKey:@"visibilityUpdate" number:@(isVisible)]; } @end diff --git a/DJIUXSDKWidgets/VisionWidget/DUXBetaVisionWidgetModel.m b/DJIUXSDKWidgets/VisionWidget/DUXBetaVisionWidgetModel.m index 23f61ed..b7d2c95 100644 --- a/DJIUXSDKWidgets/VisionWidget/DUXBetaVisionWidgetModel.m +++ b/DJIUXSDKWidgets/VisionWidget/DUXBetaVisionWidgetModel.m @@ -1,5 +1,5 @@ // -// DUXVisionWidgetModel.m +// DUXBetaVisionWidgetModel.m // DJIUXSDK // // MIT License @@ -49,7 +49,6 @@ @interface DUXBetaVisionWidgetModel() @property (strong, nonatomic) DJIActiveTrackMissionOperator *activeTrackOperator; @property (strong, nonatomic) DJIFlightAssistantObstacleAvoidanceSensorState *avoidanceStateM300; @property (nonatomic) BOOL isUpwardAvoidanceEnabled; -@property (nonatomic) BOOL isDownwardAvoidanceEnabled; @end @@ -69,7 +68,6 @@ - (instancetype)init { _activeTrackOperator = [[DJISDKManager missionControl] activeTrackMissionOperator]; _tapFlyOperator = [[DJISDKManager missionControl] tapFlyMissionOperator]; _isUpwardAvoidanceEnabled = NO; - _isDownwardAvoidanceEnabled = NO; } return self; } @@ -93,10 +91,6 @@ - (void)inSetup { subComponent:DJIFlightControllerFlightAssistantSubComponent subComponentIndex:0 andParam:DJIFlightAssistantParamVisionNoseTailSensorEnabled], isNoseTailSensorEnabled); - BindSDKKey([DJIFlightControllerKey keyWithIndex:0 - subComponent:DJIFlightControllerFlightAssistantSubComponent - subComponentIndex:0 - andParam:DJIFlightControllerParamLandingConfirmEnable], isDownwardAvoidanceEnabled); BindSDKKey([DJIFlightControllerKey keyWithIndex:0 subComponent:DJIFlightControllerFlightAssistantSubComponent subComponentIndex:0 @@ -106,7 +100,7 @@ - (void)inSetup { subComponentIndex:0 andParam:DJIFlightAssistantParamAvoidanceState], avoidanceStateM300); - BindRKVOModel(self, @selector(updateStates), isSensorWorking, aircraftModel, isCollisionAvoidanceEnabled, flightMode, isNoseTailSensorEnabled, isLeftRightSensorEnabled, isDownwardAvoidanceEnabled, isUpwardAvoidanceEnabled, avoidanceStateM300); + BindRKVOModel(self, @selector(updateStates), isSensorWorking, aircraftModel, isCollisionAvoidanceEnabled, flightMode, isNoseTailSensorEnabled, isLeftRightSensorEnabled, isUpwardAvoidanceEnabled, avoidanceStateM300); BindRKVOModel(self, @selector(updateAircraftSupport), aircraftModel); } @@ -141,8 +135,10 @@ - (void)updateStates { if (self.avoidanceStateM300 && [self.aircraftModel isEqualToString:DJIAircraftModelNameMatrice300RTK]) { DUXBetaVisionStatus verticalStatus = DUXBetaVisionStatusUnknown; DUXBetaVisionStatus horizontalStatus = DUXBetaVisionStatusUnknown; - if (self.avoidanceStateM300.isObstacleAvoidanceSensorInVerticalDirectionEnabled && self.avoidanceStateM300.isObstacleAvoidanceSensorsInVerticalDirectionWorking && - self.isDownwardAvoidanceEnabled && self.isUpwardAvoidanceEnabled) { + //TODO: fix interface names before release + if (self.avoidanceStateM300.isObstacleAvoidanceSensorInVerticalDirectionEnabled && + self.avoidanceStateM300.isObstacleAvoidanceSensorsInVerticalDirectionWorking && + self.isUpwardAvoidanceEnabled) { verticalStatus = DUXBetaVisionStatusNormal; } else { verticalStatus = DUXBetaVisionStatusClosed; @@ -251,7 +247,7 @@ - (BOOL)isVisionSystemEnabled { - (void)sendWarningMessageWithReason:(NSString *)reason andSolution:(NSString *)solution { DUXBetaWarningMessageKey *warningMessageKey = [[DUXBetaWarningMessageKey alloc] initWithIndex:0 - parameter:DUXBetaWarningMessageParameterSendWarningMessage]; + parameter:DUXBetaWarningMessageParameterSendWarningMessage]; DUXBetaWarningMessage *warningMessage = [[DUXBetaWarningMessage alloc] init]; warningMessage.reason = reason; warningMessage.solution = solution; diff --git a/DJIUXSDKWidgets/WidgetBaseClasses/DUXBetaBaseWidgetModel.m b/DJIUXSDKWidgets/WidgetBaseClasses/DUXBaseWidgetModel.m similarity index 70% rename from DJIUXSDKWidgets/WidgetBaseClasses/DUXBetaBaseWidgetModel.m rename to DJIUXSDKWidgets/WidgetBaseClasses/DUXBaseWidgetModel.m index 2f2eb96..7bcd920 100644 --- a/DJIUXSDKWidgets/WidgetBaseClasses/DUXBetaBaseWidgetModel.m +++ b/DJIUXSDKWidgets/WidgetBaseClasses/DUXBaseWidgetModel.m @@ -31,9 +31,14 @@ @import DJIUXSDKCore; @import DJIUXSDKCommunication; + +const double DUXBetaBaseWidgetModelMetersToFeet = 3.28084; +const double DUXBetaBaseWidgetModelFeetToMeters = 0.3048000097536; + @interface DUXBetaBaseWidgetModel () @property (assign, nonatomic, readwrite) BOOL isProductConnected; +@property (nonatomic, strong) NSString* unitSuffix; @end @@ -42,6 +47,7 @@ @implementation DUXBetaBaseWidgetModel - (instancetype)init { if (self = [super init]) { _unitSystem = [[self class] preferredUnitSystem]; + _unitSuffix = (_unitSystem == DUXBetaUnitSystemMetric) ? @"m" : @"ft"; _vmState = DUXBetaVMStateCreated; _isProductConnected = NO; } @@ -49,11 +55,11 @@ - (instancetype)init { } + (DUXBetaUnitSystem)preferredUnitSystem { - DUXMeasureUnitType measureUnitType = [[DUXBetaSingleton sharedGlobalPreferences] measureUnitType]; - if (measureUnitType == DUXMeasureUnitTypeUnknown) { + DUXBetaMeasureUnitType measureUnitType = [[DUXBetaSingleton sharedGlobalPreferences] measureUnitType]; + if (measureUnitType == DUXBetaMeasureUnitTypeUnknown) { return DUXBetaUnitSystemMetric; } else { - if (measureUnitType == DUXMeasureUnitTypeImperial) { + if (measureUnitType == DUXBetaMeasureUnitTypeImperial) { return DUXBetaUnitSystemImperial; } else { return DUXBetaUnitSystemMetric; @@ -101,4 +107,30 @@ - (void)cleanup { self.vmState = DUXBetaVMStateCleanedUp; } +#pragma mark - Unit Conversion Utilities + +- (double)metersToMeasurementSystem:(double)metersIn { + if (self.unitSystem == DUXBetaUnitSystemMetric) { + return metersIn; + } else { + return metersIn * DUXBetaBaseWidgetModelMetersToFeet; + } +} + +- (NSString *)measurementUnitString { + return _unitSuffix; +} + +- (NSString *)metersToUnitString:(double)metersIn { + return [NSString stringWithFormat:@"%ld%@", (long)[self metersToMeasurementSystem:metersIn], _unitSuffix]; +} + +- (double)measurementToMeters:(double)measureIn { // This assumes the measureIn is in the unitSystem of the model + if (self.unitSystem == DUXBetaUnitSystemMetric) { + return measureIn; + } else { + return measureIn * DUXBetaBaseWidgetModelFeetToMeters; + } +} + @end diff --git a/DJIUXSDKWidgets/WidgetBaseClasses/DUXBetaBaseWidget.h b/DJIUXSDKWidgets/WidgetBaseClasses/DUXBetaBaseWidget.h index b57b2f7..cc103cb 100644 --- a/DJIUXSDKWidgets/WidgetBaseClasses/DUXBetaBaseWidget.h +++ b/DJIUXSDKWidgets/WidgetBaseClasses/DUXBetaBaseWidget.h @@ -29,6 +29,7 @@ #import NS_ASSUME_NONNULL_BEGIN +@class DUXBetaTheme; typedef struct { // Use this aspect ratio for best appearance, we plan on images being resizable diff --git a/DJIUXSDKWidgets/WidgetBaseClasses/DUXBetaBaseWidget.m b/DJIUXSDKWidgets/WidgetBaseClasses/DUXBetaBaseWidget.m index 479f29f..7055a25 100644 --- a/DJIUXSDKWidgets/WidgetBaseClasses/DUXBetaBaseWidget.m +++ b/DJIUXSDKWidgets/WidgetBaseClasses/DUXBetaBaseWidget.m @@ -29,6 +29,7 @@ @interface DUXBetaBaseWidget () +@property (nonatomic, strong) DUXBetaTheme *perWidgetTheme; @property (nonatomic, strong) NSString *widgetID; @end diff --git a/DJIUXSDKWidgets/WidgetBaseClasses/DUXBetaBaseWidgetModel.h b/DJIUXSDKWidgets/WidgetBaseClasses/DUXBetaBaseWidgetModel.h index 6c559d5..dcdc1af 100644 --- a/DJIUXSDKWidgets/WidgetBaseClasses/DUXBetaBaseWidgetModel.h +++ b/DJIUXSDKWidgets/WidgetBaseClasses/DUXBetaBaseWidgetModel.h @@ -48,6 +48,11 @@ typedef NS_ENUM(NSUInteger, DUXBetaUnitSystem) { - (void)cleanup; - (void)inCleanup; +- (double)metersToMeasurementSystem:(double)metersIn; +- (NSString *)measurementUnitString; +- (NSString *)metersToUnitString:(double)metersIn; +- (double)measurementToMeters:(double)measureIn; // This assumes the measureIn is in the unitSystem of the model + @end NS_ASSUME_NONNULL_END diff --git a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemEditTextButtonWidgetModel.h b/DJIUXSDKWidgets/WidgetHelpers/DUXBetaUIImageView.h similarity index 79% rename from DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemEditTextButtonWidgetModel.h rename to DJIUXSDKWidgets/WidgetHelpers/DUXBetaUIImageView.h index 8c5ddfe..29e7072 100644 --- a/DJIUXSDKWidgets/Panels/ListItemBaseWidgets/DUXListItemEditTextButtonWidgetModel.h +++ b/DJIUXSDKWidgets/WidgetHelpers/DUXBetaUIImageView.h @@ -1,9 +1,11 @@ // -// DUXListItemEditTextButtonWidgetModel.h -// DJIUXSDKWidgets -/// -// Copyright © 2018-2020 DJI +// DUXBetaUIImageView.h +// DJIUXSDK // +// MIT License +// +// Copyright © 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 @@ -21,16 +23,15 @@ // 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. -// +// -#import -#import -#import "DUXBetaBaseWidgetModel+Protected.h" -@import DJIUXSDKCore; +#import NS_ASSUME_NONNULL_BEGIN -@interface DUXListItemEditTextButtonWidgetModel : DUXBetaBaseWidgetModel +@interface DUXBetaUIImageView : UIImageView + +@property (nonatomic, assign) NSUInteger scale; @end diff --git a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXFlightModeListItemWidgetModel.h b/DJIUXSDKWidgets/WidgetHelpers/DUXBetaUIImageView.m similarity index 72% rename from DJIUXSDKWidgets/SystemStatusListWidgets/DUXFlightModeListItemWidgetModel.h rename to DJIUXSDKWidgets/WidgetHelpers/DUXBetaUIImageView.m index b88160f..2aae74b 100644 --- a/DJIUXSDKWidgets/SystemStatusListWidgets/DUXFlightModeListItemWidgetModel.h +++ b/DJIUXSDKWidgets/WidgetHelpers/DUXBetaUIImageView.m @@ -1,9 +1,11 @@ // -// DUXFlightModeListItemWidgetModel.h -// DJIUXSDKWidgets +// DUXBetaUIImageView.m +// DJIUXSDK // +// MIT License +// // Copyright © 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 @@ -21,22 +23,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. -// - -#import - -NS_ASSUME_NONNULL_BEGIN +// -@interface DUXFlightModeListItemWidgetModel : DUXBetaBaseWidgetModel +#import "DUXBetaUIImageView.h" -@property (nonatomic, strong, readonly) NSString *flightModeString; +@implementation DUXBetaUIImageView -@end - -@interface FlightModeListItemModelState : ListItemLabelButtonModelState - -+ (instancetype)flightModeUpdated:(NSString*)newMode; +- (CGSize) intrinsicContentSize { + return CGSizeMake(self.image.size.width * self.scale, self.image.size.height * self.scale); +} @end - -NS_ASSUME_NONNULL_END diff --git a/DJIUXSDKWidgets/WidgetHelpers/NSData+DUXBetaAssets.m b/DJIUXSDKWidgets/WidgetHelpers/NSData+DUXBetaAssets.m index e481a43..a8617f6 100644 --- a/DJIUXSDKWidgets/WidgetHelpers/NSData+DUXBetaAssets.m +++ b/DJIUXSDKWidgets/WidgetHelpers/NSData+DUXBetaAssets.m @@ -32,12 +32,8 @@ @implementation NSData (DUXBetaAssets) + (NSData *)duxbeta_dataWithAssetNamed:(NSString *)assetName { - NSBundle *frameworkBundle = [NSBundle bundleForClass:[DUXBetaBaseWidget class]]; - - NSURL *assetBundleURL = [frameworkBundle.resourceURL URLByAppendingPathComponent:@"/DUXBetaAssets.bundle"]; - NSBundle *assetBundle = [NSBundle bundleWithURL:assetBundleURL]; - - NSDataAsset *asset = [[NSDataAsset alloc] initWithName:assetName bundle:assetBundle]; + NSBundle *currentBundle = [NSBundle duxbeta_currentBundle]; + NSDataAsset *asset = [[NSDataAsset alloc] initWithName:assetName bundle:currentBundle]; NSData *assetData = asset.data; if (assetData == nil) { @@ -47,5 +43,6 @@ + (NSData *)duxbeta_dataWithAssetNamed:(NSString *)assetName { return assetData; } + @end diff --git a/DJIUXSDKWidgets/WidgetHelpers/NSLayoutConstraint+DUXBetaMultiplier.h b/DJIUXSDKWidgets/WidgetHelpers/NSLayoutConstraint+DUXBetaMultiplier.h index 47e98a9..1316efb 100644 --- a/DJIUXSDKWidgets/WidgetHelpers/NSLayoutConstraint+DUXBetaMultiplier.h +++ b/DJIUXSDKWidgets/WidgetHelpers/NSLayoutConstraint+DUXBetaMultiplier.h @@ -2,6 +2,8 @@ // NSLayoutConstraint+DUXBetaMultiplier.h // DJIUXSDK // +// MIT License +// // Copyright © 2018-2020 DJI // // Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/DJIUXSDKWidgets/WidgetHelpers/NSLayoutConstraint+DUXBetaMultiplier.m b/DJIUXSDKWidgets/WidgetHelpers/NSLayoutConstraint+DUXBetaMultiplier.m index c5e78e0..bad1cc7 100644 --- a/DJIUXSDKWidgets/WidgetHelpers/NSLayoutConstraint+DUXBetaMultiplier.m +++ b/DJIUXSDKWidgets/WidgetHelpers/NSLayoutConstraint+DUXBetaMultiplier.m @@ -2,6 +2,8 @@ // NSLayoutConstraint+DUXBetaMultiplier.m // DJIUXSDKWidgets // +// MIT License +// // Copyright © 2018-2020 DJI // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -21,7 +23,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. -// +// #import "NSLayoutConstraint+DUXBetaMultiplier.h" diff --git a/DJIUXSDKWidgets/WidgetHelpers/UIFont+DUXBetaFonts.m b/DJIUXSDKWidgets/WidgetHelpers/UIFont+DUXBetaFonts.m index 6bf45e8..cb94d39 100644 --- a/DJIUXSDKWidgets/WidgetHelpers/UIFont+DUXBetaFonts.m +++ b/DJIUXSDKWidgets/WidgetHelpers/UIFont+DUXBetaFonts.m @@ -27,6 +27,7 @@ #import "UIFont+DUXBetaFonts.h" #import +#import "NSBundle+DUXBetaAssets.h" @implementation UIFont (DUXBetaFonts) @@ -49,7 +50,8 @@ + (UIFont *)duxbeta_customFontNamed:(NSString *)customFontName ofType:(NSString } + (BOOL)duxbeta_loadFontWithName:(NSString *)fontName ofType:(NSString *)type { - NSString *fontPath = [[NSBundle bundleForClass:[self class]] pathForResource:fontName ofType:type]; + NSBundle *currentBundle = [NSBundle duxbeta_currentBundle]; + NSString *fontPath = [currentBundle pathForResource:fontName ofType:type]; NSData *fontData = [NSData dataWithContentsOfFile:fontPath]; CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)fontData); diff --git a/DJIUXSDKWidgets/WidgetHelpers/UIViewController+DUXBetaHelper.swift b/DJIUXSDKWidgets/WidgetHelpers/UIViewController+DUXBetaHelper.swift new file mode 100644 index 0000000..7134c1f --- /dev/null +++ b/DJIUXSDKWidgets/WidgetHelpers/UIViewController+DUXBetaHelper.swift @@ -0,0 +1,52 @@ +// +// UIViewController+DUXBetaHelper.swift +// DJIUXSDKWidgets +// +// Copyright © 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. +// + +public extension UIViewController { + + @objc var topMostViewController: UIViewController? { + guard let rootViewController = UIApplication.shared.keyWindow?.rootViewController else { + return nil + } + + var topViewController: UIViewController? + if let navigationController = rootViewController as? UINavigationController { + topViewController = navigationController.topViewController + } else if let tabbarController = rootViewController as? UITabBarController { + topViewController = tabbarController.selectedViewController + } else { + topViewController = rootViewController + } + + while let presentedViewController = topViewController?.presentedViewController { + topViewController = presentedViewController + } + + if let presentingViewController = topViewController?.presentingViewController { + return presentingViewController + } + + return topViewController + } +} diff --git a/README.md b/README.md index 13fc29c..705728d 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,13 @@ You can find the UX SDK documentation on the [wiki](https://github.com/dji-sdk/O ## How to Contribute As always, the DJI Dev Team is committed to improving your developer experience. Please follow our guidelines on the [How to Contribute](https://github.com/dji-sdk/Open-Source-UXSDK-iOS/wiki/How-to-Contribute) page on our wiki for filling out any bugs or feature requests, or contribute to the code base. If you have any other questions, please send an email to dev@dji.com. We recommend frequently checking the Github repositories for changes and new releases. +You can also post questions, keep up to date on DJI developer news and contribute to the community by visiting the [DJI's Developer Forum here](https://forum.dji.com/forum-139-1.html?from=developer) ## Future Plans -This Beta 2 release only contains a subset of UX SDK elements. We are eager to give you a sneak peek, and are very interested in receiving your feedback and suggestions. Please refer to the release notes for the full list of elements that Beta 2 version makes available. +This Beta 3 release only contains a subset of UX SDK elements. We are eager to give you a sneak peek, and are very interested in receiving your feedback and suggestions. Please refer to the release notes for the full list of elements that Beta 3 version makes available. -Our long-term plan is for this framework to reach feature parity with the UX SDK 4.12 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 future Beta releases as they are completed. We welcome your feedback on the architecture and your ideas for addional widgets, including those not included in prior UX SDK releases, as well as your contributions and PRs for any ***currently open-sourced features***. +Our long-term plan is for this framework to reach feature parity with the UX SDK 4.13 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 future Beta releases as they are completed. We welcome your feedback on the architecture and your ideas for addional widgets, including those not included in prior UX SDK releases, as well as your contributions and PRs for any ***currently open-sourced features***. ## License diff --git a/UXSDKBetaSample/Podfile b/UXSDKBetaSample/Podfile deleted file mode 100644 index 9e23a67..0000000 --- a/UXSDKBetaSample/Podfile +++ /dev/null @@ -1,9 +0,0 @@ -platform :ios, '11.0' - -target 'UXSDKBetaSample' do - use_frameworks! - pod 'DJI-SDK-iOS', '~> 4.12' - pod 'DJIWidget', '~> 1.6.2' - pod 'DJI-UXSDK-iOS-Beta', '~> 0.2' - pod 'iOS-Color-Picker' -end diff --git a/UXSDKBetaSample/UXSDKBetaSample.xcodeproj/project.pbxproj b/UXSDKBetaSample/UXSDKBetaSample.xcodeproj/project.pbxproj deleted file mode 100644 index 6a7fa67..0000000 --- a/UXSDKBetaSample/UXSDKBetaSample.xcodeproj/project.pbxproj +++ /dev/null @@ -1,558 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 51; - objects = { - -/* Begin PBXBuildFile section */ - 536C4BAE22652AAA008E370B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 536C4BAD22652AAA008E370B /* Assets.xcassets */; }; - 5379933A22AB1434006FEE71 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5379933622AB1434006FEE71 /* LaunchScreen.storyboard */; }; - 5379933B22AB1434006FEE71 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5379933822AB1434006FEE71 /* Main.storyboard */; }; - 5379933D22AB14A5006FEE71 /* CustomSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5379933C22AB14A5006FEE71 /* CustomSplitViewController.swift */; }; - 537AA97D2265344300905FCC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537AA97C2265344200905FCC /* AppDelegate.swift */; }; - 537AA991226534AA00905FCC /* SingleWidgetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537AA98A226534AA00905FCC /* SingleWidgetViewController.swift */; }; - 537AA992226534AA00905FCC /* LogCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537AA98C226534AA00905FCC /* LogCenter.swift */; }; - 537AA993226534AA00905FCC /* ProductCommunicationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537AA98D226534AA00905FCC /* ProductCommunicationService.swift */; }; - 537AA994226534AA00905FCC /* LogEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537AA98E226534AA00905FCC /* LogEntry.swift */; }; - 537AA995226534AA00905FCC /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537AA98F226534AA00905FCC /* MainViewController.swift */; }; - 537AA996226534AA00905FCC /* WidgetsListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537AA990226534AA00905FCC /* WidgetsListViewController.swift */; }; - 53A5E36D2344013700313B15 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A5E36C2344013700313B15 /* Helpers.swift */; }; - 53A5E37223441CC800313B15 /* CustomMapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A5E37123441CC800313B15 /* CustomMapViewController.swift */; }; - B6829565246C6FAC00BDA4A3 /* DefaultLayoutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6829564246C6FAC00BDA4A3 /* DefaultLayoutViewController.swift */; }; - B6829568246C724E00BDA4A3 /* FPVCustomizations.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6829567246C724E00BDA4A3 /* FPVCustomizations.swift */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 536C4BBA22652AAA008E370B /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 536C4B9922652AA8008E370B /* Project object */; - proxyType = 1; - remoteGlobalIDString = 536C4BA022652AA8008E370B; - remoteInfo = DJIUXSDKSample; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 536C4BA122652AA8008E370B /* UXSDKBetaSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UXSDKBetaSample.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 536C4BAD22652AAA008E370B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 536C4BB222652AAA008E370B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 536C4BB922652AAA008E370B /* UXSDKBetaSampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UXSDKBetaSampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 536C4BEF22652B49008E370B /* DJISDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DJISDK.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 5379933722AB1434006FEE71 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = LaunchScreen.storyboard; sourceTree = ""; }; - 5379933922AB1434006FEE71 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Main.storyboard; sourceTree = ""; }; - 5379933C22AB14A5006FEE71 /* CustomSplitViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomSplitViewController.swift; sourceTree = ""; }; - 537AA97B2265344100905FCC /* UXSDKBetaSample-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UXSDKBetaSample-Bridging-Header.h"; sourceTree = ""; }; - 537AA97C2265344200905FCC /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 537AA98A226534AA00905FCC /* SingleWidgetViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleWidgetViewController.swift; sourceTree = ""; }; - 537AA98C226534AA00905FCC /* LogCenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogCenter.swift; sourceTree = ""; }; - 537AA98D226534AA00905FCC /* ProductCommunicationService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductCommunicationService.swift; sourceTree = ""; }; - 537AA98E226534AA00905FCC /* LogEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogEntry.swift; sourceTree = ""; }; - 537AA98F226534AA00905FCC /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; - 537AA990226534AA00905FCC /* WidgetsListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WidgetsListViewController.swift; sourceTree = ""; }; - 53A5E36C2344013700313B15 /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; - 53A5E37123441CC800313B15 /* CustomMapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomMapViewController.swift; sourceTree = ""; }; - B6829564246C6FAC00BDA4A3 /* DefaultLayoutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultLayoutViewController.swift; sourceTree = ""; }; - B6829567246C724E00BDA4A3 /* FPVCustomizations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FPVCustomizations.swift; sourceTree = ""; }; - B6CE64C824637C9600484E23 /* DJIWidget.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DJIWidget.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - B6CE64CA24637C9D00484E23 /* DJISDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DJISDK.framework; sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 536C4B9E22652AA8008E370B /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 536C4BB622652AAA008E370B /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 3A5F261302AC9ED5A40BF4DA /* Pods */ = { - isa = PBXGroup; - children = ( - ); - path = Pods; - sourceTree = ""; - }; - 536C4B9822652AA8008E370B = { - isa = PBXGroup; - children = ( - 536C4BEF22652B49008E370B /* DJISDK.framework */, - 536C4BA322652AA8008E370B /* UXSDKBetaSample */, - 536C4BA222652AA8008E370B /* Products */, - 3A5F261302AC9ED5A40BF4DA /* Pods */, - B6CE645C246375B900484E23 /* Frameworks */, - ); - sourceTree = ""; - }; - 536C4BA222652AA8008E370B /* Products */ = { - isa = PBXGroup; - children = ( - 536C4BA122652AA8008E370B /* UXSDKBetaSample.app */, - 536C4BB922652AAA008E370B /* UXSDKBetaSampleTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 536C4BA322652AA8008E370B /* UXSDKBetaSample */ = { - isa = PBXGroup; - children = ( - B6829566246C724E00BDA4A3 /* Customizations */, - 5379933522AB1434006FEE71 /* Base.lproj */, - 537AA98B226534AA00905FCC /* Services */, - 537AA97C2265344200905FCC /* AppDelegate.swift */, - 5379933C22AB14A5006FEE71 /* CustomSplitViewController.swift */, - 537AA98F226534AA00905FCC /* MainViewController.swift */, - B6829564246C6FAC00BDA4A3 /* DefaultLayoutViewController.swift */, - 537AA98A226534AA00905FCC /* SingleWidgetViewController.swift */, - 53A5E36C2344013700313B15 /* Helpers.swift */, - 53A5E37123441CC800313B15 /* CustomMapViewController.swift */, - 537AA990226534AA00905FCC /* WidgetsListViewController.swift */, - 536C4BAD22652AAA008E370B /* Assets.xcassets */, - 536C4BB222652AAA008E370B /* Info.plist */, - 537AA97B2265344100905FCC /* UXSDKBetaSample-Bridging-Header.h */, - ); - path = UXSDKBetaSample; - sourceTree = ""; - }; - 5379933522AB1434006FEE71 /* Base.lproj */ = { - isa = PBXGroup; - children = ( - 5379933622AB1434006FEE71 /* LaunchScreen.storyboard */, - 5379933822AB1434006FEE71 /* Main.storyboard */, - ); - path = Base.lproj; - sourceTree = ""; - }; - 537AA98B226534AA00905FCC /* Services */ = { - isa = PBXGroup; - children = ( - 537AA98C226534AA00905FCC /* LogCenter.swift */, - 537AA98D226534AA00905FCC /* ProductCommunicationService.swift */, - 537AA98E226534AA00905FCC /* LogEntry.swift */, - ); - path = Services; - sourceTree = ""; - }; - B6829566246C724E00BDA4A3 /* Customizations */ = { - isa = PBXGroup; - children = ( - B6829567246C724E00BDA4A3 /* FPVCustomizations.swift */, - ); - path = Customizations; - sourceTree = ""; - }; - B6CE645C246375B900484E23 /* Frameworks */ = { - isa = PBXGroup; - children = ( - B6CE64CA24637C9D00484E23 /* DJISDK.framework */, - B6CE64C824637C9600484E23 /* DJIWidget.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 536C4BA022652AA8008E370B /* UXSDKBetaSample */ = { - isa = PBXNativeTarget; - buildConfigurationList = 536C4BC222652AAA008E370B /* Build configuration list for PBXNativeTarget "UXSDKBetaSample" */; - buildPhases = ( - 536C4B9D22652AA8008E370B /* Sources */, - 536C4B9E22652AA8008E370B /* Frameworks */, - 536C4B9F22652AA8008E370B /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = UXSDKBetaSample; - productName = DJIUXSDKSample; - productReference = 536C4BA122652AA8008E370B /* UXSDKBetaSample.app */; - productType = "com.apple.product-type.application"; - }; - 536C4BB822652AAA008E370B /* UXSDKBetaSampleTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 536C4BC522652AAA008E370B /* Build configuration list for PBXNativeTarget "UXSDKBetaSampleTests" */; - buildPhases = ( - 536C4BB522652AAA008E370B /* Sources */, - 536C4BB622652AAA008E370B /* Frameworks */, - 536C4BB722652AAA008E370B /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 536C4BBB22652AAA008E370B /* PBXTargetDependency */, - ); - name = UXSDKBetaSampleTests; - productName = DJIUXSDKSampleTests; - productReference = 536C4BB922652AAA008E370B /* UXSDKBetaSampleTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 536C4B9922652AA8008E370B /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 1020; - ORGANIZATIONNAME = DUXOS; - TargetAttributes = { - 536C4BA022652AA8008E370B = { - CreatedOnToolsVersion = 10.2; - LastSwiftMigration = 1020; - }; - 536C4BB822652AAA008E370B = { - CreatedOnToolsVersion = 10.2; - TestTargetID = 536C4BA022652AA8008E370B; - }; - }; - }; - buildConfigurationList = 536C4B9C22652AA8008E370B /* Build configuration list for PBXProject "UXSDKBetaSample" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 536C4B9822652AA8008E370B; - productRefGroup = 536C4BA222652AA8008E370B /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 536C4BA022652AA8008E370B /* UXSDKBetaSample */, - 536C4BB822652AAA008E370B /* UXSDKBetaSampleTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 536C4B9F22652AA8008E370B /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 5379933B22AB1434006FEE71 /* Main.storyboard in Resources */, - 536C4BAE22652AAA008E370B /* Assets.xcassets in Resources */, - 5379933A22AB1434006FEE71 /* LaunchScreen.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 536C4BB722652AAA008E370B /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 536C4B9D22652AA8008E370B /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 5379933D22AB14A5006FEE71 /* CustomSplitViewController.swift in Sources */, - 537AA995226534AA00905FCC /* MainViewController.swift in Sources */, - B6829565246C6FAC00BDA4A3 /* DefaultLayoutViewController.swift in Sources */, - 537AA993226534AA00905FCC /* ProductCommunicationService.swift in Sources */, - 537AA991226534AA00905FCC /* SingleWidgetViewController.swift in Sources */, - 53A5E37223441CC800313B15 /* CustomMapViewController.swift in Sources */, - 53A5E36D2344013700313B15 /* Helpers.swift in Sources */, - 537AA992226534AA00905FCC /* LogCenter.swift in Sources */, - 537AA996226534AA00905FCC /* WidgetsListViewController.swift in Sources */, - 537AA97D2265344300905FCC /* AppDelegate.swift in Sources */, - B6829568246C724E00BDA4A3 /* FPVCustomizations.swift in Sources */, - 537AA994226534AA00905FCC /* LogEntry.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 536C4BB522652AAA008E370B /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 536C4BBB22652AAA008E370B /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 536C4BA022652AA8008E370B /* UXSDKBetaSample */; - targetProxy = 536C4BBA22652AAA008E370B /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 5379933622AB1434006FEE71 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 5379933722AB1434006FEE71 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; - 5379933822AB1434006FEE71 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 5379933922AB1434006FEE71 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 536C4BC022652AAA008E370B /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_BITCODE = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)", - ); - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.2; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - }; - name = Debug; - }; - 536C4BC122652AAA008E370B /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_BITCODE = NO; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.2; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 536C4BC322652AAA008E370B /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 38D4M22449; - ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\"${PODS_ROOT}/DJI-SDK-iOS/iOS_Mobile_SDK\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/DJI-UXSDK-iOS\"", - ); - INFOPLIST_FILE = "$(SRCROOT)/UXSDKBetaSample/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.dji.UXSDKBetaSample; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "UXSDKBetaSample/UXSDKBetaSample-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 536C4BC422652AAA008E370B /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 38D4M22449; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = "$(SRCROOT)/UXSDKBetaSample/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.dji.UXSDKBetaSample; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "UXSDKBetaSample/UXSDKBetaSample-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; - 536C4BC622652AAA008E370B /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 38D4M22449; - INFOPLIST_FILE = UXSDKSampleTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.dji.DUXSDKOSTestHost.DJIUXSDKSampleTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/UXSDKBetaSample.app/UXSDKBetaSample"; - }; - name = Debug; - }; - 536C4BC722652AAA008E370B /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 38D4M22449; - INFOPLIST_FILE = UXSDKSampleTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.dji.DUXSDKOSTestHost.DJIUXSDKSampleTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/UXSDKBetaSample.app/UXSDKBetaSample"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 536C4B9C22652AA8008E370B /* Build configuration list for PBXProject "UXSDKBetaSample" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 536C4BC022652AAA008E370B /* Debug */, - 536C4BC122652AAA008E370B /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 536C4BC222652AAA008E370B /* Build configuration list for PBXNativeTarget "UXSDKBetaSample" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 536C4BC322652AAA008E370B /* Debug */, - 536C4BC422652AAA008E370B /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 536C4BC522652AAA008E370B /* Build configuration list for PBXNativeTarget "UXSDKBetaSampleTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 536C4BC622652AAA008E370B /* Debug */, - 536C4BC722652AAA008E370B /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 536C4B9922652AA8008E370B /* Project object */; -} diff --git a/UXSDKBetaSample/UXSDKBetaSample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/UXSDKBetaSample/UXSDKBetaSample.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 0ddd73e..0000000 --- a/UXSDKBetaSample/UXSDKBetaSample.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/UXSDKBetaSample/UXSDKBetaSample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/UXSDKBetaSample/UXSDKBetaSample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/UXSDKBetaSample/UXSDKBetaSample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/UXSDKBetaSample/UXSDKBetaSample.xcodeproj/xcshareddata/xcschemes/UXSDKBetaSampleTests.xcscheme b/UXSDKBetaSample/UXSDKBetaSample.xcodeproj/xcshareddata/xcschemes/UXSDKBetaSampleTests.xcscheme deleted file mode 100644 index 88d403d..0000000 --- a/UXSDKBetaSample/UXSDKBetaSample.xcodeproj/xcshareddata/xcschemes/UXSDKBetaSampleTests.xcscheme +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/UXSDKBetaSample/UXSDKBetaSample/Base.lproj/Main.storyboard b/UXSDKBetaSample/UXSDKBetaSample/Base.lproj/Main.storyboard deleted file mode 100644 index b8a8657..0000000 --- a/UXSDKBetaSample/UXSDKBetaSample/Base.lproj/Main.storyboard +++ /dev/null @@ -1,1054 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/UXSDKBetaSample/UXSDKBetaSample/CustomSplitViewController.swift b/UXSDKBetaSample/UXSDKBetaSample/CustomSplitViewController.swift deleted file mode 100644 index 7985f44..0000000 --- a/UXSDKBetaSample/UXSDKBetaSample/CustomSplitViewController.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// CustomSplitViewController.swift -// DJIUXSDK -// -// Copyright © 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. -// - -import UIKit - -class CustomSplitViewController: UISplitViewController, UISplitViewControllerDelegate { - - override func viewDidLoad() { - self.delegate = self - self.preferredDisplayMode = .allVisible - } - - func splitViewController( _ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool { - return true - } -} diff --git a/UXSDKBetaSample/UXSDKBetaSample/Services/LogEntry.swift b/UXSDKBetaSample/UXSDKBetaSample/Services/LogEntry.swift deleted file mode 100644 index 77aba09..0000000 --- a/UXSDKBetaSample/UXSDKBetaSample/Services/LogEntry.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// LogEntry.swift -// DJIUXSDK -// -// Copyright © 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. -// - -import UIKit - -class LogEntry: NSObject { - public var timestamp = Date() - public var message = "" -} diff --git a/UXSDKBetaSample/UXSDKBetaSample/UXSDKBetaSample-Bridging-Header.h b/UXSDKBetaSample/UXSDKBetaSample/UXSDKBetaSample-Bridging-Header.h deleted file mode 100644 index e11d920..0000000 --- a/UXSDKBetaSample/UXSDKBetaSample/UXSDKBetaSample-Bridging-Header.h +++ /dev/null @@ -1,3 +0,0 @@ -// -// Use this file to import your target's public headers that you would like to expose to Swift. -// diff --git a/UXSDKBetaSampleApp/LICENSE b/UXSDKBetaSampleApp/LICENSE new file mode 100644 index 0000000..613acc9 --- /dev/null +++ b/UXSDKBetaSampleApp/LICENSE @@ -0,0 +1,21 @@ +MIT License + +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. \ No newline at end of file diff --git a/UXSDKBetaSampleApp/Podfile b/UXSDKBetaSampleApp/Podfile new file mode 100644 index 0000000..378b0b7 --- /dev/null +++ b/UXSDKBetaSampleApp/Podfile @@ -0,0 +1,10 @@ +platform :ios, '11.0' + +target 'UXSDKBetaSampleApp' do + use_frameworks! + pod 'DJI-SDK-iOS', '~> 4.13' + pod 'DJIWidget', '~> 1.6.3' + pod 'DJI-UXSDK-iOS-Beta', '~> 0.3' + pod 'DJIFlySafeDatabaseResource', '~> 01.00.01.17' + pod 'iOS-Color-Picker' +end \ No newline at end of file diff --git a/UXSDKBetaSampleApp/UXSDKBetaSampleApp.xcodeproj/project.pbxproj b/UXSDKBetaSampleApp/UXSDKBetaSampleApp.xcodeproj/project.pbxproj new file mode 100644 index 0000000..4891822 --- /dev/null +++ b/UXSDKBetaSampleApp/UXSDKBetaSampleApp.xcodeproj/project.pbxproj @@ -0,0 +1,608 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 22CEB9D0238DB9CC0040A8B4 /* DefaultLayoutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22CEB9CF238DB9CB0040A8B4 /* DefaultLayoutViewController.swift */; }; + 3A2E644E248EE95500BFC6E5 /* WidgetsListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2E644D248EE95500BFC6E5 /* WidgetsListViewController.swift */; }; + 3A2E6450248EFB8B00BFC6E5 /* SingleWidgetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2E644F248EFB8B00BFC6E5 /* SingleWidgetViewController.swift */; }; + 3A2E6452248EFC6600BFC6E5 /* UIControl+DUXHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2E6451248EFC6600BFC6E5 /* UIControl+DUXHelpers.swift */; }; + 3A2E6454248EFD2000BFC6E5 /* CustomMapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2E6453248EFD2000BFC6E5 /* CustomMapViewController.swift */; }; + 3A2E65FC24904E2300BFC6E5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3A2E65FB24904E2300BFC6E5 /* Main.storyboard */; }; + 530DAD0F21E534C200E32774 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 530DAD0E21E534C200E32774 /* AppDelegate.swift */; }; + 530DAD1621E534C400E32774 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 530DAD1521E534C400E32774 /* Assets.xcassets */; }; + 530DAD1921E534C400E32774 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 530DAD1721E534C400E32774 /* LaunchScreen.storyboard */; }; + 530DAD7B21E53AB300E32774 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 530DAD7A21E53AB300E32774 /* MainViewController.swift */; }; + 530DADA221E574DB00E32774 /* LogCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 530DAD9F21E574DA00E32774 /* LogCenter.swift */; }; + 530DADA321E574DB00E32774 /* LogEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 530DADA021E574DB00E32774 /* LogEntry.swift */; }; + 530DADA421E574DB00E32774 /* ProductCommunicationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 530DADA121E574DB00E32774 /* ProductCommunicationService.swift */; }; + AC60CF7821F7C6EC00825022 /* DJISDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AC60CF7721F7C6EC00825022 /* DJISDK.framework */; }; + AC60CF9B21F7F0F900825022 /* CustomSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC60CF9A21F7F0F900825022 /* CustomSplitViewController.swift */; }; + B69F5942244FDFF3009D101A /* FPVCustomizations.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69F5941244FDFF3009D101A /* FPVCustomizations.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 530DAD2021E534C400E32774 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 530DAD0321E534C200E32774 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 530DAD0A21E534C200E32774; + remoteInfo = DJIUXSDKOSTestApp; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 530DADA921E5780000E32774 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 22CEB9CF238DB9CB0040A8B4 /* DefaultLayoutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultLayoutViewController.swift; sourceTree = ""; }; + 3A2E644D248EE95500BFC6E5 /* WidgetsListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetsListViewController.swift; sourceTree = ""; }; + 3A2E644F248EFB8B00BFC6E5 /* SingleWidgetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleWidgetViewController.swift; sourceTree = ""; }; + 3A2E6451248EFC6600BFC6E5 /* UIControl+DUXHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIControl+DUXHelpers.swift"; sourceTree = ""; }; + 3A2E6453248EFD2000BFC6E5 /* CustomMapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomMapViewController.swift; sourceTree = ""; }; + 3A2E65FB24904E2300BFC6E5 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; + 530DAD0B21E534C200E32774 /* UXSDKBetaSampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UXSDKBetaSampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 530DAD0E21E534C200E32774 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 530DAD1521E534C400E32774 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 530DAD1821E534C400E32774 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 530DAD1A21E534C400E32774 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 530DAD1F21E534C400E32774 /* UXSDKBetaSampleAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UXSDKBetaSampleAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 530DAD7A21E53AB300E32774 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; + 530DAD7E21E5408700E32774 /* DJISDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DJISDK.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 530DAD9F21E574DA00E32774 /* LogCenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogCenter.swift; sourceTree = ""; }; + 530DADA021E574DB00E32774 /* LogEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogEntry.swift; sourceTree = ""; }; + 530DADA121E574DB00E32774 /* ProductCommunicationService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductCommunicationService.swift; sourceTree = ""; }; + 530DADB921E6738600E32774 /* UXSDKSampleApp-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UXSDKSampleApp-Bridging-Header.h"; sourceTree = ""; }; + AC60CF7721F7C6EC00825022 /* DJISDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DJISDK.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + AC60CF9A21F7F0F900825022 /* CustomSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSplitViewController.swift; sourceTree = ""; }; + B69F5941244FDFF3009D101A /* FPVCustomizations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPVCustomizations.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 530DAD0821E534C200E32774 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + AC60CF7821F7C6EC00825022 /* DJISDK.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 530DAD1C21E534C400E32774 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 3A9775512489D45100E54E26 /* MainView */ = { + isa = PBXGroup; + children = ( + 530DAD1721E534C400E32774 /* LaunchScreen.storyboard */, + 3A2E65FB24904E2300BFC6E5 /* Main.storyboard */, + 530DAD7A21E53AB300E32774 /* MainViewController.swift */, + 530DAD9E21E574C400E32774 /* Services */, + ); + path = MainView; + sourceTree = ""; + }; + 3A9775522489D47200E54E26 /* SampleFeatures */ = { + isa = PBXGroup; + children = ( + 3A2E6453248EFD2000BFC6E5 /* CustomMapViewController.swift */, + AC60CF9A21F7F0F900825022 /* CustomSplitViewController.swift */, + 22CEB9CF238DB9CB0040A8B4 /* DefaultLayoutViewController.swift */, + 3A2E6451248EFC6600BFC6E5 /* UIControl+DUXHelpers.swift */, + 3A2E644F248EFB8B00BFC6E5 /* SingleWidgetViewController.swift */, + 3A2E644D248EE95500BFC6E5 /* WidgetsListViewController.swift */, + B69F5940244FDFD2009D101A /* Customizations */, + ); + path = SampleFeatures; + sourceTree = ""; + }; + 530DAD0221E534C200E32774 = { + isa = PBXGroup; + children = ( + 530DAD0D21E534C200E32774 /* UXSDKBetaSampleApp */, + 530DAD0C21E534C200E32774 /* Products */, + 530DAD7C21E5406300E32774 /* Frameworks */, + D9AAC44DB988F763D05D7DE7 /* Pods */, + ); + sourceTree = ""; + }; + 530DAD0C21E534C200E32774 /* Products */ = { + isa = PBXGroup; + children = ( + 530DAD0B21E534C200E32774 /* UXSDKBetaSampleApp.app */, + 530DAD1F21E534C400E32774 /* UXSDKBetaSampleAppTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 530DAD0D21E534C200E32774 /* UXSDKBetaSampleApp */ = { + isa = PBXGroup; + children = ( + 3A9775522489D47200E54E26 /* SampleFeatures */, + 3A9775512489D45100E54E26 /* MainView */, + 530DAD0E21E534C200E32774 /* AppDelegate.swift */, + 530DADB921E6738600E32774 /* UXSDKSampleApp-Bridging-Header.h */, + 530DAD1521E534C400E32774 /* Assets.xcassets */, + 530DAD1A21E534C400E32774 /* Info.plist */, + ); + path = UXSDKBetaSampleApp; + sourceTree = ""; + }; + 530DAD7C21E5406300E32774 /* Frameworks */ = { + isa = PBXGroup; + children = ( + AC60CF7721F7C6EC00825022 /* DJISDK.framework */, + 530DAD7E21E5408700E32774 /* DJISDK.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 530DAD9E21E574C400E32774 /* Services */ = { + isa = PBXGroup; + children = ( + 530DAD9F21E574DA00E32774 /* LogCenter.swift */, + 530DADA021E574DB00E32774 /* LogEntry.swift */, + 530DADA121E574DB00E32774 /* ProductCommunicationService.swift */, + ); + path = Services; + sourceTree = ""; + }; + B69F5940244FDFD2009D101A /* Customizations */ = { + isa = PBXGroup; + children = ( + B69F5941244FDFF3009D101A /* FPVCustomizations.swift */, + ); + path = Customizations; + sourceTree = ""; + }; + D9AAC44DB988F763D05D7DE7 /* Pods */ = { + isa = PBXGroup; + children = ( + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 530DAD0A21E534C200E32774 /* UXSDKBetaSampleApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 530DAD2821E534C400E32774 /* Build configuration list for PBXNativeTarget "UXSDKBetaSampleApp" */; + buildPhases = ( + 530DAD0721E534C200E32774 /* Sources */, + 530DAD0821E534C200E32774 /* Frameworks */, + 530DAD0921E534C200E32774 /* Resources */, + 530DADA921E5780000E32774 /* Embed Frameworks */, + 3A94CFDD240F192600B1DDA2 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = UXSDKBetaSampleApp; + productName = DJIUXSDKOSTestApp; + productReference = 530DAD0B21E534C200E32774 /* UXSDKBetaSampleApp.app */; + productType = "com.apple.product-type.application"; + }; + 530DAD1E21E534C400E32774 /* UXSDKBetaSampleAppTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 530DAD2B21E534C400E32774 /* Build configuration list for PBXNativeTarget "UXSDKBetaSampleAppTests" */; + buildPhases = ( + 530DAD1B21E534C400E32774 /* Sources */, + 530DAD1C21E534C400E32774 /* Frameworks */, + 530DAD1D21E534C400E32774 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 530DAD2121E534C400E32774 /* PBXTargetDependency */, + ); + name = UXSDKBetaSampleAppTests; + productName = DJIUXSDKOSTestAppTests; + productReference = 530DAD1F21E534C400E32774 /* UXSDKBetaSampleAppTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 530DAD0321E534C200E32774 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1010; + LastUpgradeCheck = 1010; + ORGANIZATIONNAME = DJI; + TargetAttributes = { + 530DAD0A21E534C200E32774 = { + CreatedOnToolsVersion = 10.1; + LastSwiftMigration = 1010; + }; + 530DAD1E21E534C400E32774 = { + CreatedOnToolsVersion = 10.1; + TestTargetID = 530DAD0A21E534C200E32774; + }; + }; + }; + buildConfigurationList = 530DAD0621E534C200E32774 /* Build configuration list for PBXProject "UXSDKBetaSampleApp" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 530DAD0221E534C200E32774; + productRefGroup = 530DAD0C21E534C200E32774 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 530DAD0A21E534C200E32774 /* UXSDKBetaSampleApp */, + 530DAD1E21E534C400E32774 /* UXSDKBetaSampleAppTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 530DAD0921E534C200E32774 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3A2E65FC24904E2300BFC6E5 /* Main.storyboard in Resources */, + 530DAD1921E534C400E32774 /* LaunchScreen.storyboard in Resources */, + 530DAD1621E534C400E32774 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 530DAD1D21E534C400E32774 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3A94CFDD240F192600B1DDA2 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 8; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 1; + shellPath = /bin/sh; + shellScript = "APP_PATH=\"${TARGET_BUILD_DIR}/${WRAPPER_NAME}\"\n\n# This script removes unwanted architectures in the FFmpeg framework for IPA signing for enterprise\n# testing.\nfind \"$APP_PATH\" -name 'FFmpeg.framework' -type d | while read -r FRAMEWORK\ndo\n FRAMEWORK_EXECUTABLE_NAME=$(defaults read \"$FRAMEWORK/Info.plist\" CFBundleExecutable)\n FRAMEWORK_EXECUTABLE_PATH=\"$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME\"\n echo \"Executable is $FRAMEWORK_EXECUTABLE_PATH\"\n\n EXTRACTED_ARCHS=()\n\n echo \"Extracting $ARCH from $FRAMEWORK_EXECUTABLE_NAME\"\n lipo -thin \"arm64\" \"$FRAMEWORK_EXECUTABLE_PATH\" -o \"$FRAMEWORK_EXECUTABLE_PATH-arm64\"\n echo \"Replacing original executable with thinned version\"\n echo \"rm $FRAMEWORK_EXECUTABLE_PATH\"\n rm \"$FRAMEWORK_EXECUTABLE_PATH\"\n echo \"mv $FRAMEWORK_EXECUTABLE_PATH-arm64 $FRAMEWORK_EXECUTABLE_PATH\"\n mv \"$FRAMEWORK_EXECUTABLE_PATH-arm64\" \"$FRAMEWORK_EXECUTABLE_PATH\"\n\ndone\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 530DAD0721E534C200E32774 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3A2E6450248EFB8B00BFC6E5 /* SingleWidgetViewController.swift in Sources */, + 530DADA421E574DB00E32774 /* ProductCommunicationService.swift in Sources */, + AC60CF9B21F7F0F900825022 /* CustomSplitViewController.swift in Sources */, + 530DADA221E574DB00E32774 /* LogCenter.swift in Sources */, + 3A2E6454248EFD2000BFC6E5 /* CustomMapViewController.swift in Sources */, + 3A2E644E248EE95500BFC6E5 /* WidgetsListViewController.swift in Sources */, + 530DADA321E574DB00E32774 /* LogEntry.swift in Sources */, + 3A2E6452248EFC6600BFC6E5 /* UIControl+DUXHelpers.swift in Sources */, + 530DAD7B21E53AB300E32774 /* MainViewController.swift in Sources */, + 530DAD0F21E534C200E32774 /* AppDelegate.swift in Sources */, + B69F5942244FDFF3009D101A /* FPVCustomizations.swift in Sources */, + 22CEB9D0238DB9CC0040A8B4 /* DefaultLayoutViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 530DAD1B21E534C400E32774 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 530DAD2121E534C400E32774 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 530DAD0A21E534C200E32774 /* UXSDKBetaSampleApp */; + targetProxy = 530DAD2021E534C400E32774 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 530DAD1721E534C400E32774 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 530DAD1821E534C400E32774 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 530DAD2621E534C400E32774 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_BITCODE = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 530DAD2721E534C400E32774 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_BITCODE = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 530DAD2921E534C400E32774 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = arm64; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + INFOPLIST_FILE = "$(SRCROOT)/UXSDKBetaSampleApp/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 0.3.0; + PRODUCT_BUNDLE_IDENTIFIER = com.dji.UXSDKBetaSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "UXSDKBetaSampleApp/UXSDKSampleApp-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.2; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 530DAD2A21E534C400E32774 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = arm64; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + INFOPLIST_FILE = "$(SRCROOT)/UXSDKBetaSampleApp/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 0.3.0; + PRODUCT_BUNDLE_IDENTIFIER = com.dji.UXSDKBetaSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "UXSDKBetaSampleApp/UXSDKSampleApp-Bridging-Header.h"; + SWIFT_VERSION = 4.2; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 530DAD2C21E534C400E32774 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 38D4M22449; + INFOPLIST_FILE = UXSDKSampleAppTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.dji.DJIUXSDKTestAppTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.2; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/UXSDKBetaSampleApp.app/UXSDKBetaSampleApp"; + }; + name = Debug; + }; + 530DAD2D21E534C400E32774 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 38D4M22449; + INFOPLIST_FILE = UXSDKSampleAppTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.dji.DJIUXSDKTestAppTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.2; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/UXSDKBetaSampleApp.app/UXSDKBetaSampleApp"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 530DAD0621E534C200E32774 /* Build configuration list for PBXProject "UXSDKBetaSampleApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 530DAD2621E534C400E32774 /* Debug */, + 530DAD2721E534C400E32774 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 530DAD2821E534C400E32774 /* Build configuration list for PBXNativeTarget "UXSDKBetaSampleApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 530DAD2921E534C400E32774 /* Debug */, + 530DAD2A21E534C400E32774 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 530DAD2B21E534C400E32774 /* Build configuration list for PBXNativeTarget "UXSDKBetaSampleAppTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 530DAD2C21E534C400E32774 /* Debug */, + 530DAD2D21E534C400E32774 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 530DAD0321E534C200E32774 /* Project object */; +} diff --git a/UXSDKBetaSample/UXSDKBetaSample.xcodeproj/xcshareddata/xcschemes/UXSDKBetaSample.xcscheme b/UXSDKBetaSampleApp/UXSDKBetaSampleApp.xcodeproj/xcshareddata/xcschemes/UXSDKBetaSampleApp.xcscheme similarity index 57% rename from UXSDKBetaSample/UXSDKBetaSample.xcodeproj/xcshareddata/xcschemes/UXSDKBetaSample.xcscheme rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp.xcodeproj/xcshareddata/xcschemes/UXSDKBetaSampleApp.xcscheme index 45b4834..70e805f 100644 --- a/UXSDKBetaSample/UXSDKBetaSample.xcodeproj/xcshareddata/xcschemes/UXSDKBetaSample.xcscheme +++ b/UXSDKBetaSampleApp/UXSDKBetaSampleApp.xcodeproj/xcshareddata/xcschemes/UXSDKBetaSampleApp.xcscheme @@ -1,6 +1,6 @@ + BlueprintIdentifier = "530DAD0A21E534C200E32774" + BuildableName = "UXSDKBetaSampleApp.app" + BlueprintName = "UXSDKBetaSampleApp" + ReferencedContainer = "container:UXSDKBetaSampleApp.xcodeproj"> @@ -28,28 +28,7 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> - - - - - - - - - - + BlueprintIdentifier = "530DAD0A21E534C200E32774" + BuildableName = "UXSDKBetaSampleApp.app" + BlueprintName = "UXSDKBetaSampleApp" + ReferencedContainer = "container:UXSDKBetaSampleApp.xcodeproj"> - - + BlueprintIdentifier = "530DAD0A21E534C200E32774" + BuildableName = "UXSDKBetaSampleApp.app" + BlueprintName = "UXSDKBetaSampleApp" + ReferencedContainer = "container:UXSDKBetaSampleApp.xcodeproj"> diff --git a/UXSDKBetaSample/UXSDKBetaSample/AppDelegate.swift b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/AppDelegate.swift similarity index 61% rename from UXSDKBetaSample/UXSDKBetaSample/AppDelegate.swift rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/AppDelegate.swift index 5c7a1e9..47c42d9 100644 --- a/UXSDKBetaSample/UXSDKBetaSample/AppDelegate.swift +++ b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/AppDelegate.swift @@ -1,26 +1,26 @@ // // AppDelegate.swift -// DJIUXSDK +// UXSDKSampleApp // -// Copyright © 2018-2020 DJI +// Copyright © 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. +// 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. // import UIKit @@ -29,9 +29,10 @@ import UIKit class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? + public var productCommManager = ProductCommunicationService() func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - ProductCommunicationService.shared.registerWithProduct() + self.productCommManager.registerWithProduct() return true } diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Aircraft.imageset/Aircraft@1x.png b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Aircraft.imageset/Aircraft@1x.png similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Aircraft.imageset/Aircraft@1x.png rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Aircraft.imageset/Aircraft@1x.png diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Aircraft.imageset/Aircraft@2x.png b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Aircraft.imageset/Aircraft@2x.png similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Aircraft.imageset/Aircraft@2x.png rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Aircraft.imageset/Aircraft@2x.png diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Aircraft.imageset/Aircraft@3x.png b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Aircraft.imageset/Aircraft@3x.png similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Aircraft.imageset/Aircraft@3x.png rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Aircraft.imageset/Aircraft@3x.png diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Aircraft.imageset/Contents.json b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Aircraft.imageset/Contents.json similarity index 70% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Aircraft.imageset/Contents.json rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Aircraft.imageset/Contents.json index 153623b..474faf3 100644 --- a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Aircraft.imageset/Contents.json +++ b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Aircraft.imageset/Contents.json @@ -1,17 +1,17 @@ { "images" : [ { - "filename" : "aircraft@1x.png", + "filename" : "Aircraft@1x.png", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "aircraft@2x.png", + "filename" : "Aircraft@2x.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "aircraft@3x.png", + "filename" : "Aircraft@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Contents.json b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 59% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Contents.json rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json index 6fa4eb1..4b21b09 100644 --- a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -2,110 +2,110 @@ "images" : [ { "idiom" : "iphone", - "size" : "20x20", - "scale" : "2x" + "scale" : "2x", + "size" : "20x20" }, { "idiom" : "iphone", - "size" : "20x20", - "scale" : "3x" + "scale" : "3x", + "size" : "20x20" }, { - "size" : "29x29", - "idiom" : "iphone", "filename" : "Icon-Small@2x-1.png", - "scale" : "2x" + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" }, { - "size" : "29x29", - "idiom" : "iphone", "filename" : "Icon-Small@3x.png", - "scale" : "3x" + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" }, { - "size" : "40x40", - "idiom" : "iphone", "filename" : "Icon-Spotlight-40@2x.png", - "scale" : "2x" + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" }, { - "size" : "40x40", - "idiom" : "iphone", "filename" : "Icon-Spotlight-40@3x.png", - "scale" : "3x" + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" }, { - "size" : "60x60", - "idiom" : "iphone", "filename" : "Icon-60@2x.png", - "scale" : "2x" + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" }, { - "size" : "60x60", - "idiom" : "iphone", "filename" : "Icon-60@3x.png", - "scale" : "3x" + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" }, { "idiom" : "ipad", - "size" : "20x20", - "scale" : "1x" + "scale" : "1x", + "size" : "20x20" }, { "idiom" : "ipad", - "size" : "20x20", - "scale" : "2x" + "scale" : "2x", + "size" : "20x20" }, { - "size" : "29x29", - "idiom" : "ipad", "filename" : "Icon-Small.png", - "scale" : "1x" + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" }, { - "size" : "29x29", - "idiom" : "ipad", "filename" : "Icon-Small@2x.png", - "scale" : "2x" + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" }, { - "size" : "40x40", - "idiom" : "ipad", "filename" : "Icon-Spotlight-40.png", - "scale" : "1x" + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" }, { - "size" : "40x40", - "idiom" : "ipad", "filename" : "Icon-Spotlight-40@2x-1.png", - "scale" : "2x" + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" }, { - "size" : "76x76", - "idiom" : "ipad", "filename" : "Icon-76.png", - "scale" : "1x" + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" }, { - "size" : "76x76", - "idiom" : "ipad", "filename" : "Icon-76@2x.png", - "scale" : "2x" + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" }, { - "size" : "83.5x83.5", - "idiom" : "ipad", "filename" : "Icon-iPadPro@2x.png", - "scale" : "2x" + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" }, { "idiom" : "ios-marketing", - "size" : "1024x1024", - "scale" : "1x" + "scale" : "1x", + "size" : "1024x1024" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-76.png b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-76.png similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-76.png rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-76.png diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-Small.png b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-Small.png similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-Small.png rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-Small.png diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40.png b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40.png similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40.png rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40.png diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x-1.png b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x-1.png similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x-1.png rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x-1.png diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x.png b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x.png similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x.png rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x.png diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@3x.png b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@3x.png similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@3x.png rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@3x.png diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-iPadPro@2x.png b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-iPadPro@2x.png similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/AppIcon.appiconset/Icon-iPadPro@2x.png rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-iPadPro@2x.png diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Close.imageset/Close.pdf b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Close.imageset/Close.pdf similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Close.imageset/Close.pdf rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Close.imageset/Close.pdf diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Close.imageset/Contents.json b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Close.imageset/Contents.json similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Close.imageset/Contents.json rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Close.imageset/Contents.json diff --git a/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Contents.json b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/HomePoint.imageset/Contents.json b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/HomePoint.imageset/Contents.json similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/HomePoint.imageset/Contents.json rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/HomePoint.imageset/Contents.json diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/HomePoint.imageset/HomeYellow@1x.png b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/HomePoint.imageset/HomeYellow@1x.png similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/HomePoint.imageset/HomeYellow@1x.png rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/HomePoint.imageset/HomeYellow@1x.png diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/HomePoint.imageset/HomeYellow@2x.png b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/HomePoint.imageset/HomeYellow@2x.png similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/HomePoint.imageset/HomeYellow@2x.png rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/HomePoint.imageset/HomeYellow@2x.png diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/HomePoint.imageset/HomeYellow@3x.png b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/HomePoint.imageset/HomeYellow@3x.png similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/HomePoint.imageset/HomeYellow@3x.png rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/HomePoint.imageset/HomeYellow@3x.png diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Lock.imageset/Contents.json b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Lock.imageset/Contents.json similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Lock.imageset/Contents.json rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Lock.imageset/Contents.json diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Lock.imageset/Lock@1x.png b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Lock.imageset/Lock@1x.png similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Lock.imageset/Lock@1x.png rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Lock.imageset/Lock@1x.png diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Lock.imageset/Lock@2x.png b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Lock.imageset/Lock@2x.png similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Lock.imageset/Lock@2x.png rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Lock.imageset/Lock@2x.png diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Lock.imageset/Lock@3x.png b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Lock.imageset/Lock@3x.png similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Lock.imageset/Lock@3x.png rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Lock.imageset/Lock@3x.png diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Wrench.imageset/Contents.json b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Wrench.imageset/Contents.json similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Wrench.imageset/Contents.json rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Wrench.imageset/Contents.json diff --git a/UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Wrench.imageset/Wrench.pdf b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Wrench.imageset/Wrench.pdf similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Assets.xcassets/Wrench.imageset/Wrench.pdf rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Assets.xcassets/Wrench.imageset/Wrench.pdf diff --git a/UXSDKBetaSample/UXSDKBetaSample/Info.plist b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Info.plist similarity index 88% rename from UXSDKBetaSample/UXSDKBetaSample/Info.plist rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/Info.plist index f15b5a1..cae5d9d 100644 --- a/UXSDKBetaSample/UXSDKBetaSample/Info.plist +++ b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/Info.plist @@ -2,12 +2,8 @@ - NSBluetoothAlwaysUsageDescription - Bluetooth Use is Required for Mobile SDK Osmo Support CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - UXSDKBetaSample CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -19,13 +15,15 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0 + $(MARKETING_VERSION) CFBundleVersion 1 DJISDKAppKey YOUR APP KEY GOES HERE LSRequiresIPhoneOS + NSBluetoothAlwaysUsageDescription + Bluetooth Use is Required for Mobile SDK Osmo Support NSLocationAlwaysAndWhenInUseUsageDescription Location Use is Required for Map Functionality NSLocationWhenInUseUsageDescription @@ -40,20 +38,17 @@ UISupportedExternalAccessoryProtocols - com.dji.video - com.dji.common com.dji.protocol + com.dji.common + com.dji.video UISupportedInterfaceOrientations - UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight diff --git a/UXSDKBetaSample/UXSDKBetaSample/Base.lproj/LaunchScreen.storyboard b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/MainView/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from UXSDKBetaSample/UXSDKBetaSample/Base.lproj/LaunchScreen.storyboard rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/MainView/Base.lproj/LaunchScreen.storyboard diff --git a/UXSDKBetaSampleApp/UXSDKBetaSampleApp/MainView/Main.storyboard b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/MainView/Main.storyboard new file mode 100644 index 0000000..8289b30 --- /dev/null +++ b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/MainView/Main.storyboard @@ -0,0 +1,827 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UXSDKBetaSample/UXSDKBetaSample/MainViewController.swift b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/MainView/MainViewController.swift similarity index 62% rename from UXSDKBetaSample/UXSDKBetaSample/MainViewController.swift rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/MainView/MainViewController.swift index 212970a..4f71dcd 100644 --- a/UXSDKBetaSample/UXSDKBetaSample/MainViewController.swift +++ b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/MainView/MainViewController.swift @@ -1,31 +1,30 @@ // // MainViewController.swift -// DJIUXSDK +// UXSDKSampleApp // -// Copyright © 2018-2020 DJI +// Copyright © 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. +// 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. // import UIKit import DJISDK -import DJIUXSDKBeta class MainViewController: UIViewController, UITextFieldDelegate, LogCenterListener { @@ -40,7 +39,7 @@ class MainViewController: UIViewController, UITextFieldDelegate, LogCenterListen @IBOutlet weak var useBridgeSwitch: UISwitch! @IBOutlet weak var bridgeIDField: UITextField! @IBOutlet weak var debugLogView: UITextView! - + override func viewDidLoad() { super.viewDidLoad() @@ -51,25 +50,26 @@ class MainViewController: UIViewController, UITextFieldDelegate, LogCenterListen override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - self.version.text = "\(DUXBetaSDKAttributes.sdkVersion())-\(DUXBetaSDKAttributes.sdkBuildNumber())" - self.bridgeIDField.text = ProductCommunicationService.shared.bridgeAppIP - self.useBridgeSwitch.isOn = ProductCommunicationService.shared.useBridge + + self.version.text = "\(DJISDKManager.sdkVersion())" + self.bridgeIDField.text = self.appDelegate.productCommManager.bridgeAppIP + self.useBridgeSwitch.isOn = self.appDelegate.productCommManager.useBridge } - + @IBAction func registerAction() { - ProductCommunicationService.shared.registerWithProduct() + self.appDelegate.productCommManager.registerWithProduct() } @IBAction func connectAction() { - ProductCommunicationService.shared.connectToProduct() + self.appDelegate.productCommManager.connectToProduct() } @IBAction func useBridgeAction(_ sender: UISwitch) { - ProductCommunicationService.shared.useBridge = sender.isOn - ProductCommunicationService.shared.productDisconnected() + self.appDelegate.productCommManager.useBridge = sender.isOn + self.appDelegate.productCommManager.productDisconnected() } - @IBAction func pushWidgetList() { + @IBAction func pushWidgetList(_ sender: Any) { if let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "WidgetSplitViewController") as? UISplitViewController { guard let navCon = vc.viewControllers.first as? UINavigationController else { return @@ -86,7 +86,7 @@ class MainViewController: UIViewController, UITextFieldDelegate, LogCenterListen } @objc func productCommunicationDidChange() { - if ProductCommunicationService.shared.registered { + if self.appDelegate.productCommManager.registered { self.registered.text = "YES" self.register.isHidden = true } else { @@ -94,16 +94,16 @@ class MainViewController: UIViewController, UITextFieldDelegate, LogCenterListen self.register.isHidden = false } - if ProductCommunicationService.shared.connected { + if self.appDelegate.productCommManager.connected { self.connected.text = "YES" self.connect.isHidden = true - guard let productNameKey = DJIProductKey(param: DJIProductParamModelName) else { + guard let produtNameKey = DJIProductKey(param: DJIProductParamModelName) else { NSLog("Failed to create product name key") return; } - let currentProductNameValue = DJISDKManager.keyManager()?.getValueFor(productNameKey) + let currentProductNameValue = DJISDKManager.keyManager()?.getValueFor(produtNameKey) if currentProductNameValue != nil { self.productName.text = currentProductNameValue?.stringValue @@ -111,16 +111,13 @@ class MainViewController: UIViewController, UITextFieldDelegate, LogCenterListen self.productName.text = "N/A" } - DJISDKManager.keyManager()?.startListeningForChanges(on: productNameKey, - withListener: self, - andUpdate: { (oldValue: DJIKeyedValue?, newValue: DJIKeyedValue?) in + DJISDKManager.keyManager()?.startListeningForChanges(on: produtNameKey, withListener: self, andUpdate: { (oldValue: DJIKeyedValue?, newValue: DJIKeyedValue?) in if newValue != nil { self.productName.text = newValue?.stringValue } else { self.productName.text = "N/A" } }) - } else { self.connected.text = "NO" self.connect.isHidden = false @@ -132,7 +129,7 @@ class MainViewController: UIViewController, UITextFieldDelegate, LogCenterListen func textFieldDidEndEditing(_ textField: UITextField) { if textField == self.bridgeIDField { - ProductCommunicationService.shared.bridgeAppIP = textField.text! + self.appDelegate.productCommManager.bridgeAppIP = textField.text! } } @@ -146,6 +143,7 @@ class MainViewController: UIViewController, UITextFieldDelegate, LogCenterListen } // MARK: - LogCenterListener + func logCenterContentDidChange() { self.debugLogView.text = LogCenter.default.fullLog() } diff --git a/UXSDKBetaSample/UXSDKBetaSample/Services/LogCenter.swift b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/MainView/Services/LogCenter.swift similarity index 70% rename from UXSDKBetaSample/UXSDKBetaSample/Services/LogCenter.swift rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/MainView/Services/LogCenter.swift index 6c11fb1..fea79e3 100644 --- a/UXSDKBetaSample/UXSDKBetaSample/Services/LogCenter.swift +++ b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/MainView/Services/LogCenter.swift @@ -1,26 +1,26 @@ // // LogCenter.swift -// DJIUXSDK +// UXSDKSampleApp // -// Copyright © 2018-2020 DJI +// Copyright © 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. +// 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. // import UIKit @@ -97,11 +97,19 @@ open class LogCenter: NSObject { override init() { super.init() - self.diskLogSaveTimer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { [unowned self] (timer) in - self.saveLogToDisk() + + if #available(iOS 10.0, *) { + self.diskLogSaveTimer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { [unowned self] (timer) in + self.saveLogToDisk() + } + } else { + self.diskLogSaveTimer = Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(saveLog), userInfo: nil, repeats: true) } } + @objc func saveLog() { + self.saveLogToDisk() + } func saveLogToDisk() { diff --git a/UXSDKBetaSampleApp/UXSDKBetaSampleApp/MainView/Services/LogEntry.swift b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/MainView/Services/LogEntry.swift new file mode 100644 index 0000000..c327e83 --- /dev/null +++ b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/MainView/Services/LogEntry.swift @@ -0,0 +1,32 @@ +// +// LogEntry.swift +// UXSDKSampleApp +// +// Copyright © 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. +// + +import UIKit + +class LogEntry: NSObject { + public var timestamp = Date() + public var message = "" + +} diff --git a/UXSDKBetaSample/UXSDKBetaSample/Services/ProductCommunicationService.swift b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/MainView/Services/ProductCommunicationService.swift similarity index 77% rename from UXSDKBetaSample/UXSDKBetaSample/Services/ProductCommunicationService.swift rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/MainView/Services/ProductCommunicationService.swift index 5f36357..0eca5db 100644 --- a/UXSDKBetaSample/UXSDKBetaSample/Services/ProductCommunicationService.swift +++ b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/MainView/Services/ProductCommunicationService.swift @@ -1,26 +1,26 @@ // -// ProductCommunicationService.swift -// DJIUXSDK +// UXSDKSampleApp.swift +// DJI Template // -// Copyright © 2018-2020 DJI +// Copyright © 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. +// 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. // import UIKit @@ -116,9 +116,9 @@ class SimulatorControl: NSObject { updateFrequency: 20, gpsSatellitesNumber: 12) { (error:Error?) in if let e = error { - print("Start Simulator Error: \(e)") + LogCenter.default.add("Start Simulator Error: \(e)") } else { - print("Start Simulator Command Acked") + LogCenter.default.add("Start Simulator Command Acked") } } @@ -138,9 +138,9 @@ class SimulatorControl: NSObject { withArguments: nil, andCompletion: { (didSucceed:Bool, value:DJIKeyedValue?, error:Error?) in if let e = error { - print("Stop Simulator Error: \(e)") + LogCenter.default.add("Stop Simulator Error: \(e)") } else { - print("Stop Simulator Command Acked") + LogCenter.default.add("Stop Simulator Command Acked") } }) @@ -176,10 +176,10 @@ class ProductCommunicationService: NSObject, DJISDKManagerDelegate { var useBridge = defaultUseBridgeSetting() { didSet { if useBridge == false { - NSLog("Disabling bridge mode...") + LogCenter.default.add("Disabling bridge mode...") DJISDKManager.disableBridgeMode() } else { - NSLog("Enabling bridge mode with IP \(self.bridgeAppIP)...") + LogCenter.default.add("Enabling bridge mode with IP \(self.bridgeAppIP)...") DJISDKManager.enableBridgeMode(withBridgeAppIP: self.bridgeAppIP) } } @@ -197,7 +197,7 @@ class ProductCommunicationService: NSObject, DJISDKManagerDelegate { return } DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - NSLog("Registering Product with registration ID: \(appKey)") + LogCenter.default.add("Registering Product with registration ID: \(appKey)") DJISDKManager.registerApp(with: self) } } @@ -205,16 +205,16 @@ class ProductCommunicationService: NSObject, DJISDKManagerDelegate { //MARK: - Start Connecting to Product open func connectToProduct() { if self.useBridge { - NSLog("Connecting to Product using debug IP address: \(bridgeAppIP)...") + LogCenter.default.add("Connecting to Product using debug IP address: \(bridgeAppIP)...") DJISDKManager.enableBridgeMode(withBridgeAppIP: bridgeAppIP) } else { - NSLog("Connecting to product...") + LogCenter.default.add("Connecting to product...") let startedResult = DJISDKManager.startConnectionToProduct() if startedResult { - NSLog("Connecting to product started successfully!") + LogCenter.default.add("Connecting to product started successfully!") } else { - NSLog("Connecting to product failed to start!") + LogCenter.default.add("Connecting to product failed to start!") } } } @@ -227,19 +227,19 @@ class ProductCommunicationService: NSObject, DJISDKManagerDelegate { self.simulatorControl.startListeningOnProductState() self.connectToProduct() } else { - NSLog("Error Registrating App: \(String(describing: error))") + LogCenter.default.add("Error Registrating App: \(String(describing: error))") } } func didUpdateDatabaseDownloadProgress(_ progress: Progress) { - print("Downloading Database Progress: \(progress.completedUnitCount) / \(progress.totalUnitCount)") + LogCenter.default.add("Downloading Database Progress: \(progress.completedUnitCount) / \(progress.totalUnitCount)") } func productConnected(_ product: DJIBaseProduct?) { if product != nil { self.connected = true NotificationCenter.default.post(Notification(name: Notification.Name(rawValue: ProductCommunicationServiceStateDidChange))) - NSLog("Connection to new product succeeded!") + LogCenter.default.add("Connection to new product succeeded!") self.connectedProduct = product } } @@ -249,7 +249,7 @@ class ProductCommunicationService: NSObject, DJISDKManagerDelegate { self.connected = false NotificationCenter.default.post(Notification(name: Notification.Name(rawValue: ProductCommunicationServiceStateDidChange))) - NSLog("Disconnected from product!"); + LogCenter.default.add("Disconnected from product!"); } //MARK: - Bridge Mode API diff --git a/UXSDKBetaSample/UXSDKBetaSample/CustomMapViewController.swift b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/SampleFeatures/CustomMapViewController.swift similarity index 99% rename from UXSDKBetaSample/UXSDKBetaSample/CustomMapViewController.swift rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/SampleFeatures/CustomMapViewController.swift index e63f032..791bac6 100644 --- a/UXSDKBetaSample/UXSDKBetaSample/CustomMapViewController.swift +++ b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/SampleFeatures/CustomMapViewController.swift @@ -1,6 +1,6 @@ // -// WidgetsListViewController.swift -// DJIUXSDK +// CustomMapViewController.swift +// UXSDKSampleApp // // Copyright © 2018-2020 DJI // @@ -515,3 +515,4 @@ class CustomMapViewController: UIViewController, UIPickerViewDelegate, UIPickerV } } + diff --git a/UXSDKBetaSampleApp/UXSDKBetaSampleApp/SampleFeatures/CustomSplitViewController.swift b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/SampleFeatures/CustomSplitViewController.swift new file mode 100644 index 0000000..5798277 --- /dev/null +++ b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/SampleFeatures/CustomSplitViewController.swift @@ -0,0 +1,38 @@ +// +// CustomSplitViewController.swift +// UXSDKSampleApp +// +// Copyright © 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. +// + +import UIKit + +class CustomSplitViewController: UISplitViewController, UISplitViewControllerDelegate { + + override func viewDidLoad() { + self.delegate = self + self.preferredDisplayMode = .allVisible + } + + func splitViewController( _ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool { + return true + } +} diff --git a/UXSDKBetaSample/UXSDKBetaSample/Customizations/FPVCustomizations.swift b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/SampleFeatures/Customizations/FPVCustomizations.swift similarity index 86% rename from UXSDKBetaSample/UXSDKBetaSample/Customizations/FPVCustomizations.swift rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/SampleFeatures/Customizations/FPVCustomizations.swift index 5ff4699..482324d 100644 --- a/UXSDKBetaSample/UXSDKBetaSample/Customizations/FPVCustomizations.swift +++ b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/SampleFeatures/Customizations/FPVCustomizations.swift @@ -1,6 +1,6 @@ // // FPVCustomizations.swift -// DJIUXSDKTestApp +// UXSDKSampleApp // // Copyright © 2018-2020 DJI // @@ -23,8 +23,41 @@ // SOFTWARE. // +import Foundation import DJIUXSDKBeta +class FPVVideoFeedWidget: FPVCustomizationWidget { + + override func detailsTitle() -> String { + return NSLocalizedString("Video Feed", comment: "Video Feed") + } + + override func oneOfListOptions() -> [String : Any] { + return ["current" : current, "list" : [ + NSLocalizedString("Primary", comment: "Primary"), + NSLocalizedString("Secondary", comment: "Secondary")]] + } + + override func selectionUpdate() -> (Int) -> Void { + return { [weak self] selectionIndex in + if let strongSelf = self { + switch selectionIndex { + case 0: strongSelf.fpvWidget?.videoFeed = DJISDKManager.videoFeeder()?.primaryVideoFeed + case 1: strongSelf.fpvWidget?.videoFeed = DJISDKManager.videoFeeder()?.secondaryVideoFeed + default: break + } + } + } + } + + fileprivate override var current: Int { + if let fpv = fpvWidget, let videoFeeder = DJISDKManager.videoFeeder() { + return fpv.videoFeed == videoFeeder.primaryVideoFeed ? 0 : 1 + } + return super.current + } +} + class FPVCameraNameVisibilityWidget: FPVCustomizationWidget { override func detailsTitle() -> String { @@ -138,7 +171,7 @@ class FPVGridlineTypeWidget: FPVCustomizationWidget { return { [weak self] selectionIndex in if let strongSelf = self { switch selectionIndex { - case 0: strongSelf.fpvWidget?.gridView.gridViewType = .Unknown + case 0: strongSelf.fpvWidget?.gridView.gridViewType = .None case 1: strongSelf.fpvWidget?.gridView.gridViewType = .Parallel case 2: strongSelf.fpvWidget?.gridView.gridViewType = .ParallelDiagonal default: break @@ -149,12 +182,7 @@ class FPVGridlineTypeWidget: FPVCustomizationWidget { fileprivate override var current: Int { if let fpv = fpvWidget { - switch fpv.gridView.gridViewType { - case .Unknown: return 0 - case .Parallel: return 1 - case .ParallelDiagonal: return 2 - default: break - } + return fpv.gridView.gridViewType.rawValue } return super.current } @@ -214,7 +242,7 @@ class FPVCenterViewTypeWidget: FPVCustomizationWidget { return { [weak self] selectionIndex in if let strongSelf = self { switch selectionIndex { - case 0: strongSelf.fpvWidget?.centerView.imageType = .Unknown + case 0: strongSelf.fpvWidget?.centerView.imageType = .None case 1: strongSelf.fpvWidget?.centerView.imageType = .Standard case 2: strongSelf.fpvWidget?.centerView.imageType = .Cross case 3: strongSelf.fpvWidget?.centerView.imageType = .NarrowCross @@ -230,17 +258,7 @@ class FPVCenterViewTypeWidget: FPVCustomizationWidget { fileprivate override var current: Int { if let fpv = fpvWidget { - switch fpv.centerView.imageType { - case .Unknown: return 0 - case .Standard: return 1 - case .Cross: return 2 - case .NarrowCross: return 3 - case .Frame: return 4 - case .FrameAndCross: return 5 - case .Square: return 6 - case .SquareAndCross: return 7 - default: break - } + return fpv.centerView.imageType.rawValue } return super.current } @@ -255,6 +273,7 @@ class FPVCenterViewColorWidget: FPVCustomizationWidget { override func oneOfListOptions() -> [String : Any] { return ["current" : current, "list" : [ + NSLocalizedString("None", comment: "None"), NSLocalizedString("White", comment: "White"), NSLocalizedString("Yellow", comment: "Yellow"), NSLocalizedString("Red", comment: "Red"), @@ -267,12 +286,13 @@ class FPVCenterViewColorWidget: FPVCustomizationWidget { return { [weak self] selectionIndex in if let strongSelf = self { switch selectionIndex { - case 0: strongSelf.fpvWidget?.centerView.colorType = .White - case 1: strongSelf.fpvWidget?.centerView.colorType = .Yellow - case 2: strongSelf.fpvWidget?.centerView.colorType = .Red - case 3: strongSelf.fpvWidget?.centerView.colorType = .Green - case 4: strongSelf.fpvWidget?.centerView.colorType = .Blue - case 5: strongSelf.fpvWidget?.centerView.colorType = .Black + case 0: strongSelf.fpvWidget?.centerView.colorType = .None + case 1: strongSelf.fpvWidget?.centerView.colorType = .White + case 2: strongSelf.fpvWidget?.centerView.colorType = .Yellow + case 3: strongSelf.fpvWidget?.centerView.colorType = .Red + case 4: strongSelf.fpvWidget?.centerView.colorType = .Green + case 5: strongSelf.fpvWidget?.centerView.colorType = .Blue + case 6: strongSelf.fpvWidget?.centerView.colorType = .Black default: break } } @@ -281,21 +301,13 @@ class FPVCenterViewColorWidget: FPVCustomizationWidget { fileprivate override var current: Int { if let fpv = fpvWidget { - switch fpv.centerView.colorType { - case .White: return 0 - case .Yellow: return 1 - case .Red: return 2 - case .Green: return 3 - case .Blue: return 4 - case .Black: return 5 - default: break - } + return fpv.centerView.colorType.rawValue } return super.current } } -class FPVCustomizationWidget: DUXListItemTitleWidget { +class FPVCustomizationWidget: DUXBetaListItemTitleWidget { var fpvWidget: DUXBetaFPVWidget? fileprivate var current: Int { @@ -306,7 +318,7 @@ class FPVCustomizationWidget: DUXListItemTitleWidget { return true } - override func detailListType() -> DUXListType { + override func detailListType() -> DUXBetaListType { .selectOne } diff --git a/UXSDKBetaSample/UXSDKBetaSample/DefaultLayoutViewController.swift b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/SampleFeatures/DefaultLayoutViewController.swift similarity index 68% rename from UXSDKBetaSample/UXSDKBetaSample/DefaultLayoutViewController.swift rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/SampleFeatures/DefaultLayoutViewController.swift index b4fcf93..bdc42b9 100644 --- a/UXSDKBetaSample/UXSDKBetaSample/DefaultLayoutViewController.swift +++ b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/SampleFeatures/DefaultLayoutViewController.swift @@ -1,40 +1,52 @@ // // DefaultLayoutViewController.swift -// UXSDKBetaSample +// UXSDKSampleApp // -// Copyright © 2018-2020 DJI +// Copyright © 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. +// 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 is a version of the FullDefaultLayoutViewController from the old SDKTestApp with + some major changes. This class is not derived from DUXBetaDefaultLayoutViewController. + The plan is for this to be a flat development class where widgets can be placed and positioned + as needed. It is an experimental playground during development. Once a more solidified layout + is defined, we can create a new class to finalize into. + As we define widgets for the OS release, we will be experimenting and finding better patterns + for placement. At this point we don't want to define strict layout hierarchies yet. + */ import UIKit import DJIUXSDKBeta -class DefaultLayoutViewController: UIViewController { +class DefaultLayoutViewController : UIViewController { + var appDelegate = UIApplication.shared.delegate as! AppDelegate let rootView = UIView() - var topBar: DUXBarPanelWidget? - var leftBar: DUXBarPanelWidget? + var topBar: DUXBetaBarPanelWidget? + var leftBar: DUXBetaBarPanelWidget? var fpvWidget: DUXBetaFPVWidget? - var systemStatusListPanel: DUXListPanelWidget? - var fpvCustomizationPanel: DUXListPanelWidget? + var systemStatusListPanel: DUXBetaListPanelWidget? + var fpvCustomizationPanel: DUXBetaListPanelWidget? + override var prefersStatusBarHidden: Bool { return true } + override func viewDidLoad() { super.viewDidLoad() self.view.addSubview(rootView) @@ -43,7 +55,6 @@ class DefaultLayoutViewController: UIViewController { rootView.translatesAutoresizingMaskIntoConstraints = false let statusBarInsets = UIApplication.shared.keyWindow!.safeAreaInsets - var edgeInsets: UIEdgeInsets if #available(iOS 11, * ) { edgeInsets = self.view.safeAreaInsets @@ -67,21 +78,22 @@ class DefaultLayoutViewController: UIViewController { } override func viewDidDisappear(_ animated: Bool) { - DUXStateChangeBroadcaster.instance().unregisterListener(self) + DUXBetaStateChangeBroadcaster.instance().unregisterListener(self) super.viewDidDisappear(animated) } - override var preferredStatusBarStyle: UIStatusBarStyle { - return .lightContent; - } - - @IBAction func close() { - self.dismiss(animated: true) + @IBAction func close () { + self.dismiss(animated: true) { + DJISDKManager.keyManager()?.stopAllListening(ofListeners: self) + } } - + + // Method for setting up the top bar. We may want to refactor this into another file at some point + // but for now, let's use this as out basic playground file. We are designing for lower complexity + // if possible than having multiple view containers defined in other classes and getting attached. func setupTopBar() { - let topBarWidget = DUXTopBarWidget() + let topBarWidget = DUXBetaTopBarWidget() let logoBtn = UIButton() logoBtn.translatesAutoresizingMaskIntoConstraints = false @@ -122,7 +134,7 @@ class DefaultLayoutViewController: UIViewController { topBar?.bottomMargin = margin topBar?.leftMargin = margin - DUXStateChangeBroadcaster.instance().registerListener(self, analyticsClassName: "DUXSystemStatusWidgetUIState") { [weak self] (analyticsData) in + DUXBetaStateChangeBroadcaster.instance().registerListener(self, analyticsClassName: "DUXBetaSystemStatusWidgetUIState") { [weak self] (analyticsData) in DispatchQueue.main.async { if let weakSelf = self { if let list = weakSelf.systemStatusListPanel { @@ -150,8 +162,8 @@ class DefaultLayoutViewController: UIViewController { } func setupLeftBar() { - let configSettings = DUXPanelWidgetConfiguration(type:.bar, variant:.vertical) - let leftSideBarWidget = DUXBarPanelWidget() + let configSettings = DUXBetaPanelWidgetConfiguration(type:.bar, variant:.vertical) + let leftSideBarWidget = DUXBetaBarPanelWidget() _ = leftSideBarWidget.configure(configSettings) leftSideBarWidget.install(in: self) @@ -172,16 +184,22 @@ class DefaultLayoutViewController: UIViewController { leftSideBarWidget.addRightWidgetArray([DUXBetaVisionWidget(), flightModeWidget, DUXBetaRemoteControllerSignalWidget(), DUXBetaVideoSignalWidget()] ) leftSideBarWidget.addLeftWidgetArray([DUXBetaGPSSignalWidget()] ) self.leftBar = leftSideBarWidget + } func setupSystemStatusList() { - let listPanel = DUXSystemStatusListWidget() - - listPanel.install(in: self) + let listPanel = DUXBetaSystemStatusListWidget() + listPanel.onCloseTapped( { [weak self] listPanel in + guard let self = self else { return } + if listPanel == self.systemStatusListPanel { + self.systemStatusListPanel = nil + } + }) + listPanel.install(in: self) // Very important to use this method listPanel.view.topAnchor.constraint(equalTo: self.topBar!.view.bottomAnchor).isActive = true - listPanel.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 00.0).isActive = true - listPanel.view.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 0.6).isActive = true + listPanel.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant:00.0).isActive = true + listPanel.view.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier:0.6).isActive = true listPanel.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -30.0).isActive = true systemStatusListPanel = listPanel @@ -200,6 +218,15 @@ class DefaultLayoutViewController: UIViewController { ]) self.fpvWidget = fpvWidget + + let rtkWidget = DUXBetaRTKWidget() + rtkWidget.view.translatesAutoresizingMaskIntoConstraints = false + rtkWidget.install(in: self) + + NSLayoutConstraint.activate([ + rtkWidget.view.topAnchor.constraint(equalTo: topBar?.view.bottomAnchor ?? view.topAnchor), + rtkWidget.view.centerXAnchor.constraint(equalTo: view.centerXAnchor) + ]) } func setupDashboard() { @@ -233,10 +260,14 @@ class DefaultLayoutViewController: UIViewController { fpvCustomizationPanel?.closeTapped() } - let listPanel = DUXListPanelWidget() - _ = listPanel.configure(DUXPanelWidgetConfiguration(type: .list, listKind: .widgets) + let listPanel = DUXBetaListPanelWidget() + _ = listPanel.configure(DUXBetaPanelWidgetConfiguration(type: .list, listKind: .widgets) .configureTitlebar(visible: true, withCloseBox: true, title: "FPV Customizations")) + listPanel.onCloseTapped({ [weak self] inPanel in + guard let self = self else { return } + self.fpvCustomizationPanel = nil; + }) listPanel.install(in: self) NSLayoutConstraint.activate([ @@ -275,13 +306,26 @@ class DefaultLayoutViewController: UIViewController { centerImageColor.setTitle("CenterPoint Color", andIconName: nil) centerImageColor.fpvWidget = fpvWidget - listPanel.addWidgetArray([cameraNameVisibility, - cameraSideVisibility, - gridlinesVisibility, - gridlinesType, - centerImageVisibility, - centerImageType, - centerImageColor]) + let hardwareDecodeWidget = DUXBetaListItemSwitchWidget() + hardwareDecodeWidget.setTitle("Enable Hardware Decode", andIconName: nil) + hardwareDecodeWidget.onOffSwitch.isOn = self.fpvWidget?.enableHardwareDecode ?? false + hardwareDecodeWidget.setSwitchAction { (enabled) in + self.fpvWidget?.enableHardwareDecode = enabled + } + + let fpvFeedCustomization = FPVVideoFeedWidget().setTitle("Video Feed", andIconName: nil) + fpvFeedCustomization.fpvWidget = fpvWidget + + listPanel.addWidgetArray([ + hardwareDecodeWidget, + fpvFeedCustomization, + cameraNameVisibility, + cameraSideVisibility, + gridlinesVisibility, + gridlinesType, + centerImageVisibility, + centerImageType, + centerImageColor]) fpvCustomizationPanel = listPanel } } diff --git a/UXSDKBetaSample/UXSDKBetaSample/SingleWidgetViewController.swift b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/SampleFeatures/SingleWidgetViewController.swift similarity index 97% rename from UXSDKBetaSample/UXSDKBetaSample/SingleWidgetViewController.swift rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/SampleFeatures/SingleWidgetViewController.swift index ea39632..f1be2c0 100644 --- a/UXSDKBetaSample/UXSDKBetaSample/SingleWidgetViewController.swift +++ b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/SampleFeatures/SingleWidgetViewController.swift @@ -1,6 +1,6 @@ // // SingleWidgetViewController.swift -// DJIUXSDK +// UXSDKSampleApp // // Copyright © 2018-2020 DJI // @@ -52,6 +52,7 @@ class SingleWidgetViewController: UIViewController { @IBOutlet var pinchToResizeLabel: UILabel! @IBOutlet var aspectRatioLabel: UILabel! @IBOutlet var currentSizeLabel: UILabel! + @IBOutlet var widgetDescriptionLabel: UILabel! var showCustomizationViewButton: UIButton? @@ -93,6 +94,11 @@ class SingleWidgetViewController: UIViewController { self.showCustomizationViewButton?.isHidden = !self.shouldShowCustomizationView widget.install(in: self) + + if let rtkWidget = widget as? DUXBetaRTKWidget { + // Show RTK Widget + rtkWidget.view.isHidden = false; + } self.configureConstraints() self.recursivelySetBorderForView(view: widget.view, borderEnabled: true, level: 0) @@ -135,7 +141,7 @@ class SingleWidgetViewController: UIViewController { doubleSizeButton.translatesAutoresizingMaskIntoConstraints = false doubleSizeButton.setTitle("Double Size", for: .normal) doubleSizeButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 25.0).isActive = true - self.doubleSizeControlAction = doubleSizeButton.connect(action: { + self.doubleSizeControlAction = doubleSizeButton.duxbeta_connect(action: { if let hc = self.heightConstraint { hc.constant = hc.constant * 2 self.view.setNeedsLayout() @@ -146,7 +152,7 @@ class SingleWidgetViewController: UIViewController { showCustomizationViewButton.translatesAutoresizingMaskIntoConstraints = false showCustomizationViewButton.setTitle("Show Customization", for: .normal) showCustomizationViewButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 25.0).isActive = true - self.showCustomizationViewAction = showCustomizationViewButton.connect(action: { + self.showCustomizationViewAction = showCustomizationViewButton.duxbeta_connect(action: { self.showMapCustomizationView() }, for: .touchUpInside) self.showCustomizationViewButton = showCustomizationViewButton diff --git a/UXSDKBetaSample/UXSDKBetaSample/Helpers.swift b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/SampleFeatures/UIControl+DUXHelpers.swift similarity index 69% rename from UXSDKBetaSample/UXSDKBetaSample/Helpers.swift rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/SampleFeatures/UIControl+DUXHelpers.swift index b0a64db..409089c 100644 --- a/UXSDKBetaSample/UXSDKBetaSample/Helpers.swift +++ b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/SampleFeatures/UIControl+DUXHelpers.swift @@ -1,6 +1,6 @@ // -// WidgetsListViewController.swift -// DJIUXSDK +// UIControl+DUXHelpers.swift +// UXSDKSampleApp // // Copyright © 2018-2020 DJI // @@ -26,13 +26,13 @@ import UIKit extension UIControl { - func connect(controlAction:ControlAction, for event:UIControl.Event) { + func duxbeta_connect(controlAction:ControlAction, for event:UIControl.Event) { self.addTarget(controlAction, action: #selector(ControlAction.performAction(_:)), for: event) } - func connect(controlAction:ControlAction, for events:[UIControl.Event]) { + func duxbeta_connect(controlAction:ControlAction, for events:[UIControl.Event]) { for event in events { self.addTarget(controlAction, action: #selector(ControlAction.performAction(_:)), @@ -40,30 +40,30 @@ extension UIControl { } } - func connect(action: @escaping ControlActionClosure, for event:UIControl.Event) -> ControlAction { + func duxbeta_connect(action: @escaping DUXBetaControlActionClosure, for event:UIControl.Event) -> ControlAction { let controlAction = ControlAction(action) - self.connect(controlAction: controlAction, - for: event) + self.duxbeta_connect(controlAction: controlAction, + for: event) return controlAction } - func connect(action: @escaping ControlActionClosure, for events:[UIControl.Event]) -> ControlAction { + func duxbeta_connect(action: @escaping DUXBetaControlActionClosure, for events:[UIControl.Event]) -> ControlAction { let controlAction = ControlAction(action) - self.connect(controlAction: controlAction, - for: events) + self.duxbeta_connect(controlAction: controlAction, + for: events) return controlAction } } -typealias ControlActionClosure = () -> Void +typealias DUXBetaControlActionClosure = () -> Void public final class ControlAction { - let action: ControlActionClosure - init(_ action: @escaping ControlActionClosure) { + let action: DUXBetaControlActionClosure + init(_ action: @escaping DUXBetaControlActionClosure) { self.action = action } diff --git a/UXSDKBetaSample/UXSDKBetaSample/WidgetsListViewController.swift b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/SampleFeatures/WidgetsListViewController.swift similarity index 95% rename from UXSDKBetaSample/UXSDKBetaSample/WidgetsListViewController.swift rename to UXSDKBetaSampleApp/UXSDKBetaSampleApp/SampleFeatures/WidgetsListViewController.swift index 9d8a67d..077bade 100644 --- a/UXSDKBetaSample/UXSDKBetaSample/WidgetsListViewController.swift +++ b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/SampleFeatures/WidgetsListViewController.swift @@ -1,6 +1,6 @@ // // WidgetsListViewController.swift -// DJIUXSDK +// UXSDKSampleApp // // Copyright © 2018-2020 DJI // @@ -47,6 +47,7 @@ class WidgetsListViewController: UITableViewController { "Map Widget" : "Widget that 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.", "Remaining Flight Time Widget" : "Widget that shows the remaining flight time information for the aircraft.", "Remote Control Signal Widget" : "Widget that displays the drone's connection strength with the remote controller.", + "RTK Widget" : "Composed widget used to display a switch that will enable or disable RTK and the state of RTK base station with a table of RTK positioning values.", "Simulator Indicator Widget" : "Widget that displays the state of the simulator indicator.", "System Status Widget" : "Widget that displays a string message indicating the system status of the aircraft.", "System Status List Widget" : "Composed widget used to construct and display a standardized system status list.", @@ -106,6 +107,10 @@ class WidgetsListViewController: UITableViewController { return DUXBetaRemoteControllerSignalWidget() } + let rtkWidgetClosure: () -> DUXBetaBaseWidget = { + return DUXBetaRTKWidget() + } + let simulatorIndicatorWidgetClosure: () -> DUXBetaBaseWidget = { return DUXBetaSimulatorIndicatorWidget() } @@ -115,7 +120,7 @@ class WidgetsListViewController: UITableViewController { } let systemStatusPanelWidgetClosure: () -> DUXBetaBaseWidget = { - return DUXSystemStatusListWidget() + return DUXBetaSystemStatusListWidget() } let videoSignalWidgetClosure: () -> DUXBetaBaseWidget = { @@ -127,7 +132,7 @@ class WidgetsListViewController: UITableViewController { } let topBarWidgetClosure: () -> DUXBetaBaseWidget = { - return DUXTopBarWidget() + return DUXBetaTopBarWidget() } return [ @@ -143,6 +148,7 @@ class WidgetsListViewController: UITableViewController { mapWidgetClosure, remainingFlightTimeWidgetClosure, remoteControlSignalWidgetClosure, + rtkWidgetClosure, simulatorIndicatorWidgetClosure, systemStatusWidgetClosure, systemStatusPanelWidgetClosure, @@ -165,6 +171,7 @@ class WidgetsListViewController: UITableViewController { ("Map Widget", true), ("Remaining Flight Time Widget", false), ("Remote Control Signal Widget", false), + ("RTK Widget", false), ("Simulator Indicator Widget", false), ("System Status Widget", false), ("System Status List Widget", false), diff --git a/UXSDKBetaSampleApp/UXSDKBetaSampleApp/UXSDKSampleApp-Bridging-Header.h b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/UXSDKSampleApp-Bridging-Header.h new file mode 100644 index 0000000..18919e8 --- /dev/null +++ b/UXSDKBetaSampleApp/UXSDKBetaSampleApp/UXSDKSampleApp-Bridging-Header.h @@ -0,0 +1,24 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// +// Copyright © 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. +// +