diff --git a/LGBluetooth/LGPeripheral.h b/LGBluetooth/LGPeripheral.h index 58b7d87..391e284 100755 --- a/LGBluetooth/LGPeripheral.h +++ b/LGBluetooth/LGPeripheral.h @@ -131,6 +131,12 @@ typedef void(^LGPeripheralRSSIValueCallback)(NSNumber *RSSI, NSError *error); */ @property (strong, nonatomic) NSDictionary *advertisingData; +// Added by Jianying Shi +/** + * Represent if current device was auto-connected in the past + */ +@property (nonatomic, readwrite) BOOL pastAutoConnected; + #pragma mark - Public Methods - /** diff --git a/LGBluetooth/LGPeripheral.m b/LGBluetooth/LGPeripheral.m index 8eacccd..f5949e1 100755 --- a/LGBluetooth/LGPeripheral.m +++ b/LGBluetooth/LGPeripheral.m @@ -107,9 +107,9 @@ - (void)connectWithTimeout:(NSUInteger)aWatchDogInterval completion:(LGPeripheralConnectionCallback)aCallback { [self connectWithCompletion:aCallback]; - [self performSelector:@selector(connectionWatchDogFired) - withObject:nil - afterDelay:aWatchDogInterval]; + [self performSelector : @selector(connectionWatchDogFired) + withObject : nil + afterDelay : aWatchDogInterval]; } - (void)disconnectWithCompletion:(LGPeripheralConnectionCallback)aCallback diff --git a/Mooshimeter.xcodeproj/project.pbxproj b/Mooshimeter.xcodeproj/project.pbxproj index 4373b14..970cf61 100644 --- a/Mooshimeter.xcodeproj/project.pbxproj +++ b/Mooshimeter.xcodeproj/project.pbxproj @@ -46,6 +46,21 @@ 4ACF8AE11A0C918B00EC402B /* SmartNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ACF8AE01A0C918B00EC402B /* SmartNavigationController.m */; }; 4ADDC5D51A882CFB00FC5093 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4ADDC5D41A882CFB00FC5093 /* Accelerate.framework */; }; 4AF4D8DD1A2BCD2600622F58 /* MeterSettingsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AF4D8DC1A2BCD2600622F58 /* MeterSettingsView.m */; }; + 842D9F641AE423B300ABBB89 /* LDProgressView.m in Sources */ = {isa = PBXBuildFile; fileRef = 842D9F631AE423B200ABBB89 /* LDProgressView.m */; }; + 842D9F671AE424ED00ABBB89 /* UIColor+RGBValues.m in Sources */ = {isa = PBXBuildFile; fileRef = 842D9F661AE424ED00ABBB89 /* UIColor+RGBValues.m */; }; + 8457CBB11B1CF64200572CFE /* ReachabilityManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 8457CBB01B1CF64200572CFE /* ReachabilityManager.m */; }; + 8457CBB51B1CF69F00572CFE /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 8457CBB41B1CF69F00572CFE /* Reachability.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 846403F21AF5603A002C78FF /* _MPWButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 846403E71AF5603A002C78FF /* _MPWButton.m */; }; + 846403F31AF5603A002C78FF /* MPBarsGraphView.m in Sources */ = {isa = PBXBuildFile; fileRef = 846403E91AF5603A002C78FF /* MPBarsGraphView.m */; }; + 846403F41AF5603A002C78FF /* MPGraphView.m in Sources */ = {isa = PBXBuildFile; fileRef = 846403EB1AF5603A002C78FF /* MPGraphView.m */; }; + 846403F51AF5603A002C78FF /* MPPlot.m in Sources */ = {isa = PBXBuildFile; fileRef = 846403ED1AF5603A002C78FF /* MPPlot.m */; }; + 846403F61AF5603A002C78FF /* UIBezierPath+curved.m in Sources */ = {isa = PBXBuildFile; fileRef = 846403EF1AF5603A002C78FF /* UIBezierPath+curved.m */; }; + 846403F71AF5603A002C78FF /* UIView+Frame.m in Sources */ = {isa = PBXBuildFile; fileRef = 846403F11AF5603A002C78FF /* UIView+Frame.m */; }; + 847121951AE61AC400035BB2 /* SVProgressHUD.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 847121921AE61AC400035BB2 /* SVProgressHUD.bundle */; }; + 847121961AE61AC400035BB2 /* SVProgressHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 847121941AE61AC400035BB2 /* SVProgressHUD.m */; }; + 847A13A11AE3B94900E36E17 /* KxMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 847A139E1AE3B94900E36E17 /* KxMenu.m */; }; + 847A13A21AE3B94900E36E17 /* UIImage+ImageEffects.m in Sources */ = {isa = PBXBuildFile; fileRef = 847A13A01AE3B94900E36E17 /* UIImage+ImageEffects.m */; }; + 847A42241AE774FF00D4F00D /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 847A42231AE774FF00D4F00D /* MessageUI.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -205,6 +220,34 @@ 4ADDC5D41A882CFB00FC5093 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; 4AF4D8DB1A2BCD2600622F58 /* MeterSettingsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MeterSettingsView.h; sourceTree = ""; }; 4AF4D8DC1A2BCD2600622F58 /* MeterSettingsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MeterSettingsView.m; sourceTree = ""; }; + 842D9F621AE423B200ABBB89 /* LDProgressView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LDProgressView.h; sourceTree = ""; }; + 842D9F631AE423B200ABBB89 /* LDProgressView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LDProgressView.m; sourceTree = ""; }; + 842D9F651AE424ED00ABBB89 /* UIColor+RGBValues.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIColor+RGBValues.h"; sourceTree = ""; }; + 842D9F661AE424ED00ABBB89 /* UIColor+RGBValues.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIColor+RGBValues.m"; sourceTree = ""; }; + 8457CBAF1B1CF64200572CFE /* ReachabilityManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ReachabilityManager.h; path = Mooshimeter/ReachabilityManager.h; sourceTree = ""; }; + 8457CBB01B1CF64200572CFE /* ReachabilityManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ReachabilityManager.m; path = Mooshimeter/ReachabilityManager.m; sourceTree = ""; }; + 8457CBB31B1CF69F00572CFE /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Reachability.h; sourceTree = ""; }; + 8457CBB41B1CF69F00572CFE /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Reachability.m; sourceTree = ""; }; + 846403E61AF5603A002C78FF /* _MPWButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _MPWButton.h; sourceTree = ""; }; + 846403E71AF5603A002C78FF /* _MPWButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _MPWButton.m; sourceTree = ""; }; + 846403E81AF5603A002C78FF /* MPBarsGraphView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPBarsGraphView.h; sourceTree = ""; }; + 846403E91AF5603A002C78FF /* MPBarsGraphView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPBarsGraphView.m; sourceTree = ""; }; + 846403EA1AF5603A002C78FF /* MPGraphView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPGraphView.h; sourceTree = ""; }; + 846403EB1AF5603A002C78FF /* MPGraphView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPGraphView.m; sourceTree = ""; }; + 846403EC1AF5603A002C78FF /* MPPlot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPlot.h; sourceTree = ""; }; + 846403ED1AF5603A002C78FF /* MPPlot.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPlot.m; sourceTree = ""; }; + 846403EE1AF5603A002C78FF /* UIBezierPath+curved.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+curved.h"; sourceTree = ""; }; + 846403EF1AF5603A002C78FF /* UIBezierPath+curved.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+curved.m"; sourceTree = ""; }; + 846403F01AF5603A002C78FF /* UIView+Frame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+Frame.h"; sourceTree = ""; }; + 846403F11AF5603A002C78FF /* UIView+Frame.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+Frame.m"; sourceTree = ""; }; + 847121921AE61AC400035BB2 /* SVProgressHUD.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = SVProgressHUD.bundle; sourceTree = ""; }; + 847121931AE61AC400035BB2 /* SVProgressHUD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVProgressHUD.h; sourceTree = ""; }; + 847121941AE61AC400035BB2 /* SVProgressHUD.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVProgressHUD.m; sourceTree = ""; }; + 847A139D1AE3B94900E36E17 /* KxMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KxMenu.h; sourceTree = ""; }; + 847A139E1AE3B94900E36E17 /* KxMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KxMenu.m; sourceTree = ""; }; + 847A139F1AE3B94900E36E17 /* UIImage+ImageEffects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+ImageEffects.h"; sourceTree = ""; }; + 847A13A01AE3B94900E36E17 /* UIImage+ImageEffects.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+ImageEffects.m"; sourceTree = ""; }; + 847A42231AE774FF00D4F00D /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -212,6 +255,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 847A42241AE774FF00D4F00D /* MessageUI.framework in Frameworks */, 4ADDC5D51A882CFB00FC5093 /* Accelerate.framework in Frameworks */, 4ABE54161A07FF0000277456 /* CoreGraphics.framework in Frameworks */, 4ABE54151A07FEE900277456 /* Foundation.framework in Frameworks */, @@ -237,6 +281,7 @@ 0C63852017D694DC00D3FB31 = { isa = PBXGroup; children = ( + 847A13941AE3B27800E36E17 /* Vendor */, 4A6B13C91A2EA97C000AF17B /* Default-568h@2x.png */, 4AA9EA1E1A12B53F0031C12B /* LGBluetooth */, 4ABE54171A080A8300277456 /* OAD */, @@ -261,6 +306,7 @@ 0C63852B17D694DC00D3FB31 /* Frameworks */ = { isa = PBXGroup; children = ( + 847A42231AE774FF00D4F00D /* MessageUI.framework */, 4ADDC5D41A882CFB00FC5093 /* Accelerate.framework */, 0C78971F17E3762000D1439E /* QuartzCore.framework */, 0C7895C817D786A400D1439E /* CoreBluetooth.framework */, @@ -502,6 +548,8 @@ 4ABE54191A080A9000277456 /* OADProfile.m */, 4ABE541C1A080A9000277456 /* OADProgressViewController.h */, 4ABE541D1A080A9000277456 /* OADProgressViewController.m */, + 8457CBAF1B1CF64200572CFE /* ReachabilityManager.h */, + 8457CBB01B1CF64200572CFE /* ReachabilityManager.m */, ); name = OAD; sourceTree = ""; @@ -515,6 +563,83 @@ name = GraphSettingsView; sourceTree = ""; }; + 842D9F611AE423B200ABBB89 /* LDProgressView */ = { + isa = PBXGroup; + children = ( + 842D9F651AE424ED00ABBB89 /* UIColor+RGBValues.h */, + 842D9F661AE424ED00ABBB89 /* UIColor+RGBValues.m */, + 842D9F621AE423B200ABBB89 /* LDProgressView.h */, + 842D9F631AE423B200ABBB89 /* LDProgressView.m */, + ); + name = LDProgressView; + path = Mooshimeter/Vendor/LDProgressView; + sourceTree = SOURCE_ROOT; + }; + 8457CBB21B1CF69F00572CFE /* Reachability */ = { + isa = PBXGroup; + children = ( + 8457CBB31B1CF69F00572CFE /* Reachability.h */, + 8457CBB41B1CF69F00572CFE /* Reachability.m */, + ); + name = Reachability; + path = Mooshimeter/Vendor/Reachability; + sourceTree = SOURCE_ROOT; + }; + 846403E51AF5603A002C78FF /* MPPlot */ = { + isa = PBXGroup; + children = ( + 846403E61AF5603A002C78FF /* _MPWButton.h */, + 846403E71AF5603A002C78FF /* _MPWButton.m */, + 846403E81AF5603A002C78FF /* MPBarsGraphView.h */, + 846403E91AF5603A002C78FF /* MPBarsGraphView.m */, + 846403EA1AF5603A002C78FF /* MPGraphView.h */, + 846403EB1AF5603A002C78FF /* MPGraphView.m */, + 846403EC1AF5603A002C78FF /* MPPlot.h */, + 846403ED1AF5603A002C78FF /* MPPlot.m */, + 846403EE1AF5603A002C78FF /* UIBezierPath+curved.h */, + 846403EF1AF5603A002C78FF /* UIBezierPath+curved.m */, + 846403F01AF5603A002C78FF /* UIView+Frame.h */, + 846403F11AF5603A002C78FF /* UIView+Frame.m */, + ); + name = MPPlot; + path = Mooshimeter/Vendor/MPPlot; + sourceTree = SOURCE_ROOT; + }; + 847121911AE61AC400035BB2 /* SVProgressHUD */ = { + isa = PBXGroup; + children = ( + 847121921AE61AC400035BB2 /* SVProgressHUD.bundle */, + 847121931AE61AC400035BB2 /* SVProgressHUD.h */, + 847121941AE61AC400035BB2 /* SVProgressHUD.m */, + ); + name = SVProgressHUD; + path = Mooshimeter/Vendor/SVProgressHUD; + sourceTree = SOURCE_ROOT; + }; + 847A13941AE3B27800E36E17 /* Vendor */ = { + isa = PBXGroup; + children = ( + 8457CBB21B1CF69F00572CFE /* Reachability */, + 846403E51AF5603A002C78FF /* MPPlot */, + 847121911AE61AC400035BB2 /* SVProgressHUD */, + 842D9F611AE423B200ABBB89 /* LDProgressView */, + 847A139C1AE3B94900E36E17 /* KxMenu */, + ); + path = Vendor; + sourceTree = ""; + }; + 847A139C1AE3B94900E36E17 /* KxMenu */ = { + isa = PBXGroup; + children = ( + 847A139D1AE3B94900E36E17 /* KxMenu.h */, + 847A139E1AE3B94900E36E17 /* KxMenu.m */, + 847A139F1AE3B94900E36E17 /* UIImage+ImageEffects.h */, + 847A13A01AE3B94900E36E17 /* UIImage+ImageEffects.m */, + ); + name = KxMenu; + path = Mooshimeter/Vendor/KxMenu; + sourceTree = SOURCE_ROOT; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -565,11 +690,6 @@ LastTestingUpgradeCheck = 0610; LastUpgradeCheck = 0610; ORGANIZATIONNAME = mooshim; - TargetAttributes = { - 0C63852817D694DC00D3FB31 = { - DevelopmentTeam = S9QZRBTSZT; - }; - }; }; buildConfigurationList = 0C63852417D694DC00D3FB31 /* Build configuration list for PBXProject "Mooshimeter" */; compatibilityVersion = "Xcode 3.2"; @@ -594,6 +714,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 847121951AE61AC400035BB2 /* SVProgressHUD.bundle in Resources */, 0C63853717D694DC00D3FB31 /* InfoPlist.strings in Resources */, 4A6B13CC1A2FE808000AF17B /* Images.xcassets in Resources */, 0C349D731885252E00B6D64F /* Default.png in Resources */, @@ -648,25 +769,38 @@ buildActionMask = 2147483647; files = ( 4AA9EA301A12B54E0031C12B /* LGService.m in Sources */, + 846403F31AF5603A002C78FF /* MPBarsGraphView.m in Sources */, + 846403F51AF5603A002C78FF /* MPPlot.m in Sources */, 4A6716D71A8AC85E000A4F52 /* ScanSettingsView.m in Sources */, 4AA9EA2D1A12B54E0031C12B /* LGCentralManager.m in Sources */, 0C63853917D694DC00D3FB31 /* main.m in Sources */, 4A777EA71A09B2F2007FEEB9 /* ScanTableViewCell.m in Sources */, + 847121961AE61AC400035BB2 /* SVProgressHUD.m in Sources */, 4AA9EA2C1A12B54E0031C12B /* CBUUID+StringExtraction.m in Sources */, 4ABE541E1A080A9000277456 /* OADProfile.m in Sources */, 4A5F55581AB784B100212D1D /* MooshimeterDeviceSimulator.m in Sources */, + 847A13A21AE3B94900E36E17 /* UIImage+ImageEffects.m in Sources */, 4ACF8AE11A0C918B00EC402B /* SmartNavigationController.m in Sources */, + 846403F41AF5603A002C78FF /* MPGraphView.m in Sources */, + 8457CBB11B1CF64200572CFE /* ReachabilityManager.m in Sources */, 4AF4D8DD1A2BCD2600622F58 /* MeterSettingsView.m in Sources */, 0C63853D17D694DC00D3FB31 /* AppDelegate.m in Sources */, + 846403F61AF5603A002C78FF /* UIBezierPath+curved.m in Sources */, 0C63854C17D694DC00D3FB31 /* ScanViewController.m in Sources */, + 846403F21AF5603A002C78FF /* _MPWButton.m in Sources */, + 842D9F641AE423B300ABBB89 /* LDProgressView.m in Sources */, 4AA9EA2F1A12B54E0031C12B /* LGPeripheral.m in Sources */, + 846403F71AF5603A002C78FF /* UIView+Frame.m in Sources */, 4AA9EA2E1A12B54E0031C12B /* LGCharacteristic.m in Sources */, + 8457CBB51B1CF69F00572CFE /* Reachability.m in Sources */, 0C63854F17D694DC00D3FB31 /* MeterViewController.m in Sources */, 4AA9EA311A12B54E0031C12B /* LGUtils.m in Sources */, 0C78962417D7E56B00D1439E /* BLEUtility.m in Sources */, 4ABE54201A080A9000277456 /* OADProgressViewController.m in Sources */, 4AAB4A261A1052C900794EC8 /* ChannelView.m in Sources */, + 842D9F671AE424ED00ABBB89 /* UIColor+RGBValues.m in Sources */, 4AC27CC31A19C0C3008CF592 /* GraphSettingsView.m in Sources */, + 847A13A11AE3B94900E36E17 /* KxMenu.m in Sources */, 0C78967717DE74B800D1439E /* MooshimeterDevice.m in Sources */, 0C78972317E378FE00D1439E /* GraphViewController.m in Sources */, ); @@ -782,6 +916,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -790,6 +925,7 @@ GCC_PREFIX_HEADER = "Mooshimeter/Mooshimeter-Prefix.pch"; INFOPLIST_FILE = "Mooshimeter/Mooshimeter-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 6.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)", @@ -800,10 +936,11 @@ "-all_load", ); PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = ""; - "PROVISIONING_PROFILE[sdk=iphoneos*]" = ""; + PROVISIONING_PROFILE = "0b2a1bee-e707-40a3-b180-a4b90b179733"; + "PROVISIONING_PROFILE[sdk=iphoneos*]" = "0b2a1bee-e707-40a3-b180-a4b90b179733"; SDKROOT = iphoneos; SEPARATE_STRIP = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; WRAPPER_EXTENSION = app; }; @@ -814,6 +951,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -821,6 +959,7 @@ GCC_PREFIX_HEADER = "Mooshimeter/Mooshimeter-Prefix.pch"; INFOPLIST_FILE = "Mooshimeter/Mooshimeter-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 6.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)", @@ -831,7 +970,7 @@ "-all_load", ); PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE = "0b2a1bee-e707-40a3-b180-a4b90b179733"; "PROVISIONING_PROFILE[sdk=iphoneos*]" = ""; SDKROOT = iphoneos; SEPARATE_STRIP = NO; diff --git a/Mooshimeter.xcodeproj/xcuserdata/admin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Mooshimeter.xcodeproj/xcuserdata/admin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..ea51c70 --- /dev/null +++ b/Mooshimeter.xcodeproj/xcuserdata/admin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,271 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mooshimeter.xcodeproj/xcuserdata/admin.xcuserdatad/xcschemes/Mooshimeter.xcscheme b/Mooshimeter.xcodeproj/xcuserdata/admin.xcuserdatad/xcschemes/Mooshimeter.xcscheme new file mode 100644 index 0000000..fb3a6ab --- /dev/null +++ b/Mooshimeter.xcodeproj/xcuserdata/admin.xcuserdatad/xcschemes/Mooshimeter.xcscheme @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mooshimeter/AppDelegate.h b/Mooshimeter/AppDelegate.h index aa85c99..4b346fc 100644 --- a/Mooshimeter/AppDelegate.h +++ b/Mooshimeter/AppDelegate.h @@ -52,7 +52,10 @@ along with this program. If not, see . @property (strong,nonatomic) UILabel* bat_label; @property (strong,nonatomic) UILabel* rssi_label; -@property (strong, nonatomic) UIButton* settings_button; +@property (strong,nonatomic) UIButton* settings_button; + +// By Jianying +@property (strong,nonatomic) NSString* mlastConnectedUDID; -(UINavigationController*)getNav; -(void)scanForMeters; diff --git a/Mooshimeter/AppDelegate.m b/Mooshimeter/AppDelegate.m index 338aa17..dc2cf94 100644 --- a/Mooshimeter/AppDelegate.m +++ b/Mooshimeter/AppDelegate.m @@ -17,6 +17,9 @@ ***************************/ #import "AppDelegate.h" +#import "Vendor/SVProgressHUD/SVProgressHUD.h" + +#define SHOW_WAIT_DIALOG(MESSAGE) [SVProgressHUD showWithStatus:MESSAGE maskType:SVProgressHUDMaskTypeClear] @implementation AppDelegate @@ -64,8 +67,15 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( b = [UIButton buttonWithType:UIButtonTypeSystem]; b.userInteractionEnabled = YES; [b addTarget:self action:@selector(settings_button_press) forControlEvents:UIControlEventTouchUpInside]; + +#if 1 // Commented if needs classic button [b.titleLabel setFont:[UIFont systemFontOfSize:24]]; [b setTitle:@"\u2699" forState:UIControlStateNormal]; +#else + [b setFrame:CGRectMake(0, 0, 44, 30)]; + [b setImage:[UIImage imageNamed:@"common_setting"] forState:UIControlStateNormal]; +#endif + [b setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; [[b layer] setBorderWidth:2]; [[b layer] setBorderColor:[UIColor darkGrayColor].CGColor]; @@ -80,6 +90,12 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( self.oad_profile.navCtrl = self.nav; [self.window setRootViewController:self.nav]; + + // By Jianying Shi. + // Install Key Window + [self.window makeKeyAndVisible]; + self.mlastConnectedUDID = nil; + return YES; } @@ -87,12 +103,23 @@ - (void)applicationWillResignActive:(UIApplication *)application { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. + + // By Jianying Shi + // 05/08/2015 + if( g_meter ) // It means, current mooshim device is connected + { + // Save current peripheral UUID to dictionary + self.mlastConnectedUDID = g_meter.p.UUIDString; + + // Trying to disconnect. + [g_meter disconnect:nil]; + } } - (void)applicationDidEnterBackground:(UIApplication *)application { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits } - (void)applicationWillEnterForeground:(UIApplication *)application @@ -103,6 +130,17 @@ - (void)applicationWillEnterForeground:(UIApplication *)application - (void)applicationDidBecomeActive:(UIApplication *)application { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + + // By Jianying Shi + // 05/08/2015 + if( self.mlastConnectedUDID != nil && self.mlastConnectedUDID.length > 0 ) // Previously background disconnect + { + // Refresh Device List && + // Display Please wait dialg + [SVProgressHUD showWithStatus:@"Restoring connection state, Please wait for a second."]; + + [self handleScanViewRefreshRequest]; + } } - (void)applicationWillTerminate:(UIApplication *)application @@ -135,34 +173,43 @@ - (void)scanForMeters NSArray* services = [NSArray arrayWithObjects:[BLEUtility expandToMooshimUUID:METER_SERVICE_UUID], [BLEUtility expandToMooshimUUID:OAD_SERVICE_UUID], [CBUUID UUIDWithData:[NSData dataWithBytes:&tmp length:2]], nil]; NSLog(@"Refresh requested"); - [self.scan_vc.refreshControl beginRefreshing]; - NSTimer* refresh_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.scan_vc selector:@selector(reloadData) userInfo:nil repeats:YES]; - //self.scan_vc.title = @"Scan in progress..."; - self.nav.navigationItem.title = @"Scan in progress..."; - [c scanForPeripheralsByInterval:5 services:services options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @YES} completion:^(NSArray *peripherals) { NSLog(@"Found: %d", (int)peripherals.count); [refresh_timer invalidate]; - [self.scan_vc.refreshControl endRefreshing]; [self.scan_vc reloadData]; - self.nav.navigationItem.title = @"Pull down to scan"; + // By Jianying Shi + // Auto-Connect + [self.scan_vc performAutoConnect]; }]; } #pragma mark ScanViewDelegate -(void)handleScanViewRefreshRequest { + + // By Jianying Shi. + // Check if application is in background, no need to check + UIApplicationState state = [[UIApplication sharedApplication] applicationState]; + if( state == UIApplicationStateBackground || state == UIApplicationStateInactive ) + { + //Do checking here. + NSLog(@"App is background mode = %d", state); + return; + } + [self scanForMeters]; } -(void)handleScanViewSelect:(LGPeripheral*)p { + switch( p.cbPeripheral.state ) { case CBPeripheralStateConnected:{ + NSLog(@"Already connected, igonre connection request & disconnecting..."); // We selected one that's already connected, disconnect [p disconnectWithCompletion:^(NSError *error) { [self meterDisconnected]; @@ -186,8 +233,11 @@ -(void)handleScanViewSelect:(LGPeripheral*)p { } [g_meter connect]; - [self.scan_vc reloadData]; - break;} + + // Commented by Jianying Shi + // [self.scan_vc reloadData]; + break; + } } } @@ -203,6 +253,37 @@ -(void)switchToGraphView:(UIDeviceOrientation)new_o { [self.nav pushViewController:self.graph_vc animated:YES]; } } +-(void)updateFirmwareIfNeeded +{ + if ( g_meter == nil ) + return; + + if( g_meter->oad_mode ) { + // We connected to a meter in OAD mode as requested previously. Update firmware. + NSLog(@"Connected in OAD mode"); + if( YES || [g_meter getAdvertisedBuildTime] != self.oad_profile->imageHeader.build_time ) { + NSLog(@"Starting upload"); + + // Add by Jianying Shi + // Display updating state. + [SVProgressHUD showWithStatus:@"Updating firmware..." maskType : SVProgressHUDMaskTypeClear]; + [self.oad_profile setCompletionBlock:^(NSError* error) { + [SVProgressHUD dismiss]; + }]; + + [self.oad_profile startUpload]; + } else { + NSLog(@"We connected to an up-to-date meter in OAD mode. Disconnecting."); + [g_meter.p disconnectWithCompletion:nil]; + } + } + else if( [g_meter getAdvertisedBuildTime] < self.oad_profile->imageHeader.build_time ) { + // Require a firmware update! + NSLog(@"FIRMWARE UPDATE REQUIRED."); + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Firmware Update" message:@"This meter requires a firmware update. This will take about a minute. Upgrade now?" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Upgrade Now", nil]; + [alert show]; + } +} #pragma mark ScatterViewControllerDelegate @@ -220,8 +301,15 @@ -(void)switchToMeterView { #pragma mark MooshimeterDeviceDelegate -(void)finishedMeterSetup { + + // Delete SVProgressHUD, if needed. + [SVProgressHUD dismiss]; + NSLog(@"Finished meter setup"); - [self.scan_vc reloadData]; + + // ??? Commented By Jianying Shi + // [self.scan_vc reloadData]; + if( g_meter->oad_mode ) { // We connected to a meter in OAD mode as requested previously. Update firmware. NSLog(@"Connected in OAD mode"); @@ -262,8 +350,18 @@ -(void)meterDisconnected { [self.bat_label setText:@""]; [self.rssi_label setText:@""]; [NSTimer cancelPreviousPerformRequestsWithTarget:self selector:@selector(updateRSSI) object:nil]; + + // By Jianying Shi + // Setup Portrait Orientation + NSNumber *value = [NSNumber numberWithInt:UIInterfaceOrientationPortrait]; + [[UIDevice currentDevice] setValue:value forKey:@"orientation"]; + + // Check if current view controller is scan view controller [self.nav popToViewController:self.scan_vc animated:YES]; - [self.scan_vc reloadData]; + + // No need??? Jianying Shi + // [self.scan_vc reloadData]; + g_meter = nil; } @@ -310,4 +408,25 @@ -(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)button } } +#pragma mark TopViewController Judge Part +- (UIViewController*) topViewController { + + return [self topViewController : self.window.rootViewController]; +} + +- (UIViewController*) topViewController : (UIViewController*) rootViewController { + + if( rootViewController.presentedViewController == nil ) { + return rootViewController; + } + else if( [rootViewController.presentedViewController isKindOfClass:[UINavigationController class]] ) + { + UINavigationController* navController = (UINavigationController*) rootViewController.presentedViewController; + UIViewController* lastVC = (UIViewController*) navController.viewControllers.lastObject; + return [self topViewController:lastVC]; + } + + return (UIViewController*) rootViewController.presentedViewController; +} + @end diff --git a/Mooshimeter/GraphViewController.m b/Mooshimeter/GraphViewController.m index 52bd454..70c6b47 100644 --- a/Mooshimeter/GraphViewController.m +++ b/Mooshimeter/GraphViewController.m @@ -47,6 +47,11 @@ -(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOr } -(void) viewDidAppear:(BOOL)animated { + + // By Jianying Shi. + // Null Check + + [super viewDidAppear:animated]; self.navigationController.navigationBar.hidden = YES; self.config_view = nil; @@ -79,6 +84,11 @@ -(void)handleBackgroundTap { } -(void)startTrendView { + // By Jianying Shi + // Null Check + if( g_meter == nil ) + return; + g_meter->meter_settings.rw.calc_settings &=~(METER_CALC_SETTINGS_ONESHOT); g_meter->meter_settings.rw.calc_settings |= METER_CALC_SETTINGS_MEAN|METER_CALC_SETTINGS_MS; g_meter->meter_settings.rw.target_meter_state = METER_RUNNING; @@ -99,6 +109,15 @@ -(void)startTrendView { } -(void) pause { + + // By Jianying + // Null check + if( g_meter == nil ) + { + NSLog(@"Meter already disconnected, igonring request"); + return; + } + self->play = NO; g_meter->meter_settings.rw.target_meter_state = METER_PAUSED; [g_meter sendMeterSettings:^(NSError *error) { @@ -138,6 +157,15 @@ -(void) trendViewUpdate { } -(void) redrawTrendView:(NSTimer*)timer { + + // By Jianying Shi + // Null Check + if( g_meter == nil ) + { + NSLog(@"Invalid meter, igonoring redraw request"); + return; + } + NSLog(@"Redrawing"); CPTGraph *graph = self.hostView.hostedGraph; [graph reloadData]; diff --git a/Mooshimeter/Images.xcassets/checkicon.imageset/Contents.json b/Mooshimeter/Images.xcassets/checkicon.imageset/Contents.json new file mode 100644 index 0000000..7a73488 --- /dev/null +++ b/Mooshimeter/Images.xcassets/checkicon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "check_icon.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "check_icon@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Mooshimeter/Images.xcassets/checkicon.imageset/check_icon.png b/Mooshimeter/Images.xcassets/checkicon.imageset/check_icon.png new file mode 100644 index 0000000..46728d1 Binary files /dev/null and b/Mooshimeter/Images.xcassets/checkicon.imageset/check_icon.png differ diff --git a/Mooshimeter/Images.xcassets/checkicon.imageset/check_icon@2x.png b/Mooshimeter/Images.xcassets/checkicon.imageset/check_icon@2x.png new file mode 100644 index 0000000..212f50a Binary files /dev/null and b/Mooshimeter/Images.xcassets/checkicon.imageset/check_icon@2x.png differ diff --git a/Mooshimeter/Images.xcassets/info_icon.imageset/Contents.json b/Mooshimeter/Images.xcassets/info_icon.imageset/Contents.json new file mode 100644 index 0000000..e5c7c10 --- /dev/null +++ b/Mooshimeter/Images.xcassets/info_icon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "info@2x.PNG" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Mooshimeter/Images.xcassets/info_icon.imageset/info@2x.PNG b/Mooshimeter/Images.xcassets/info_icon.imageset/info@2x.PNG new file mode 100644 index 0000000..2511b57 Binary files /dev/null and b/Mooshimeter/Images.xcassets/info_icon.imageset/info@2x.PNG differ diff --git a/Mooshimeter/Images.xcassets/mail_icon.imageset/Contents.json b/Mooshimeter/Images.xcassets/mail_icon.imageset/Contents.json new file mode 100644 index 0000000..619a0ac --- /dev/null +++ b/Mooshimeter/Images.xcassets/mail_icon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "mail@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Mooshimeter/Images.xcassets/mail_icon.imageset/mail@2x.png b/Mooshimeter/Images.xcassets/mail_icon.imageset/mail@2x.png new file mode 100644 index 0000000..b5453bd Binary files /dev/null and b/Mooshimeter/Images.xcassets/mail_icon.imageset/mail@2x.png differ diff --git a/Mooshimeter/Images.xcassets/menuicon.imageset/Contents.json b/Mooshimeter/Images.xcassets/menuicon.imageset/Contents.json new file mode 100644 index 0000000..36560f8 --- /dev/null +++ b/Mooshimeter/Images.xcassets/menuicon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "menu_icon@2x 2.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Mooshimeter/Images.xcassets/menuicon.imageset/menu_icon@2x 2.png b/Mooshimeter/Images.xcassets/menuicon.imageset/menu_icon@2x 2.png new file mode 100644 index 0000000..72b8c61 Binary files /dev/null and b/Mooshimeter/Images.xcassets/menuicon.imageset/menu_icon@2x 2.png differ diff --git a/Mooshimeter/Images.xcassets/reloadicon.imageset/Contents.json b/Mooshimeter/Images.xcassets/reloadicon.imageset/Contents.json new file mode 100644 index 0000000..b11c419 --- /dev/null +++ b/Mooshimeter/Images.xcassets/reloadicon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "refresh_icon@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Mooshimeter/Images.xcassets/reloadicon.imageset/refresh_icon@2x.png b/Mooshimeter/Images.xcassets/reloadicon.imageset/refresh_icon@2x.png new file mode 100644 index 0000000..628e9d1 Binary files /dev/null and b/Mooshimeter/Images.xcassets/reloadicon.imageset/refresh_icon@2x.png differ diff --git a/Mooshimeter/Images.xcassets/scanicon.imageset/Contents.json b/Mooshimeter/Images.xcassets/scanicon.imageset/Contents.json new file mode 100644 index 0000000..faf4e4d --- /dev/null +++ b/Mooshimeter/Images.xcassets/scanicon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "search_icon.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "search_icon@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Mooshimeter/Images.xcassets/scanicon.imageset/search_icon.png b/Mooshimeter/Images.xcassets/scanicon.imageset/search_icon.png new file mode 100644 index 0000000..b88ab72 Binary files /dev/null and b/Mooshimeter/Images.xcassets/scanicon.imageset/search_icon.png differ diff --git a/Mooshimeter/Images.xcassets/scanicon.imageset/search_icon@2x.png b/Mooshimeter/Images.xcassets/scanicon.imageset/search_icon@2x.png new file mode 100644 index 0000000..3d4c02e Binary files /dev/null and b/Mooshimeter/Images.xcassets/scanicon.imageset/search_icon@2x.png differ diff --git a/Mooshimeter/Images.xcassets/settingicon.imageset/Contents.json b/Mooshimeter/Images.xcassets/settingicon.imageset/Contents.json new file mode 100644 index 0000000..da26ae0 --- /dev/null +++ b/Mooshimeter/Images.xcassets/settingicon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "settings@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Mooshimeter/Images.xcassets/settingicon.imageset/settings@2x.png b/Mooshimeter/Images.xcassets/settingicon.imageset/settings@2x.png new file mode 100644 index 0000000..d099e64 Binary files /dev/null and b/Mooshimeter/Images.xcassets/settingicon.imageset/settings@2x.png differ diff --git a/Mooshimeter/Images.xcassets/web_icon.imageset/Contents.json b/Mooshimeter/Images.xcassets/web_icon.imageset/Contents.json new file mode 100644 index 0000000..7ea9175 --- /dev/null +++ b/Mooshimeter/Images.xcassets/web_icon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "web@2x.PNG" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Mooshimeter/Images.xcassets/web_icon.imageset/web@2x.PNG b/Mooshimeter/Images.xcassets/web_icon.imageset/web@2x.PNG new file mode 100644 index 0000000..88fcdaa Binary files /dev/null and b/Mooshimeter/Images.xcassets/web_icon.imageset/web@2x.PNG differ diff --git a/Mooshimeter/MeterViewController.m b/Mooshimeter/MeterViewController.m index 1da51d2..01e3676 100644 --- a/Mooshimeter/MeterViewController.m +++ b/Mooshimeter/MeterViewController.m @@ -17,9 +17,19 @@ ***************************/ #import "MeterViewController.h" +#import "Vendor/KxMenu/KxMenu.h" + +#import "MooshimeterDeviceSimulator.h" dispatch_semaphore_t tmp_sem; +#define kContextMenu_Tag 2000 + +@interface MeterViewController () { + KxMenu* settingsMenu; +} +@end + @implementation MeterViewController -(BOOL)prefersStatusBarHidden { return YES; } @@ -77,8 +87,25 @@ - (void)viewDidLoad [v addSubview:sv]; [self.view addSubview:v]; + + // By Jianying Shi + // Trying to customized back button + UIBarButtonItem* back_item = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRewind target:self action:@selector(backToScanView:)]; + self.navigationItem.leftBarButtonItem = back_item; +} + +- (void) backToScanView : (id) sender +{ + [g_meter disconnect:nil]; + + // Add exception for Simulator + // By Jianying Shi + if ([g_meter isKindOfClass:[MooshimeterDeviceSimulator class]] == true) { + [self.navigationController popViewControllerAnimated:YES]; + } } +#pragma mark - View lifey cycle -(void) viewWillDisappear:(BOOL)animated { [self pause]; } diff --git a/Mooshimeter/Mooshimeter-Info.plist b/Mooshimeter/Mooshimeter-Info.plist index 9ca5130..1272246 100644 --- a/Mooshimeter/Mooshimeter-Info.plist +++ b/Mooshimeter/Mooshimeter-Info.plist @@ -25,7 +25,7 @@ CFBundleSignature ???? CFBundleVersion - 173 + 681 LSRequiresIPhoneOS UIMainStoryboardFile~ipad diff --git a/Mooshimeter/MooshimeterDevice.m b/Mooshimeter/MooshimeterDevice.m index be7888e..9df10a5 100644 --- a/Mooshimeter/MooshimeterDevice.m +++ b/Mooshimeter/MooshimeterDevice.m @@ -77,11 +77,14 @@ -(void)populateLGDict:(NSArray*)characteristics { -(void)connect { self.chars = [[NSMutableDictionary alloc] init]; - [self.p connectWithTimeout:5 completion:^(NSError *error) { + [self.p connectWithTimeout : 30 completion:^(NSError *error) { NSLog(@"Discovering services"); + [self.p discoverServicesWithCompletion:^(NSArray *services, NSError *error) { for (LGService *service in services) { - if([service.UUIDString isEqualToString:[BLEUtility expandToMooshimUUIDString:METER_SERVICE_UUID]]) { + + NSString* meterServiceUUID = [BLEUtility expandToMooshimUUIDString:METER_SERVICE_UUID]; + if([service.UUIDString isEqualToString : meterServiceUUID]) { NSLog(@"METER SERVICE FOUND. Discovering characteristics."); self->oad_mode = NO; [service discoverCharacteristicsWithCompletion:^(NSArray *characteristics, NSError *error) { diff --git a/Mooshimeter/OADProfile.h b/Mooshimeter/OADProfile.h index a772db6..d719dab 100644 --- a/Mooshimeter/OADProfile.h +++ b/Mooshimeter/OADProfile.h @@ -13,13 +13,22 @@ @class BLETIOADProgressViewController; +typedef void (^FirmwareUpdateFinishCallback)(NSError *error); + @interface OADProfile : NSObject { @public img_hdr_t imageHeader; + + // Add by Jianying Shi. + FirmwareUpdateFinishCallback aCompletionBlock; } @property (strong,nonatomic) NSData *imageData; +// Added by Jianying Shi +@property BOOL image_downloaded; +@property NSString* download_imagePath; + @property int nBlocks; @property int nBytes; @property int iBlocks; @@ -36,6 +45,10 @@ -(instancetype) init:(NSString*) filename; +// Add by Jianying Shi. +// Download new Firmware +-(void) setCompletionBlock : (FirmwareUpdateFinishCallback) aCallback; + -(void) startUpload; -(void) completionDialog; diff --git a/Mooshimeter/OADProfile.m b/Mooshimeter/OADProfile.m index f16397d..eef7d1f 100644 --- a/Mooshimeter/OADProfile.m +++ b/Mooshimeter/OADProfile.m @@ -9,7 +9,7 @@ #import "OADProfile.h" #import "BLEUtility.h" - +#import "ReachabilityManager.h" @implementation OADProfile @@ -19,21 +19,48 @@ -(id) init:(NSString*) filename { self.canceled = FALSE; self.inProgramming = FALSE; self.start = YES; - NSString *stringURL = @"https://moosh.im/s/f/mooshimeter-firmware-latest.bin"; - NSURL *url = [NSURL URLWithString:stringURL]; - self.imageData = [NSData dataWithContentsOfURL:url]; - NSLog(@"Loaded firmware \"%@\"of size : %d",filename,(int)self.imageData.length); - if(self.imageData.length==0) { - // We failed to load the firmware. Should we do something? - self->imageHeader.build_time=0; - } else { - [self.imageData getBytes:&self->imageHeader length:sizeof(img_hdr_t)]; + + // By Jianying + self.download_imagePath = filename; + self.image_downloaded = false; + + // Add complete block + [ReachabilityManager sharedInstance]; + + if( [[ReachabilityManager sharedInstance] connectedToInternet] == true ) { + [self downloadImage]; + } + else { + [[ReachabilityManager sharedInstance] addConnectionStatusChangeHandler:^(BOOL connectedToInternet) { + + [self downloadImage]; + } withIdentifier:@"imageDownloaded"]; } } return self; } +-(void) downloadImage { + + NSString *stringURL = @"https://moosh.im/s/f/mooshimeter-firmware-latest.bin"; + NSURL *url = [NSURL URLWithString:stringURL]; + self.imageData = [NSData dataWithContentsOfURL:url]; + NSLog(@"Loaded firmware \"%@\"of size : %d",self.download_imagePath,(int)self.imageData.length); + if(self.imageData.length==0) { + // We failed to load the firmware. Should we do something? + self->imageHeader.build_time=0; + } else { + [self.imageData getBytes:&self->imageHeader length:sizeof(img_hdr_t)]; + } + + self.image_downloaded = true; + + // Once download become success, delete handler + [[ReachabilityManager sharedInstance] removeConnectionStatusChangeHandlerWithIdentifier:@"imageDownloaded"]; +} + -(void) startUpload { + NSLog(@"Configuring OAD Profile"); self.start = YES; LGCharacteristic* image_notify = [g_meter getLGChar:OAD_IMAGE_NOTIFY]; @@ -73,6 +100,13 @@ -(void)handleDisconnect:(NSNotification *) notification { } -(void) uploadImage { + + // By Jianying Shi + // If image is not downloaded yet, skip upload Image to device + if(self.image_downloaded == false) { + return; + } + self.inProgramming = YES; self.canceled = NO; @@ -157,6 +191,10 @@ -(void)sendNextBlock { self.inProgramming = NO; dispatch_queue_t mq = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(mq, ^{ + + // Call completion block here! + aCompletionBlock(nil); + [self completionDialog]; }); return; @@ -187,9 +225,11 @@ -(void) completionDialog { complete = [[UIAlertView alloc]initWithTitle:@"Firmware upgrade complete" message:@"Firmware upgrade was successfully completed, device needs to be reconnected" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [complete show]; } -@end - - +-(void) setCompletionBlock : (FirmwareUpdateFinishCallback) aCallback; +{ + aCompletionBlock = aCallback; +} +@end diff --git a/Mooshimeter/Reachability/Reachability.h b/Mooshimeter/Reachability/Reachability.h new file mode 100755 index 0000000..aa9244f --- /dev/null +++ b/Mooshimeter/Reachability/Reachability.h @@ -0,0 +1,91 @@ +/* + + File: Reachability.h + Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs. + + Version: 2.2 + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. + ("Apple") in consideration of your agreement to the following terms, and your + use, installation, modification or redistribution of this Apple software + constitutes acceptance of these terms. If you do not agree with these terms, + please do not use, install, modify or redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject + to these terms, Apple grants you a personal, non-exclusive license, under + Apple's copyrights in this original Apple software (the "Apple Software"), to + use, reproduce, modify and redistribute the Apple Software, with or without + modifications, in source and/or binary forms; provided that if you redistribute + the Apple Software in its entirety and without modifications, you must retain + this notice and the following text and disclaimers in all such redistributions + of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Inc. may be used + to endorse or promote products derived from the Apple Software without specific + prior written permission from Apple. Except as expressly stated in this notice, + no other rights or licenses, express or implied, are granted by Apple herein, + including but not limited to any patent rights that may be infringed by your + derivative works or by other works in which the Apple Software may be + incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO + WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED + WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN + COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR + DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF + CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF + APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2010 Apple Inc. All Rights Reserved. + + */ + + +#import +#import +#import + +typedef enum { + NotReachable = 0, + ReachableViaWiFi, + ReachableViaWWAN +} NetworkStatus; +#define kReachabilityChangedNotification @"kNetworkReachabilityChangedNotification" + +@interface Reachability: NSObject +{ + BOOL localWiFiRef; + SCNetworkReachabilityRef reachabilityRef; +} + +//reachabilityWithHostName- Use to check the reachability of a particular host name. ++ (Reachability*) reachabilityWithHostName: (NSString*) hostName; + +//reachabilityWithAddress- Use to check the reachability of a particular IP address. ++ (Reachability*) reachabilityWithAddress: (const struct sockaddr_in*) hostAddress; + +//reachabilityForInternetConnection- checks whether the default route is available. +// Should be used by applications that do not connect to a particular host ++ (Reachability*) reachabilityForInternetConnection; + +//reachabilityForLocalWiFi- checks whether a local wifi connection is available. ++ (Reachability*) reachabilityForLocalWiFi; + ++ (BOOL)isNetAvailable; + +//Start listening for reachability notifications on the current run loop +- (BOOL) startNotifier; +- (void) stopNotifier; + +- (NetworkStatus) currentReachabilityStatus; +//WWAN may be available, but not active until a connection has been established. +//WiFi may require a connection for VPN on Demand. +- (BOOL) connectionRequired; +@end + + diff --git a/Mooshimeter/Reachability/Reachability.m b/Mooshimeter/Reachability/Reachability.m new file mode 100755 index 0000000..1abc7c4 --- /dev/null +++ b/Mooshimeter/Reachability/Reachability.m @@ -0,0 +1,285 @@ +/* + + File: Reachability.m + Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs. + + Version: 2.2 + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. + ("Apple") in consideration of your agreement to the following terms, and your + use, installation, modification or redistribution of this Apple software + constitutes acceptance of these terms. If you do not agree with these terms, + please do not use, install, modify or redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject + to these terms, Apple grants you a personal, non-exclusive license, under + Apple's copyrights in this original Apple software (the "Apple Software"), to + use, reproduce, modify and redistribute the Apple Software, with or without + modifications, in source and/or binary forms; provided that if you redistribute + the Apple Software in its entirety and without modifications, you must retain + this notice and the following text and disclaimers in all such redistributions + of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Inc. may be used + to endorse or promote products derived from the Apple Software without specific + prior written permission from Apple. Except as expressly stated in this notice, + no other rights or licenses, express or implied, are granted by Apple herein, + including but not limited to any patent rights that may be infringed by your + derivative works or by other works in which the Apple Software may be + incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO + WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED + WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN + COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR + DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF + CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF + APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2010 Apple Inc. All Rights Reserved. + + */ + +#import +#import +#import +#import +#import +#import + +#import + +#import "Reachability.h" +#import "KNDefines.h" + +#define kShouldPrintReachabilityFlags 0 + +static BOOL netAvailable = YES; + +static void PrintReachabilityFlags(SCNetworkReachabilityFlags flags, const char* comment) +{ +#if kShouldPrintReachabilityFlags + + DebugNSLog(@"Reachability Flag Status: %c%c %c%c%c%c%c%c%c %s\n", + (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', + (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', + + (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', + (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', + (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', + (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-', + comment + ); +#endif +} + + +@implementation Reachability +static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) +{ +#pragma unused (target, flags) + NSCAssert(info != NULL, @"info was NULL in ReachabilityCallback"); + NSCAssert([(NSObject*) info isKindOfClass: [Reachability class]], @"info was wrong class in ReachabilityCallback"); + + //We're on the main RunLoop, so an NSAutoreleasePool is not necessary, but is added defensively + // in case someon uses the Reachablity object in a different thread. + NSAutoreleasePool* myPool = [[NSAutoreleasePool alloc] init]; + + Reachability* noteObject = (Reachability*) info; + // Post a notification to notify the client that the network reachability changed. + [[NSNotificationCenter defaultCenter] postNotificationName: kReachabilityChangedNotification object: noteObject]; + + [myPool release]; +} + +- (BOOL) startNotifier +{ + BOOL retVal = NO; + SCNetworkReachabilityContext context = {0, self, NULL, NULL, NULL}; + if(SCNetworkReachabilitySetCallback(reachabilityRef, ReachabilityCallback, &context)) + { + if(SCNetworkReachabilityScheduleWithRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)) + { + retVal = YES; + } + } + return retVal; +} + +- (void) stopNotifier +{ + if(reachabilityRef!= NULL) + { + SCNetworkReachabilityUnscheduleFromRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + } +} + +- (void) dealloc +{ + [self stopNotifier]; + if(reachabilityRef) CFRelease(reachabilityRef); + [super dealloc]; +} + ++ (Reachability*) reachabilityWithHostName: (NSString*) hostName +{ + Reachability* retVal = NULL; + SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]); + if(reachability!= NULL) + { + retVal= [[[self alloc] init] autorelease]; + if(retVal!= NULL) + { + retVal->reachabilityRef = reachability; + retVal->localWiFiRef = NO; + } + } + return retVal; +} + ++ (Reachability*) reachabilityWithAddress: (const struct sockaddr_in*) hostAddress +{ + SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress); + Reachability* retVal = NULL; + if(reachability!= NULL) + { + retVal= [[[self alloc] init] autorelease]; + if(retVal!= NULL) + { + retVal->reachabilityRef = reachability; + retVal->localWiFiRef = NO; + } + } + return retVal; +} + ++ (Reachability*) reachabilityForInternetConnection +{ + struct sockaddr_in zeroAddress; + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + return [self reachabilityWithAddress: &zeroAddress]; +} + ++ (Reachability*) reachabilityForLocalWiFi +{ + struct sockaddr_in localWifiAddress; + bzero(&localWifiAddress, sizeof(localWifiAddress)); + localWifiAddress.sin_len = sizeof(localWifiAddress); + localWifiAddress.sin_family = AF_INET; + // IN_LINKLOCALNETNUM is defined in as 169.254.0.0 + localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); + Reachability* retVal = [self reachabilityWithAddress: &localWifiAddress]; + if(retVal!= NULL) + { + retVal->localWiFiRef = YES; + } + return retVal; +} + + + ++ (BOOL)isNetAvailable { + return netAvailable; +} + + +#pragma mark Network Flag Handling + +- (NetworkStatus) localWiFiStatusForFlags: (SCNetworkReachabilityFlags) flags +{ + PrintReachabilityFlags(flags, "localWiFiStatusForFlags"); + + BOOL retVal = NotReachable; + if((flags & kSCNetworkReachabilityFlagsReachable) && (flags & kSCNetworkReachabilityFlagsIsDirect)) + { + retVal = ReachableViaWiFi; + } + return retVal; +} + +- (NetworkStatus) networkStatusForFlags: (SCNetworkReachabilityFlags) flags +{ + PrintReachabilityFlags(flags, "networkStatusForFlags"); + if ((flags & kSCNetworkReachabilityFlagsReachable) == 0) + { + // if target host is not reachable + return NotReachable; + } + + BOOL retVal = NotReachable; + + if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0) + { + // if target host is reachable and no connection is required + // then we'll assume (for now) that your on Wi-Fi + retVal = ReachableViaWiFi; + } + + + if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0)) + { + // ... and the connection is on-demand (or on-traffic) if the + // calling application is using the CFSocketStream or higher APIs + + if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0) + { + // ... and no [user] intervention is needed + retVal = ReachableViaWiFi; + } + } + + if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN) + { + // ... but WWAN connections are OK if the calling application + // is using the CFNetwork (CFSocketStream?) APIs. + retVal = ReachableViaWWAN; + } + return retVal; +} + +- (BOOL) connectionRequired; +{ + NSAssert(reachabilityRef != NULL, @"connectionRequired called with NULL reachabilityRef"); + SCNetworkReachabilityFlags flags; + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + return (flags & kSCNetworkReachabilityFlagsConnectionRequired); + } + return NO; +} + +- (NetworkStatus) currentReachabilityStatus +{ + NSAssert(reachabilityRef != NULL, @"currentNetworkStatus called with NULL reachabilityRef"); + NetworkStatus retVal = NotReachable; + SCNetworkReachabilityFlags flags; + netAvailable = NO; + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + if(localWiFiRef) + { + retVal = [self localWiFiStatusForFlags: flags]; + } + else + { + retVal = [self networkStatusForFlags: flags]; + } + } + + if(retVal != NotReachable) + netAvailable = YES; + + return retVal; +} +@end diff --git a/Mooshimeter/ReachabilityManager.h b/Mooshimeter/ReachabilityManager.h new file mode 100644 index 0000000..d8ed9e8 --- /dev/null +++ b/Mooshimeter/ReachabilityManager.h @@ -0,0 +1,20 @@ +// +// ReachabilityManager.h +// Mooshimeter +// +// Created by Admin on 6/2/15. +// Copyright (c) 2015 mooshim. All rights reserved. +// + +#import +#import "Vendor/Reachability/Reachability.h" + +@interface ReachabilityManager : NSObject + ++(ReachabilityManager*)sharedInstance; + +-(BOOL)connectedToInternet; +-(void)addConnectionStatusChangeHandler:(void(^)(BOOL connectedToInternet))block withIdentifier:(NSString*)identifier; +-(void)removeConnectionStatusChangeHandlerWithIdentifier:(NSString*)identifier; + +@end diff --git a/Mooshimeter/ReachabilityManager.m b/Mooshimeter/ReachabilityManager.m new file mode 100644 index 0000000..0813f81 --- /dev/null +++ b/Mooshimeter/ReachabilityManager.m @@ -0,0 +1,118 @@ +// +// ReachabilityManager.m +// Mooshimeter +// +// Created by Admin on 6/2/15. +// Copyright (c) 2015 mooshim. All rights reserved. +// + +#import "ReachabilityManager.h" + +@interface ReachabilityManager() +{ + +} + +@property Reachability* reachability; +@property BOOL isConnectedToInternet; +@property BOOL isWaiting; +@property NSMutableDictionary* blockDictionary; +@property NSMutableArray* blockKeysArray; + +@end + +@implementation ReachabilityManager + ++(ReachabilityManager*)sharedInstance +{ + static id sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[self alloc] init]; + }); + return (ReachabilityManager*)sharedInstance; +} + +-(id) init +{ + self = [super init]; + if (self) + { + self.reachability = [Reachability reachabilityForInternetConnection]; + [self.reachability startNotifier]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onConnectionStatusChange) name:kReachabilityChangedNotification object:nil]; + self.blockDictionary = [[NSMutableDictionary alloc] init]; + self.blockKeysArray = [[NSMutableArray alloc] init]; + + if([self.reachability currentReachabilityStatus] == NotReachable) + { + self.isConnectedToInternet = false; + } + else + { + self.isConnectedToInternet = true; + } + + self.isWaiting = false; + + } + return self; +} + +-(BOOL)connectedToInternet +{ + return self.isConnectedToInternet; +} + +-(void)addConnectionStatusChangeHandler:(void(^)(BOOL connectedToInternet))block withIdentifier:(NSString*)identifier +{ + if(![self.blockKeysArray containsObject:identifier]) + { + [self.blockKeysArray addObject:identifier]; + self.blockDictionary[identifier] = block; + } +} + +-(void)removeConnectionStatusChangeHandlerWithIdentifier:(NSString*)identifier +{ + if([self.blockKeysArray containsObject:identifier]) + { + [self.blockKeysArray removeObject:identifier]; + } +} + +-(void)onConnectionStatusChange +{ + if(self.isWaiting == false) + { + [NSTimer scheduledTimerWithTimeInterval:0.25 target:self selector:@selector(afterRapidAlternating) userInfo:nil repeats:NO]; + } +} + +-(void)afterRapidAlternating +{ + if([self.reachability currentReachabilityStatus] == NotReachable) + { + if(self.isConnectedToInternet == true) + { + self.isConnectedToInternet = false; + for(NSString* key in self.blockKeysArray) + { + ((void(^)(BOOL))self.blockDictionary[key])(self.isConnectedToInternet); + } + } + } + else + { + if(self.isConnectedToInternet == false) + { + self.isConnectedToInternet = true; + for(NSString* key in self.blockKeysArray) + { + ((void(^)(BOOL))self.blockDictionary[key])(self.isConnectedToInternet); + } + } + } +} + +@end diff --git a/Mooshimeter/ScanSettingsView.h b/Mooshimeter/ScanSettingsView.h index 1fe3a87..d694fbb 100644 --- a/Mooshimeter/ScanSettingsView.h +++ b/Mooshimeter/ScanSettingsView.h @@ -18,7 +18,20 @@ #import +@protocol ScanSettingsViewDelegate +@required +-(void)handleSendSupporMail; +@end + @interface ScanSettingsView : UIView +{ + +} + @property (strong,nonatomic) UILabel* about_section; @property (strong,nonatomic) UIButton* help_button; +@property (strong,nonatomic) UIButton* mail_button; + +@property (strong,nonatomic) id delegate; + @end \ No newline at end of file diff --git a/Mooshimeter/ScanSettingsView.m b/Mooshimeter/ScanSettingsView.m index 15f935b..09da11e 100644 --- a/Mooshimeter/ScanSettingsView.m +++ b/Mooshimeter/ScanSettingsView.m @@ -8,6 +8,8 @@ #import "ScanSettingsView.h" +#import + @implementation ScanSettingsView @@ -15,18 +17,36 @@ -(instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; self.userInteractionEnabled = YES; + // FIXME: - fixed icon size + const CGFloat fixedIconSize = 30; + const CGFloat fixedPadding = 10; + const CGFloat fixedOffsetX = fixedIconSize + fixedPadding * 2; + const CGFloat adjustedWidth = frame.size.width - fixedOffsetX; + // Lay out the controls - const int nrow = 2; - const int ncol = 1; + const NSInteger nrow = 3; + const NSInteger ncol = 1; - float h = frame.size.height/nrow; - float w = frame.size.width/ncol; + CGFloat h = frame.size.height/nrow; + CGFloat w = adjustedWidth / ncol; -#define cg(nx,ny,nw,nh) CGRectMake(nx*w,ny*h,nw*w,nh*h) - self.about_section = [[UILabel alloc]initWithFrame:cg(0,0,1,1)]; - self.help_button = [[UIButton alloc]initWithFrame:cg(0,1,1,1)]; +#define cg(nx,ny,nw,nh,ox,oy) CGRectMake(nx*w+ox,ny*h+oy,nw*w,nh*h) + self.about_section = [[UILabel alloc]initWithFrame:cg(0,0,1,1,fixedOffsetX,0)]; + self.help_button = [[UIButton alloc]initWithFrame:cg(0,1,1,1,fixedOffsetX,0)]; + self.mail_button = [[UIButton alloc]initWithFrame:cg(0,2,1,1,fixedOffsetX,0)]; #undef cg + // Place suitable icons + // By Jianying Shi + const CGFloat fixedOffsetY = (h - fixedIconSize) / 2; + UIImageView *infoImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"info_icon"]]; + [infoImage setFrame:CGRectMake(fixedPadding, fixedOffsetY, fixedIconSize, fixedIconSize)]; + + UIImageView *webImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"web_icon"]]; + [webImage setFrame:CGRectMake(fixedPadding, fixedOffsetY + h, fixedIconSize, fixedIconSize)]; + UIImageView *mailImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"mail_icon"]]; + [mailImage setFrame:CGRectMake(fixedPadding, fixedOffsetY + h * 2, fixedIconSize, fixedIconSize)]; + // Set properties NSString * appBuildString = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; NSString * appVersionString = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; @@ -35,22 +55,32 @@ -(instancetype)initWithFrame:(CGRect)frame { [self.about_section setText:about_string]; [self.about_section setFont:[UIFont systemFontOfSize:20]]; [self.about_section setTextAlignment:NSTextAlignmentCenter]; - [self.about_section setTextColor:[UIColor darkGrayColor]]; + [self.about_section setTextColor:[UIColor whiteColor]]; self.about_section.numberOfLines = 3; [ self.help_button addTarget:self action:@selector(launchHelp) forControlEvents:UIControlEventTouchUpInside]; [ self.help_button setTitle:@"Open Help Site" forState:UIControlStateNormal]; - [ self.help_button.titleLabel setFont:[UIFont systemFontOfSize:30]]; - [ self.help_button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; - [[self.help_button layer] setBorderWidth:2]; - [[self.help_button layer] setBorderColor:[UIColor darkGrayColor].CGColor]; + [ self.help_button.titleLabel setFont:[UIFont systemFontOfSize:24]]; + [ self.help_button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; + + // Added by Jianying Shi + // 04/22/2015 + [ self.mail_button addTarget:self action:@selector(launchMail) forControlEvents:UIControlEventTouchUpInside]; + [ self.mail_button setTitle:@"Mail Support" forState:UIControlStateNormal]; + [ self.mail_button.titleLabel setFont:[UIFont systemFontOfSize:24]]; + [ self.mail_button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; // Add as subviews [self addSubview:self.about_section]; [self addSubview:self.help_button]; + [self addSubview:self.mail_button]; + + // Add icons + // By Jianying Shi + [self addSubview: infoImage]; + [self addSubview: webImage]; + [self addSubview: mailImage]; - [[self layer] setBorderWidth:5]; - [[self layer] setBorderColor:[UIColor darkGrayColor].CGColor]; return self; } @@ -60,5 +90,9 @@ -(void)launchHelp { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://moosh.im/support/"]]; } +// By Jianying Shi +-(void)launchMail { + [_delegate handleSendSupporMail]; +} @end diff --git a/Mooshimeter/ScanViewController.h b/Mooshimeter/ScanViewController.h index 2ddb79d..a726872 100644 --- a/Mooshimeter/ScanViewController.h +++ b/Mooshimeter/ScanViewController.h @@ -18,6 +18,8 @@ along with this program. If not, see . #import #import +#import + #import "LGPeripheral.h" #import "MooshimeterDevice.h" #import "ScanTableViewCell.h" @@ -27,9 +29,10 @@ along with this program. If not, see . @required -(void)handleScanViewRefreshRequest; -(void)handleScanViewSelect:(LGPeripheral*)p; +-(void)updateFirmwareIfNeeded; @end -@interface ScanViewController : UITableViewController +@interface ScanViewController : UITableViewController @property (strong,nonatomic) id delegate; @property (strong,nonatomic) NSArray* peripherals; @@ -39,4 +42,6 @@ along with this program. If not, see . -(instancetype)initWithDelegate:(id)d; -(void)reloadData; +-(void) performAutoConnect; + @end diff --git a/Mooshimeter/ScanViewController.m b/Mooshimeter/ScanViewController.m index 00c175d..ba08267 100644 --- a/Mooshimeter/ScanViewController.m +++ b/Mooshimeter/ScanViewController.m @@ -19,12 +19,40 @@ #import "ScanViewController.h" #import "meterViewController.h" +#import "Vendor/KxMenu/KxMenu.h" +#import "Vendor/LDProgressView/LDProgressView.h" + +// Bar chart +// Jianying Shi. 05/02/2015 +#import "Vendor/MPPlot/MPPlot.h" +#import "Vendor/MPPlot/MPGraphView.h" +#import "Vendor/MPPlot/MPBarsGraphView.h" +#import "Vendor/SVProgressHUD/SVProgressHUD.h" + +#import "AppDelegate.h" + +#import +#include +#include + // Uncomment if you want a simulated meter to appear in the scan list #define SIMULATED_METER +#define kRSSIView_Tag 1000 +#define kContextMenu_Tag 2000 + +#define Round(a) (NSInteger) (a + 0.5) + +#define kAutoConnectDictKey @"autoUUIDs" + @interface ScanViewController () { NSMutableArray *_objects; + + KxMenu* settingsMenu; } + +-(BOOL) connectToPeripheral : (NSString*) uuidString withPeripherals : (NSArray*)peripheral_array; + @end @implementation ScanViewController @@ -41,32 +69,111 @@ - (void)awakeFromNib [super awakeFromNib]; } +// By Jianying Shi +-(BOOL) connectToPeripheral : (NSString*) uuidString withPeripherals : (NSArray*)peripheral_array { + + if (uuidString == nil || [uuidString length] < 1) { + return false; + } + + if (peripheral_array == nil || peripheral_array.count < 1 ) { + return false; + } + + for( LGPeripheral* p in peripheral_array ) + { + if( [[p UUIDString] isEqualToString : uuidString] == YES ) + { + [self.delegate handleScanViewSelect:p]; + return true; + } + } + + return false; +} + -(void)reloadData { + NSLog(@"Reload requested"); LGCentralManager* c = [LGCentralManager sharedInstance]; self.peripherals = [c.peripherals copy]; [self.tableView reloadData]; } +// By Jianying Shi +// Auto - connect logics. +-(void) performAutoConnect { + + AppDelegate* delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate]; + if( [self connectToPeripheral:delegate.mlastConnectedUDID withPeripherals:self.peripherals] == YES ) // Restoring connection for background + + delegate.mlastConnectedUDID = nil; + else { + + NSArray* savedUUIDs = [[NSUserDefaults standardUserDefaults] objectForKey : kAutoConnectDictKey]; + if ( savedUUIDs != nil && savedUUIDs.count > 0 ) { + + for( LGPeripheral* p in self.peripherals ) + { + NSLog(@"Trying to auto-connect"); + + if( [savedUUIDs containsObject:p.UUIDString] == YES && p.cbPeripheral.state == CBPeripheralStateDisconnected && p.pastAutoConnected == NO ) + { + // Check if this device was auto-connected in the past + [SVProgressHUD showWithStatus:@"Auto-connecting. Please wait for a second"]; + + // Set Flag for Auto-Connect + p.pastAutoConnected = YES; + + [self.delegate handleScanViewSelect:p]; + break; + } + } + } + } +} + +// by Jianying Shi. +// Hanlder of Menu Setting +#pragma mark - View lifecycle + - (void)viewDidLoad { [super viewDidLoad]; [self.tableView registerClass:[ScanTableViewCell class] forCellReuseIdentifier:@"Cell"]; + + // Added by Jianying Shi + // 04/19/2015 + + // -- Scan Mooshimeter button + UIBarButtonItem* nav_scan_btn = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"reloadicon"] style:UIBarButtonItemStyleBordered target:self.delegate action:@selector(handleScanViewRefreshRequest)]; + + if( self.navigationItem != nil ) + self.navigationItem.leftBarButtonItem = nav_scan_btn; + + // Long-press table-view handler + UILongPressGestureRecognizer *longpressScanSetting = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longpressScanSetting:)]; - NSLog(@"Creating refresh handler..."); - UIRefreshControl *rescan_control = [[UIRefreshControl alloc] init]; - [rescan_control addTarget:self.delegate action:@selector(handleScanViewRefreshRequest) forControlEvents:UIControlEventValueChanged]; - self.refreshControl = rescan_control; + longpressScanSetting.minimumPressDuration = 1.0; + longpressScanSetting.delegate = self; + + [self.tableView addGestureRecognizer:longpressScanSetting]; } +#pragma mark - View lifecycle -(void)viewDidAppear:(BOOL)animated { - [self setTitle:@"Swipe down to scan"]; + // [self setTitle:@"Swipe down to scan"] + // [self setTitle:@"Tap Scan Button"]; + +#if 0 if(g_meter) { // If we've appeared, disconnect whatever we were talking to. [g_meter disconnect:nil]; } +#endif + // Start a new scan for meters [self.delegate handleScanViewRefreshRequest]; } @@ -77,17 +184,170 @@ - (NSUInteger)supportedInterfaceOrientations { return UIInterfaceOrientationMaskPortrait; } +#pragma mark - Long-press menu +- (void) longpressScanSetting :(UILongPressGestureRecognizer *)gestureRecognizer +{ + // Check tapped table view cell + CGPoint p = [gestureRecognizer locationInView:self.tableView]; + NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:p]; + if (indexPath == nil) { + NSLog(@"long press on table view but not on a row"); + } else if (gestureRecognizer.state == UIGestureRecognizerStateBegan) { + + // Display auto-connect checked state + ScanTableViewCell* c = (ScanTableViewCell*)[self.tableView cellForRowAtIndexPath:indexPath]; + LGPeripheral* peripheral = c.p; + + NSMutableArray* savedUUIDs = [[NSUserDefaults standardUserDefaults] objectForKey : kAutoConnectDictKey]; + NSString* deviceUUID = [peripheral UUIDString]; + + BOOL bChecked = NO; + if( savedUUIDs != nil && savedUUIDs.count > 0 ) + { + if ( [savedUUIDs containsObject:deviceUUID] == true ) + bChecked = YES; + } + + // if (!settingsMenu) + { + + settingsMenu = [KxMenu new]; + settingsMenu.menuItems = [self createSettingsMenuItems:bChecked]; + settingsMenu.blurredBackground = NO; + + if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1) { + settingsMenu.blurredBackground = YES; + } + + settingsMenu.tintColor = [UIColor darkGrayColor];//[[UIColor whiteColor] colorWithAlphaComponent:0.5f]; + settingsMenu.tintColor1 = [UIColor lightGrayColor]; + + settingsMenu.selectedColor = [UIColor colorWithRed:0.9f green:0 blue:0 alpha:1.f]; + settingsMenu.selectedColor1 = [UIColor colorWithRed:0.8f green:0 blue:0 alpha:1.f]; + } + + + // Display setting menu + CGRect boundRect = CGRectMake(p.x, p.y, 5, 5); + boundRect.origin.y += 64; + + [settingsMenu setTag : indexPath.row + kContextMenu_Tag]; + [settingsMenu showMenuInView:self.tableView + fromRect:boundRect]; + + } else { + NSLog(@"gestureRecognizer.state = %d", gestureRecognizer.state); + } +} + + +- (NSArray*) createSettingsMenuItems : (BOOL) bChecked { + + KxMenuItem* checkItem = nil; + if( bChecked ) + checkItem = [KxMenuItem menuItem:@"Auto Connect" image:[UIImage imageNamed:@"checkicon"] target:self action:@selector(autoConnectPressed:)]; + else + checkItem = [KxMenuItem menuItem:@"Auto Connect" image:nil target:self action:@selector(autoConnectPressed:)]; + + NSArray *menuItems = + @[ + + [KxMenuItem menuItem:@"Setting" + image:nil + target:nil + action:NULL], + + [KxMenuItem menuItem:@"Firmware Update" + image:nil + target:self + action:@selector(firmwareUpdatePressed:)], + + checkItem, + ]; + + KxMenuItem *first = menuItems[0]; + first.foreColor = [UIColor colorWithRed:47/255.0f green:112/255.0f blue:225/255.0f alpha:1.0]; + first.alignment = NSTextAlignmentCenter; + + return menuItems; +} + +#pragma mark - Menu Item Press Handler + +- (void) firmwareUpdatePressed : (id) sender +{ +#if 0 // MARK + + NSLog(@"Firmware Update"); + + // By Jianying Shi. 05/02/2015 + // First of all, connect to peripheral + + // Get selected table view cell + NSInteger tag = settingsMenu.tag; + NSIndexPath* indexPath = [NSIndexPath indexPathForRow:tag inSection:0]; + + ScanTableViewCell* c = (ScanTableViewCell*)[self.tableView cellForRowAtIndexPath:indexPath]; + [self.delegate handleScanViewSelect:c.p]; + + // Then upldate + // [self.delegate updateFirmwareIfNeeded]; +#endif +} + +- (void) autoConnectPressed : (id) sender +{ + NSLog(@"Auto connect"); + + // Get current selected peripheral + NSInteger selectedRow = [settingsMenu tag] - kContextMenu_Tag; + LGPeripheral* p = [self.peripherals objectAtIndex:selectedRow]; + + NSString* deviceUUID = p.UUIDString; + if( deviceUUID ) + { + // Save current device UUID to user defaults + NSMutableArray* savedUUIDs = (NSMutableArray*)[[NSUserDefaults standardUserDefaults] objectForKey:kAutoConnectDictKey]; + + if( savedUUIDs != nil ) // already has saved id + { + NSMutableArray* mutaleArray = [NSMutableArray arrayWithArray:savedUUIDs]; + + if( [mutaleArray containsObject:deviceUUID] == YES ) // It means, this device already saved, but user want's to disable auto-connect + [mutaleArray removeObject:deviceUUID]; + else + [mutaleArray addObject:deviceUUID]; + + savedUUIDs = mutaleArray; + } + else { + savedUUIDs = [[NSMutableArray alloc] initWithObjects:deviceUUID, nil]; + } + + [[NSUserDefaults standardUserDefaults] setObject:savedUUIDs forKey:kAutoConnectDictKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + } + +} + -(void)settings_button_press { if(!self.settings_view) { + CGFloat fixedHeight = 250; CGRect frame = self.view.frame; frame.origin.x += .05*frame.size.width; - frame.origin.y += (frame.size.height - 250)/2; + frame.origin.y += (frame.size.height - fixedHeight)/2; frame.size.width *= 0.9; - frame.size.height = 250; + frame.size.height = fixedHeight; ScanSettingsView* g = [[ScanSettingsView alloc] initWithFrame:frame]; - [g setBackgroundColor:[UIColor whiteColor]]; + [g setBackgroundColor:[UIColor darkGrayColor]]; [g setAlpha:0.9]; + + // Make round rect of settings view + g.layer.cornerRadius = 25; + g.layer.masksToBounds = YES; + self.settings_view = g; + self.settings_view.delegate = self; } if([self.view.subviews containsObject:self.settings_view]) { [self.settings_view removeFromSuperview]; @@ -126,8 +386,42 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N ScanTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath]; + // Add progress view to show signal strength + // Updated by Jianying Shi. 05/02/2015 + // Detect if progress view has already created. + MPBarsGraphView* chartView = (MPBarsGraphView*) [cell viewWithTag:indexPath.row + kRSSIView_Tag]; + if( chartView == nil ) { // Not yet created + + CGRect cellBounds = [cell frame]; + CGRect chartRect = CGRectMake(cellBounds.size.width - 80, 10, 30, cellBounds.size.height - 20); + + chartView = [MPPlot plotWithType : MPPlotTypeBars frame : chartRect]; + chartView.valueRanges = MPMakeGraphValuesRange(0, 100); + chartView.values = [[NSArray alloc] initWithObjects:@0, @0, @0, @0, @0, nil]; + chartView.graphColor = [UIColor colorWithRed:0.120 green:0.806 blue:0.157 alpha:1.000]; + + [chartView setTag:indexPath.row + kRSSIView_Tag]; + + [cell addSubview : chartView]; + } + + // Set signal strength as percent. + // Assuming RSSI Value range -100 ~ 0 + CGFloat percentage = (p.RSSI + 100); + + NSInteger step = Round(percentage / 20.0); // 20.f = Tick + NSMutableArray* valueArray = [[NSMutableArray alloc] init]; + for(NSInteger i = 0; i < 5; i ++) { + + if( i < step ) + [valueArray addObject:@((i+1) * 20)]; + else + [valueArray addObject:@0]; + } + + chartView.values = [NSArray arrayWithArray:valueArray]; + [cell setPeripheral:p]; - return cell; } @@ -145,4 +439,113 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath [self.delegate handleScanViewSelect:c.p]; } +#pragma mark - ScanSettingViewDelegate + +- (NSString *)platformRawString { + size_t size; + sysctlbyname("hw.machine", NULL, &size, NULL, 0); + char *machine = malloc(size); + sysctlbyname("hw.machine", machine, &size, NULL, 0); + NSString *platform = [NSString stringWithUTF8String:machine]; + free(machine); + return platform; +} + +- (NSString *)platformNiceString { + NSString *platform = [self platformRawString]; + if ([platform isEqualToString:@"iPhone1,1"]) return @"iPhone 1G"; + if ([platform isEqualToString:@"iPhone1,2"]) return @"iPhone 3G"; + if ([platform isEqualToString:@"iPhone2,1"]) return @"iPhone 3GS"; + if ([platform isEqualToString:@"iPhone3,1"]) return @"iPhone 4"; + if ([platform isEqualToString:@"iPhone3,3"]) return @"Verizon iPhone 4"; + if ([platform isEqualToString:@"iPhone4,1"]) return @"iPhone 4S"; + if ([platform isEqualToString:@"iPhone5,1"]) return @"iPhone 5"; + if ([platform isEqualToString:@"iPod1,1"]) return @"iPod Touch 1G"; + if ([platform isEqualToString:@"iPod2,1"]) return @"iPod Touch 2G"; + if ([platform isEqualToString:@"iPod3,1"]) return @"iPod Touch 3G"; + if ([platform isEqualToString:@"iPod4,1"]) return @"iPod Touch 4G"; + if ([platform isEqualToString:@"iPad1,1"]) return @"iPad 1"; + if ([platform isEqualToString:@"iPad2,1"]) return @"iPad 2 (WiFi)"; + if ([platform isEqualToString:@"iPad2,2"]) return @"iPad 2 (GSM)"; + if ([platform isEqualToString:@"iPad2,3"]) return @"iPad 2 (CDMA)"; + if ([platform isEqualToString:@"iPad3,1"]) return @"iPad 3 (WiFi)"; + if ([platform isEqualToString:@"iPad3,2"]) return @"iPad 3 (4G,2)"; + if ([platform isEqualToString:@"iPad3,3"]) return @"iPad 3 (4G,3)"; + if ([platform isEqualToString:@"i386"]) return @"Simulator"; + if ([platform isEqualToString:@"x86_64"]) return @"Simulator"; + return platform; +} + +- (void) handleSendSupporMail +{ + + if( [MFMailComposeViewController canSendMail] ) + { + MFMailComposeViewController *mail = [[MFMailComposeViewController alloc] init]; + mail.mailComposeDelegate = self; + [mail setSubject:@"Support Request"]; + + // Compose Message Body + NSString *iOSVersion = [[UIDevice currentDevice] systemVersion]; + NSString *devLabel = [self platformRawString]; + + NSString* hardwareVersion = [NSString stringWithFormat:@"iOS version : %@ v%@\n", devLabel, iOSVersion]; + NSString* bundleVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; + NSString* appVersionString = [NSString stringWithFormat:@"Mooshimeter iOS App Version = %@\n", bundleVersion]; + NSString* mailBody = [hardwareVersion stringByAppendingString:appVersionString]; + + [mail setMessageBody:mailBody isHTML:NO]; + [mail setToRecipients:@[@"hello@moosh.im"]]; + + [self presentViewController:mail animated:YES completion:nil]; + } + else{ + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Support Mail" message:@"This device can not send mail." delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil]; + [alert show]; + } +} + +- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error +{ + switch (result) { + case MFMailComposeResultSent: + { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Support Mail" message:@"Mail sent." delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil]; + [alert show]; + NSLog(@"You sent the email."); + } + break; + case MFMailComposeResultSaved: + { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Support Mail" message:@"Saved a draft of this email." delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil]; + [alert show]; + NSLog(@"You saved a draft of this email"); + } + break; + case MFMailComposeResultCancelled: + { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Support Mail" message:@"Cancelled sending this email." delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil]; + [alert show]; + NSLog(@"You cancelled sending this email."); + } + break; + case MFMailComposeResultFailed: + { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Support Mail" message:@"An error occurred when trying to compose this email." delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil]; + [alert show]; + NSLog(@"Mail failed: An error occurred when trying to compose this email"); + } + break; + default: + { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Support Mail" message:@"An error occurred when trying to compose this email." delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil]; + [alert show]; + NSLog(@"An error occurred when trying to compose this email"); + } + break; + } + + [self dismissViewControllerAnimated:YES completion:NULL]; +} + @end diff --git a/Mooshimeter/Vendor/KxMenu/KxMenu.h b/Mooshimeter/Vendor/KxMenu/KxMenu.h new file mode 100644 index 0000000..7997b0e --- /dev/null +++ b/Mooshimeter/Vendor/KxMenu/KxMenu.h @@ -0,0 +1,78 @@ +// +// KxMenu.h +// kxmenu project +// https://github.com/kolyvan/kxmenu/ +// +// Created by Kolyvan on 17.05.13. +// + +/* + Copyright (c) 2013 Konstantin Bukreev. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + - Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#import + +@interface KxMenuItem : NSObject + +@property (readwrite, nonatomic, strong) UIImage *image; +@property (readwrite, nonatomic, strong) NSString *title; +@property (readwrite, nonatomic, weak) id target; +@property (readwrite, nonatomic) SEL action; +@property (readwrite, nonatomic, strong) UIColor *foreColor; +@property (readwrite, nonatomic) NSTextAlignment alignment; + ++ (instancetype) menuItem:(NSString *) title + image:(UIImage *) image + target:(id)target + action:(SEL) action; + +@end + +@interface KxMenu : UIView + +@property (readwrite, nonatomic, strong) UIFont *titleFont; +@property (readwrite, nonatomic, strong) UIColor *tintColor; +@property (readwrite, nonatomic, strong) UIColor *tintColor1; +@property (readwrite, nonatomic, strong) UIColor *selectedColor; +@property (readwrite, nonatomic, strong) UIColor *selectedColor1; +@property (readwrite, nonatomic, strong) NSArray *menuItems; +@property (readwrite, nonatomic) BOOL blurredBackground; +@property (readwrite, nonatomic, strong) Class overlayClass; + ++ (instancetype) showMenuInView:(UIView *)view + fromRect:(CGRect)rect + menuItems:(NSArray *)menuItems; + +- (void) showMenuInView:(UIView *)view + fromRect:(CGRect)rect; + +- (void)dismissMenu; +- (void)dismissMenu:(BOOL) animated; + +@end + +@interface KxMenuOverlay : UIView +- (KxMenu *) menuView; +@end diff --git a/Mooshimeter/Vendor/KxMenu/KxMenu.m b/Mooshimeter/Vendor/KxMenu/KxMenu.m new file mode 100644 index 0000000..c9e08c7 --- /dev/null +++ b/Mooshimeter/Vendor/KxMenu/KxMenu.m @@ -0,0 +1,876 @@ +// +// KxMenu.m +// kxmenu project +// https://github.com/kolyvan/kxmenu/ +// +// Created by Kolyvan on 17.05.13. +// + +/* + Copyright (c) 2013 Konstantin Bukreev. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + - Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* + Some ideas was taken from QBPopupMenu project by Katsuma Tanaka. + https://github.com/questbeat/QBPopupMenu +*/ + +#import "KxMenu.h" +#import +#import "UIImage+ImageEffects.h" +@import Accelerate; + +const CGFloat kArrowSize = 12.f; + +//////////////////////////////////////////////////////////////////////////////// + +@implementation KxMenuOverlay + +//- (void) dealloc { NSLog(@"dealloc <%@ %p>", [self class], self); } + +- (id)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + self.backgroundColor = [UIColor clearColor]; + self.opaque = NO; + } + return self; +} + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event +{ + UIView *touched = [[touches anyObject] view]; + if (touched == self) { + //[self.nextResponder touchesBegan:touches withEvent:event]; + // [self.menuView dismissMenu:YES]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self.menuView dismissMenu:YES]; + }); + } +} + +- (KxMenu *) menuView +{ + for (UIView *v in self.subviews) { + if ([v isKindOfClass:[KxMenu class]]) { + return (KxMenu *)v; + } + } + return nil; +} + +@end + +//////////////////////////////////////////////////////////////////////////////// + +@implementation KxMenuItem + ++ (instancetype) menuItem:(NSString *) title + image:(UIImage *) image + target:(id)target + action:(SEL) action +{ + return [[KxMenuItem alloc] init:title + image:image + target:target + action:action]; +} + +- (id) init:(NSString *) title + image:(UIImage *) image + target:(id)target + action:(SEL) action +{ + NSParameterAssert(title.length || image); + + self = [super init]; + if (self) { + + _title = title; + _image = image; + _target = target; + _action = action; + } + return self; +} + +- (BOOL) enabled +{ + return _target != nil && _action != NULL; +} + +- (void) performAction +{ + __strong id target = self.target; + + if (target && [target respondsToSelector:_action]) { + + [target performSelectorOnMainThread:_action withObject:self waitUntilDone:YES]; + } +} + +- (NSString *) description +{ + return [NSString stringWithFormat:@"<%@ #%p %@>", [self class], self, _title]; +} + +@end + +//////////////////////////////////////////////////////////////////////////////// + +typedef enum { + + KxMenuViewArrowDirectionNone, + KxMenuViewArrowDirectionUp, + KxMenuViewArrowDirectionDown, + KxMenuViewArrowDirectionLeft, + KxMenuViewArrowDirectionRight, + +} KxMenuViewArrowDirection; + +@implementation KxMenu { + + KxMenuViewArrowDirection _arrowDirection; + CGFloat _arrowPosition; + UIView *_contentView; + NSArray *_menuItems; + UIImage *_backImage; + BOOL _didObserve; +} + +- (id)init +{ + self = [super initWithFrame:CGRectZero]; + if(self) { + + self.backgroundColor = [UIColor clearColor]; + self.opaque = YES; + self.alpha = 0; + + _didObserve = YES; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(orientationWillChange:) + name:UIApplicationWillChangeStatusBarOrientationNotification + object:nil]; + } + + return self; +} + +- (void) dealloc +{ + if (_didObserve) { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + } + //NSLog(@"dealloc <%@ %p>", [self class], self); +} + +- (void) orientationWillChange: (NSNotification *) n +{ + [self dismissMenu:NO]; +} + +- (void) setupFrameInView:(UIView *)view + fromRect:(CGRect)fromRect +{ + const CGSize contentSize = _contentView.frame.size; + + const CGFloat outerWidth = view.bounds.size.width; + const CGFloat outerHeight = view.bounds.size.height; + + const CGFloat rectX0 = fromRect.origin.x; + const CGFloat rectX1 = fromRect.origin.x + fromRect.size.width; + const CGFloat rectXM = fromRect.origin.x + fromRect.size.width * 0.5f; + const CGFloat rectY0 = fromRect.origin.y; + const CGFloat rectY1 = fromRect.origin.y + fromRect.size.height; + const CGFloat rectYM = fromRect.origin.y + fromRect.size.height * 0.5f;; + + const CGFloat widthPlusArrow = contentSize.width + kArrowSize; + const CGFloat heightPlusArrow = contentSize.height + kArrowSize; + const CGFloat widthHalf = contentSize.width * 0.5f; + const CGFloat heightHalf = contentSize.height * 0.5f; + + const CGFloat kMargin = 5.f; + + if (heightPlusArrow < (outerHeight - rectY1)) { + + _arrowDirection = KxMenuViewArrowDirectionUp; + CGPoint point = (CGPoint){ + rectXM - widthHalf, + rectY1 + }; + + if (point.x < kMargin) + point.x = kMargin; + + if ((point.x + contentSize.width + kMargin) > outerWidth) + point.x = outerWidth - contentSize.width - kMargin; + + _arrowPosition = rectXM - point.x; + _arrowPosition = MAX(20.f, MIN(_arrowPosition, contentSize.width - 20.f)); + _contentView.frame = (CGRect){0, kArrowSize, contentSize}; + + self.frame = (CGRect) { + + point, + contentSize.width, + contentSize.height + kArrowSize + }; + + } else if (heightPlusArrow < rectY0) { + + _arrowDirection = KxMenuViewArrowDirectionDown; + CGPoint point = (CGPoint){ + rectXM - widthHalf, + rectY0 - heightPlusArrow + }; + + if (point.x < kMargin) + point.x = kMargin; + + if ((point.x + contentSize.width + kMargin) > outerWidth) + point.x = outerWidth - contentSize.width - kMargin; + + _arrowPosition = rectXM - point.x; + _arrowPosition = MAX(20.f, MIN(_arrowPosition, contentSize.width - 20.f)); + _contentView.frame = (CGRect){CGPointZero, contentSize}; + + self.frame = (CGRect) { + + point, + contentSize.width, + contentSize.height + kArrowSize + }; + + } else if (widthPlusArrow < (outerWidth - rectX1)) { + + _arrowDirection = KxMenuViewArrowDirectionLeft; + CGPoint point = (CGPoint){ + rectX1, + rectYM - heightHalf + }; + + if (point.y < kMargin) + point.y = kMargin; + + if ((point.y + contentSize.height + kMargin) > outerHeight) + point.y = outerHeight - contentSize.height - kMargin; + + _arrowPosition = rectYM - point.y; + _contentView.frame = (CGRect){kArrowSize, 0, contentSize}; + + self.frame = (CGRect) { + + point, + contentSize.width + kArrowSize, + contentSize.height + }; + + } else if (widthPlusArrow < rectX0) { + + _arrowDirection = KxMenuViewArrowDirectionRight; + CGPoint point = (CGPoint){ + rectX0 - widthPlusArrow, + rectYM - heightHalf + }; + + if (point.y < kMargin) + point.y = kMargin; + + if ((point.y + contentSize.height + 5) > outerHeight) + point.y = outerHeight - contentSize.height - kMargin; + + _arrowPosition = rectYM - point.y; + _contentView.frame = (CGRect){CGPointZero, contentSize}; + + self.frame = (CGRect) { + + point, + contentSize.width + kArrowSize, + contentSize.height + }; + + } else { + + _arrowDirection = KxMenuViewArrowDirectionNone; + + self.frame = (CGRect) { + + (outerWidth - contentSize.width) * 0.5f, + (outerHeight - contentSize.height) * 0.5f, + contentSize, + }; + } +} + +- (void)showMenuInView:(UIView *)view + fromRect:(CGRect)rect +{ + _contentView = [self mkContentView]; + [self addSubview:_contentView]; + + [self setupFrameInView:view fromRect:rect]; + + _contentView.hidden = YES; + + const CGRect toFrame = self.frame; + + if (_blurredBackground) { + + UIColor *tintColor = self.tintColor; + if (!tintColor) { + tintColor = [UIColor colorWithRed:0.04f green:0.04f blue:0.04f alpha:0.5f]; + } + + _backImage = [KxMenu blurredBackground:view + inRect:toFrame + tintColor:tintColor]; + } + + self.frame = (CGRect){self.arrowPoint, 1, 1}; + + Class overlayClass = _overlayClass ? _overlayClass : [KxMenuOverlay class]; + KxMenuOverlay *overlay = [[overlayClass alloc] initWithFrame:view.bounds]; + [overlay addSubview:self]; + [view addSubview:overlay]; + + [self setNeedsDisplay]; + + [UIView animateWithDuration:0.2 + animations:^(void) { + + self.alpha = 1.0f; + self.frame = toFrame; + + } completion:^(BOOL completed) { + + _contentView.hidden = NO; + }]; + +} + +- (void)dismissMenu +{ + [self dismissMenu:YES]; +} + +- (void)dismissMenu:(BOOL) animated +{ + if (!self.superview) { + return; + } + + if (animated) { + + if (_contentView.hidden) { + [self dismissMenu:NO]; + return; + } + + _contentView.hidden = YES; + const CGRect toFrame = (CGRect){self.arrowPoint, 1, 1}; + + [UIView animateWithDuration:0.2 + animations:^(void) { + + self.alpha = 0; + self.frame = toFrame; + + } completion:^(BOOL finished) { + + [self dismissMenu:NO]; + }]; + + } else { + + Class overlayClass = _overlayClass ? _overlayClass : [KxMenuOverlay class]; + UIView *v = self.superview; + [self removeFromSuperview]; + if ([v isKindOfClass:overlayClass]) { + [v removeFromSuperview]; + } + } +} + +- (void)performAction:(id)sender +{ + [self dismissMenu:YES]; + + UIButton *button = (UIButton *)sender; + KxMenuItem *menuItem = _menuItems[button.tag]; + [menuItem performAction]; +} + +- (UIView *) mkContentView +{ + for (UIView *v in self.subviews) { + [v removeFromSuperview]; + } + + if (!_menuItems.count) + return nil; + + const CGFloat kMinMenuItemHeight = 32.f; + const CGFloat kMinMenuItemWidth = 32.f; + const CGFloat kMarginX = 10.f; + const CGFloat kMarginY = 5.f; + + UIFont *titleFont = self.titleFont; + if (!titleFont) titleFont = [UIFont boldSystemFontOfSize:16]; + + CGFloat maxImageWidth = 0; + CGFloat maxItemHeight = 0; + CGFloat maxItemWidth = 0; + + for (KxMenuItem *menuItem in _menuItems) { + + const CGSize imageSize = menuItem.image.size; + if (imageSize.width > maxImageWidth) + maxImageWidth = imageSize.width; + } + + if (maxImageWidth) { + maxImageWidth += kMarginX; + } + + for (KxMenuItem *menuItem in _menuItems) { + + const CGSize titleSize = [menuItem.title sizeWithFont:titleFont]; + const CGSize imageSize = menuItem.image.size; + + const CGFloat itemHeight = MAX(titleSize.height, imageSize.height) + kMarginY * 2; + const CGFloat itemWidth = ((!menuItem.enabled && !menuItem.image) ? titleSize.width : maxImageWidth + titleSize.width) + kMarginX * 4; + + if (itemHeight > maxItemHeight) + maxItemHeight = itemHeight; + + if (itemWidth > maxItemWidth) + maxItemWidth = itemWidth; + } + + maxItemWidth = MAX(maxItemWidth, kMinMenuItemWidth); + maxItemHeight = MAX(maxItemHeight, kMinMenuItemHeight); + + const CGFloat titleX = kMarginX * 2 + maxImageWidth; + const CGFloat titleWidth = maxItemWidth - titleX - kMarginX * 2; + + UIImage *selectedImage = [self selectedImage:(CGSize){maxItemWidth, maxItemHeight + 2}]; + UIImage *gradientLine = [KxMenu gradientLine: (CGSize){maxItemWidth - kMarginX * 4, 1}]; + + UIView *contentView = [[UIView alloc] initWithFrame:CGRectZero]; + contentView.autoresizingMask = UIViewAutoresizingNone; + contentView.backgroundColor = [UIColor clearColor]; + contentView.opaque = NO; + + CGFloat itemY = kMarginY * 2; + NSUInteger itemNum = 0; + + for (KxMenuItem *menuItem in _menuItems) { + + const CGRect itemFrame = (CGRect){0, itemY, maxItemWidth, maxItemHeight}; + + UIView *itemView = [[UIView alloc] initWithFrame:itemFrame]; + itemView.autoresizingMask = UIViewAutoresizingNone; + itemView.backgroundColor = [UIColor clearColor]; + itemView.opaque = NO; + + [contentView addSubview:itemView]; + + if (menuItem.enabled) { + + UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; + button.tag = itemNum; + button.frame = itemView.bounds; + button.enabled = menuItem.enabled; + button.backgroundColor = [UIColor clearColor]; + button.opaque = NO; + button.autoresizingMask = UIViewAutoresizingNone; + + [button addTarget:self + action:@selector(performAction:) + forControlEvents:UIControlEventTouchUpInside]; + + [button setBackgroundImage:selectedImage forState:UIControlStateHighlighted]; + + [itemView addSubview:button]; + } + + if (menuItem.title.length) { + + CGRect titleFrame; + + if (!menuItem.enabled && !menuItem.image) { + + titleFrame = (CGRect){ + kMarginX * 2, + kMarginY, + maxItemWidth - kMarginX * 4, + maxItemHeight - kMarginY * 2 + }; + + } else { + + titleFrame = (CGRect){ + titleX, + kMarginY, + titleWidth, + maxItemHeight - kMarginY * 2 + }; + } + + UILabel *titleLabel = [[UILabel alloc] initWithFrame:titleFrame]; + titleLabel.text = menuItem.title; + titleLabel.font = titleFont; + titleLabel.textAlignment = menuItem.alignment; + titleLabel.textColor = menuItem.foreColor ? menuItem.foreColor : [UIColor whiteColor]; + titleLabel.backgroundColor = [UIColor clearColor]; + titleLabel.autoresizingMask = UIViewAutoresizingNone; + //titleLabel.backgroundColor = [UIColor greenColor]; + [itemView addSubview:titleLabel]; + } + + if (menuItem.image) { + + const CGRect imageFrame = {kMarginX * 2, kMarginY, maxImageWidth, maxItemHeight - kMarginY * 2}; + UIImageView *imageView = [[UIImageView alloc] initWithFrame:imageFrame]; + imageView.image = menuItem.image; + imageView.clipsToBounds = YES; + imageView.contentMode = UIViewContentModeCenter; + imageView.autoresizingMask = UIViewAutoresizingNone; + [itemView addSubview:imageView]; + } + + if (itemNum < _menuItems.count - 1) { + + UIImageView *gradientView = [[UIImageView alloc] initWithImage:gradientLine]; + gradientView.frame = (CGRect){kMarginX * 2, maxItemHeight + 1, gradientLine.size}; + gradientView.contentMode = UIViewContentModeLeft; + [itemView addSubview:gradientView]; + + itemY += 2; + } + + itemY += maxItemHeight; + ++itemNum; + } + + contentView.frame = (CGRect){0, 0, maxItemWidth, itemY + kMarginY * 2}; + + return contentView; +} + +- (CGPoint) arrowPoint +{ + CGPoint point; + + if (_arrowDirection == KxMenuViewArrowDirectionUp) { + + point = (CGPoint){ CGRectGetMinX(self.frame) + _arrowPosition, CGRectGetMinY(self.frame) }; + + } else if (_arrowDirection == KxMenuViewArrowDirectionDown) { + + point = (CGPoint){ CGRectGetMinX(self.frame) + _arrowPosition, CGRectGetMaxY(self.frame) }; + + } else if (_arrowDirection == KxMenuViewArrowDirectionLeft) { + + point = (CGPoint){ CGRectGetMinX(self.frame), CGRectGetMinY(self.frame) + _arrowPosition }; + + } else if (_arrowDirection == KxMenuViewArrowDirectionRight) { + + point = (CGPoint){ CGRectGetMaxX(self.frame), CGRectGetMinY(self.frame) + _arrowPosition }; + + } else { + + point = self.center; + } + + return point; +} + +- (UIImage *) selectedImage:(CGSize) size +{ + CGFloat R0 = 0.216, G0 = 0.471, B0 = 0.871; + CGFloat R1 = 0.059, G1 = 0.353, B1 = 0.839; + + if (_selectedColor) { + CGFloat a; + [_selectedColor getRed:&R0 green:&G0 blue:&B0 alpha:&a]; + } + + if (_selectedColor1) { + CGFloat a; + [_selectedColor1 getRed:&R1 green:&G1 blue:&B1 alpha:&a]; + } + + const CGFloat locations[] = {0,1}; + const CGFloat components[] = { + R0, G0, B0, 1, + R1, G1, B1, 1, + }; + + return [KxMenu gradientImageWithSize:size locations:locations components:components count:2]; +} + ++ (UIImage *) gradientLine:(CGSize) size +{ + const CGFloat locations[5] = {0,0.2,0.5,0.8,1}; + + const CGFloat R = 0.44f, G = 0.44f, B = 0.44f; + + const CGFloat components[20] = { + R,G,B,0.1, + R,G,B,0.4, + R,G,B,0.7, + R,G,B,0.4, + R,G,B,0.1 + }; + + return [self gradientImageWithSize:size locations:locations components:components count:5]; +} + ++ (UIImage *) gradientImageWithSize:(CGSize) size + locations:(const CGFloat []) locations + components:(const CGFloat []) components + count:(NSUInteger)count +{ + UIGraphicsBeginImageContextWithOptions(size, NO, 0); + CGContextRef context = UIGraphicsGetCurrentContext(); + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGGradientRef colorGradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, 2); + CGColorSpaceRelease(colorSpace); + CGContextDrawLinearGradient(context, colorGradient, (CGPoint){0, 0}, (CGPoint){size.width, 0}, 0); + CGGradientRelease(colorGradient); + + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; +} + +- (void) drawRect:(CGRect)rect +{ + [self drawBackground:self.bounds + inContext:UIGraphicsGetCurrentContext()]; +} + +- (void)drawBackground:(CGRect)frame + inContext:(CGContextRef) context +{ + CGFloat X0 = frame.origin.x; + CGFloat X1 = frame.origin.x + frame.size.width; + CGFloat Y0 = frame.origin.y; + CGFloat Y1 = frame.origin.y + frame.size.height; + + // render arrow + + UIBezierPath *arrowPath = [UIBezierPath bezierPath]; + + if (_arrowDirection == KxMenuViewArrowDirectionUp) { + + const CGFloat arrowXM = _arrowPosition; + const CGFloat arrowX0 = arrowXM - kArrowSize; + const CGFloat arrowX1 = arrowXM + kArrowSize; + const CGFloat arrowY0 = Y0; + const CGFloat arrowY1 = Y0 + kArrowSize; + + [arrowPath moveToPoint: (CGPoint){arrowXM, arrowY0}]; + [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY1}]; + [arrowPath addLineToPoint: (CGPoint){arrowX0, arrowY1}]; + [arrowPath addLineToPoint: (CGPoint){arrowXM, arrowY0}]; + + Y0 += kArrowSize; + + } else if (_arrowDirection == KxMenuViewArrowDirectionDown) { + + const CGFloat arrowXM = _arrowPosition; + const CGFloat arrowX0 = arrowXM - kArrowSize; + const CGFloat arrowX1 = arrowXM + kArrowSize; + const CGFloat arrowY0 = Y1 - kArrowSize; + const CGFloat arrowY1 = Y1; + + [arrowPath moveToPoint: (CGPoint){arrowXM, arrowY1}]; + [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY0}]; + [arrowPath addLineToPoint: (CGPoint){arrowX0, arrowY0}]; + [arrowPath addLineToPoint: (CGPoint){arrowXM, arrowY1}]; + + Y1 -= kArrowSize; + + } else if (_arrowDirection == KxMenuViewArrowDirectionLeft) { + + const CGFloat arrowYM = _arrowPosition; + const CGFloat arrowX0 = X0; + const CGFloat arrowX1 = X0 + kArrowSize; + const CGFloat arrowY0 = arrowYM - kArrowSize;; + const CGFloat arrowY1 = arrowYM + kArrowSize; + + [arrowPath moveToPoint: (CGPoint){arrowX0, arrowYM}]; + [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY0}]; + [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY1}]; + [arrowPath addLineToPoint: (CGPoint){arrowX0, arrowYM}]; + + X0 += kArrowSize; + + } else if (_arrowDirection == KxMenuViewArrowDirectionRight) { + + const CGFloat arrowYM = _arrowPosition; + const CGFloat arrowX0 = X1; + const CGFloat arrowX1 = X1 - kArrowSize; + const CGFloat arrowY0 = arrowYM - kArrowSize;; + const CGFloat arrowY1 = arrowYM + kArrowSize; + + [arrowPath moveToPoint: (CGPoint){arrowX0, arrowYM}]; + [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY0}]; + [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY1}]; + [arrowPath addLineToPoint: (CGPoint){arrowX0, arrowYM}]; + + X1 -= kArrowSize; + } + + const CGRect bodyFrame = {X0, Y0, X1 - X0, Y1 - Y0}; + + UIBezierPath *borderPath = [UIBezierPath bezierPathWithRoundedRect:bodyFrame + cornerRadius:8]; + + if (_backImage) + { + [borderPath appendPath:arrowPath]; + [borderPath addClip]; + [_backImage drawInRect:frame]; + + } else { + + CGFloat R0 = 0.267, G0 = 0.303, B0 = 0.335; + CGFloat R1 = 0.040, G1 = 0.040, B1 = 0.040; + + if (_tintColor) { + CGFloat a; + [_tintColor getRed:&R0 green:&G0 blue:&B0 alpha:&a]; + } + + if (_tintColor1) { + CGFloat a; + [_tintColor1 getRed:&R1 green:&G1 blue:&B1 alpha:&a]; + } + + if (_arrowDirection == KxMenuViewArrowDirectionUp || + _arrowDirection == KxMenuViewArrowDirectionLeft) { + + [[UIColor colorWithRed:R0 green:G0 blue:B0 alpha:1] set]; + + } else { + + [[UIColor colorWithRed:R1 green:G1 blue:B1 alpha:1] set]; + } + + [arrowPath fill]; + + // render body + + const CGFloat locations[] = {0, 1}; + const CGFloat components[] = { + R0, G0, B0, 1, + R1, G1, B1, 1, + }; + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, + components, + locations, + sizeof(locations)/sizeof(locations[0])); + CGColorSpaceRelease(colorSpace); + + + [borderPath addClip]; + + CGPoint start, end; + + if (_arrowDirection == KxMenuViewArrowDirectionLeft || + _arrowDirection == KxMenuViewArrowDirectionRight) { + + start = (CGPoint){X0, Y0}; + end = (CGPoint){X1, Y0}; + + } else { + + start = (CGPoint){X0, Y0}; + end = (CGPoint){X0, Y1}; + } + + CGContextDrawLinearGradient(context, gradient, start, end, 0); + + CGGradientRelease(gradient); + } +} + ++ (UIImage *) blurredBackground:(UIView *)v + inRect:(CGRect)rect + tintColor:(UIColor *)tintColor +{ + const CGFloat screenScale = 1.0f; //v.window.screen.scale; + + UIImage *image; + + UIGraphicsBeginImageContextWithOptions(rect.size, NO, screenScale); + + CGContextTranslateCTM(UIGraphicsGetCurrentContext(), + -rect.origin.x, + -rect.origin.y); + + if ([v respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)] && + [v drawViewHierarchyInRect:v.bounds afterScreenUpdates:NO]) + { + image = UIGraphicsGetImageFromCurrentImageContext(); + } + + if (!image) { + + [v.layer renderInContext:UIGraphicsGetCurrentContext()]; + image = UIGraphicsGetImageFromCurrentImageContext(); + } + + UIGraphicsEndImageContext(); + + return [image applyBlurWithRadius:6 + tintColor:tintColor + saturationDeltaFactor:1.8 + maskImage:nil]; +} + ++ (instancetype) showMenuInView:(UIView *)view + fromRect:(CGRect)rect + menuItems:(NSArray *)menuItems +{ + KxMenu *menu = [[self alloc] init]; + menu.menuItems = menuItems; + dispatch_async(dispatch_get_main_queue(), ^{ + // it allows to tune parameters before showing menu + [menu showMenuInView:view fromRect:rect]; + }); + return menu; +} + +@end diff --git a/Mooshimeter/Vendor/KxMenu/UIImage+ImageEffects.h b/Mooshimeter/Vendor/KxMenu/UIImage+ImageEffects.h new file mode 100644 index 0000000..3b41fef --- /dev/null +++ b/Mooshimeter/Vendor/KxMenu/UIImage+ImageEffects.h @@ -0,0 +1,17 @@ + + +#import + +@interface UIImage (ImageEffects) + +- (UIImage *)applyLightEffect; +- (UIImage *)applyExtraLightEffect; +- (UIImage *)applyDarkEffect; +- (UIImage *)applyTintEffectWithColor:(UIColor *)tintColor; + +- (UIImage *)applyBlurWithRadius:(CGFloat)blurRadius + tintColor:(UIColor *)tintColor + saturationDeltaFactor:(CGFloat)saturationDeltaFactor + maskImage:(UIImage *)maskImage; + +@end diff --git a/Mooshimeter/Vendor/KxMenu/UIImage+ImageEffects.m b/Mooshimeter/Vendor/KxMenu/UIImage+ImageEffects.m new file mode 100644 index 0000000..87c9ffe --- /dev/null +++ b/Mooshimeter/Vendor/KxMenu/UIImage+ImageEffects.m @@ -0,0 +1,185 @@ + + +#import "UIImage+ImageEffects.h" + +#import +#import + + +@implementation UIImage (ImageEffects) + + +- (UIImage *)applyLightEffect +{ + UIColor *tintColor = [UIColor colorWithWhite:1.0 alpha:0.3]; + return [self applyBlurWithRadius:30 tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil]; +} + + +- (UIImage *)applyExtraLightEffect +{ + UIColor *tintColor = [UIColor colorWithWhite:0.97 alpha:0.82]; + return [self applyBlurWithRadius:20 tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil]; +} + + +- (UIImage *)applyDarkEffect +{ + UIColor *tintColor = [UIColor colorWithWhite:0.11 alpha:0.73]; + return [self applyBlurWithRadius:20 tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil]; +} + + +- (UIImage *)applyTintEffectWithColor:(UIColor *)tintColor +{ + const CGFloat EffectColorAlpha = 0.6; + UIColor *effectColor = tintColor; + int componentCount = CGColorGetNumberOfComponents(tintColor.CGColor); + if (componentCount == 2) { + CGFloat b; + if ([tintColor getWhite:&b alpha:NULL]) { + effectColor = [UIColor colorWithWhite:b alpha:EffectColorAlpha]; + } + } + else { + CGFloat r, g, b; + if ([tintColor getRed:&r green:&g blue:&b alpha:NULL]) { + effectColor = [UIColor colorWithRed:r green:g blue:b alpha:EffectColorAlpha]; + } + } + return [self applyBlurWithRadius:10 tintColor:effectColor saturationDeltaFactor:-1.0 maskImage:nil]; +} + + +- (UIImage *)applyBlurWithRadius:(CGFloat)blurRadius tintColor:(UIColor *)tintColor saturationDeltaFactor:(CGFloat)saturationDeltaFactor maskImage:(UIImage *)maskImage +{ + // Check pre-conditions. + if (self.size.width < 1 || self.size.height < 1) { + NSLog (@"*** error: invalid size: (%.2f x %.2f). Both dimensions must be >= 1: %@", self.size.width, self.size.height, self); + return nil; + } + if (!self.CGImage) { + NSLog (@"*** error: image must be backed by a CGImage: %@", self); + return nil; + } + if (maskImage && !maskImage.CGImage) { + NSLog (@"*** error: maskImage must be backed by a CGImage: %@", maskImage); + return nil; + } + + CGRect imageRect = { CGPointZero, self.size }; + UIImage *effectImage = self; + + BOOL hasBlur = blurRadius > __FLT_EPSILON__; + BOOL hasSaturationChange = fabs(saturationDeltaFactor - 1.) > __FLT_EPSILON__; + if (hasBlur || hasSaturationChange) { + UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] scale]); + CGContextRef effectInContext = UIGraphicsGetCurrentContext(); + CGContextScaleCTM(effectInContext, 1.0, -1.0); + CGContextTranslateCTM(effectInContext, 0, -self.size.height); + CGContextDrawImage(effectInContext, imageRect, self.CGImage); + + vImage_Buffer effectInBuffer; + effectInBuffer.data = CGBitmapContextGetData(effectInContext); + effectInBuffer.width = CGBitmapContextGetWidth(effectInContext); + effectInBuffer.height = CGBitmapContextGetHeight(effectInContext); + effectInBuffer.rowBytes = CGBitmapContextGetBytesPerRow(effectInContext); + + UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] scale]); + CGContextRef effectOutContext = UIGraphicsGetCurrentContext(); + vImage_Buffer effectOutBuffer; + effectOutBuffer.data = CGBitmapContextGetData(effectOutContext); + effectOutBuffer.width = CGBitmapContextGetWidth(effectOutContext); + effectOutBuffer.height = CGBitmapContextGetHeight(effectOutContext); + effectOutBuffer.rowBytes = CGBitmapContextGetBytesPerRow(effectOutContext); + + if (hasBlur) { + // A description of how to compute the box kernel width from the Gaussian + // radius (aka standard deviation) appears in the SVG spec: + // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement + // + // For larger values of 's' (s >= 2.0), an approximation can be used: Three + // successive box-blurs build a piece-wise quadratic convolution kernel, which + // approximates the Gaussian kernel to within roughly 3%. + // + // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5) + // + // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel. + // + CGFloat inputRadius = blurRadius * [[UIScreen mainScreen] scale]; + NSUInteger radius = floor(inputRadius * 3. * sqrt(2 * M_PI) / 4 + 0.5); + if (radius % 2 != 1) { + radius += 1; // force radius to be odd so that the three box-blur methodology works. + } + vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, NULL, 0, 0, radius, radius, 0, kvImageEdgeExtend); + vImageBoxConvolve_ARGB8888(&effectOutBuffer, &effectInBuffer, NULL, 0, 0, radius, radius, 0, kvImageEdgeExtend); + vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, NULL, 0, 0, radius, radius, 0, kvImageEdgeExtend); + } + BOOL effectImageBuffersAreSwapped = NO; + if (hasSaturationChange) { + CGFloat s = saturationDeltaFactor; + CGFloat floatingPointSaturationMatrix[] = { + 0.0722 + 0.9278 * s, 0.0722 - 0.0722 * s, 0.0722 - 0.0722 * s, 0, + 0.7152 - 0.7152 * s, 0.7152 + 0.2848 * s, 0.7152 - 0.7152 * s, 0, + 0.2126 - 0.2126 * s, 0.2126 - 0.2126 * s, 0.2126 + 0.7873 * s, 0, + 0, 0, 0, 1, + }; + const int32_t divisor = 256; + NSUInteger matrixSize = sizeof(floatingPointSaturationMatrix)/sizeof(floatingPointSaturationMatrix[0]); + int16_t saturationMatrix[matrixSize]; + for (NSUInteger i = 0; i < matrixSize; ++i) { + saturationMatrix[i] = (int16_t)roundf(floatingPointSaturationMatrix[i] * divisor); + } + if (hasBlur) { + vImageMatrixMultiply_ARGB8888(&effectOutBuffer, &effectInBuffer, saturationMatrix, divisor, NULL, NULL, kvImageNoFlags); + effectImageBuffersAreSwapped = YES; + } + else { + vImageMatrixMultiply_ARGB8888(&effectInBuffer, &effectOutBuffer, saturationMatrix, divisor, NULL, NULL, kvImageNoFlags); + } + } + if (!effectImageBuffersAreSwapped) + effectImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + if (effectImageBuffersAreSwapped) + effectImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + } + + // Set up output context. + UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] scale]); + CGContextRef outputContext = UIGraphicsGetCurrentContext(); + CGContextScaleCTM(outputContext, 1.0, -1.0); + CGContextTranslateCTM(outputContext, 0, -self.size.height); + + // Draw base image. + CGContextDrawImage(outputContext, imageRect, self.CGImage); + + // Draw effect image. + if (hasBlur) { + CGContextSaveGState(outputContext); + if (maskImage) { + CGContextClipToMask(outputContext, imageRect, maskImage.CGImage); + } + CGContextDrawImage(outputContext, imageRect, effectImage.CGImage); + CGContextRestoreGState(outputContext); + } + + // Add in color tint. + if (tintColor) { + CGContextSaveGState(outputContext); + CGContextSetFillColorWithColor(outputContext, tintColor.CGColor); + CGContextFillRect(outputContext, imageRect); + CGContextRestoreGState(outputContext); + } + + // Output image is ready. + UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return outputImage; +} + + +@end diff --git a/Mooshimeter/Vendor/LDProgressView/LDProgressView.h b/Mooshimeter/Vendor/LDProgressView/LDProgressView.h new file mode 100755 index 0000000..7eec2bc --- /dev/null +++ b/Mooshimeter/Vendor/LDProgressView/LDProgressView.h @@ -0,0 +1,29 @@ +// +// LDProgressView.h +// LDProgressView +// +// Created by Christian Di Lorenzo on 9/27/13. +// Copyright (c) 2013 Light Design. All rights reserved. +// + +#import + +typedef enum { + LDProgressStripes, + LDProgressGradient, + LDProgressSolid +} LDProgressType; + +@interface LDProgressView : UIView + +@property (nonatomic) CGFloat progress; + +@property (nonatomic, strong) UIColor *color UI_APPEARANCE_SELECTOR; +@property (nonatomic, strong) NSNumber *flat UI_APPEARANCE_SELECTOR; +@property (nonatomic, strong) NSNumber *animate UI_APPEARANCE_SELECTOR; +@property (nonatomic, strong) NSNumber *showText UI_APPEARANCE_SELECTOR; +@property (nonatomic, strong) NSNumber *borderRadius UI_APPEARANCE_SELECTOR; + +@property (nonatomic) LDProgressType type; + +@end diff --git a/Mooshimeter/Vendor/LDProgressView/LDProgressView.m b/Mooshimeter/Vendor/LDProgressView/LDProgressView.m new file mode 100755 index 0000000..01741e2 --- /dev/null +++ b/Mooshimeter/Vendor/LDProgressView/LDProgressView.m @@ -0,0 +1,276 @@ +// +// LDProgressView.m +// LDProgressView +// +// Created by Christian Di Lorenzo on 9/27/13. +// Copyright (c) 2013 Light Design. All rights reserved. +// + +#import "LDProgressView.h" +#import "UIColor+RGBValues.h" + +@interface LDProgressView () +@property (nonatomic) CGFloat offset; +@property (nonatomic, strong) NSTimer *timer; +@property (nonatomic) CGFloat stripeWidth; +@property (nonatomic, strong) UIImage *gradientProgress; +@property (nonatomic) CGSize stripeSize; + +// Animation of progress +@property (nonatomic, strong) NSTimer *animationTimer; +@property (nonatomic) CGFloat progressToAnimateTo; +@end + +@implementation LDProgressView +@synthesize animate=_animate, color=_color; + +- (id)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self initialize]; + } + return self; +} + +- (id)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCoder:aDecoder]; + if (self) { + [self initialize]; + } + return self; +} + +- (void)initialize { + self.backgroundColor = [UIColor clearColor]; +} + +- (void)setAnimate:(NSNumber *)animate { + _animate = animate; + if ([animate boolValue]) { + self.timer = [NSTimer scheduledTimerWithTimeInterval:0.02 target:self selector:@selector(incrementOffset) userInfo:nil repeats:YES]; + } else if (self.timer) { + [self.timer invalidate]; + } +} + +- (void)setProgress:(CGFloat)progress { + self.progressToAnimateTo = progress; + if (self.animationTimer) { + [self.animationTimer invalidate]; + } + self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:0.008 target:self selector:@selector(incrementAnimatingProgress) userInfo:nil repeats:YES]; +} + +- (void)incrementAnimatingProgress { + if (_progress >= self.progressToAnimateTo-0.01 && _progress <= self.progressToAnimateTo+0.01) { + _progress = self.progressToAnimateTo; + [self.animationTimer invalidate]; + [self setNeedsDisplay]; + } else { + _progress = (_progress < self.progressToAnimateTo) ? _progress + 0.01 : _progress - 0.01; + [self setNeedsDisplay]; + } +} + +- (void)incrementOffset { + if (self.offset >= 0) { + self.offset = -self.stripeWidth; + } else { + self.offset += 1; + } + [self setNeedsDisplay]; +} + +- (void)drawRect:(CGRect)rect { + CGContextRef context = UIGraphicsGetCurrentContext(); + [self drawProgressBackground:context inRect:rect]; + if (self.progress > 0) { + [self drawProgress:context withFrame:rect]; + } +} + +- (void)drawProgressBackground:(CGContextRef)context inRect:(CGRect)rect { + CGContextSaveGState(context); + UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:self.borderRadius.floatValue]; + CGContextSetFillColorWithColor(context, [UIColor colorWithRed:0.51f green:0.51f blue:0.51f alpha:1.00f].CGColor); + [roundedRect fill]; + + UIBezierPath *roundedRectangleNegativePath = [UIBezierPath bezierPathWithRect:CGRectMake(-10, -10, rect.size.width+10, rect.size.height+10)]; + [roundedRectangleNegativePath appendPath:roundedRect]; + roundedRectangleNegativePath.usesEvenOddFillRule = YES; + + CGSize shadowOffset = CGSizeMake(0.5, 1); + CGContextSaveGState(context); + CGFloat xOffset = shadowOffset.width + round(rect.size.width); + CGFloat yOffset = shadowOffset.height; + CGContextSetShadowWithColor(context, + CGSizeMake(xOffset + copysign(0.1, xOffset), yOffset + copysign(0.1, yOffset)), 5, [[UIColor blackColor] colorWithAlphaComponent:0.7].CGColor); + + [roundedRect addClip]; + CGAffineTransform transform = CGAffineTransformMakeTranslation(-round(rect.size.width), 0); + [roundedRectangleNegativePath applyTransform:transform]; + [[UIColor grayColor] setFill]; + [roundedRectangleNegativePath fill]; + CGContextRestoreGState(context); + + // Add clip for drawing progress + [roundedRect addClip]; +} + +- (void)drawProgress:(CGContextRef)context withFrame:(CGRect)frame { + CGRect rectToDrawIn = CGRectMake(0, 0, frame.size.width * self.progress, frame.size.height); + CGRect insetRect = CGRectInset(rectToDrawIn, self.progress > 0.03 ? 0.5 : -0.5, 0.5); + + UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:insetRect cornerRadius:self.borderRadius.floatValue]; + if ([self.flat boolValue]) { + CGContextSetFillColorWithColor(context, self.color.CGColor); + [roundedRect fill]; + } else { + CGContextSaveGState(context); + [roundedRect addClip]; + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGFloat locations[] = {0.0, 1.0}; + NSArray *colors = @[(__bridge id)[self.color lighterColor].CGColor, (__bridge id)[self.color darkerColor].CGColor]; + CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef) colors, locations); + + CGContextDrawLinearGradient(context, gradient, CGPointMake(insetRect.size.width / 2, 0), CGPointMake(insetRect.size.width / 2, insetRect.size.height), 0); + CGContextRestoreGState(context); + + CGGradientRelease(gradient); + CGColorSpaceRelease(colorSpace); + } + + if (self.progress != 1.0) { + switch (self.type) { + case LDProgressGradient: + [self drawGradients:context inRect:insetRect]; + break; + case LDProgressStripes: + [self drawStripes:context inRect:insetRect]; + break; + default: + break; + } + } + CGContextSetStrokeColorWithColor(context, [[self.color darkerColor] darkerColor].CGColor); + [roundedRect stroke]; + + if ([self.showText boolValue]) { + [self drawRightAlignedLabelInRect:insetRect]; + } +} + +- (void)drawGradients:(CGContextRef)context inRect:(CGRect)rect { + self.stripeSize = CGSizeMake(self.stripeWidth, rect.size.height); + CGContextSaveGState(context); + [[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:self.borderRadius.floatValue] addClip]; + CGFloat xStart = self.offset; + while (xStart < rect.size.width) { + [self.gradientProgress drawAtPoint:CGPointMake(xStart, 0)]; + xStart += self.stripeWidth; + } + CGContextRestoreGState(context); +} + +- (void)drawStripes:(CGContextRef)context inRect:(CGRect)rect { + CGContextSaveGState(context); + [[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:self.borderRadius.floatValue] addClip]; + CGContextSetFillColorWithColor(context, [[UIColor whiteColor] colorWithAlphaComponent:0.2].CGColor); + CGFloat xStart = self.offset, height = rect.size.height, width = self.stripeWidth; + while (xStart < rect.size.width) { + CGContextSaveGState(context); + CGContextMoveToPoint(context, xStart, height); + CGContextAddLineToPoint(context, xStart + width * 0.25, 0); + CGContextAddLineToPoint(context, xStart + width * 0.75, 0); + CGContextAddLineToPoint(context, xStart + width * 0.50, height); + CGContextClosePath(context); + CGContextFillPath(context); + CGContextRestoreGState(context); + xStart += width; + } + CGContextRestoreGState(context); +} + +- (void)drawRightAlignedLabelInRect:(CGRect)rect { + if (rect.size.width > 40) { + UILabel *label = [[UILabel alloc] initWithFrame:rect]; + label.adjustsFontSizeToFitWidth = YES; + label.backgroundColor = [UIColor clearColor]; + label.textAlignment = NSTextAlignmentRight; + label.text = [NSString stringWithFormat:@"%.0f%%", self.progress*100]; + label.font = [UIFont boldSystemFontOfSize:17]; + UIColor *baseLabelColor = [self.color isLighterColor] ? [UIColor blackColor] : [UIColor whiteColor]; + label.textColor = [baseLabelColor colorWithAlphaComponent:0.6]; + [label drawTextInRect:CGRectMake(6, 0, rect.size.width-12, rect.size.height)]; + } +} + +#pragma mark - Accessors + +- (UIImage *)gradientProgress { + if (!_gradientProgress) { + UIGraphicsBeginImageContext(self.stripeSize); + CGContextRef imageCxt = UIGraphicsGetCurrentContext(); + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGFloat locations[] = {0.0, 0.5, 1.0}; + NSArray *colors = @[(__bridge id)[UIColor clearColor].CGColor, (__bridge id)[[[self.color darkerColor] darkerColor] colorWithAlphaComponent:0.3].CGColor, (__bridge id)[UIColor clearColor].CGColor]; + CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef) colors, locations); + + CGContextDrawLinearGradient(imageCxt, gradient, CGPointMake(0, self.stripeSize.height / 2), CGPointMake(self.stripeSize.width, self.stripeSize.height / 2), 0); + + CGGradientRelease(gradient); + CGColorSpaceRelease(colorSpace); + + _gradientProgress = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + } + return _gradientProgress; +} + +- (NSNumber *)animate { + if (_animate == nil) { + return @YES; + } + return _animate; +} + +- (NSNumber *)showText { + if (_showText == nil) { + return @YES; + } + return _showText; +} + +- (void)setColor:(UIColor *)color { + _color = color; + self.gradientProgress = nil; +} + +- (UIColor *)color { + if (!_color) { + return [UIColor colorWithRed:0.07 green:0.56 blue:1.0 alpha:1.0]; + } + return _color; +} + +- (CGFloat)stripeWidth { + switch (self.type) { + case LDProgressGradient: + _stripeWidth = 15; + break; + default: + _stripeWidth = 50; + break; + } + return _stripeWidth; +} + +- (NSNumber *)borderRadius { + if (!_borderRadius) { + return @(self.frame.size.height / 2.0); + } + return _borderRadius; +} + +@end diff --git a/Mooshimeter/Vendor/LDProgressView/UIColor+RGBValues.h b/Mooshimeter/Vendor/LDProgressView/UIColor+RGBValues.h new file mode 100755 index 0000000..96d89a8 --- /dev/null +++ b/Mooshimeter/Vendor/LDProgressView/UIColor+RGBValues.h @@ -0,0 +1,23 @@ +// +// UIColor+RGBValues.h +// LDBarButtonItemExample +// +// Created by Christian Di Lorenzo on 1/24/13. +// Copyright (c) 2013 Light Design. All rights reserved. +// + +#import + +@interface UIColor (RGBValues) + +- (CGFloat)red; +- (CGFloat)green; +- (CGFloat)blue; +- (CGFloat)alpha; + +- (UIColor *)darkerColor; +- (UIColor *)lighterColor; +- (BOOL)isLighterColor; +- (BOOL)isClearColor; + +@end diff --git a/Mooshimeter/Vendor/LDProgressView/UIColor+RGBValues.m b/Mooshimeter/Vendor/LDProgressView/UIColor+RGBValues.m new file mode 100755 index 0000000..d9f2f20 --- /dev/null +++ b/Mooshimeter/Vendor/LDProgressView/UIColor+RGBValues.m @@ -0,0 +1,65 @@ +// +// UIColor+RGBValues.m +// LDBarButtonItemExample +// +// Created by Christian Di Lorenzo on 1/24/13. +// Copyright (c) 2013 Light Design. All rights reserved. +// + +#import "UIColor+RGBValues.h" + +@implementation UIColor (RGBValues) + +- (CGFloat)red { + const CGFloat* components = CGColorGetComponents(self.CGColor); + return components[0]; +} + +- (CGFloat)green { + const CGFloat* components = CGColorGetComponents(self.CGColor); + return components[1]; +} + +- (CGFloat)blue { + const CGFloat* components = CGColorGetComponents(self.CGColor); + return components[2]; +} + +- (CGFloat)alpha { + return CGColorGetAlpha(self.CGColor); +} + +- (BOOL)isClearColor { + return [self isEqual:[UIColor clearColor]]; +} + +- (BOOL)isLighterColor { + const CGFloat* components = CGColorGetComponents(self.CGColor); + return (components[0]+components[1]+components[2])/3 >= 0.5; +} + +- (UIColor *)lighterColor { + if ([self isEqual:[UIColor whiteColor]]) return [UIColor colorWithWhite:0.99 alpha:1.0]; + if ([self isEqual:[UIColor blackColor]]) return [UIColor colorWithWhite:0.01 alpha:1.0]; + float hue, saturation, brightness, alpha; + if ([self getHue:&hue saturation:&saturation brightness:&brightness alpha:&alpha]) + return [UIColor colorWithHue:hue + saturation:saturation + brightness:MIN(brightness * 1.3, 1.0) + alpha:alpha]; + return nil; +} + +- (UIColor *)darkerColor { + if ([self isEqual:[UIColor whiteColor]]) return [UIColor colorWithWhite:0.99 alpha:1.0]; + if ([self isEqual:[UIColor blackColor]]) return [UIColor colorWithWhite:0.01 alpha:1.0]; + float hue, saturation, brightness, alpha; + if ([self getHue:&hue saturation:&saturation brightness:&brightness alpha:&alpha]) + return [UIColor colorWithHue:hue + saturation:saturation + brightness:brightness * 0.75 + alpha:alpha]; + return nil; +} + +@end diff --git a/Mooshimeter/Vendor/MPPlot/MPBarsGraphView.h b/Mooshimeter/Vendor/MPPlot/MPBarsGraphView.h new file mode 100644 index 0000000..ff07f61 --- /dev/null +++ b/Mooshimeter/Vendor/MPPlot/MPBarsGraphView.h @@ -0,0 +1,21 @@ +// +// MPBarsGraphView.h +// MPPlot +// +// Created by Alex Manzella on 22/05/14. +// Copyright (c) 2014 mpow. All rights reserved. +// + +#import "MPPlot.h" + +@interface MPBarsGraphView : MPPlot{ + + BOOL shouldAnimate; + +} + +@property (nonatomic, readwrite) CGFloat topCornerRadius; + +@property (nonatomic, readwrite) NSArray* backValues; // background gray values - always be 0.2, 0.4, 0.6, 0.8, 1.0 + +@end diff --git a/Mooshimeter/Vendor/MPPlot/MPBarsGraphView.m b/Mooshimeter/Vendor/MPPlot/MPBarsGraphView.m new file mode 100644 index 0000000..dd19329 --- /dev/null +++ b/Mooshimeter/Vendor/MPPlot/MPBarsGraphView.m @@ -0,0 +1,146 @@ +// +// MPBarsGraphView.m +// MPPlot +// +// Created by Alex Manzella on 22/05/14. +// Copyright (c) 2014 mpow. All rights reserved. +// + +#import "MPBarsGraphView.h" + +@implementation MPBarsGraphView + +- (instancetype)initWithCoder:(NSCoder *)aDecoder +{ + self = [super initWithCoder:aDecoder]; + + if (self) { + self.backgroundColor = [UIColor clearColor]; + currentTag = -1; + self.topCornerRadius = -1; + + // Initialize backValues + self.backValues = [[NSArray alloc] initWithObjects:@0.2, @0.4, @0.6, @0.8, @1.0, nil]; + } + + return self; +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + + if (self) { + self.backgroundColor = [UIColor clearColor]; + currentTag = -1; + self.topCornerRadius = -1; + + // Initialize backValues + self.backValues = [[NSArray alloc] initWithObjects:@0.2, @0.4, @0.6, @0.8, @1.0, nil]; + } + + return self; +} + +- (void)drawRect:(CGRect)rect +{ + [super drawRect:rect]; + + if (self.values.count && !self.waitToUpdate) { + + for (UIView *subview in self.subviews) { + [subview removeFromSuperview]; + } + + [self addBarsAnimated:shouldAnimate]; + [self.graphColor setStroke]; + + UIBezierPath *line = [UIBezierPath bezierPath]; + [line moveToPoint:CGPointMake(PADDING, self.height)]; + [line addLineToPoint:CGPointMake(self.width-PADDING, self.height)]; + [line setLineWidth:1]; + [line stroke]; + } +} + +- (void)addBarsAnimated:(BOOL)animated +{ + for (UIButton* button in buttons) { + [button removeFromSuperview]; + } + + buttons=[[NSMutableArray alloc] init]; + + if (animated) { + self.layer.masksToBounds=YES; + } + + CGFloat barWidth = self.width/(points.count*2+1); + CGFloat radius = barWidth*(self.topCornerRadius >=0 ? self.topCornerRadius : 0.3); + + for (NSInteger i=0; i0.0 ? _animationDuration : .25; +} + +- (void)animate +{ + self.waitToUpdate=NO; + shouldAnimate=YES; + [self setNeedsDisplay]; +} + +@end diff --git a/Mooshimeter/Vendor/MPPlot/MPGraphView.h b/Mooshimeter/Vendor/MPPlot/MPGraphView.h new file mode 100644 index 0000000..59d6fa7 --- /dev/null +++ b/Mooshimeter/Vendor/MPPlot/MPGraphView.h @@ -0,0 +1,20 @@ +// +// MPGraphView.h +// +// +// Created by Alex Manzella on 18/05/14. +// Copyright (c) 2014 mpow. All rights reserved. +// + +#import +#import "MPPlot.h" + +@interface MPGraphView : MPPlot{ + + CAGradientLayer *gradient; +} + +@property (nonatomic, assign) BOOL curved; +@property (nonatomic, retain) NSArray *fillColors; // array of colors or CGColor + +@end diff --git a/Mooshimeter/Vendor/MPPlot/MPGraphView.m b/Mooshimeter/Vendor/MPPlot/MPGraphView.m new file mode 100644 index 0000000..b76efaf --- /dev/null +++ b/Mooshimeter/Vendor/MPPlot/MPGraphView.m @@ -0,0 +1,225 @@ +// +// MPGraphView.m +// +// +// Created by Alex Manzella on 18/05/14. +// Copyright (c) 2014 mpow. All rights reserved. +// + +#import "MPGraphView.h" +#import "UIBezierPath+curved.h" + +@implementation MPGraphView + ++ (Class)layerClass +{ + return [CAShapeLayer class]; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder +{ + self = [super initWithCoder:aDecoder]; + + if (self) { + self.backgroundColor = [UIColor clearColor]; + currentTag = -1; + } + + return self; +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + + if (self) { + self.backgroundColor = [UIColor clearColor]; + currentTag = -1; + } + + return self; +} + +- (void)drawRect:(CGRect)rect +{ + [super drawRect:rect]; + + if (self.values.count && !self.waitToUpdate) { + ((CAShapeLayer *)self.layer).fillColor = [UIColor clearColor].CGColor; + ((CAShapeLayer *)self.layer).strokeColor = self.graphColor.CGColor; + ((CAShapeLayer *)self.layer).path = [self graphPathFromPoints].CGPath; + ((CAShapeLayer *)self.layer).lineWidth = self.lineWidth ? self.lineWidth : 1; + } +} + +- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event +{ + UIView *view = [super hitTest:point withEvent:event]; + + if (view==self) { + return nil; + } + + return view; +} + +- (UIBezierPath *)graphPathFromPoints +{ + BOOL fill=self.fillColors.count; + UIBezierPath *path=[UIBezierPath bezierPath]; + + for (UIButton* button in buttons) { + [button removeFromSuperview]; + } + + buttons=[[NSMutableArray alloc] init]; + + for (NSInteger i=0;i +#import "UIView+Frame.h" +#import "_MPWButton.h" + +#define PADDING 12 + +#define ANIMATIONDURATION 1.5 + +typedef NS_ENUM(NSUInteger, MPPlotType) { + MPPlotTypeUnknown=0, + MPPlotTypeGraph=1, + MPPlotTypeBars=2, + MPPlotTypeCake=3, +}; + +typedef CGFloat(^GraphPointsAlgorithm)(CGFloat x); + +struct _MPValuesRange { + CGFloat max; + CGFloat min; +}; + +typedef struct _MPValuesRange MPGraphValuesRange; + +NS_INLINE MPGraphValuesRange MPMakeGraphValuesRange(CGFloat min, CGFloat max) { + MPGraphValuesRange r; + r.min = min; + r.max = max; + return r; +} + +NS_INLINE BOOL MPValuesRangeNULL(MPGraphValuesRange r) { + return r.min==CGFLOAT_MIN && r.max==CGFLOAT_MAX; +} + +NS_INLINE MPGraphValuesRange MPGetBiggestRange(MPGraphValuesRange r1,MPGraphValuesRange r2) { + MPGraphValuesRange r; + r.min=(r1.minr2.max ? r1.max : r2.max); + return r; +} + +@protocol MPDetailView + +- (void)setText:(NSString *)text; + +@end + +@interface MPPlot : UIView{ + + MPPlotType plotType; + UIColor *_graphColor; + CGFloat _animationDuration; + NSArray *points; + NSMutableArray *buttons; + NSInteger currentTag; + GraphPointsAlgorithm _customAlgorithm; + NSUInteger _numberOfPoints; + +} + +// Abstract Class ++ (id)plotWithType:(MPPlotType)type frame:(CGRect)frame; ++ (MPGraphValuesRange)rangeForValues:(NSArray *)values; + +@property (nonatomic, copy) NSArray *values; // array of NSNumber or NSString +@property (nonatomic, retain) UIColor *graphColor; // color of the line +@property (nonatomic, assign) CGFloat lineWidth; +@property (nonatomic, assign) BOOL waitToUpdate; +@property (nonatomic, assign) CGFloat animationDuration; +@property (nonatomic, assign) MPGraphValuesRange valueRanges; // specify or read the max min... useful if you want to make 2 graph based on the same values, otherwise they would be inconsistent between them, for example graph of download has max 20 min 10 , and graph of updates has max 5 and min 0, without that the 20 and the 5 would be in the same place +// detail View customization +@property (nonatomic, readwrite) UIView *detailView; +@property (nonatomic, retain) UIColor *detailBackgroundColor; +@property (nonatomic, retain) UIColor *detailTextColor; +@property (nonatomic, retain) NSNumberFormatter *detailLabelFormatter; + +- (void)animate; +- (void)setAlgorithm:(GraphPointsAlgorithm)customAlgorithm numberOfPoints:(NSUInteger)numberOfPoints; +- (void)tap:(UIButton *)button; + +@end diff --git a/Mooshimeter/Vendor/MPPlot/MPPlot.m b/Mooshimeter/Vendor/MPPlot/MPPlot.m new file mode 100644 index 0000000..c0ce809 --- /dev/null +++ b/Mooshimeter/Vendor/MPPlot/MPPlot.m @@ -0,0 +1,300 @@ +// +// MPPlot.m +// MPPlot +// +// Created by Alex Manzella on 22/05/14. +// Copyright (c) 2014 mpow. All rights reserved. +// + +#import "MPPlot.h" +#import "MPGraphView.h" +#import "MPBarsGraphView.h" + +@implementation MPPlot + ++ (MPGraphValuesRange)rangeForValues:(NSArray *)values +{ + CGFloat min,max; + + min = max = [[values firstObject] floatValue]; + + for (NSInteger i=0; imax) { + max = val; + } + + if (val0.0 ? _animationDuration : ANIMATIONDURATION; +} + +#pragma mark Internal + +- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event +{ + // Marked by Jianying Shi. + // No need to display detail view. + // 05/03/2015 + +#if 0 + UIView *view = [super hitTest:point withEvent:event]; + + if (view==self && self.detailView.superview) { + [UIView animateWithDuration:.15 animations:^{ + self.detailView.transform=CGAffineTransformMakeScale(0, 0); + }completion:^(BOOL finished) { + [self.detailView removeFromSuperview]; + currentTag=-1; + }]; + } + + return view; +#else + + return nil; +#endif +} + +- (void)animate +{ + self.waitToUpdate=NO; + [self setNeedsDisplay]; +} + +#pragma mark Actions + +- (void)tap:(UIButton *)button +{ + if (button.tag==currentTag) { + currentTag = -1; + }else{ + currentTag = button.tag; + } + + if (self.detailView.superview) { + + [UIView animateWithDuration:.15 animations:^{ + self.detailView.transform = CGAffineTransformMakeScale(0, 0); + }completion:^(BOOL finished) { + [self.detailView removeFromSuperview]; + if(currentTag>=0) { + [self displayDetailViewAtPoint:button.center]; + } + }]; + + }else{ + [self displayDetailViewAtPoint:button.center]; + } +} + +- (void)displayDetailViewAtPoint:(CGPoint)point +{ + if ([[self.values objectAtIndex:currentTag] isKindOfClass:[NSString class]]) { + self.detailView.text = [self.values objectAtIndex:currentTag]; + }else{ + self.detailView.text = [NSString stringWithFormat:@"%@",[self.detailLabelFormatter stringFromNumber:[self.values objectAtIndex:currentTag]]]; + } + + self.detailView.center = point; + self.detailView.transform = CGAffineTransformMakeScale(0, 0); + [self addSubview:self.detailView]; + + [UIView animateWithDuration:.2 animations:^{ + self.detailView.transform = CGAffineTransformMakeScale(1, 1); + }]; +} + +-(UIView *)detailView +{ + if (_detailView) { + return _detailView; + } + + UILabel* label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 80, 20)]; + label.textAlignment = NSTextAlignmentCenter; + label.textColor = self.detailTextColor; + label.backgroundColor = self.detailBackgroundColor; + label.layer.borderColor = label.textColor.CGColor; + label.layer.borderWidth = .5; + label.layer.cornerRadius = 3; + label.adjustsFontSizeToFitWidth = YES; + label.clipsToBounds = YES; + + self.detailView = (UILabel *)label; + + return _detailView; +} + +@end diff --git a/Mooshimeter/Vendor/MPPlot/UIBezierPath+curved.h b/Mooshimeter/Vendor/MPPlot/UIBezierPath+curved.h new file mode 100644 index 0000000..41ddf81 --- /dev/null +++ b/Mooshimeter/Vendor/MPPlot/UIBezierPath+curved.h @@ -0,0 +1,15 @@ +// +// UIBezierPath+curved.h +// MPPlot +// +// Created by Alex Manzella on 22/05/14. +// Copyright (c) 2014 mpow. All rights reserved. +// + +#import + +@interface UIBezierPath (curved) + +- (UIBezierPath*)smoothedPathWithGranularity:(NSInteger)granularity; + +@end diff --git a/Mooshimeter/Vendor/MPPlot/UIBezierPath+curved.m b/Mooshimeter/Vendor/MPPlot/UIBezierPath+curved.m new file mode 100644 index 0000000..dcb7321 --- /dev/null +++ b/Mooshimeter/Vendor/MPPlot/UIBezierPath+curved.m @@ -0,0 +1,91 @@ +// +// UIBezierPath+curved.m +// MPPlot +// +// Created by Alex Manzella on 22/05/14. +// Copyright (c) 2014 mpow. All rights reserved. +// + +#import "UIBezierPath+curved.h" + +// Based on code from Erica Sadun +void getPointsFromBezier(void *info, const CGPathElement *element); +NSArray *pointsFromBezierPath(UIBezierPath *bpath); + +#define VALUE(_INDEX_) [NSValue valueWithCGPoint:points[_INDEX_]] +#define POINT(_INDEX_) [(NSValue *)[points objectAtIndex:_INDEX_] CGPointValue] + +@implementation UIBezierPath (curved) + +// Get points from Bezier Curve +void getPointsFromBezier(void *info, const CGPathElement *element) +{ + NSMutableArray *bezierPoints = (__bridge NSMutableArray *)info; + // Retrieve the path element type and its points + CGPathElementType type = element->type; + CGPoint *points = element->points; + + // Add the points if they're available (per type) + if (type != kCGPathElementCloseSubpath) + { + [bezierPoints addObject:VALUE(0)]; + if ((type != kCGPathElementAddLineToPoint) && + (type != kCGPathElementMoveToPoint)) + [bezierPoints addObject:VALUE(1)]; + } + if (type == kCGPathElementAddCurveToPoint) + [bezierPoints addObject:VALUE(2)]; +} + +NSArray *pointsFromBezierPath(UIBezierPath *bpath) +{ + NSMutableArray *points = [NSMutableArray array]; + CGPathApply(bpath.CGPath, (__bridge void *)points, getPointsFromBezier); + return points; +} + +- (UIBezierPath*)smoothedPathWithGranularity:(NSInteger)granularity; +{ + NSMutableArray *points = [pointsFromBezierPath(self) mutableCopy]; + + if (points.count < 4) return [self copy]; + + // Add control points to make the math make sense + [points insertObject:[points objectAtIndex:0] atIndex:0]; + [points addObject:[points lastObject]]; + + UIBezierPath *smoothedPath = [self copy]; + [smoothedPath removeAllPoints]; + [smoothedPath moveToPoint:POINT(0)]; + + for (NSUInteger index = 1; index < points.count - 2; index++) + { + CGPoint p0 = POINT(index - 1); + CGPoint p1 = POINT(index); + CGPoint p2 = POINT(index + 1); + CGPoint p3 = POINT(index + 2); + + // now add n points starting at p1 + dx/dy up until p2 using Catmull-Rom splines + for (int i = 1; i < granularity; i++) + { + float t = (float) i * (1.0f / (float) granularity); + float tt = t * t; + float ttt = tt * t; + + CGPoint pi; // intermediate point + pi.x = 0.5 * (2*p1.x+(p2.x-p0.x)*t + (2*p0.x-5*p1.x+4*p2.x-p3.x)*tt + (3*p1.x-p0.x-3*p2.x+p3.x)*ttt); + pi.y = 0.5 * (2*p1.y+(p2.y-p0.y)*t + (2*p0.y-5*p1.y+4*p2.y-p3.y)*tt + (3*p1.y-p0.y-3*p2.y+p3.y)*ttt); + [smoothedPath addLineToPoint:pi]; + } + + // Now add p2 + [smoothedPath addLineToPoint:p2]; + } + + // finish by adding the last point + [smoothedPath addLineToPoint:POINT(points.count - 1)]; + + return smoothedPath; +} + +@end diff --git a/Mooshimeter/Vendor/MPPlot/UIView+Frame.h b/Mooshimeter/Vendor/MPPlot/UIView+Frame.h new file mode 100644 index 0000000..0b77c91 --- /dev/null +++ b/Mooshimeter/Vendor/MPPlot/UIView+Frame.h @@ -0,0 +1,15 @@ +// +// UIView+Frame.h +// GoodBarberV2 +// +// Created by MPow on 31/10/13. +// Copyright (c) 2013 DuoApps. All rights reserved. +// + +#import + +@interface UIView (Frame) + +@property(nonatomic, readwrite) CGFloat x,y,width,height; + +@end diff --git a/Mooshimeter/Vendor/MPPlot/UIView+Frame.m b/Mooshimeter/Vendor/MPPlot/UIView+Frame.m new file mode 100644 index 0000000..1bf3a4c --- /dev/null +++ b/Mooshimeter/Vendor/MPPlot/UIView+Frame.m @@ -0,0 +1,61 @@ +// +// UIView+Frame.m +// GoodBarberV2 +// +// Created by MPow on 31/10/13. +// Copyright (c) 2013 DuoApps. All rights reserved. +// + +#import "UIView+Frame.h" + +@implementation UIView (Frame) + +- (void)setX:(CGFloat)x +{ + CGRect frame=self.frame; + frame.origin.x=x; + self.frame=frame; +} + +- (void)setY:(CGFloat)y +{ + CGRect frame=self.frame; + frame.origin.y=y; + self.frame=frame; +} + +- (void)setWidth:(CGFloat)width +{ + CGRect frame=self.frame; + frame.size.width=width; + self.frame=frame; +} + +- (void)setHeight:(CGFloat)height +{ + CGRect frame=self.frame; + frame.size.height=height; + self.frame=frame; +} + +- (CGFloat)x +{ + return self.frame.origin.x; +} + +- (CGFloat)y +{ + return self.frame.origin.y; +} + +- (CGFloat)width +{ + return self.bounds.size.width; +} + +- (CGFloat)height +{ + return self.bounds.size.height; +} + +@end diff --git a/Mooshimeter/Vendor/MPPlot/_MPWButton.h b/Mooshimeter/Vendor/MPPlot/_MPWButton.h new file mode 100644 index 0000000..d067655 --- /dev/null +++ b/Mooshimeter/Vendor/MPPlot/_MPWButton.h @@ -0,0 +1,15 @@ +// +// _MPWButton.h +// MPPlotExample +// +// Created by Alex Manzella on 23/10/14. +// Copyright (c) 2014 mpow. All rights reserved. +// + +#import + +@interface _MPWButton : UIButton + +@property (nonatomic, assign) UIOffset tappableAreaOffset; + +@end diff --git a/Mooshimeter/Vendor/MPPlot/_MPWButton.m b/Mooshimeter/Vendor/MPPlot/_MPWButton.m new file mode 100644 index 0000000..5272a7e --- /dev/null +++ b/Mooshimeter/Vendor/MPPlot/_MPWButton.m @@ -0,0 +1,19 @@ +// +// _MPWButton.m +// MPPlotExample +// +// Created by Alex Manzella on 23/10/14. +// Copyright (c) 2014 mpow. All rights reserved. +// + +#import "_MPWButton.h" + +@implementation _MPWButton + +- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event +{ + return CGRectContainsPoint(CGRectInset(self.bounds, -self.tappableAreaOffset.horizontal, -self.tappableAreaOffset.vertical), point); +} + +@end + diff --git a/Mooshimeter/Vendor/PDChart/PDBarChart/PDBarChart.swift b/Mooshimeter/Vendor/PDChart/PDBarChart/PDBarChart.swift new file mode 100644 index 0000000..c0cf2fc --- /dev/null +++ b/Mooshimeter/Vendor/PDChart/PDBarChart/PDBarChart.swift @@ -0,0 +1,229 @@ +// +// PDBarChart.swift +// PDChart +// +// Created by Pandara on 14-7-11. +// Copyright (c) 2014年 Pandara. All rights reserved. +// + +import UIKit +import QuartzCore + +class PDBarChartDataItem { + //optional + var axesColor: UIColor = UIColor(red: 80.0 / 255, green: 80.0 / 255, blue: 80.0 / 255, alpha: 1.0) //坐标轴颜色 + var axesTipColor: UIColor = UIColor(red: 80.0 / 255, green: 80.0 / 255, blue: 80.0 / 255, alpha: 1.0) //坐标轴刻度值颜色 + var chartLayerColor: UIColor = UIColor(red: 61.0 / 255, green: 189.0 / 255, blue: 100.0 / 255, alpha: 1.0) //折线的颜色 + + var barBgColor: UIColor = UIColor(red: 234.0 / 255, green: 234.0 / 255, blue: 234.0 / 255, alpha: 1.0) + var barColor: UIColor = UIColor(red: 69.0 / 255, green: 189.0 / 255, blue: 100.0 / 255, alpha: 1.0)//69 189 100 + + var showAxes: Bool = true + var axesWidth: CGFloat = 1.0 + + var barMargin: CGFloat = 4.0 + var barWidth: CGFloat! + + var barCornerRadius: CGFloat = 0 + + //require + var xMax: CGFloat! + var xInterval: CGFloat! + + var yMax: CGFloat! + var yInterval: CGFloat! + + var xAxesDegreeTexts: [String]? + var yAxesDegreeTexts: [String]? + + var barPointArray: [CGPoint]? + + init() { + + } +} + +class PDBarChart: PDChart { + + var axesComponent: PDChartAxesComponent! + var dataItem: PDBarChartDataItem! + + //property + var barBgArray: [UIView] = [] + var barLayerArray: [CAShapeLayer] = [] + + init(frame: CGRect, dataItem: PDBarChartDataItem) { + super.init(frame: frame) + self.dataItem = dataItem + + var axesDataItem: PDChartAxesComponentDataItem = PDChartAxesComponentDataItem() + axesDataItem.arrowBodyLength += 10 + axesDataItem.targetView = self + axesDataItem.featureH = getFeatureHeight() + axesDataItem.featureW = getFeatureWidth() + axesDataItem.xMax = dataItem.xMax + axesDataItem.xInterval = dataItem.xInterval + axesDataItem.yMax = dataItem.yMax + axesDataItem.yInterval = dataItem.yInterval + axesDataItem.xAxesDegreeTexts = dataItem.xAxesDegreeTexts + axesDataItem.showXDegree = false + axesDataItem.axesWidth = dataItem.axesWidth + + self.axesComponent = PDChartAxesComponent(dataItem: axesDataItem) + + //bar width + var xDegreeInterval = self.axesComponent.getXDegreeInterval() + self.dataItem.barWidth = xDegreeInterval - dataItem.barMargin * 2 + + self.addBarBackgroundView() + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func getFeatureWidth() -> CGFloat { + return CGFloat(self.frame.size.width) + } + + func getFeatureHeight() -> CGFloat { + return CGFloat(self.frame.size.height) + } + + func getBarView(frame: CGRect) -> UIView { + var barView: UIView = UIView(frame: frame) + barView.backgroundColor = self.dataItem.barBgColor + barView.clipsToBounds = true + barView.layer.cornerRadius = self.dataItem.barCornerRadius + return barView + } + + func getBarShapeLayer(layerFrame: CGRect, barHeight: CGFloat) -> CAShapeLayer { + var shapeLayer: CAShapeLayer = CAShapeLayer() + shapeLayer.fillColor = UIColor.whiteColor().CGColor + shapeLayer.strokeColor = self.dataItem.barColor.CGColor + shapeLayer.lineWidth = self.dataItem.barWidth + shapeLayer.strokeStart = 0.0 + shapeLayer.strokeEnd = 1.0 + shapeLayer.frame = layerFrame + + var barPath: UIBezierPath = UIBezierPath() + barPath.moveToPoint(CGPointMake(self.dataItem.barWidth / 2, layerFrame.size.height)) + barPath.addLineToPoint(CGPointMake(self.dataItem.barWidth / 2, layerFrame.size.height - barHeight)) + barPath.stroke() + + shapeLayer.path = barPath.CGPath + + return shapeLayer + } + + func getBarAnimation() -> CABasicAnimation { + CATransaction.begin() + + var pathAnimation: CABasicAnimation = CABasicAnimation(keyPath: "strokeEnd") + pathAnimation.duration = 1.0 + pathAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + pathAnimation.fromValue = 0.0 + pathAnimation.toValue = 1.0 + + CATransaction.setCompletionBlock({ + () -> Void in + return + }) + + CATransaction.commit() + + return pathAnimation + } + + func addBarBackgroundView() { + var xDegreeInterval: CGFloat = self.axesComponent.getXDegreeInterval() + var basePoint: CGPoint = self.axesComponent.getBasePoint() + var xAxesWidth: CGFloat = self.axesComponent.getXAxesWidth() + var yAxesHeight: CGFloat = self.axesComponent.getYAxesHeight() + + for var i = 0; i < self.dataItem.barPointArray!.count; i++ { + var point: CGPoint = self.dataItem.barPointArray![i] + + var bvw: CGFloat = self.dataItem.barWidth + var bvh: CGFloat = yAxesHeight + var bvx: CGFloat = basePoint.x + xDegreeInterval / 2 + self.dataItem.barMargin + (self.dataItem.barWidth + self.dataItem.barMargin * 2) * CGFloat(i) + var bvy: CGFloat = basePoint.y - bvh - self.dataItem.axesWidth / 2 + + var barView: UIView = self.getBarView(CGRectMake(bvx, bvy, bvw, bvh)) + self.barBgArray.append(barView) + self.addSubview(barView) + } + } + + override func strokeChart() { + if !(self.dataItem.barPointArray != nil) { + return + } + + UIGraphicsBeginImageContext(self.frame.size) + + var yAxesHeight: CGFloat = self.axesComponent.getYAxesHeight() + + for var i = 0; i < self.dataItem.barPointArray!.count; i++ { + var point: CGPoint = self.dataItem.barPointArray![i] + var barView: UIView = self.barBgArray[i] + + //barShape + var barShapeLayer: CAShapeLayer = self.getBarShapeLayer(CGRectMake(0, 0, barView.frame.size.width, barView.frame.size.height), barHeight: point.y / self.dataItem.yMax * yAxesHeight) + barShapeLayer.addAnimation(self.getBarAnimation(), forKey: "barAnimation") + self.barLayerArray.append(barShapeLayer) + barView.layer.addSublayer(barShapeLayer) + } + + UIGraphicsEndImageContext() + } + + override func drawRect(rect: CGRect) + { + super.drawRect(rect) + + var context: CGContextRef = UIGraphicsGetCurrentContext() + axesComponent.strokeAxes(context) + } + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mooshimeter/Vendor/PDChart/PDChart.swift b/Mooshimeter/Vendor/PDChart/PDChart.swift new file mode 100644 index 0000000..31521e3 --- /dev/null +++ b/Mooshimeter/Vendor/PDChart/PDChart.swift @@ -0,0 +1,36 @@ +// +// PDChart.swift +// Swift_iPhone_demo +// +// Created by Pandara on 14-7-2. +// Copyright (c) 2014年 Pandara. All rights reserved. +// + +import UIKit + +let yMargin: Double = 10 +let xMargin: Double = 10 + +class PDChart: UIView { + + + + override init(frame: CGRect) { + super.init(frame: frame) + self.backgroundColor = UIColor.whiteColor() + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func strokeChart() { + + } + + override func drawRect(rect: CGRect) + { + // Drawing code + + } +} diff --git a/Mooshimeter/Vendor/PDChart/PDChartComponent/PDChartAxesComponent.swift b/Mooshimeter/Vendor/PDChart/PDChartComponent/PDChartAxesComponent.swift new file mode 100644 index 0000000..ac13145 --- /dev/null +++ b/Mooshimeter/Vendor/PDChart/PDChartComponent/PDChartAxesComponent.swift @@ -0,0 +1,254 @@ +// +// PDChartAxesComponent.swift +// PDChart +// +// Created by Pandara on 14-7-11. +// Copyright (c) 2014年 Pandara. All rights reserved. +// + +import UIKit + +class PDChartAxesComponentDataItem: NSObject { + //required + var targetView: UIView! + + var featureH: CGFloat! + var featureW: CGFloat! + + var xMax: CGFloat! + var xInterval: CGFloat! + var yMax: CGFloat! + var yInterval: CGFloat! + + var xAxesDegreeTexts: [String]? + var yAxesDegreeTexts: [String]? + + //optional default + var showAxes: Bool = true + + var showXDegree: Bool = true + var showYDegree: Bool = true + + var axesColor: UIColor = UIColor(red: 80.0 / 255, green: 80.0 / 255, blue: 80.0 / 255, alpha: 1.0)//坐标轴颜色 + var axesTipColor: UIColor = UIColor(red: 80.0 / 255, green: 80.0 / 255, blue: 80.0 / 255, alpha: 1.0)//坐标轴刻度值颜色 + + var xAxesLeftMargin: CGFloat = 40 //坐标系左边margin + var xAxesRightMargin: CGFloat = 40 //坐标系右边margin + var yAxesBottomMargin: CGFloat = 40 //坐标系下面margin + var yAxesTopMargin: CGFloat = 40 //坐标系上方marign + + var axesWidth: CGFloat = 1.0 //坐标轴的粗细 + + var arrowHeight: CGFloat = 5.0 + var arrowWidth: CGFloat = 5.0 + var arrowBodyLength: CGFloat = 10.0 + + var degreeLength: CGFloat = 5.0 //坐标轴刻度直线的长度 + var degreeTipFontSize: CGFloat = 10.0 + var degreeTipMarginHorizon: CGFloat = 5.0 + var degreeTipMarginVertical: CGFloat = 5.0 + + override init() { + + } +} + +class PDChartAxesComponent: NSObject { + + var dataItem: PDChartAxesComponentDataItem! + + init(dataItem: PDChartAxesComponentDataItem) { + self.dataItem = dataItem + } + + func getYAxesHeight() -> CGFloat {//heigth between 0~yMax + var basePoint: CGPoint = self.getBasePoint() + var yAxesHeight = basePoint.y - dataItem.arrowHeight - dataItem.yAxesTopMargin - dataItem.arrowBodyLength + return yAxesHeight + } + + func getXAxesWidth() -> CGFloat {//width between 0~xMax + var basePoint: CGPoint = self.getBasePoint() + var xAxesWidth = dataItem.featureW - basePoint.x - dataItem.arrowHeight - dataItem.xAxesRightMargin - dataItem.arrowBodyLength + return xAxesWidth + } + + func getBasePoint() -> CGPoint { + + var neededAxesWidth: CGFloat! + if dataItem.showAxes { + neededAxesWidth = CGFloat(dataItem.axesWidth) + } else { + neededAxesWidth = 0 + } + + var basePoint: CGPoint = CGPoint(x: dataItem.xAxesLeftMargin + neededAxesWidth / 2.0, y: dataItem.featureH - (dataItem.yAxesBottomMargin + neededAxesWidth / 2.0)) + return basePoint + } + + func getXDegreeInterval() -> CGFloat { + var xAxesWidth: CGFloat = self.getXAxesWidth() + var xDegreeInterval: CGFloat = dataItem.xInterval / dataItem.xMax * xAxesWidth + return xDegreeInterval + } + + func getYDegreeInterval() -> CGFloat { + var yAxesHeight: CGFloat = self.getYAxesHeight() + var yDegreeInterval: CGFloat = dataItem.yInterval / dataItem.yMax * yAxesHeight + return yDegreeInterval + } + + func getAxesDegreeTipLabel(tipText: String, center: CGPoint, size: CGSize, fontSize: CGFloat, textAlignment: NSTextAlignment, textColor: UIColor) -> UILabel { + var label: UILabel = UILabel(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: size)) + label.text = tipText + label.center = center + label.textAlignment = textAlignment + label.textColor = textColor + label.backgroundColor = UIColor.clearColor() + label.adjustsFontSizeToFitWidth = true + label.font = UIFont.systemFontOfSize(fontSize) + return label + } + + func getXAxesDegreeTipLabel(tipText: String, center: CGPoint, size: CGSize, fontSize: CGFloat) -> UILabel { + return self.getAxesDegreeTipLabel(tipText, center: center, size: size, fontSize: fontSize, textAlignment: NSTextAlignment.Center, textColor: dataItem.axesTipColor) + } + + func getYAxesDegreeTipLabel(tipText: String, center: CGPoint, size: CGSize, fontSize: CGFloat) -> UILabel { + return self.getAxesDegreeTipLabel(tipText, center: center, size: size, fontSize: fontSize, textAlignment: NSTextAlignment.Right, textColor: dataItem.axesTipColor) + } + + func strokeAxes(context: CGContextRef?) { + var xAxesWidth: CGFloat = self.getXAxesWidth() + var yAxesHeight: CGFloat = self.getYAxesHeight() + var basePoint: CGPoint = self.getBasePoint() + + + if dataItem.showAxes { + CGContextSetStrokeColorWithColor(context, dataItem.axesColor.CGColor) + CGContextSetFillColorWithColor(context, dataItem.axesColor.CGColor) + + var axesPath: UIBezierPath = UIBezierPath() + axesPath.lineWidth = dataItem.axesWidth + axesPath.lineCapStyle = kCGLineCapRound + axesPath.lineJoinStyle = kCGLineJoinRound + + //x axes-------------------------------------- + axesPath.moveToPoint(CGPoint(x: basePoint.x, y: basePoint.y)) + axesPath.addLineToPoint(CGPoint(x: basePoint.x + xAxesWidth, y: basePoint.y)) + + //degrees in x axes + var xDegreeNum: Int = Int((dataItem.xMax - (dataItem.xMax % dataItem.xInterval)) / dataItem.xInterval) + var xDegreeInterval: CGFloat = self.getXDegreeInterval() + + if dataItem.showXDegree { + for var i = 0; i < xDegreeNum; i++ { + var degreeX: CGFloat = basePoint.x + xDegreeInterval * CGFloat(i + 1) + axesPath.moveToPoint(CGPoint(x: degreeX, y: basePoint.y)) + axesPath.addLineToPoint(CGPoint(x: degreeX, y: basePoint.y - dataItem.degreeLength)) + } + } + + //x axes arrow + //arrow body + axesPath.moveToPoint(CGPoint(x: basePoint.x + xAxesWidth, y: basePoint.y)) + axesPath.addLineToPoint(CGPoint(x: basePoint.x + xAxesWidth + dataItem.arrowBodyLength, y: basePoint.y)) + //arrow head + var arrowPath: UIBezierPath = UIBezierPath() + arrowPath.lineWidth = dataItem.axesWidth + arrowPath.lineCapStyle = kCGLineCapRound + arrowPath.lineJoinStyle = kCGLineJoinRound + + var xArrowTopPoint: CGPoint = CGPoint(x: basePoint.x + xAxesWidth + dataItem.arrowBodyLength + dataItem.arrowHeight, y: basePoint.y) + arrowPath.moveToPoint(xArrowTopPoint) + arrowPath.addLineToPoint(CGPoint(x: basePoint.x + xAxesWidth + dataItem.arrowBodyLength, y: basePoint.y - dataItem.arrowWidth / 2)) + arrowPath.addLineToPoint(CGPoint(x: basePoint.x + xAxesWidth + dataItem.arrowBodyLength, y: basePoint.y + dataItem.arrowWidth / 2)) + arrowPath.addLineToPoint(xArrowTopPoint) + + //y axes-------------------------------------- + axesPath.moveToPoint(CGPoint(x: basePoint.x, y: basePoint.y)) + axesPath.addLineToPoint(CGPoint(x: basePoint.x, y: basePoint.y - yAxesHeight)) + + //degrees in y axes + var yDegreesNum: Int = Int((dataItem.yMax - (dataItem.yMax % dataItem.yInterval)) / dataItem.yInterval) + var yDegreeInterval: CGFloat = self.getYDegreeInterval() + if dataItem.showYDegree { + for var i = 0; i < yDegreesNum; i++ { + var degreeY: CGFloat = basePoint.y - yDegreeInterval * CGFloat(i + 1) + axesPath.moveToPoint(CGPoint(x: basePoint.x, y: degreeY)) + axesPath.addLineToPoint(CGPoint(x: basePoint.x + dataItem.degreeLength, y: degreeY)) + } + } + + //y axes arrow + //arrow body + axesPath.moveToPoint(CGPoint(x: basePoint.x, y: basePoint.y - yAxesHeight)) + axesPath.addLineToPoint(CGPoint(x: basePoint.x, y: basePoint.y - yAxesHeight - dataItem.arrowBodyLength)) + //arrow head + var yArrowTopPoint: CGPoint = CGPoint(x: basePoint.x, y: basePoint.y - yAxesHeight - dataItem.arrowBodyLength - dataItem.arrowHeight) + arrowPath.moveToPoint(yArrowTopPoint) + arrowPath.addLineToPoint(CGPoint(x: basePoint.x - dataItem.arrowWidth / 2, y: basePoint.y - yAxesHeight - dataItem.arrowBodyLength)) + arrowPath.addLineToPoint(CGPoint(x: basePoint.x + dataItem.arrowWidth / 2, y: basePoint.y - yAxesHeight - dataItem.arrowBodyLength)) + arrowPath.addLineToPoint(yArrowTopPoint) + + axesPath.stroke() + arrowPath.stroke() + + //axes tips------------------------------------ + //func getXAxesDegreeTipLabel(tipText: String, frame: CGRect, fontSize: CGFloat) -> UILabel { + if (dataItem.xAxesDegreeTexts != nil) { + for var i = 0; i < dataItem.xAxesDegreeTexts!.count; i++ { + var size: CGSize = CGSize(width: xDegreeInterval - dataItem.degreeTipMarginHorizon * 2, height: dataItem.degreeTipFontSize) + var center: CGPoint = CGPoint(x: basePoint.x + xDegreeInterval * CGFloat(i + 1), y: basePoint.y + dataItem.degreeTipMarginVertical + size.height / 2) + var label: UILabel = self.getXAxesDegreeTipLabel(dataItem.xAxesDegreeTexts![i], center: center, size: size, fontSize: dataItem.degreeTipFontSize) + dataItem.targetView.addSubview(label) + } + } else { + for var i = 0; i < xDegreeNum; i++ { + var size: CGSize = CGSize(width: xDegreeInterval - dataItem.degreeTipMarginHorizon * 2, height: dataItem.degreeTipFontSize) + var center: CGPoint = CGPoint(x: basePoint.x + xDegreeInterval * CGFloat(i + 1), y: basePoint.y + dataItem.degreeTipMarginVertical + size.height / 2) + var label: UILabel = self.getXAxesDegreeTipLabel("\(CGFloat(i + 1) * dataItem.xInterval)", center: center, size: size, fontSize: dataItem.degreeTipFontSize) + dataItem.targetView.addSubview(label) + } + } + + if (dataItem.yAxesDegreeTexts != nil) { + for var i = 0; i < dataItem.yAxesDegreeTexts!.count; i++ { + var size: CGSize = CGSize(width: dataItem.xAxesLeftMargin - dataItem.degreeTipMarginHorizon * 2, height: dataItem.degreeTipFontSize) + var center: CGPoint = CGPoint(x: dataItem.xAxesLeftMargin / 2, y: basePoint.y - yDegreeInterval * CGFloat(i + 1)) + var label: UILabel = self.getYAxesDegreeTipLabel(dataItem.yAxesDegreeTexts![i], center: center, size: size, fontSize: dataItem.degreeTipFontSize) + dataItem.targetView.addSubview(label) + } + } else { + for var i = 0; i < yDegreesNum; i++ { + var size: CGSize = CGSize(width: dataItem.xAxesLeftMargin - dataItem.degreeTipMarginHorizon * 2, height: dataItem.degreeTipFontSize) + var center: CGPoint = CGPoint(x: dataItem.xAxesLeftMargin / 2, y: basePoint.y - yDegreeInterval * CGFloat(i + 1)) + var label: UILabel = self.getYAxesDegreeTipLabel("\(CGFloat(i + 1) * dataItem.yInterval)", center: center, size: size, fontSize: dataItem.degreeTipFontSize) + dataItem.targetView.addSubview(label) + } + } + } + + } +} + + + + + + + + + + + + + + + + + + + + + diff --git a/Mooshimeter/Vendor/PDChart/PDLineChart/PDLineChart.swift b/Mooshimeter/Vendor/PDChart/PDLineChart/PDLineChart.swift new file mode 100644 index 0000000..164b691 --- /dev/null +++ b/Mooshimeter/Vendor/PDChart/PDLineChart/PDLineChart.swift @@ -0,0 +1,158 @@ +// +// PDLineChart.swift +// Swift_iPhone_demo +// +// Created by Pandara on 14-7-3. +// Copyright (c) 2014年 Pandara. All rights reserved. +// + +import UIKit +import QuartzCore + +class PDLineChartDataItem { + //optional + var axesColor: UIColor = UIColor(red: 80.0 / 255, green: 80.0 / 255, blue: 80.0 / 255, alpha: 1.0) //坐标轴颜色 + var axesTipColor: UIColor = UIColor(red: 80.0 / 255, green: 80.0 / 255, blue: 80.0 / 255, alpha: 1.0) //坐标轴刻度值颜色 + var chartLayerColor: UIColor = UIColor(red: 61.0 / 255, green: 189.0 / 255, blue: 100.0 / 255, alpha: 1.0) //折线的颜色 + + var showAxes: Bool = true + + var xAxesDegreeTexts: [String]? + var yAxesDegreeTexts: [String]? + + //require + var xMax: CGFloat! + var xInterval: CGFloat! + + var yMax: CGFloat! + var yInterval: CGFloat! + + var pointArray: [CGPoint]?//按照数学中的平面二维坐标系输入数据 + + init() { + + } +} + +class PDLineChart: PDChart { + var axesComponent: PDChartAxesComponent! + var dataItem: PDLineChartDataItem! + + init(frame: CGRect, dataItem: PDLineChartDataItem) { + super.init(frame: frame) + + self.dataItem = dataItem + + var axesDataItem: PDChartAxesComponentDataItem = PDChartAxesComponentDataItem() + axesDataItem.targetView = self + axesDataItem.featureH = self.getFeatureHeight() + axesDataItem.featureW = self.getFeatureWidth() + axesDataItem.xMax = dataItem.xMax + axesDataItem.xInterval = dataItem.xInterval + axesDataItem.yMax = dataItem.yMax + axesDataItem.yInterval = dataItem.yInterval + axesDataItem.xAxesDegreeTexts = dataItem.xAxesDegreeTexts + axesDataItem.yAxesDegreeTexts = dataItem.yAxesDegreeTexts + + axesComponent = PDChartAxesComponent(dataItem: axesDataItem) + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func getFeatureWidth() -> CGFloat { + return CGFloat(self.frame.size.width) + } + + func getFeatureHeight() -> CGFloat { + return CGFloat(self.frame.size.height) + } + + override func strokeChart() { + if !(self.dataItem.pointArray != nil) { + return + } + + //绘图layer + var chartLayer: CAShapeLayer = CAShapeLayer() + chartLayer.lineCap = kCALineCapRound + chartLayer.lineJoin = kCALineJoinRound + chartLayer.fillColor = UIColor.whiteColor().CGColor + chartLayer.strokeColor = self.dataItem.chartLayerColor.CGColor + chartLayer.lineWidth = 2.0 + chartLayer.strokeStart = 0.0 + chartLayer.strokeEnd = 1.0 + self.layer.addSublayer(chartLayer) + + //画线段 + UIGraphicsBeginImageContext(self.frame.size) + + var progressLine: UIBezierPath = UIBezierPath() + + var basePoint: CGPoint = axesComponent.getBasePoint() + var xAxesWidth: CGFloat = axesComponent.getXAxesWidth() + var yAxesHeight: CGFloat = axesComponent.getYAxesHeight() + for var i = 0; i < self.dataItem.pointArray!.count; i++ { + var point: CGPoint = self.dataItem.pointArray![i] + var pixelPoint: CGPoint = CGPoint(x: basePoint.x + point.x / self.dataItem.xMax * xAxesWidth, y: basePoint.y - point.y / self.dataItem.yMax * yAxesHeight)//转换为可以绘制的,屏幕中的像素点 + + if i == 0 { + progressLine.moveToPoint(pixelPoint) + } else { + progressLine.addLineToPoint(pixelPoint) + } + } + + progressLine.stroke() + + chartLayer.path = progressLine.CGPath + + //动画 + CATransaction.begin() + var pathAnimation: CABasicAnimation = CABasicAnimation(keyPath: "strokeEnd") + pathAnimation.duration = 1.0 + pathAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + pathAnimation.fromValue = 0.0 + pathAnimation.toValue = 1.0 + + //func addAnimation(anim: CAAnimation!, forKey key: String!) + chartLayer.addAnimation(pathAnimation, forKey: "strokeEndAnimation") + + + //class func setCompletionBlock(block: (() -> Void)!) + CATransaction.setCompletionBlock({ + () -> Void in + }) + CATransaction.commit() + + UIGraphicsEndImageContext() + } + + override func drawRect(rect: CGRect) { + super.drawRect(rect) + + var context: CGContext = UIGraphicsGetCurrentContext() + axesComponent.strokeAxes(context) + } +} + + + + + + + + + + + + + + + + + + + + diff --git a/Mooshimeter/Vendor/PDChart/PDPieChart/PDPieChart.swift b/Mooshimeter/Vendor/PDChart/PDPieChart/PDPieChart.swift new file mode 100644 index 0000000..728651e --- /dev/null +++ b/Mooshimeter/Vendor/PDChart/PDPieChart/PDPieChart.swift @@ -0,0 +1,179 @@ +// +// PDPieChart.swift +// Swift_iPhone_demo +// +// Created by Pandara on 14-7-7. +// Copyright (c) 2014年 Pandara. All rights reserved. +// + +import UIKit +import QuartzCore + +struct PieDataItem { + var description: String? + var color: UIColor? + var percentage: CGFloat! +} + +let lightGreen: UIColor = UIColor(red: 69.0 / 255, green: 212.0 / 255, blue: 103.0 / 255, alpha: 1.0) +let middleGreen: UIColor = UIColor(red: 66.0 / 255, green: 187.0 / 255, blue: 102.0 / 255, alpha: 1.0) +let deepGreen: UIColor = UIColor(red: 64.0 / 255, green: 164.0 / 255, blue: 102.0 / 255, alpha: 1.0) + +class PDPieChartDataItem { + //optional + var animationDur: CGFloat = 1.0 + var clockWise: Bool = true + var chartStartAngle: CGFloat = CGFloat(-M_PI / 2) + var pieTipFontSize: CGFloat = 20.0 + var pieTipTextColor: UIColor = UIColor.whiteColor() + var pieMargin: CGFloat = 0 + + //required + var pieWidth: CGFloat! + var dataArray: [PieDataItem]! + + init() { + + } +} + +class PDPieChart: PDChart { + var dataItem: PDPieChartDataItem! + + init(frame: CGRect, dataItem: PDPieChartDataItem) { + super.init(frame: frame) + self.dataItem = dataItem + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func getShapeLayerWithARCPath(color: UIColor?, lineWidth: CGFloat, center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, clockWise: Bool) -> CAShapeLayer { + //layer + var pathLayer: CAShapeLayer = CAShapeLayer() + pathLayer.lineCap = kCALineCapButt + pathLayer.fillColor = UIColor.clearColor().CGColor + if (color != nil) { + pathLayer.strokeColor = color!.CGColor + } + pathLayer.lineWidth = self.dataItem.pieWidth + pathLayer.strokeStart = 0.0 + pathLayer.strokeEnd = 1.0 + pathLayer.backgroundColor = UIColor.clearColor().CGColor + + //path + var path: UIBezierPath = UIBezierPath() + path.lineWidth = lineWidth + path.addArcWithCenter(center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: self.dataItem.clockWise) + path.stroke() + + pathLayer.path = path.CGPath + return pathLayer + } + + override func strokeChart() { + var pieCenterPointArray: [CGPoint] = [] + var radius: CGFloat = self.frame.size.width / 2 - self.dataItem.pieMargin - self.dataItem.pieWidth / 2 + var center: CGPoint = CGPoint(x: self.frame.size.width / 2, y: self.frame.size.height / 2) + + var chartLayer: CAShapeLayer = CAShapeLayer() + chartLayer.backgroundColor = UIColor.clearColor().CGColor + self.layer.addSublayer(chartLayer) + + UIGraphicsBeginImageContext(self.frame.size) + //每一段饼 + var totalPercentage: CGFloat = 0.0 + for var i = 0; i < self.dataItem.dataArray.count; i++ { + //data + var dataItem: PieDataItem = self.dataItem.dataArray[i] + var startAngle: CGFloat = CGFloat(M_PI * 2.0) * totalPercentage + self.dataItem.chartStartAngle + var endAngle: CGFloat = startAngle + dataItem.percentage * CGFloat(M_PI * 2) + + //pie center point + var sign: Int = self.dataItem.clockWise ? -1 : 1 + var angle: Float = Float(dataItem.percentage) * Float(M_PI) * Float(2.0) / 2.0 + Float(totalPercentage) * Float(M_PI * 2.0) + + var pieCenterPointX: CGFloat = center.x + radius * CGFloat(sinf(angle)) + var pieCenterPointY: CGFloat = center.y - radius * CGFloat(cosf(angle)) + var pieCenterPoint: CGPoint = CGPoint(x: pieCenterPointX, y: pieCenterPointY) + pieCenterPointArray.append(pieCenterPoint) + + totalPercentage += dataItem.percentage + + //arc path layer + var pathLayer = self.getShapeLayerWithARCPath(dataItem.color, lineWidth: self.dataItem.pieWidth, center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockWise: true) + chartLayer.addSublayer(pathLayer) + } + + //mask + var maskCenter: CGPoint = CGPointMake(self.frame.size.width / 2.0, self.frame.size.height / 2) + var maskRadius: CGFloat = self.frame.size.width / 2 - self.dataItem.pieMargin - self.dataItem.pieWidth / 2 + var maskStartAngle: CGFloat = -CGFloat(M_PI) / 2 + var maskEndAngle: CGFloat = CGFloat(M_PI) * 2 - CGFloat(M_PI) / 2 + var maskLayer: CAShapeLayer = self.getShapeLayerWithARCPath(UIColor.whiteColor(), lineWidth: self.dataItem.pieWidth, center: maskCenter, radius: maskRadius, startAngle: maskStartAngle, endAngle: maskEndAngle, clockWise: true) + maskLayer.strokeStart = 0.0 + maskLayer.strokeEnd = 1.0 + + var animation: CABasicAnimation = CABasicAnimation(keyPath: "strokeEnd") + animation.duration = CFTimeInterval(self.dataItem.animationDur) + animation.fromValue = 0.0 + animation.toValue = 1.0 + animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + animation.removedOnCompletion = true + maskLayer.addAnimation(animation, forKey: "maskAnimation") + + self.layer.mask = maskLayer + + UIGraphicsEndImageContext() + + //pie tip + for var i = 0; i < pieCenterPointArray.count; i++ { + var dataItem: PieDataItem = self.dataItem.dataArray[i] + + var pieTipLabel: UILabel = UILabel() + pieTipLabel.backgroundColor = UIColor.clearColor(); + pieTipLabel.font = UIFont.systemFontOfSize(self.dataItem.pieTipFontSize) + pieTipLabel.textColor = self.dataItem.pieTipTextColor + if (dataItem.description != nil) { + pieTipLabel.text = dataItem.description + } else { + pieTipLabel.text = "\(dataItem.percentage * 100)%" + } + pieTipLabel.sizeToFit() + pieTipLabel.alpha = 0.0 + pieTipLabel.center = pieCenterPointArray[i] + + UIView.animateWithDuration(0.5, delay: NSTimeInterval(self.dataItem.animationDur), options: UIViewAnimationOptions.CurveEaseInOut, animations:{ + () -> Void in + pieTipLabel.alpha = 1.0 + }, completion: { + (completion: Bool) -> Void in + return + }) + + self.addSubview(pieTipLabel) + } + } + + override func drawRect(rect: CGRect) + { + super.drawRect(rect) + } + +} + + + + + + + + + + + + + + + diff --git a/Mooshimeter/Vendor/Reachability/Reachability.h b/Mooshimeter/Vendor/Reachability/Reachability.h new file mode 100755 index 0000000..aa9244f --- /dev/null +++ b/Mooshimeter/Vendor/Reachability/Reachability.h @@ -0,0 +1,91 @@ +/* + + File: Reachability.h + Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs. + + Version: 2.2 + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. + ("Apple") in consideration of your agreement to the following terms, and your + use, installation, modification or redistribution of this Apple software + constitutes acceptance of these terms. If you do not agree with these terms, + please do not use, install, modify or redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject + to these terms, Apple grants you a personal, non-exclusive license, under + Apple's copyrights in this original Apple software (the "Apple Software"), to + use, reproduce, modify and redistribute the Apple Software, with or without + modifications, in source and/or binary forms; provided that if you redistribute + the Apple Software in its entirety and without modifications, you must retain + this notice and the following text and disclaimers in all such redistributions + of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Inc. may be used + to endorse or promote products derived from the Apple Software without specific + prior written permission from Apple. Except as expressly stated in this notice, + no other rights or licenses, express or implied, are granted by Apple herein, + including but not limited to any patent rights that may be infringed by your + derivative works or by other works in which the Apple Software may be + incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO + WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED + WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN + COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR + DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF + CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF + APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2010 Apple Inc. All Rights Reserved. + + */ + + +#import +#import +#import + +typedef enum { + NotReachable = 0, + ReachableViaWiFi, + ReachableViaWWAN +} NetworkStatus; +#define kReachabilityChangedNotification @"kNetworkReachabilityChangedNotification" + +@interface Reachability: NSObject +{ + BOOL localWiFiRef; + SCNetworkReachabilityRef reachabilityRef; +} + +//reachabilityWithHostName- Use to check the reachability of a particular host name. ++ (Reachability*) reachabilityWithHostName: (NSString*) hostName; + +//reachabilityWithAddress- Use to check the reachability of a particular IP address. ++ (Reachability*) reachabilityWithAddress: (const struct sockaddr_in*) hostAddress; + +//reachabilityForInternetConnection- checks whether the default route is available. +// Should be used by applications that do not connect to a particular host ++ (Reachability*) reachabilityForInternetConnection; + +//reachabilityForLocalWiFi- checks whether a local wifi connection is available. ++ (Reachability*) reachabilityForLocalWiFi; + ++ (BOOL)isNetAvailable; + +//Start listening for reachability notifications on the current run loop +- (BOOL) startNotifier; +- (void) stopNotifier; + +- (NetworkStatus) currentReachabilityStatus; +//WWAN may be available, but not active until a connection has been established. +//WiFi may require a connection for VPN on Demand. +- (BOOL) connectionRequired; +@end + + diff --git a/Mooshimeter/Vendor/Reachability/Reachability.m b/Mooshimeter/Vendor/Reachability/Reachability.m new file mode 100755 index 0000000..918855c --- /dev/null +++ b/Mooshimeter/Vendor/Reachability/Reachability.m @@ -0,0 +1,284 @@ +/* + + File: Reachability.m + Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs. + + Version: 2.2 + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. + ("Apple") in consideration of your agreement to the following terms, and your + use, installation, modification or redistribution of this Apple software + constitutes acceptance of these terms. If you do not agree with these terms, + please do not use, install, modify or redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject + to these terms, Apple grants you a personal, non-exclusive license, under + Apple's copyrights in this original Apple software (the "Apple Software"), to + use, reproduce, modify and redistribute the Apple Software, with or without + modifications, in source and/or binary forms; provided that if you redistribute + the Apple Software in its entirety and without modifications, you must retain + this notice and the following text and disclaimers in all such redistributions + of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Inc. may be used + to endorse or promote products derived from the Apple Software without specific + prior written permission from Apple. Except as expressly stated in this notice, + no other rights or licenses, express or implied, are granted by Apple herein, + including but not limited to any patent rights that may be infringed by your + derivative works or by other works in which the Apple Software may be + incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO + WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED + WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN + COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR + DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF + CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF + APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2010 Apple Inc. All Rights Reserved. + + */ + +#import +#import +#import +#import +#import +#import + +#import + +#import "Reachability.h" + +#define kShouldPrintReachabilityFlags 0 + +static BOOL netAvailable = YES; + +static void PrintReachabilityFlags(SCNetworkReachabilityFlags flags, const char* comment) +{ +#if kShouldPrintReachabilityFlags + + DebugNSLog(@"Reachability Flag Status: %c%c %c%c%c%c%c%c%c %s\n", + (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', + (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', + + (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', + (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', + (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', + (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-', + comment + ); +#endif +} + + +@implementation Reachability +static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) +{ +#pragma unused (target, flags) + NSCAssert(info != NULL, @"info was NULL in ReachabilityCallback"); + NSCAssert([(NSObject*) info isKindOfClass: [Reachability class]], @"info was wrong class in ReachabilityCallback"); + + //We're on the main RunLoop, so an NSAutoreleasePool is not necessary, but is added defensively + // in case someon uses the Reachablity object in a different thread. + NSAutoreleasePool* myPool = [[NSAutoreleasePool alloc] init]; + + Reachability* noteObject = (Reachability*) info; + // Post a notification to notify the client that the network reachability changed. + [[NSNotificationCenter defaultCenter] postNotificationName: kReachabilityChangedNotification object: noteObject]; + + [myPool release]; +} + +- (BOOL) startNotifier +{ + BOOL retVal = NO; + SCNetworkReachabilityContext context = {0, self, NULL, NULL, NULL}; + if(SCNetworkReachabilitySetCallback(reachabilityRef, ReachabilityCallback, &context)) + { + if(SCNetworkReachabilityScheduleWithRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)) + { + retVal = YES; + } + } + return retVal; +} + +- (void) stopNotifier +{ + if(reachabilityRef!= NULL) + { + SCNetworkReachabilityUnscheduleFromRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + } +} + +- (void) dealloc +{ + [self stopNotifier]; + if(reachabilityRef) CFRelease(reachabilityRef); + [super dealloc]; +} + ++ (Reachability*) reachabilityWithHostName: (NSString*) hostName +{ + Reachability* retVal = NULL; + SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]); + if(reachability!= NULL) + { + retVal= [[[self alloc] init] autorelease]; + if(retVal!= NULL) + { + retVal->reachabilityRef = reachability; + retVal->localWiFiRef = NO; + } + } + return retVal; +} + ++ (Reachability*) reachabilityWithAddress: (const struct sockaddr_in*) hostAddress +{ + SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress); + Reachability* retVal = NULL; + if(reachability!= NULL) + { + retVal= [[[self alloc] init] autorelease]; + if(retVal!= NULL) + { + retVal->reachabilityRef = reachability; + retVal->localWiFiRef = NO; + } + } + return retVal; +} + ++ (Reachability*) reachabilityForInternetConnection +{ + struct sockaddr_in zeroAddress; + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + return [self reachabilityWithAddress: &zeroAddress]; +} + ++ (Reachability*) reachabilityForLocalWiFi +{ + struct sockaddr_in localWifiAddress; + bzero(&localWifiAddress, sizeof(localWifiAddress)); + localWifiAddress.sin_len = sizeof(localWifiAddress); + localWifiAddress.sin_family = AF_INET; + // IN_LINKLOCALNETNUM is defined in as 169.254.0.0 + localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); + Reachability* retVal = [self reachabilityWithAddress: &localWifiAddress]; + if(retVal!= NULL) + { + retVal->localWiFiRef = YES; + } + return retVal; +} + + + ++ (BOOL)isNetAvailable { + return netAvailable; +} + + +#pragma mark Network Flag Handling + +- (NetworkStatus) localWiFiStatusForFlags: (SCNetworkReachabilityFlags) flags +{ + PrintReachabilityFlags(flags, "localWiFiStatusForFlags"); + + BOOL retVal = NotReachable; + if((flags & kSCNetworkReachabilityFlagsReachable) && (flags & kSCNetworkReachabilityFlagsIsDirect)) + { + retVal = ReachableViaWiFi; + } + return retVal; +} + +- (NetworkStatus) networkStatusForFlags: (SCNetworkReachabilityFlags) flags +{ + PrintReachabilityFlags(flags, "networkStatusForFlags"); + if ((flags & kSCNetworkReachabilityFlagsReachable) == 0) + { + // if target host is not reachable + return NotReachable; + } + + BOOL retVal = NotReachable; + + if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0) + { + // if target host is reachable and no connection is required + // then we'll assume (for now) that your on Wi-Fi + retVal = ReachableViaWiFi; + } + + + if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0)) + { + // ... and the connection is on-demand (or on-traffic) if the + // calling application is using the CFSocketStream or higher APIs + + if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0) + { + // ... and no [user] intervention is needed + retVal = ReachableViaWiFi; + } + } + + if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN) + { + // ... but WWAN connections are OK if the calling application + // is using the CFNetwork (CFSocketStream?) APIs. + retVal = ReachableViaWWAN; + } + return retVal; +} + +- (BOOL) connectionRequired; +{ + NSAssert(reachabilityRef != NULL, @"connectionRequired called with NULL reachabilityRef"); + SCNetworkReachabilityFlags flags; + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + return (flags & kSCNetworkReachabilityFlagsConnectionRequired); + } + return NO; +} + +- (NetworkStatus) currentReachabilityStatus +{ + NSAssert(reachabilityRef != NULL, @"currentNetworkStatus called with NULL reachabilityRef"); + NetworkStatus retVal = NotReachable; + SCNetworkReachabilityFlags flags; + netAvailable = NO; + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + if(localWiFiRef) + { + retVal = [self localWiFiStatusForFlags: flags]; + } + else + { + retVal = [self networkStatusForFlags: flags]; + } + } + + if(retVal != NotReachable) + netAvailable = YES; + + return retVal; +} +@end diff --git a/Mooshimeter/Vendor/SVProgressHUD/SVProgressHUD.bundle/error.png b/Mooshimeter/Vendor/SVProgressHUD/SVProgressHUD.bundle/error.png new file mode 100644 index 0000000..3fc7da8 Binary files /dev/null and b/Mooshimeter/Vendor/SVProgressHUD/SVProgressHUD.bundle/error.png differ diff --git a/Mooshimeter/Vendor/SVProgressHUD/SVProgressHUD.bundle/error@2x.png b/Mooshimeter/Vendor/SVProgressHUD/SVProgressHUD.bundle/error@2x.png new file mode 100644 index 0000000..c1e7d83 Binary files /dev/null and b/Mooshimeter/Vendor/SVProgressHUD/SVProgressHUD.bundle/error@2x.png differ diff --git a/Mooshimeter/Vendor/SVProgressHUD/SVProgressHUD.bundle/success.png b/Mooshimeter/Vendor/SVProgressHUD/SVProgressHUD.bundle/success.png new file mode 100644 index 0000000..45424a9 Binary files /dev/null and b/Mooshimeter/Vendor/SVProgressHUD/SVProgressHUD.bundle/success.png differ diff --git a/Mooshimeter/Vendor/SVProgressHUD/SVProgressHUD.bundle/success@2x.png b/Mooshimeter/Vendor/SVProgressHUD/SVProgressHUD.bundle/success@2x.png new file mode 100644 index 0000000..b2195b1 Binary files /dev/null and b/Mooshimeter/Vendor/SVProgressHUD/SVProgressHUD.bundle/success@2x.png differ diff --git a/Mooshimeter/Vendor/SVProgressHUD/SVProgressHUD.h b/Mooshimeter/Vendor/SVProgressHUD/SVProgressHUD.h new file mode 100644 index 0000000..b805df8 --- /dev/null +++ b/Mooshimeter/Vendor/SVProgressHUD/SVProgressHUD.h @@ -0,0 +1,44 @@ +// +// SVProgressHUD.h +// +// Created by Sam Vermette on 27.03.11. +// Copyright 2011 Sam Vermette. All rights reserved. +// +// https://github.com/samvermette/SVProgressHUD +// + +#import +#import + +enum { + SVProgressHUDMaskTypeNone = 1, // allow user interactions while HUD is displayed + SVProgressHUDMaskTypeClear, // don't allow + SVProgressHUDMaskTypeBlack, // don't allow and dim the UI in the back of the HUD + SVProgressHUDMaskTypeGradient // don't allow and dim the UI with a a-la-alert-view bg gradient +}; + +typedef NSUInteger SVProgressHUDMaskType; + +@interface SVProgressHUD : UIView + ++ (void)show; ++ (void)showWithStatus:(NSString*)status; ++ (void)showWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType; ++ (void)showWithMaskType:(SVProgressHUDMaskType)maskType; + ++ (void)showSuccessWithStatus:(NSString*)string; ++ (void)showSuccessWithStatus:(NSString *)string duration:(NSTimeInterval)duration; ++ (void)showErrorWithStatus:(NSString *)string; ++ (void)showErrorWithStatus:(NSString *)string duration:(NSTimeInterval)duration; + ++ (void)setStatus:(NSString*)string; // change the HUD loading status while it's showing + ++ (void)dismiss; // simply dismiss the HUD with a fade+scale out animation ++ (void)dismissWithSuccess:(NSString*)successString; // also displays the success icon image ++ (void)dismissWithSuccess:(NSString*)successString afterDelay:(NSTimeInterval)seconds; ++ (void)dismissWithError:(NSString*)errorString; // also displays the error icon image ++ (void)dismissWithError:(NSString*)errorString afterDelay:(NSTimeInterval)seconds; + ++ (BOOL)isVisible; + +@end diff --git a/Mooshimeter/Vendor/SVProgressHUD/SVProgressHUD.m b/Mooshimeter/Vendor/SVProgressHUD/SVProgressHUD.m new file mode 100644 index 0000000..9c68402 --- /dev/null +++ b/Mooshimeter/Vendor/SVProgressHUD/SVProgressHUD.m @@ -0,0 +1,542 @@ +// +// SVProgressHUD.m +// +// Created by Sam Vermette on 27.03.11. +// Copyright 2011 Sam Vermette. All rights reserved. +// +// https://github.com/samvermette/SVProgressHUD +// + +#import "SVProgressHUD.h" +#import + +@interface SVProgressHUD () + +@property (nonatomic, readwrite) SVProgressHUDMaskType maskType; +@property (nonatomic, strong, readonly) NSTimer *fadeOutTimer; + +@property (nonatomic, strong, readonly) UIWindow *overlayWindow; +@property (nonatomic, strong, readonly) UIView *hudView; +@property (nonatomic, strong, readonly) UILabel *stringLabel; +@property (nonatomic, strong, readonly) UIImageView *imageView; +@property (nonatomic, strong, readonly) UIActivityIndicatorView *spinnerView; + +@property (nonatomic, readonly) CGFloat visibleKeyboardHeight; + +- (void)showWithStatus:(NSString*)string maskType:(SVProgressHUDMaskType)hudMaskType networkIndicator:(BOOL)show; +- (void)setStatus:(NSString*)string; +- (void)registerNotifications; +- (void)moveToPoint:(CGPoint)newCenter rotateAngle:(CGFloat)angle; +- (void)positionHUD:(NSNotification*)notification; + +- (void)dismiss; +- (void)dismissWithStatus:(NSString*)string error:(BOOL)error; +- (void)dismissWithStatus:(NSString*)string error:(BOOL)error afterDelay:(NSTimeInterval)seconds; + +@end + + +@implementation SVProgressHUD + +@synthesize overlayWindow, hudView, maskType, fadeOutTimer, stringLabel, imageView, spinnerView, visibleKeyboardHeight; + +- (void)dealloc { + self.fadeOutTimer = nil; + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + + ++ (SVProgressHUD*)sharedView { + static dispatch_once_t once; + static SVProgressHUD *sharedView; + dispatch_once(&once, ^ { sharedView = [[SVProgressHUD alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; }); + return sharedView; +} + + ++ (void)setStatus:(NSString *)string { + [[SVProgressHUD sharedView] setStatus:string]; +} + +#pragma mark - Show Methods + ++ (void)show { + [[SVProgressHUD sharedView] showWithStatus:nil maskType:SVProgressHUDMaskTypeNone networkIndicator:NO]; +} + ++ (void)showWithStatus:(NSString *)status { + [[SVProgressHUD sharedView] showWithStatus:status maskType:SVProgressHUDMaskTypeNone networkIndicator:NO]; +} + ++ (void)showWithMaskType:(SVProgressHUDMaskType)maskType { + [[SVProgressHUD sharedView] showWithStatus:nil maskType:maskType networkIndicator:NO]; +} + ++ (void)showWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { + [[SVProgressHUD sharedView] showWithStatus:status maskType:maskType networkIndicator:NO]; +} + ++ (void)showSuccessWithStatus:(NSString *)string { + [SVProgressHUD showSuccessWithStatus:string duration:1]; +} + ++ (void)showSuccessWithStatus:(NSString *)string duration:(NSTimeInterval)duration { + [SVProgressHUD show]; + [SVProgressHUD dismissWithSuccess:string afterDelay:duration]; +} + ++ (void)showErrorWithStatus:(NSString *)string { + [SVProgressHUD showErrorWithStatus:string duration:1]; +} + ++ (void)showErrorWithStatus:(NSString *)string duration:(NSTimeInterval)duration { + [SVProgressHUD show]; + [SVProgressHUD dismissWithError:string afterDelay:duration]; +} + + +#pragma mark - Dismiss Methods + ++ (void)dismiss { + [[SVProgressHUD sharedView] dismiss]; +} + ++ (void)dismissWithSuccess:(NSString*)successString { + [[SVProgressHUD sharedView] dismissWithStatus:successString error:NO]; +} + ++ (void)dismissWithSuccess:(NSString *)successString afterDelay:(NSTimeInterval)seconds { + [[SVProgressHUD sharedView] dismissWithStatus:successString error:NO afterDelay:seconds]; +} + ++ (void)dismissWithError:(NSString*)errorString { + [[SVProgressHUD sharedView] dismissWithStatus:errorString error:YES]; +} + ++ (void)dismissWithError:(NSString *)errorString afterDelay:(NSTimeInterval)seconds { + [[SVProgressHUD sharedView] dismissWithStatus:errorString error:YES afterDelay:seconds]; +} + + +#pragma mark - Instance Methods + +- (id)initWithFrame:(CGRect)frame { + + if ((self = [super initWithFrame:frame])) { + self.userInteractionEnabled = NO; + self.backgroundColor = [UIColor clearColor]; + self.alpha = 0; + self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + } + + return self; +} + +- (void)drawRect:(CGRect)rect { + + CGContextRef context = UIGraphicsGetCurrentContext(); + + switch (self.maskType) { + + case SVProgressHUDMaskTypeBlack: { + [[UIColor colorWithWhite:0 alpha:0.5] set]; + CGContextFillRect(context, self.bounds); + break; + } + + case SVProgressHUDMaskTypeGradient: { + + size_t locationsCount = 2; + CGFloat locations[2] = {0.0f, 1.0f}; + CGFloat colors[8] = {0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.75f}; + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, colors, locations, locationsCount); + CGColorSpaceRelease(colorSpace); + + CGPoint center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); + float radius = MIN(self.bounds.size.width , self.bounds.size.height) ; + CGContextDrawRadialGradient (context, gradient, center, 0, center, radius, kCGGradientDrawsAfterEndLocation); + CGGradientRelease(gradient); + + break; + } + } +} + +- (void)setStatus:(NSString *)string { + + CGFloat hudWidth = 100; + CGFloat hudHeight = 100; + CGFloat stringWidth = 0; + CGFloat stringHeight = 0; + CGRect labelRect = CGRectZero; + + if(string) { + CGSize stringSize = [string sizeWithFont:self.stringLabel.font constrainedToSize:CGSizeMake(200, 300)]; + stringWidth = stringSize.width; + stringHeight = stringSize.height; + hudHeight = 80+stringHeight; + + if(stringWidth > hudWidth) + hudWidth = ceil(stringWidth/2)*2; + + if(hudHeight > 100) { + labelRect = CGRectMake(12, 66, hudWidth, stringHeight); + hudWidth+=24; + } else { + hudWidth+=24; + labelRect = CGRectMake(0, 66, hudWidth, stringHeight); + } + } + + self.hudView.bounds = CGRectMake(0, 0, hudWidth, hudHeight); + + if(string) + self.imageView.center = CGPointMake(CGRectGetWidth(self.hudView.bounds)/2, 36); + else + self.imageView.center = CGPointMake(CGRectGetWidth(self.hudView.bounds)/2, CGRectGetHeight(self.hudView.bounds)/2); + + self.stringLabel.hidden = NO; + self.stringLabel.text = string; + self.stringLabel.frame = labelRect; + + if(string) + self.spinnerView.center = CGPointMake(ceil(CGRectGetWidth(self.hudView.bounds)/2)+0.5, 40.5); + else + self.spinnerView.center = CGPointMake(ceil(CGRectGetWidth(self.hudView.bounds)/2)+0.5, ceil(self.hudView.bounds.size.height/2)+0.5); +} + +- (void)setFadeOutTimer:(NSTimer *)newTimer { + + if(fadeOutTimer) + [fadeOutTimer invalidate], fadeOutTimer = nil; + + if(newTimer) + fadeOutTimer = newTimer; +} + + +- (void)registerNotifications { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(positionHUD:) + name:UIApplicationDidChangeStatusBarOrientationNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(positionHUD:) + name:UIKeyboardWillHideNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(positionHUD:) + name:UIKeyboardDidHideNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(positionHUD:) + name:UIKeyboardWillShowNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(positionHUD:) + name:UIKeyboardDidShowNotification + object:nil]; +} + + +- (void)positionHUD:(NSNotification*)notification { + + CGFloat keyboardHeight; + double animationDuration; + + UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; + + if(notification) { + NSDictionary* keyboardInfo = [notification userInfo]; + CGRect keyboardFrame = [[keyboardInfo valueForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue]; + animationDuration = [[keyboardInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + + if(notification.name == UIKeyboardWillShowNotification || notification.name == UIKeyboardDidShowNotification) { + if(UIInterfaceOrientationIsPortrait(orientation)) + keyboardHeight = keyboardFrame.size.height; + else + keyboardHeight = keyboardFrame.size.width; + } else + keyboardHeight = 0; + } else { + keyboardHeight = self.visibleKeyboardHeight; + } + + CGRect orientationFrame = [UIScreen mainScreen].bounds; + CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame; + + if(UIInterfaceOrientationIsLandscape(orientation)) { + float temp = orientationFrame.size.width; + orientationFrame.size.width = orientationFrame.size.height; + orientationFrame.size.height = temp; + + temp = statusBarFrame.size.width; + statusBarFrame.size.width = statusBarFrame.size.height; + statusBarFrame.size.height = temp; + } + + CGFloat activeHeight = orientationFrame.size.height; + + if(keyboardHeight > 0) + activeHeight += statusBarFrame.size.height*2; + + activeHeight -= keyboardHeight; + CGFloat posY = floor(activeHeight*0.45); + CGFloat posX = orientationFrame.size.width/2; + + CGPoint newCenter; + CGFloat rotateAngle; + + switch (orientation) { + case UIInterfaceOrientationPortraitUpsideDown: + rotateAngle = M_PI; + newCenter = CGPointMake(posX, orientationFrame.size.height-posY); + break; + case UIInterfaceOrientationLandscapeLeft: + rotateAngle = -M_PI/2.0f; + newCenter = CGPointMake(posY, posX); + break; + case UIInterfaceOrientationLandscapeRight: + rotateAngle = M_PI/2.0f; + newCenter = CGPointMake(orientationFrame.size.height-posY, posX); + break; + default: // as UIInterfaceOrientationPortrait + rotateAngle = 0.0; + newCenter = CGPointMake(posX, posY); + break; + } + + if(notification) { + [UIView animateWithDuration:animationDuration + delay:0 + options:UIViewAnimationOptionAllowUserInteraction + animations:^{ + [self moveToPoint:newCenter rotateAngle:rotateAngle]; + } completion:NULL]; + } + + else { + [self moveToPoint:newCenter rotateAngle:rotateAngle]; + } + +} + +- (void)moveToPoint:(CGPoint)newCenter rotateAngle:(CGFloat)angle { + self.hudView.transform = CGAffineTransformMakeRotation(angle); + self.hudView.center = newCenter; +} + +#pragma mark - Master show/dismiss methods + +- (void)showWithStatus:(NSString*)string maskType:(SVProgressHUDMaskType)hudMaskType networkIndicator:(BOOL)show { + dispatch_async(dispatch_get_main_queue(), ^{ + if(!self.superview) + [self.overlayWindow addSubview:self]; + + self.fadeOutTimer = nil; + self.imageView.hidden = YES; + self.maskType = hudMaskType; + + [self setStatus:string]; + [self.spinnerView startAnimating]; + + if(self.maskType != SVProgressHUDMaskTypeNone) { + self.overlayWindow.userInteractionEnabled = YES; + } else { + self.overlayWindow.userInteractionEnabled = NO; + } + + [self.overlayWindow makeKeyAndVisible]; + [self positionHUD:nil]; + + if(self.alpha != 1) { + [self registerNotifications]; + self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1.3, 1.3); + + [UIView animateWithDuration:0.15 + delay:0 + options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState + animations:^{ + self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1/1.3, 1/1.3); + self.alpha = 1; + } + completion:NULL]; + } + + [self setNeedsDisplay]; + }); +} + + +- (void)dismissWithStatus:(NSString*)string error:(BOOL)error { + [self dismissWithStatus:string error:error afterDelay:0.9]; +} + + +- (void)dismissWithStatus:(NSString *)string error:(BOOL)error afterDelay:(NSTimeInterval)seconds { + dispatch_async(dispatch_get_main_queue(), ^{ + if(self.alpha != 1) + return; + + if(error) + self.imageView.image = [UIImage imageNamed:@"SVProgressHUD.bundle/error.png"]; + else + self.imageView.image = [UIImage imageNamed:@"SVProgressHUD.bundle/success.png"]; + + self.imageView.hidden = NO; + [self setStatus:string]; + [self.spinnerView stopAnimating]; + + self.fadeOutTimer = [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(dismiss) userInfo:nil repeats:NO]; + }); +} + +- (void)dismiss { + dispatch_async(dispatch_get_main_queue(), ^{ + + [UIView animateWithDuration:0.15 + delay:0 + options:UIViewAnimationCurveEaseIn | UIViewAnimationOptionAllowUserInteraction + animations:^{ + self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 0.8, 0.8); + self.alpha = 0; + } + completion:^(BOOL finished){ + if(self.alpha == 0) { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [hudView removeFromSuperview]; + hudView = nil; + + // Make sure to remove the overlay window from the list of windows + // before trying to find the key window in that same list + NSMutableArray *windows = [[NSMutableArray alloc] initWithArray:[UIApplication sharedApplication].windows]; + [windows removeObject:overlayWindow]; + overlayWindow = nil; + + [windows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(UIWindow *window, NSUInteger idx, BOOL *stop) { + if([window isKindOfClass:[UIWindow class]] && window.windowLevel == UIWindowLevelNormal) { + [window makeKeyWindow]; + *stop = YES; + } + }]; + + // uncomment to make sure UIWindow is gone from app.windows + //NSLog(@"%@", [UIApplication sharedApplication].windows); + //NSLog(@"keyWindow = %@", [UIApplication sharedApplication].keyWindow); + } + }]; + }); +} + +#pragma mark - Utilities + ++ (BOOL)isVisible { + return ([SVProgressHUD sharedView].alpha == 1); +} + + +#pragma mark - Getters + +- (UIWindow *)overlayWindow { + if(!overlayWindow) { + overlayWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + overlayWindow.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + overlayWindow.backgroundColor = [UIColor clearColor]; + overlayWindow.userInteractionEnabled = NO; + } + return overlayWindow; +} + +- (UIView *)hudView { + if(!hudView) { + hudView = [[UIView alloc] initWithFrame:CGRectZero]; + hudView.layer.cornerRadius = 10; + hudView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.8]; + hudView.autoresizingMask = (UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin | + UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin); + + [self addSubview:hudView]; + } + return hudView; +} + +- (UILabel *)stringLabel { + if (stringLabel == nil) { + stringLabel = [[UILabel alloc] initWithFrame:CGRectZero]; + stringLabel.textColor = [UIColor whiteColor]; + stringLabel.backgroundColor = [UIColor clearColor]; + stringLabel.adjustsFontSizeToFitWidth = YES; + stringLabel.textAlignment = NSTextAlignmentCenter; + stringLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters; + stringLabel.font = [UIFont boldSystemFontOfSize:16]; + stringLabel.shadowColor = [UIColor blackColor]; + stringLabel.shadowOffset = CGSizeMake(0, -1); + stringLabel.numberOfLines = 0; + } + + if(!stringLabel.superview) + [self.hudView addSubview:stringLabel]; + + return stringLabel; +} + +- (UIImageView *)imageView { + if (imageView == nil) + imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 28, 28)]; + + if(!imageView.superview) + [self.hudView addSubview:imageView]; + + return imageView; +} + +- (UIActivityIndicatorView *)spinnerView { + if (spinnerView == nil) { + spinnerView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; + spinnerView.hidesWhenStopped = YES; + spinnerView.bounds = CGRectMake(0, 0, 37, 37); + } + + if(!spinnerView.superview) + [self.hudView addSubview:spinnerView]; + + return spinnerView; +} + +- (CGFloat)visibleKeyboardHeight { + + UIWindow *keyboardWindow = nil; + for (UIWindow *testWindow in [[UIApplication sharedApplication] windows]) { + if(![[testWindow class] isEqual:[UIWindow class]]) { + keyboardWindow = testWindow; + break; + } + } + + // Locate UIKeyboard. + UIView *foundKeyboard = nil; + for (__strong UIView *possibleKeyboard in [keyboardWindow subviews]) { + + // iOS 4 sticks the UIKeyboard inside a UIPeripheralHostView. + if ([[possibleKeyboard description] hasPrefix:@" 100) + return foundKeyboard.bounds.size.height; + + return 0; +} + +@end diff --git a/Mooshimeter/self.iBytes += OAD_BLOC.textClipping b/Mooshimeter/self.iBytes += OAD_BLOC.textClipping new file mode 100644 index 0000000..e69de29 diff --git a/Provisioning/Mooshi.p12 b/Provisioning/Mooshi.p12 new file mode 100755 index 0000000..dd7fe41 Binary files /dev/null and b/Provisioning/Mooshi.p12 differ diff --git a/Provisioning/Mooshimeter_Development.mobileprovision b/Provisioning/Mooshimeter_Development.mobileprovision new file mode 100755 index 0000000..f6ed099 Binary files /dev/null and b/Provisioning/Mooshimeter_Development.mobileprovision differ diff --git a/ScanTableViewCell.m b/ScanTableViewCell.m index 2f0e864..003129a 100644 --- a/ScanTableViewCell.m +++ b/ScanTableViewCell.m @@ -78,6 +78,7 @@ -(void) setPeripheral:(LGPeripheral *)device { - (void)awakeFromNib { // Initialization code + } - (void)setSelected:(BOOL)selected animated:(BOOL)animated {