From 6819c30fd3bf6ec28bd6096fa08630510f547327 Mon Sep 17 00:00:00 2001 From: John Kotz Date: Sat, 27 Jul 2019 13:19:39 -0400 Subject: [PATCH] Moved all of the DALI cocoapod framework into a local framework Closes #22 --- DALI Lab.xcodeproj/project.pbxproj | 439 ++++++- .../xcshareddata/xcschemes/Stickers.xcscheme | 16 +- .../xcshareddata/xcschemes/iOS.xcscheme | 10 + DALI/CheckOutRecord.swift | 50 + DALI/DALI.h | 19 + DALI/DALIConfig.swift | 132 ++ DALI/DALIEquipment.swift | 427 ++++++ DALI/DALIErrors.swift | 56 + DALI/DALIEvent.swift | 1156 +++++++++++++++++ DALI/DALIFood.swift | 113 ++ DALI/DALILights.swift | 292 +++++ DALI/DALILocation.swift | 316 +++++ DALI/DALIMember.swift | 112 ++ DALI/DALIPhoto.swift | 34 + DALI/DALIProject.swift | 19 + DALI/DALIapi.swift | 267 ++++ DALI/Enumerations.swift | 56 + DALI/Info.plist | 22 + DALI/ServerCommunicator.swift | 195 +++ DALITests/DALITests.swift | 34 + DALITests/Info.plist | 22 + Podfile | 23 +- Podfile.lock | 19 +- 23 files changed, 3794 insertions(+), 35 deletions(-) create mode 100644 DALI/CheckOutRecord.swift create mode 100644 DALI/DALI.h create mode 100644 DALI/DALIConfig.swift create mode 100644 DALI/DALIEquipment.swift create mode 100644 DALI/DALIErrors.swift create mode 100644 DALI/DALIEvent.swift create mode 100644 DALI/DALIFood.swift create mode 100644 DALI/DALILights.swift create mode 100644 DALI/DALILocation.swift create mode 100644 DALI/DALIMember.swift create mode 100644 DALI/DALIPhoto.swift create mode 100644 DALI/DALIProject.swift create mode 100644 DALI/DALIapi.swift create mode 100644 DALI/Enumerations.swift create mode 100644 DALI/Info.plist create mode 100644 DALI/ServerCommunicator.swift create mode 100644 DALITests/DALITests.swift create mode 100644 DALITests/Info.plist diff --git a/DALI Lab.xcodeproj/project.pbxproj b/DALI Lab.xcodeproj/project.pbxproj index aaf6c92..e289f22 100644 --- a/DALI Lab.xcodeproj/project.pbxproj +++ b/DALI Lab.xcodeproj/project.pbxproj @@ -44,10 +44,29 @@ 524D86FB1F57A0E100479C0A /* OrderedVotingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 524D86FA1F57A0E100479C0A /* OrderedVotingViewController.swift */; }; 524D86FD1F57A3F400479C0A /* ResultsVotingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 524D86FC1F57A3F400479C0A /* ResultsVotingViewController.swift */; }; 5251D9E31F4BD7C80020D958 /* CheckinViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5251D9E21F4BD7C80020D958 /* CheckinViewController.swift */; }; + 525CFB8B22ECBC76002AA2BE /* CheckOutRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 525CFB8A22ECBC76002AA2BE /* CheckOutRecord.swift */; }; 5260330C21CF15690089D5E6 /* FutureExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5260330B21CF15690089D5E6 /* FutureExtension.swift */; }; 526778F322D250F900DBAF97 /* MainViewControllerHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526778F222D250F900DBAF97 /* MainViewControllerHeaderView.swift */; }; 526778F622D2530F00DBAF97 /* MainViewControllerEventCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526778F522D2530F00DBAF97 /* MainViewControllerEventCell.swift */; }; 526778F822D255A600DBAF97 /* SCLAlertView+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526778F722D255A600DBAF97 /* SCLAlertView+Appearance.swift */; }; + 526A5F9F22ECB2D5001A96FA /* DALI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 526A5F9622ECB2D5001A96FA /* DALI.framework */; }; + 526A5FA622ECB2D5001A96FA /* DALITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A5FA522ECB2D5001A96FA /* DALITests.swift */; }; + 526A5FA822ECB2D5001A96FA /* DALI.h in Headers */ = {isa = PBXBuildFile; fileRef = 526A5F9822ECB2D5001A96FA /* DALI.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 526A5FAB22ECB2D5001A96FA /* DALI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 526A5F9622ECB2D5001A96FA /* DALI.framework */; }; + 526A5FAC22ECB2D5001A96FA /* DALI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 526A5F9622ECB2D5001A96FA /* DALI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 526A5FC122ECB2FB001A96FA /* DALIMember.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A5FB422ECB2FA001A96FA /* DALIMember.swift */; }; + 526A5FC222ECB2FB001A96FA /* ServerCommunicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A5FB522ECB2FA001A96FA /* ServerCommunicator.swift */; }; + 526A5FC322ECB2FB001A96FA /* DALIConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A5FB622ECB2FA001A96FA /* DALIConfig.swift */; }; + 526A5FC422ECB2FB001A96FA /* DALILights.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A5FB722ECB2FA001A96FA /* DALILights.swift */; }; + 526A5FC522ECB2FB001A96FA /* Enumerations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A5FB822ECB2FA001A96FA /* Enumerations.swift */; }; + 526A5FC622ECB2FB001A96FA /* DALIErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A5FB922ECB2FA001A96FA /* DALIErrors.swift */; }; + 526A5FC722ECB2FB001A96FA /* DALIEquipment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A5FBA22ECB2FA001A96FA /* DALIEquipment.swift */; }; + 526A5FC822ECB2FB001A96FA /* DALILocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A5FBB22ECB2FA001A96FA /* DALILocation.swift */; }; + 526A5FC922ECB2FB001A96FA /* DALIFood.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A5FBC22ECB2FA001A96FA /* DALIFood.swift */; }; + 526A5FCA22ECB2FB001A96FA /* DALIPhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A5FBD22ECB2FA001A96FA /* DALIPhoto.swift */; }; + 526A5FCB22ECB2FB001A96FA /* DALIEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A5FBE22ECB2FA001A96FA /* DALIEvent.swift */; }; + 526A5FCC22ECB2FB001A96FA /* DALIapi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A5FBF22ECB2FB001A96FA /* DALIapi.swift */; }; + 526A5FCD22ECB2FB001A96FA /* DALIProject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A5FC022ECB2FB001A96FA /* DALIProject.swift */; }; 526A7CEE21C9AD9B00F25E99 /* EquipmentScanAndListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A7CED21C9AD9B00F25E99 /* EquipmentScanAndListViewController.swift */; }; 526F3627215C5E60002F5CD2 /* QRCodeReaderView+DALIAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 5203FF1F2150422C00A406E3 /* QRCodeReaderView+DALIAdditions.m */; }; 526F362B215C5F23002F5CD2 /* DatePickerAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526F362A215C5F23002F5CD2 /* DatePickerAlert.swift */; }; @@ -84,12 +103,34 @@ 52FA82E61F7350E900CEC807 /* SlideShowViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52FA82E51F7350E900CEC807 /* SlideShowViewController.swift */; }; 60B9E4DD07D51BAB7BAB009E /* Pods_tvOSTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9EFBC5F89C445633AFBD4EAD /* Pods_tvOSTests.framework */; }; 6E56A1523425CD44DBF04B5B /* Pods_tvOSUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E06F4B7AF9FBEC4DC8DCC919 /* Pods_tvOSUITests.framework */; }; + 86812BECB82AF577E06654FC /* Pods_DALI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C76D06BC42BC80489FBF7509 /* Pods_DALI.framework */; }; A729DF33C2BA9A4D7EBB98BD /* Pods_iOSTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7045FE4735BC436C030988C8 /* Pods_iOSTests.framework */; }; AF3514C21CD5A4E26B64FDBF /* Pods_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 062FA5BDE3125B48ADB5510D /* Pods_iOS.framework */; }; C631B1D59E8D1E5232DFB5FD /* Pods_tvOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 18558C014FD8296488A99C79 /* Pods_tvOS.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 526A5FA022ECB2D5001A96FA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 52B138471F47D39B0021D7AE /* Project object */; + proxyType = 1; + remoteGlobalIDString = 526A5F9522ECB2D5001A96FA; + remoteInfo = DALI; + }; + 526A5FA222ECB2D5001A96FA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 52B138471F47D39B0021D7AE /* Project object */; + proxyType = 1; + remoteGlobalIDString = 52B138B51F47D4590021D7AE; + remoteInfo = iOS; + }; + 526A5FA922ECB2D5001A96FA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 52B138471F47D39B0021D7AE /* Project object */; + proxyType = 1; + remoteGlobalIDString = 526A5F9522ECB2D5001A96FA; + remoteInfo = DALI; + }; 52B138CA1F47D4590021D7AE /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 52B138471F47D39B0021D7AE /* Project object */; @@ -128,6 +169,17 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + 526A5FB222ECB2D5001A96FA /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 526A5FAC22ECB2D5001A96FA /* DALI.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; 52B139201F47D47C0021D7AE /* Embed App Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -156,6 +208,7 @@ 38F9423ACD748D9DBDD7C198 /* Pods-tvOSUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-tvOSUITests.release.xcconfig"; path = "Pods/Target Support Files/Pods-tvOSUITests/Pods-tvOSUITests.release.xcconfig"; sourceTree = ""; }; 40CC816FA24F8E106E9DDD55 /* Pods-iOS-OneSignalNotification.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-OneSignalNotification.debug.xcconfig"; path = "Pods/Target Support Files/Pods-iOS-OneSignalNotification/Pods-iOS-OneSignalNotification.debug.xcconfig"; sourceTree = ""; }; 439D3A8726F67BB493B1A183 /* Pods_OneSignalNotificationServiceExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_OneSignalNotificationServiceExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4DC9C267DB494773FCC8E769 /* Pods-DALI.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DALI.release.xcconfig"; path = "Pods/Target Support Files/Pods-DALI/Pods-DALI.release.xcconfig"; sourceTree = ""; }; 5200B9AE2159793A0011DA73 /* CheckOutQRReaderLoadingOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckOutQRReaderLoadingOverlayView.swift; sourceTree = ""; }; 5203FF1D21503A9B00A406E3 /* CheckOutQRViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckOutQRViewController.swift; sourceTree = ""; }; 5203FF1F2150422C00A406E3 /* QRCodeReaderView+DALIAdditions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "QRCodeReaderView+DALIAdditions.m"; sourceTree = ""; }; @@ -197,10 +250,30 @@ 524D86FA1F57A0E100479C0A /* OrderedVotingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderedVotingViewController.swift; sourceTree = ""; }; 524D86FC1F57A3F400479C0A /* ResultsVotingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResultsVotingViewController.swift; sourceTree = ""; }; 5251D9E21F4BD7C80020D958 /* CheckinViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckinViewController.swift; sourceTree = ""; }; + 525CFB8A22ECBC76002AA2BE /* CheckOutRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckOutRecord.swift; sourceTree = ""; }; 5260330B21CF15690089D5E6 /* FutureExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FutureExtension.swift; sourceTree = ""; }; 526778F222D250F900DBAF97 /* MainViewControllerHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewControllerHeaderView.swift; sourceTree = ""; }; 526778F522D2530F00DBAF97 /* MainViewControllerEventCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewControllerEventCell.swift; sourceTree = ""; }; 526778F722D255A600DBAF97 /* SCLAlertView+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SCLAlertView+Appearance.swift"; sourceTree = ""; }; + 526A5F9622ECB2D5001A96FA /* DALI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DALI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 526A5F9822ECB2D5001A96FA /* DALI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DALI.h; sourceTree = ""; }; + 526A5F9922ECB2D5001A96FA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 526A5F9E22ECB2D5001A96FA /* DALITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DALITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 526A5FA522ECB2D5001A96FA /* DALITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DALITests.swift; sourceTree = ""; }; + 526A5FA722ECB2D5001A96FA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 526A5FB422ECB2FA001A96FA /* DALIMember.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DALIMember.swift; sourceTree = ""; }; + 526A5FB522ECB2FA001A96FA /* ServerCommunicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerCommunicator.swift; sourceTree = ""; }; + 526A5FB622ECB2FA001A96FA /* DALIConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DALIConfig.swift; sourceTree = ""; }; + 526A5FB722ECB2FA001A96FA /* DALILights.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DALILights.swift; sourceTree = ""; }; + 526A5FB822ECB2FA001A96FA /* Enumerations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Enumerations.swift; sourceTree = ""; }; + 526A5FB922ECB2FA001A96FA /* DALIErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DALIErrors.swift; sourceTree = ""; }; + 526A5FBA22ECB2FA001A96FA /* DALIEquipment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DALIEquipment.swift; sourceTree = ""; }; + 526A5FBB22ECB2FA001A96FA /* DALILocation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DALILocation.swift; sourceTree = ""; }; + 526A5FBC22ECB2FA001A96FA /* DALIFood.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DALIFood.swift; sourceTree = ""; }; + 526A5FBD22ECB2FA001A96FA /* DALIPhoto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DALIPhoto.swift; sourceTree = ""; }; + 526A5FBE22ECB2FA001A96FA /* DALIEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DALIEvent.swift; sourceTree = ""; }; + 526A5FBF22ECB2FB001A96FA /* DALIapi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DALIapi.swift; sourceTree = ""; }; + 526A5FC022ECB2FB001A96FA /* DALIProject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DALIProject.swift; sourceTree = ""; }; 526A7CED21C9AD9B00F25E99 /* EquipmentScanAndListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EquipmentScanAndListViewController.swift; sourceTree = ""; }; 526F362A215C5F23002F5CD2 /* DatePickerAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerAlert.swift; sourceTree = ""; }; 526FDA7320323A09002A1BA1 /* LightsViewColorPickerCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LightsViewColorPickerCell.xib; sourceTree = ""; }; @@ -210,6 +283,7 @@ 529A63261F4E90DF00BFBFE9 /* NewVotingEventViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewVotingEventViewController.swift; sourceTree = ""; }; 529A63291F4E98FA00BFBFE9 /* NewVotingEventConfigViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewVotingEventConfigViewController.swift; sourceTree = ""; }; 529A632B1F4EBC3400BFBFE9 /* VotingEventOptionsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VotingEventOptionsViewController.swift; sourceTree = ""; }; + 52A33CBA22ECB42F0023E661 /* SwiftyJSON.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftyJSON.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 52B138B61F47D4590021D7AE /* iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; 52B138BF1F47D4590021D7AE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 52B138C41F47D4590021D7AE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -259,10 +333,12 @@ A0DE7839B2B540836211C153 /* Pods-OneSignalNotificationServiceExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OneSignalNotificationServiceExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-OneSignalNotificationServiceExtension/Pods-OneSignalNotificationServiceExtension.debug.xcconfig"; sourceTree = ""; }; ADDDEEAEEB8AA0C96CB83316 /* Pods-iOS-OneSignalNotification.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-OneSignalNotification.release.xcconfig"; path = "Pods/Target Support Files/Pods-iOS-OneSignalNotification/Pods-iOS-OneSignalNotification.release.xcconfig"; sourceTree = ""; }; B0EAE7ACC6ACF8EC525C0FA3 /* Pods-iOSTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOSTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-iOSTests/Pods-iOSTests.release.xcconfig"; sourceTree = ""; }; + B14D6D286A77E6629B92C32C /* Pods-DALI.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DALI.debug.xcconfig"; path = "Pods/Target Support Files/Pods-DALI/Pods-DALI.debug.xcconfig"; sourceTree = ""; }; B1C60E501F17CBB15316462B /* Pods_DALI_LabUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DALI_LabUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B7F67E5E32109F90D3E6195C /* Pods-tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-tvOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-tvOS/Pods-tvOS.debug.xcconfig"; sourceTree = ""; }; BA9F8302BFC20DEF4586CCA0 /* Pods-tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-tvOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-tvOS/Pods-tvOS.release.xcconfig"; sourceTree = ""; }; C00FE410BA8D344671095445 /* Pods_iOS_OneSignalNotification.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_OneSignalNotification.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C76D06BC42BC80489FBF7509 /* Pods_DALI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DALI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C9FC597F114057EA4EE9A174 /* Pods-OneSignalNotificationServiceExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OneSignalNotificationServiceExtension.release.xcconfig"; path = "Pods/Target Support Files/Pods-OneSignalNotificationServiceExtension/Pods-OneSignalNotificationServiceExtension.release.xcconfig"; sourceTree = ""; }; CB9FDAF188DC4EA7D736FA36 /* Pods_OneSignalNotification.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_OneSignalNotification.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D00C5112CC26BAB183C8C065 /* Pods_DALI_Lab.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DALI_Lab.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -275,10 +351,27 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 526A5F9322ECB2D5001A96FA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 86812BECB82AF577E06654FC /* Pods_DALI.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 526A5F9B22ECB2D5001A96FA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 526A5F9F22ECB2D5001A96FA /* DALI.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 52B138B31F47D4590021D7AE /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 526A5FAB22ECB2D5001A96FA /* DALI.framework in Frameworks */, 522F31FC1F47DAF800B4BEB4 /* libsqlite3.tbd in Frameworks */, 522F31F81F47DAC800B4BEB4 /* CoreBluetooth.framework in Frameworks */, AF3514C21CD5A4E26B64FDBF /* Pods_iOS.framework in Frameworks */, @@ -398,6 +491,38 @@ name = Main; sourceTree = ""; }; + 526A5F9722ECB2D5001A96FA /* DALI */ = { + isa = PBXGroup; + children = ( + 526A5F9822ECB2D5001A96FA /* DALI.h */, + 525CFB8A22ECBC76002AA2BE /* CheckOutRecord.swift */, + 526A5FBF22ECB2FB001A96FA /* DALIapi.swift */, + 526A5FB622ECB2FA001A96FA /* DALIConfig.swift */, + 526A5FBA22ECB2FA001A96FA /* DALIEquipment.swift */, + 526A5FB922ECB2FA001A96FA /* DALIErrors.swift */, + 526A5FBE22ECB2FA001A96FA /* DALIEvent.swift */, + 526A5FBC22ECB2FA001A96FA /* DALIFood.swift */, + 526A5FB722ECB2FA001A96FA /* DALILights.swift */, + 526A5FBB22ECB2FA001A96FA /* DALILocation.swift */, + 526A5FB422ECB2FA001A96FA /* DALIMember.swift */, + 526A5FBD22ECB2FA001A96FA /* DALIPhoto.swift */, + 526A5FC022ECB2FB001A96FA /* DALIProject.swift */, + 526A5FB822ECB2FA001A96FA /* Enumerations.swift */, + 526A5FB522ECB2FA001A96FA /* ServerCommunicator.swift */, + 526A5F9922ECB2D5001A96FA /* Info.plist */, + ); + path = DALI; + sourceTree = ""; + }; + 526A5FA422ECB2D5001A96FA /* DALITests */ = { + isa = PBXGroup; + children = ( + 526A5FA522ECB2D5001A96FA /* DALITests.swift */, + 526A5FA722ECB2D5001A96FA /* Info.plist */, + ); + path = DALITests; + sourceTree = ""; + }; 526A7CEF21C9AE1F00F25E99 /* QR */ = { isa = PBXGroup; children = ( @@ -434,6 +559,8 @@ 52B138FB1F47D4660021D7AE /* tvOSTests */, 52B139061F47D4660021D7AE /* tvOSUITests */, 52E2F4351F853FEE00CED245 /* Stickers */, + 526A5F9722ECB2D5001A96FA /* DALI */, + 526A5FA422ECB2D5001A96FA /* DALITests */, 52B138501F47D39B0021D7AE /* Products */, D9D686CBB542D45138040180 /* Pods */, 81A83CAE5D4CC45443AA4AAE /* Frameworks */, @@ -450,6 +577,8 @@ 52B138F81F47D4660021D7AE /* tvOSTests.xctest */, 52B139031F47D4660021D7AE /* tvOSUITests.xctest */, 52E2F4341F853FEE00CED245 /* Stickers.appex */, + 526A5F9622ECB2D5001A96FA /* DALI.framework */, + 526A5F9E22ECB2D5001A96FA /* DALITests.xctest */, ); name = Products; sourceTree = ""; @@ -570,6 +699,7 @@ 81A83CAE5D4CC45443AA4AAE /* Frameworks */ = { isa = PBXGroup; children = ( + 52A33CBA22ECB42F0023E661 /* SwiftyJSON.framework */, 522F31FB1F47DAF800B4BEB4 /* libsqlite3.tbd */, 522F31F91F47DAE100B4BEB4 /* CoreLocation.framework */, 522F31F71F47DAC800B4BEB4 /* CoreBluetooth.framework */, @@ -586,6 +716,7 @@ C00FE410BA8D344671095445 /* Pods_iOS_OneSignalNotification.framework */, CB9FDAF188DC4EA7D736FA36 /* Pods_OneSignalNotification.framework */, 439D3A8726F67BB493B1A183 /* Pods_OneSignalNotificationServiceExtension.framework */, + C76D06BC42BC80489FBF7509 /* Pods_DALI.framework */, ); name = Frameworks; sourceTree = ""; @@ -619,13 +750,64 @@ 386617CA772BB0E3522B106B /* Pods-OneSignalNotification.release.xcconfig */, A0DE7839B2B540836211C153 /* Pods-OneSignalNotificationServiceExtension.debug.xcconfig */, C9FC597F114057EA4EE9A174 /* Pods-OneSignalNotificationServiceExtension.release.xcconfig */, + B14D6D286A77E6629B92C32C /* Pods-DALI.debug.xcconfig */, + 4DC9C267DB494773FCC8E769 /* Pods-DALI.release.xcconfig */, ); name = Pods; sourceTree = ""; }; /* End PBXGroup section */ +/* Begin PBXHeadersBuildPhase section */ + 526A5F9122ECB2D5001A96FA /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 526A5FA822ECB2D5001A96FA /* DALI.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + /* Begin PBXNativeTarget section */ + 526A5F9522ECB2D5001A96FA /* DALI */ = { + isa = PBXNativeTarget; + buildConfigurationList = 526A5FB122ECB2D5001A96FA /* Build configuration list for PBXNativeTarget "DALI" */; + buildPhases = ( + 52CFDDBD825E6302E5DE8002 /* [CP] Check Pods Manifest.lock */, + 526A5F9122ECB2D5001A96FA /* Headers */, + 526A5F9222ECB2D5001A96FA /* Sources */, + 526A5F9322ECB2D5001A96FA /* Frameworks */, + 526A5F9422ECB2D5001A96FA /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = DALI; + productName = DALI; + productReference = 526A5F9622ECB2D5001A96FA /* DALI.framework */; + productType = "com.apple.product-type.framework"; + }; + 526A5F9D22ECB2D5001A96FA /* DALITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 526A5FB322ECB2D5001A96FA /* Build configuration list for PBXNativeTarget "DALITests" */; + buildPhases = ( + 526A5F9A22ECB2D5001A96FA /* Sources */, + 526A5F9B22ECB2D5001A96FA /* Frameworks */, + 526A5F9C22ECB2D5001A96FA /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 526A5FA122ECB2D5001A96FA /* PBXTargetDependency */, + 526A5FA322ECB2D5001A96FA /* PBXTargetDependency */, + ); + name = DALITests; + productName = DALITests; + productReference = 526A5F9E22ECB2D5001A96FA /* DALITests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 52B138B51F47D4590021D7AE /* iOS */ = { isa = PBXNativeTarget; buildConfigurationList = 52B138DB1F47D4590021D7AE /* Build configuration list for PBXNativeTarget "iOS" */; @@ -639,11 +821,13 @@ 52B139201F47D47C0021D7AE /* Embed App Extensions */, 522F31E51F47DA4900B4BEB4 /* ShellScript */, 52880FA22255805B009D0F8E /* ShellScript */, + 526A5FB222ECB2D5001A96FA /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( 52E2F43A1F853FEE00CED245 /* PBXTargetDependency */, + 526A5FAA22ECB2D5001A96FA /* PBXTargetDependency */, ); name = iOS; productName = iOS; @@ -767,10 +951,22 @@ 52B138471F47D39B0021D7AE /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0830; + LastSwiftUpdateCheck = 1030; LastUpgradeCheck = 1020; ORGANIZATIONNAME = BrunchLabs; TargetAttributes = { + 526A5F9522ECB2D5001A96FA = { + CreatedOnToolsVersion = 10.3; + DevelopmentTeam = JQ28K6Y7JE; + LastSwiftMigration = 1030; + ProvisioningStyle = Automatic; + }; + 526A5F9D22ECB2D5001A96FA = { + CreatedOnToolsVersion = 10.3; + DevelopmentTeam = JQ28K6Y7JE; + ProvisioningStyle = Automatic; + TestTargetID = 52B138B51F47D4590021D7AE; + }; 52B138B51F47D4590021D7AE = { CreatedOnToolsVersion = 8.3.3; DevelopmentTeam = JQ28K6Y7JE; @@ -849,11 +1045,27 @@ 52B138E71F47D4650021D7AE /* tvOS */, 52B138F71F47D4660021D7AE /* tvOSTests */, 52B139021F47D4660021D7AE /* tvOSUITests */, + 526A5F9522ECB2D5001A96FA /* DALI */, + 526A5F9D22ECB2D5001A96FA /* DALITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 526A5F9422ECB2D5001A96FA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 526A5F9C22ECB2D5001A96FA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 52B138B41F47D4590021D7AE /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -970,6 +1182,28 @@ shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/SwiftLint/swiftlint\"\n"; }; + 52CFDDBD825E6302E5DE8002 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-DALI-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 5FDA45A694C0B5C7F51C5D3C /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1013,16 +1247,14 @@ ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-tvOS/Pods-tvOS-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/DALI-tvOS/DALI.framework", - "${BUILT_PRODUCTS_DIR}/EmitterKit-tvOS/EmitterKit.framework", - "${BUILT_PRODUCTS_DIR}/FutureKit-tvOS/FutureKit.framework", - "${BUILT_PRODUCTS_DIR}/Socket.IO-Client-Swift-tvOS/SocketIO.framework", - "${BUILT_PRODUCTS_DIR}/Starscream-tvOS/Starscream.framework", - "${BUILT_PRODUCTS_DIR}/SwiftyJSON-tvOS/SwiftyJSON.framework", + "${BUILT_PRODUCTS_DIR}/EmitterKit-tvOS10.2/EmitterKit.framework", + "${BUILT_PRODUCTS_DIR}/FutureKit-tvOS10.2/FutureKit.framework", + "${BUILT_PRODUCTS_DIR}/Socket.IO-Client-Swift-tvOS10.2/SocketIO.framework", + "${BUILT_PRODUCTS_DIR}/Starscream-tvOS10.2/Starscream.framework", + "${BUILT_PRODUCTS_DIR}/SwiftyJSON-tvOS10.2/SwiftyJSON.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DALI.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EmitterKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FutureKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SocketIO.framework", @@ -1079,25 +1311,23 @@ "${PODS_ROOT}/Target Support Files/Pods-iOS/Pods-iOS-frameworks.sh", "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", "${BUILT_PRODUCTS_DIR}/ChromaColorPicker/ChromaColorPicker.framework", - "${BUILT_PRODUCTS_DIR}/DALI-iOS/DALI.framework", - "${BUILT_PRODUCTS_DIR}/EmitterKit-iOS/EmitterKit.framework", + "${BUILT_PRODUCTS_DIR}/EmitterKit-iOS12.0/EmitterKit.framework", "${BUILT_PRODUCTS_DIR}/Eureka/Eureka.framework", - "${BUILT_PRODUCTS_DIR}/FutureKit-iOS/FutureKit.framework", + "${BUILT_PRODUCTS_DIR}/FutureKit-iOS12.0/FutureKit.framework", "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework", "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac/GoogleToolboxForMac.framework", "${BUILT_PRODUCTS_DIR}/Log/Log.framework", "${BUILT_PRODUCTS_DIR}/QRCodeReaderViewController/QRCodeReaderViewController.framework", "${BUILT_PRODUCTS_DIR}/RLBAlertsPickers/RLBAlertsPickers.framework", "${BUILT_PRODUCTS_DIR}/SCLAlertView/SCLAlertView.framework", - "${BUILT_PRODUCTS_DIR}/Socket.IO-Client-Swift-iOS/SocketIO.framework", - "${BUILT_PRODUCTS_DIR}/Starscream-iOS/Starscream.framework", - "${BUILT_PRODUCTS_DIR}/SwiftyJSON-iOS/SwiftyJSON.framework", + "${BUILT_PRODUCTS_DIR}/Socket.IO-Client-Swift-iOS12.0/SocketIO.framework", + "${BUILT_PRODUCTS_DIR}/Starscream-iOS12.0/Starscream.framework", + "${BUILT_PRODUCTS_DIR}/SwiftyJSON-iOS12.0/SwiftyJSON.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ChromaColorPicker.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DALI.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EmitterKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Eureka.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FutureKit.framework", @@ -1155,6 +1385,35 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 526A5F9222ECB2D5001A96FA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 526A5FCC22ECB2FB001A96FA /* DALIapi.swift in Sources */, + 526A5FC922ECB2FB001A96FA /* DALIFood.swift in Sources */, + 525CFB8B22ECBC76002AA2BE /* CheckOutRecord.swift in Sources */, + 526A5FC822ECB2FB001A96FA /* DALILocation.swift in Sources */, + 526A5FCD22ECB2FB001A96FA /* DALIProject.swift in Sources */, + 526A5FC722ECB2FB001A96FA /* DALIEquipment.swift in Sources */, + 526A5FC422ECB2FB001A96FA /* DALILights.swift in Sources */, + 526A5FC122ECB2FB001A96FA /* DALIMember.swift in Sources */, + 526A5FC322ECB2FB001A96FA /* DALIConfig.swift in Sources */, + 526A5FCB22ECB2FB001A96FA /* DALIEvent.swift in Sources */, + 526A5FC522ECB2FB001A96FA /* Enumerations.swift in Sources */, + 526A5FC622ECB2FB001A96FA /* DALIErrors.swift in Sources */, + 526A5FCA22ECB2FB001A96FA /* DALIPhoto.swift in Sources */, + 526A5FC222ECB2FB001A96FA /* ServerCommunicator.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 526A5F9A22ECB2D5001A96FA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 526A5FA622ECB2D5001A96FA /* DALITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 52B138B21F47D4590021D7AE /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1255,6 +1514,21 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 526A5FA122ECB2D5001A96FA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 526A5F9522ECB2D5001A96FA /* DALI */; + targetProxy = 526A5FA022ECB2D5001A96FA /* PBXContainerItemProxy */; + }; + 526A5FA322ECB2D5001A96FA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 52B138B51F47D4590021D7AE /* iOS */; + targetProxy = 526A5FA222ECB2D5001A96FA /* PBXContainerItemProxy */; + }; + 526A5FAA22ECB2D5001A96FA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 526A5F9522ECB2D5001A96FA /* DALI */; + targetProxy = 526A5FA922ECB2D5001A96FA /* PBXContainerItemProxy */; + }; 52B138CB1F47D4590021D7AE /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 52B138B51F47D4590021D7AE /* iOS */; @@ -1283,6 +1557,121 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + 526A5FAD22ECB2D5001A96FA /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B14D6D286A77E6629B92C32C /* Pods-DALI.debug.xcconfig */; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = JQ28K6Y7JE; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = DALI/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = edu.dali.dartmouth.DALI; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 526A5FAE22ECB2D5001A96FA /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4DC9C267DB494773FCC8E769 /* Pods-DALI.release.xcconfig */; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = JQ28K6Y7JE; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = DALI/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = edu.dali.dartmouth.DALI; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 526A5FAF22ECB2D5001A96FA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = JQ28K6Y7JE; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = DALITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = edu.dali.dartmouth.DALITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOS.app/iOS"; + }; + name = Debug; + }; + 526A5FB022ECB2D5001A96FA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = JQ28K6Y7JE; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = DALITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = edu.dali.dartmouth.DALITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOS.app/iOS"; + }; + name = Release; + }; 52B138751F47D39C0021D7AE /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1398,6 +1787,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 2D680826E06D0CB05826D88E /* Pods-iOS.debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = iOS/iOS.entitlements; @@ -1423,6 +1813,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 30E4FF739BA2E6ADB3C63F39 /* Pods-iOS.release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = iOS/iOS.entitlements; @@ -1675,6 +2066,24 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 526A5FB122ECB2D5001A96FA /* Build configuration list for PBXNativeTarget "DALI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 526A5FAD22ECB2D5001A96FA /* Debug */, + 526A5FAE22ECB2D5001A96FA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 526A5FB322ECB2D5001A96FA /* Build configuration list for PBXNativeTarget "DALITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 526A5FAF22ECB2D5001A96FA /* Debug */, + 526A5FB022ECB2D5001A96FA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 52B1384A1F47D39B0021D7AE /* Build configuration list for PBXProject "DALI Lab" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/DALI Lab.xcodeproj/xcshareddata/xcschemes/Stickers.xcscheme b/DALI Lab.xcodeproj/xcshareddata/xcschemes/Stickers.xcscheme index 26e5b56..67681e0 100644 --- a/DALI Lab.xcodeproj/xcshareddata/xcschemes/Stickers.xcscheme +++ b/DALI Lab.xcodeproj/xcshareddata/xcschemes/Stickers.xcscheme @@ -43,13 +43,23 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + diff --git a/DALI Lab.xcodeproj/xcshareddata/xcschemes/iOS.xcscheme b/DALI Lab.xcodeproj/xcshareddata/xcschemes/iOS.xcscheme index 9244c52..6bf1ee1 100644 --- a/DALI Lab.xcodeproj/xcshareddata/xcschemes/iOS.xcscheme +++ b/DALI Lab.xcodeproj/xcshareddata/xcschemes/iOS.xcscheme @@ -62,6 +62,16 @@ ReferencedContainer = "container:DALI Lab.xcodeproj"> + + + + + +//! Project version number for DALI. +FOUNDATION_EXPORT double DALIVersionNumber; + +//! Project version string for DALI. +FOUNDATION_EXPORT const unsigned char DALIVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/DALI/DALIConfig.swift b/DALI/DALIConfig.swift new file mode 100644 index 0000000..8660beb --- /dev/null +++ b/DALI/DALIConfig.swift @@ -0,0 +1,132 @@ +// +// HandleConfigFile.swift +// DALIapi +// +// Created by John Kotz on 7/28/17. +// Copyright © 2017 DALI Lab. All rights reserved. +// + +import Foundation +import SwiftyJSON + +/** +Configurations for the DALIapi framework can be stored and handled using this + +Example usage: + + let file = NSDictionary(dictionary: [ + "server_url": "https://dalilab-api.herokuapp.com/" + ]) + let config = DALIConfig(dict: file) + DALIapi.configure(config: config) +*/ +open class DALIConfig: Codable { + /// The URL to the server. This is required + public var serverURL: String + /// Used to connect to the server without needing user signin + internal var apiKey: String? + + /// Token. This is needed for requests when needing user signin + internal var token_stored: String? + public var token: String? { + /* + The token is stored in the UserDefaults so it can be recalled after the app is restarted + */ + get { + if let token_stored = token_stored { + return token_stored + }else if let token = UserDefaults.standard.string(forKey: "DALIapi:token") { + token_stored = token + return token + } else { + return nil + } + } + set { + self.token_stored = newValue + if let token = newValue { + UserDefaults.standard.set(token, forKey: "DALIapi:token") + } else { + UserDefaults.standard.removeObject(forKey: "DALIapi:token") + } + } + } + + internal var serverURLobject: URL { + return URL(string: DALIapi.config.serverURL)! + } + + /// The current member signed in + internal var member_stored: DALIMember? + internal var member: DALIMember? { + get { + if let member_stored = member_stored { + return member_stored + }else if let stored = UserDefaults.standard.data(forKey: "DALIapi:member"), let member = DALIMember(json: JSON(stored)) { + member_stored = member + return member + } + return nil + } + set { + self.member_stored = newValue + if let data = ((try? newValue?.json.rawData()) as Data??) { + UserDefaults.standard.set(data, forKey: "DALIapi:member") + }else if newValue == nil { + UserDefaults.standard.removeObject(forKey: "DALIapi:member") + } + } + } + + /// A default value for the sharing preference + public var sharingDefault = true + private var enableSockets_internal = false + /// Allows to enable or disable the use of sockets + public var enableSockets: Bool { + get { return enableSockets_internal } + set { enableSockets_internal = newValue; if newValue { DALIapi.enableSockets() } else { DALIapi.disableSockets() } } + } + /// Enables automatic connecting and disconnecting of sockets when going between forground and background + public var socketAutoSwitching = true + public var forceWebsockets = false + + /** + Creates a DALIConfig object + + - parameter dict: A dictionary containing server_url + */ + public convenience init(dict: NSDictionary) { + guard let serverURL = dict["server_url"] as? String else { + fatalError("DALIConfig: Server URL Missing! Make sure server_url is in your config dictionary") + } + + self.init(serverURL: serverURL, apiKey: dict["api_key"] as? String, enableSockets: dict["enableSockets"] as? Bool) + } + + /** + Initializes the configuration with a server url and an API key + + - parameter serverURL: The base URL for the server to use + - parameter apiKey: The key to use to authenticate requests + - parameter enableSockets: Allows sockets to be used + */ + public init(serverURL: String, apiKey: String? = nil, enableSockets: Bool? = true) { + self.serverURL = serverURL + self.apiKey = apiKey + + if self.serverURL.last == "/" { + self.serverURL = String(self.serverURL[..= totalStock + } + var updatesSocket: SocketIOClient! + static private var staticUpdatesSocket: SocketIOClient! + static private var staticUpdatesEvent = Event<[DALIEquipment]>() + static private let populateString = "[\"lastCheckOut\", \"lastCheckOut.user\"]" + + // MARK: - Setup + + internal init?(json: JSON) { + guard let dict = json.dictionary, + let name = dict["name"]?.string, + let id = dict["id"]?.string, + let typeString = dict["type"]?.string, + let type = EquipmentType(rawValue: typeString), + let checkingOutUsersData = dict["checkingOutUsers"]?.array + else { + return nil + } + + self.name = name + self.id = id + self.password = dict["password"]?.string + self.iconName = dict["iconName"]?.string + self.make = dict["make"]?.string + self.model = dict["model"]?.string + self.serialNumber = dict["serialNumber"]?.string + self.totalStock = dict["totalStock"]?.int ?? 1 + self.description = dict["description"]?.string + self.type = type + + var lastCheckedOut: CheckOutRecord? + if let lastCheckedOutJSON = dict["lastCheckOut"] { + lastCheckedOut = CheckOutRecord(json: lastCheckedOutJSON) + } else { + lastCheckedOut = nil + } + self.hasHistory = dict["hasHistory"]?.bool ?? (lastCheckedOut != nil) + self.lastCheckedOut = lastCheckedOut + + self.checkingOutUsers = checkingOutUsersData.compactMap({ (json) -> DALIMember? in + return DALIMember(json: json) + }) + } + + private func update(json: JSON) { + guard let dict = json.dictionary else { + return + } + + self.name = dict["name"]?.string ?? self.name + self.password = dict["password"]?.string ?? self.password + self.iconName = dict["iconName"]?.string ?? self.iconName + self.make = dict["make"]?.string ?? self.make + self.model = dict["model"]?.string ?? self.model + self.serialNumber = dict["serialNumber"]?.string ?? self.serialNumber + self.description = dict["description"]?.string ?? self.description + self.totalStock = dict["totalStock"]?.int ?? self.totalStock + + if let typeString = dict["type"]?.string { + self.type = EquipmentType(rawValue: typeString) ?? self.type + } + if let lastCheckedOutJSON = dict["lastCheckOut"] { + self.lastCheckedOut = CheckOutRecord(json: lastCheckedOutJSON) + } + } + + public static func == (lhs: DALIEquipment, rhs: DALIEquipment) -> Bool { + return lhs.id == rhs.id + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + } + + // MARK: - Public API + + // MARK: Creators + + public static func create(withName name: String, extraInfo: [String:Any]) -> Future { + var dict = extraInfo + dict["name"] = name + return ServerCommunicator.post(url: "\(DALIapi.config.serverURL)/api/equipment", json: JSON(dict)) + .onSuccess { (response) in + if let json = response.json, let equipment = DALIEquipment(json: json) { + return equipment + } else { + throw response.assertedError + } + } + } + + // MARK: Static Getters + + /** + Get a single equipment object with a given id + */ + public static func equipment(for id: String) -> Future { + return ServerCommunicator.get(url: "\(DALIapi.config.serverURL)/api/equipment/\(id)", params: ["populate": populateString]).onSuccess(block: { (response) -> DALIEquipment in + if let json = response.json, let equipment = DALIEquipment(json: json) { + return equipment + } else { + throw response.assertedError + } + }) + } + + /** + Get all the equipment + */ + public static func allEquipment() -> Future<[DALIEquipment]> { + return ServerCommunicator.get(url: "\(DALIapi.config.serverURL)/api/equipment", + params: ["populate": populateString]) + .onSuccess(block: { (response) -> [DALIEquipment] in + if let dataArray = response.json?.array { + var array = [DALIEquipment]() + + dataArray.forEach({ (json) in + if let equipment = DALIEquipment(json: json) { + array.append(equipment) + } + }) + + return array + } else { + throw response.assertedError + } + }) + } + + // MARK: Single equipment methods + + /** + Reload the information stored in this equipment + */ + public func reload() -> Future { + return ServerCommunicator.get(url: "\(DALIapi.config.serverURL)/api/equipment/\(id)", + params: ["populate": DALIEquipment.populateString]) + .onSuccess(block: { (response) -> DALIEquipment in + guard let json = response.json else { + throw response.assertedError + } + + self.update(json: json) + return self + }) + } + + /** + Get all the checkouts in the past for this equipment + */ + public func getHistory() -> Future<[CheckOutRecord]> { + return ServerCommunicator.get(url: "\(DALIapi.config.serverURL)/api/equipment/\(self.id)/checkout").onSuccess { (response) -> [CheckOutRecord] in + guard let array = response.json?.array else { + throw response.assertedError + } + + let list = array.compactMap({ (json) -> CheckOutRecord? in + return CheckOutRecord(json: json) + }) + return list + } + } + + /** + Check out this equipment + + - note: Will only succeed when the user is signed in and it is not currently checked out + */ + public func checkout(expectedEndDate: Date?) -> Future { + guard !isCheckedOut else { + return Future(fail: DALIError.Equipment.AlreadyCheckedOut) + } + + var data: Data? + + if let expectedEndDate = expectedEndDate { + let dict = ["projectedEndDate" : DALIEvent.dateFormatter().string(from: expectedEndDate)] + do { + data = try JSONSerialization.data(withJSONObject: dict, options: []) + } catch { + return Future(fail: error) + } + } + + let url = "\(DALIapi.config.serverURL)/api/equipment/\(id)/checkout" + return ServerCommunicator.post(url: url, data: data).onSuccess { (response) -> Future in + if let json = response.json { + return Future(success: CheckOutRecord(json: json)) + } else { + return self.reload().onSuccess { (equipment) in + if equipment.isCheckedOut { + throw DALIError.Equipment.AlreadyCheckedOut + } else { + throw DALIError.General.UnexpectedResponse + } + } + } + } + } + + public func update(returnDate: Date) -> Future { + guard isCheckedOut else { + return Future(fail: DALIError.Equipment.AlreadyCheckedOut) + } + + let dict: [String:Any] = ["projectedEndDate" : DALIEvent.dateFormatter().string(from: returnDate)] + + let url = "\(DALIapi.config.serverURL)/api/equipment/\(id)/checkout" + return ServerCommunicator.put(url: url, json: JSON(dict)).onSuccess(block: { (response) -> DALIEquipment in + if let json = response.json { + self.update(json: json) + return self + } else { + throw response.assertedError + } + }) + } + + /** + Return a peice of equipment + */ + public func returnEquipment() -> Future { + return ServerCommunicator.post(url: "\(DALIapi.config.serverURL)/api/equipment/\(id)/return", data: nil).onSuccess(block: { (response) -> Future in + if !response.success { + throw response.assertedError + } + return self.reload() + }) + } + + // MARK: Observing changes + + /** + Observe all the equipment, get updates whenever there are changes + + - parameter block: The block that will be called when new information is available + - returns: Observation to allow you to control the flow of new information + */ + public static func observeAllEquipment(block: @escaping ([DALIEquipment]) -> Void) -> Observation { + assertStaticSocket() + let listener = staticUpdatesEvent.on(block) + + return Observation(stop: { + listener.isListening = false + updateStaticSocketEnabled() + }, listener: listener, restartBlock: { + listener.isListening = true + assertStaticSocket() + return true + }) + } + + // MARK: - Helpers + + /// Check to see if the static socket is open. If not, open one + private static func assertStaticSocket() { + guard staticUpdatesSocket == nil else { + return + } + staticUpdatesSocket = DALIapi.socketManager.socket(forNamespace: "/equipment") + + staticUpdatesSocket.on("equipmentUpdate") { (data, ack) in + guard let array = data[0] as? [[String: Any]] else { + staticUpdatesEvent.emit([]) + return + } + + let equipment = array.compactMap({ (data) -> DALIEquipment? in + return DALIEquipment(json: JSON(data)) + }) + + self.staticUpdatesEvent.emit(equipment) + } + + staticUpdatesSocket.connect() + staticUpdatesSocket.on(clientEvent: .connect, callback: { (data, ack) in + ServerCommunicator.authenticateSocket(socket: staticUpdatesSocket!) + }) + } + + /// Disconnect the socket if no one is listening + private static func updateStaticSocketEnabled() { + guard let staticUpdatesSocket = staticUpdatesSocket else { + return + } + + let listeners = staticUpdatesEvent.getListeners(nil).filter { (listener) -> Bool in + return listener.isListening + } + + if listeners.count <= 0 { + staticUpdatesSocket.disconnect() + self.staticUpdatesSocket = nil + } + } + + private var observeCallback: ((DALIEquipment) -> Void)? + private var observeCheckoutsCallback: (([CheckOutRecord], DALIEquipment) -> Void)? + private var observeDeletionCallback: ((DALIEquipment) -> Void)? + + private func assertSocket() { + if (updatesSocket == nil) { + updatesSocket = DALIapi.socketManager.socket(forNamespace: "/equipment") + + updatesSocket.on("checkOuts") { (data, ack) in + if let observeCheckoutsCallback = self.observeCheckoutsCallback, let data = data[0] as? [[String: Any]] { + var checkOuts = [CheckOutRecord]() + for obj in data { + if let checkOut = CheckOutRecord(json: JSON(obj)) { + checkOuts.append(checkOut) + } + } + observeCheckoutsCallback(checkOuts, self) + } + } + updatesSocket.on("update") { (data, ack) in + if let observeCallback = self.observeCallback, let data = data[0] as? [String:Any] { + self.update(json: JSON(data)) + observeCallback(self) + } + } + updatesSocket.on("deleted") { (data, ack) in + if let observeDeletionCallback = self.observeDeletionCallback { + observeDeletionCallback(self) + } + } + updatesSocket.on(clientEvent: .error) { (data, ack) in + print(data) + } + } + updatesSocket.connect() + updatesSocket.once(clientEvent: .connect) { (_, _) in + guard let rawString = JSON([DALIapi.config.token!, self.id]).rawString(), let socket = self.updatesSocket else { + return + } + socket.emit("equipmentSelect", rawString) + } + } + + private func cleanupSocket() { + if (observeCallback == nil && observeCheckoutsCallback == nil && observeDeletionCallback == nil) { + updatesSocket?.disconnect() + updatesSocket = nil + } + } + + public func observe(callback: @escaping (DALIEquipment) -> Void) -> Observation { + self.assertSocket() + observeCallback = callback + + return Observation(stop: { + self.observeCallback = nil + self.cleanupSocket() + }, id: "observing-\(id)") + } + + public func observeCheckouts(callback: @escaping ([CheckOutRecord], DALIEquipment) -> Void) -> Observation { + self.assertSocket() + observeCheckoutsCallback = callback + + return Observation(stop: { + self.observeCheckoutsCallback = nil + self.cleanupSocket() + }, id: "observingCheckOuts-\(id)") + } + + public func observeDeletion(callback: @escaping (DALIEquipment) -> Void) -> Observation { + self.assertSocket() + observeDeletionCallback = callback + + return Observation(stop: { + self.observeDeletionCallback = nil + self.cleanupSocket() + }, id: "observingDeletion-\(id)") + } + + public enum EquipmentType: String { + case single + case collection + } +} diff --git a/DALI/DALIErrors.swift b/DALI/DALIErrors.swift new file mode 100644 index 0000000..0026f57 --- /dev/null +++ b/DALI/DALIErrors.swift @@ -0,0 +1,56 @@ +// +// DALIErrors.swift +// DALIapi +// +// Created by John Kotz on 7/28/17. +// Copyright © 2017 DALI Lab. All rights reserved. +// + +import Foundation +import SwiftyJSON + +/// A set of errors used by the DALIapi framework to signal the user when there is an issue +open class DALIError { + /// Pertaining to the Save opperation + public enum Save: Error { + + } + + /// Pertaining to the Create opperation + public enum Create: Error { + /// The object you are calling create on has already been created + case AlreadyCreated + } + + public enum Equipment: Error { + case AlreadyCheckedOut + case NotCheckedOut + } + + /// Pertaining to the General opperations. Mostly used by the ServerCommunicator + public enum General: Error { + /// The request did not have proper authorization + case Unauthorized + /// A unknown error has occured. Information about the error is stored. Code will be -1 if there is no code + case UnknownError(error: Error?, text: String?, code: Int?) + /// Response was not JSON! Text version is stored + case InvalidJSON(error: SwiftyJSONError) + /// The data sent to the server was not able to be processed for whatever reason + case Unprocessable + /// The data sent was not valid for the route. Consult the documentation for the route you are using + case BadRequest + /// The response to the request was not as expected. Consult the documentation for the route you are using + case UnexpectedResponse + /// The requested object(s) were not found on the server + case Unfound + + public var localizedDescription: String { + switch self { + case .Unprocessable: + return "Data was unprocessable" + default: + return "Unknown error has occured" + } + } + } +} diff --git a/DALI/DALIEvent.swift b/DALI/DALIEvent.swift new file mode 100644 index 0000000..22d09c0 --- /dev/null +++ b/DALI/DALIEvent.swift @@ -0,0 +1,1156 @@ +// +// DALIEvent.swift +// DALIapi +// +// Created by John Kotz on 7/28/17. +// Copyright © 2017 DALI Lab. All rights reserved. +// + +import Foundation +import SwiftyJSON +import SocketIO +import EmitterKit +import FutureKit + +/** +A DALI event +*/ +public class DALIEvent { + // MARK: - Properties + private var name_in: String + private var description_in: String? + private var location_in: String? + private var start_in: Date + private var end_in: Date + + /// Name of the event + public var name: String { + get { return name_in } + set { if self.editable { self.name_in = newValue; self.dict?["name"] = JSON(newValue); self.dirty = true } } + } + /// Description of the event + public var description: String? { + get { return description_in } + set { + if self.editable { + self.description_in = newValue + if let newValue = newValue { + self.dict?["description"] = JSON(newValue) + } else { + self.dict?.removeValue(forKey: "description") + } + self.dirty = true + } + } + } + /// Location of the event + public var location: String? { + get { return location_in } + set { + if self.editable { + self.location_in = newValue + if let newValue = newValue { + self.dict?["location"] = JSON(newValue) + } else { + self.dict?.removeValue(forKey: "location") + } + self.dirty = true + } + } + } + /// Start time of the event + public var start: Date { + get { return start_in } + set { if self.editable { self.start_in = newValue; self.dict?["start"] = JSON(newValue); self.dirty = true } } + } + /// Start time of the event + public var end: Date { + get { return end_in } + set { if self.editable { self.end_in = newValue; self.dict?["end"] = JSON(newValue); self.dirty = true } } + } + + fileprivate var googleID: String? + + /// The identifier used by the server + public private(set) var id: String! + + /// Signifies when this event object contains information that has not been saved + public private(set) var dirty: Bool + + /// A flag that indicates if this event can be edited + public var editable: Bool { + return googleID == nil + } + /// A flag that indicates if this event is happening now + public var isNow: Bool { + return self.start_in <= Date() && self.end_in >= Date() + } + /// The dictionary data that was parsed to this event + internal var dict: [String: JSON]? + + + // MARK: - Subclasses + + /** + Handles all voting communications + */ + public class VotingEvent: DALIEvent { + // MARK: - Properties + /// The configure the voting + public private(set) var config: Config + /// The options connected to the event + public private(set) var options: [Option]? + /// Voting results have been released + public private(set) var resultsReleased: Bool + + // MARK: - Structures + + /** + An option that a user can vote for. + */ + public struct Option { + /// The title of the option + public private(set) var name: String + /// The number of points the option has gotten. Only accessable by admin + public private(set) var points: Int? + /// Identifier for the option + public private(set) var id: String + /// The awards this option has earned. Available only for admins or for events with results released + public var awards: [String]? + + /// A boolean to indicate if the user is voting for this one + public var isVotedFor: Bool = false + /// An int to indicate the order (starting at 1 ending at numSelected). Only nescesary if event is ordered + public var voteOrder: Int? = nil + + /// Parse the given json object + public static func parse(object: JSON) -> Option? { + guard let dict = object.dictionary else { + return nil + } + + let points = dict["points"]?.int + let awards = dict["awards"]?.arrayObject as? [String] + + guard let name = dict["name"]?.string, let id = dict["id"]?.string else { + return nil + } + + return Option(name: name, points: points, id: id, awards: awards, isVotedFor: false, voteOrder: nil) + } + + /// Get the JSON value of this option + public func json() -> JSON { + var data: [String:Any] = [ + "name": name, + "id": id, + ] + if let awards = awards { + data["awards"] = awards + } + + return JSON(data) + } + } + + /** + The configuration for voting events + */ + public struct Config { + /// The number of options a user can select when voting + public private(set) var numSelected: Int + /// Boolean to indicate whether the user should put their options in order + public private(set) var ordered: Bool + + /// Get the JSON value of this config + public func json() -> JSON { + return JSON([ "numSelected": numSelected, "ordered": ordered ]) + } + } + + // MARK: Initialization Methods + + /** + Create a new voting event with all the given information + */ + init(name: String, description: String?, location: String?, start: Date, end: Date, votingConfig config: Config, options: [Option]?, resultsReleased: Bool) { + self.config = config + self.options = options + self.resultsReleased = resultsReleased + + super.init(name: name, description: description, location: location, start: start, end: end) + } + + /** + Converts the event into a voting event + */ + init(event: DALIEvent, votingConfig config: Config, options: [Option]?, resultsReleased: Bool) { + self.config = config + self.options = options + self.resultsReleased = resultsReleased + + super.init(name: event.name_in, description: event.description_in, location: event.description_in, start: event.start_in, end: event.end_in) + + self.dict = event.dict + self.id = event.id + self.googleID = event.googleID + self.dirty = event.dirty + + self.dict?["votingConfig"] = config.json() + self.dict?["votingResultsReleased"] = JSON(resultsReleased) + self.dict?["votingEnabled"] = JSON(true) + } + + /** + Try to extract a voting event from the event's data. + + NOTE: This is a fallable initializer, meaning it may return null + + - parameter event: The DALIEvent to attempt to cast into a VotingEvent + */ + convenience init?(event: DALIEvent) { + guard let dict = event.dict, let resultsReleased = dict["votingResultsReleased"]?.bool else { + return nil + } + + guard let configDict = dict["votingConfig"]?.dictionary, let numSelected = configDict["numSelected"]?.int, let ordered = configDict["ordered"]?.bool else { + return nil + } + + let config = Config(numSelected: numSelected, ordered: ordered) + + self.init(event: event, votingConfig: config, options: nil, resultsReleased: resultsReleased) + } + + // MARK: JSON Methods + + /** + Converts the data stored in the event into a JSON format that the API will understand + + - returns: JSON data describing the event + */ + public override func json() -> JSON { + if let dict = self.dict { + return JSON(dict) + } + + let dict: [String: Any?] = [ + "name": self.name_in, + "startTime": DALIEvent.dateFormatter().string(from: self.start_in), + "endTime": DALIEvent.dateFormatter().string(from: self.end_in), + "description": self.description, + "id": self.id, + "votingEnabled": true, + "votingResultsReleased": resultsReleased, + "votingConfig": config.json(), + "googleID": self.googleID, + ] + + self.dict = JSON(dict).dictionary + + return JSON(dict) + } + + /** + Parses a JSON object. May return nil if this is not a voting event or if it fails + + - parameter object: The JSON object to parse + */ + public override class func parse(_ object: JSON) -> VotingEvent? { + return super.parse(object) as? VotingEvent + } + + // MARK: Public Methods + + /** + Get if the current device and user have already voted for this event + + - parameter callback: Function called when done + - parameter haveVoted: Flag expressing if the user and device has voted already (default false) + - parameter error: The error encountered, if any + */ + public func haveVoted() -> Future { + guard let id = self.id else { + return Future(fail: DALIError.General.BadRequest) + } + + return ServerCommunicator.get(url: "\(DALIapi.config.serverURL)/api/voting/public/\(id)/hasVoted").onSuccess { (response) -> Bool in + guard let dict = response.json?.dictionary, let haveVoted = dict["voted"]?.bool else { + throw response.assertedError + } + return haveVoted + } + } + + /** + Get the public results for this event + + - parameters event: Event to get the results of + - parameters callback: Function to be called when done + */ + public func getResults() -> Future<[Option]> { + guard let id = self.id else { + return Future(fail: DALIError.General.BadRequest) + } + + return ServerCommunicator.get(url: "\(DALIapi.config.serverURL)/api/voting/public/\(id)/results").onSuccess { (response) -> [Option] in + guard let array = response.json?.array else { + throw response.assertedError + } + + self.options = array.compactMap({ (json) -> Option? in + return Option.parse(object: json) + }) + return self.options! + } + } + + /** + Get all the options for this event + + - parameter event: Event to get the options for + - parameters callback: Function to be called when done + */ + public func getOptions() -> Future<[Option]> { + let promise = Promise<[Option]>() + if let options = self.options { + promise.completeWithSuccess(options) + } + + guard let id = self.id else { + return promise.futureWithFailure(error: DALIError.General.BadRequest) + } + + _ = ServerCommunicator.get(url: "\(DALIapi.config.serverURL)/api/voting/public/\(id)").onSuccess { (response) in + guard let array = response.json?.array else { + promise.completeWithFail(response.assertedError) + return + } + + self.options = array.compactMap({ (json) -> Option? in + return Option.parse(object: json) + }) + promise.completeWithSuccess(self.options!) + } + + return promise.future + } + + /** + Submit the given options as a vote + + - parameter options: The options to be submitted. If the voting event is ordered then they need to be in 1st, 2nd, 3rd, ..., nth choice order + - parameter callback: Function to be called when done + */ + public func submitVote(options: [Option]) -> Future { + var optionsData: [[String: Any]] = [] + + for option in options { + if let storedOptions = self.options, storedOptions.contains(where: { (storedOption) -> Bool in return storedOption.id == option.id }) { + optionsData.append([ + "id": option.id, + "name": option.name + ]) + } else { + // TODO: Have an error + } + } + + guard let id = self.id else { + return Future(fail: DALIError.General.BadRequest) + } + + do { + return try ServerCommunicator.post(url: "\(DALIapi.config.serverURL)/api/voting/public/\(id)", json: JSON(optionsData)).onSuccess(block: { (response) in + if !response.success { + throw response.assertedError + } + }) + }catch { + return Future(fail: error) + } + } + + // ===================== Admin only methods ====================== + // MARK: Admin Methods + + /** + Save the awards given to the given options + + ![Admin only](http://icons.iconarchive.com/icons/graphicloads/flat-finance/64/lock-icon.png) + + - parameter options: The options to save + - parameter callback: Function called when done + */ + public func saveResults(options: [Option]) -> Future { + guard DALIapi.config.member?.isAdmin ?? false else { + return Future(fail: DALIError.General.Unauthorized) + } + guard let id = self.id else { + return Future(fail: DALIError.General.BadRequest) + } + + let optionsData = options.map { (option) -> [String:Any] in + return ["id": option.id, "awards": option.awards ?? []] + } + + do { + return try ServerCommunicator.post(url: "\(DALIapi.config.serverURL)/api/voting/admin/\(id)/results", json: JSON(optionsData)).onSuccess(block: { (response) in + if !response.success { + throw response.assertedError + } + }) + } catch { + return Future(fail: error) + } + } + + /** + Get the results of an event. + + ![Admin only](http://icons.iconarchive.com/icons/graphicloads/flat-finance/64/lock-icon.png) + + - parameter event: Event to get the events of + - parameter callback: Function to be called when done + - parameter results: The results requested + - parameter error: Error encountered (if any) + */ + public func getUnreleasedResults() -> Future<[Option]> { + guard DALIapi.config.member?.isAdmin ?? false else { + return Future(fail: DALIError.General.Unauthorized) + } + + guard let id = self.id else { + return Future(fail: DALIError.General.BadRequest) + } + + return ServerCommunicator.get(url: "\(DALIapi.config.serverURL)/api/voting/admin/\(id)").onSuccess { (response) -> [Option] in + guard let array = response.json?.array else { + throw response.assertedError + } + + self.options = array.compactMap({ (json) -> Option? in + return Option.parse(object: json) + }) + return self.options! + } + } + + /** + Releases the results + + ![Admin only](http://icons.iconarchive.com/icons/graphicloads/flat-finance/64/lock-icon.png) + + - parameter callback: Function called when done + */ + public func release() -> Future { + if !(DALIapi.config.member?.isAdmin ?? false) { + return Future(fail: DALIError.General.Unauthorized) + } + + guard let id = self.id else { + return Future(fail: DALIError.General.BadRequest) + } + + return ServerCommunicator.post(url: "\(DALIapi.config.serverURL)/api/voting/admin/\(id)", data: "".data(using: .utf8)!).onSuccess { (response) in + if response.success { + self.resultsReleased = true + self.dict?["votingResultsReleased"] = JSON(true) + } else { + throw response.assertedError + } + } + } + + /** + Adds an option to the event + + ![Admin only](http://icons.iconarchive.com/icons/graphicloads/flat-finance/64/lock-icon.png) + + - parameter option: The option to be added + - parameter callback: Function called when done + */ + public func addOption(option: String) -> Future { + guard DALIapi.config.member?.isAdmin ?? false else { + return Future(fail: DALIError.General.Unauthorized) + } + guard let id = self.id else { + return Future(fail: DALIError.General.BadRequest) + } + + let dict: [String: String] = [ + "option": option + ] + + do { + return try ServerCommunicator.post(url: "\(DALIapi.config.serverURL)/api/voting/admin/\(id)/options", json: JSON(dict)).onSuccess(block: { (response) in + guard let data = response.json, let option = Option.parse(object: data) else { + throw response.assertedError + } + self.options = self.options ?? [] + self.options!.append(option) + }) + } catch { + return Future(fail: error) + } + } + + /** + Removes the given option from the list of options + + ![Admin only](http://icons.iconarchive.com/icons/graphicloads/flat-finance/64/lock-icon.png) + + - parameter option: The option to remove + - parameter callback: Function called when done + */ + public func removeOption(option: Option) -> Future { + guard DALIapi.config.member?.isAdmin ?? false else { + return Future(fail: DALIError.General.Unauthorized) + } + guard let id = self.id else { + return Future(fail: DALIError.General.BadRequest) + } + + let dict: [String: String] = [ + "option": option.id + ] + + do { + return try ServerCommunicator.delete(url: "\(DALIapi.config.serverURL)/api/voting/admin/\(id)/options", json: JSON(dict)).onSuccess(block: { (response) in + if response.success { + if let index = self.options?.firstIndex(where: { (option2) -> Bool in return option2.id == option.id }) { + self.options?.remove(at: index) + } + } else { + throw response.assertedError + } + }) + } catch { + return Future(fail: error) + } + } + + // =================== Static Methods ======================= + // MARK: Static Getter Methods + + /** + Get the current voting event + + - parameter callback: Function called when done + - parameter event: Event found (if any) + - parameter error: Error encountered (if any) + */ + public static func getCurrent() -> Future<[VotingEvent]> { + + // TODO: Observe current and released events + return ServerCommunicator.get(url: "\(DALIapi.config.serverURL)/api/voting/public/current").onSuccess { (response) -> [VotingEvent] in + guard let list = response.json?.array else { + throw response.assertedError + } + + return list.compactMap({ (json) -> VotingEvent? in + return VotingEvent.parse(json) + }) + } + } + + private static func handleEventList(response: ServerCommunicator.Response) throws -> [VotingEvent] { + guard let eventObjects = response.json?.array else { + throw response.assertedError + } + + return eventObjects.compactMap { (json) -> VotingEvent? in + VotingEvent.parse(json) + } + } + + /** + Get all events that have results released + + - parameter callback: Function called when done + - parameter events: List of events retrieved + - parameter error: Error encountered (if any) + */ + public static func getReleasedEvents() -> Future<[VotingEvent]> { + return ServerCommunicator.get(url: "\(DALIapi.config.serverURL)/api/voting/public").onSuccess { (response) -> [VotingEvent] in + return try handleEventList(response: response) + } + } + + /** + Get voting events as an admin. The signed in user __must__ be an admin, otherwise will exit immediately + + ![Admin only](http://icons.iconarchive.com/icons/graphicloads/flat-finance/64/lock-icon.png) + + - parameter callback: Function called when done + - parameter events: List of events retrieved + - parameter error: Error encountered (if any) + */ + public static func get() -> Future<[VotingEvent]> { + guard DALIapi.config.member?.isAdmin ?? false else { + return Future(fail: DALIError.General.Unauthorized) + } + + return ServerCommunicator.get(url: "\(DALIapi.config.serverURL)/api/voting/admin").onSuccess { (response) -> [VotingEvent] in + return try handleEventList(response: response) + } + } + + // MARK: Static Observation Methods + internal static var votingEventSocket: SocketIOClient? + internal static var observationEvent: Event<[VotingEvent]>? + + public static func observe() -> Event<[VotingEvent]> { + guard observationEvent == nil else { + return observationEvent! + } + + observationEvent = Event<[VotingEvent]>() + votingEventSocket = DALIapi.socketManager.socket(forNamespace: "/voting") + + votingEventSocket?.connect() + ServerCommunicator.authenticateSocket(socket: votingEventSocket!) + + votingEventSocket?.on("events", callback: { (data, ack) in + guard let eventsData = data[0] as? [Any] else { + DispatchQueue.main.async { + observationEvent?.emit([]) + } + return; + } + + var events: [VotingEvent] = [] + for eventDataObj in eventsData { + if let event = VotingEvent.parse(JSON(eventDataObj)) { + events.append(event) + } + } + + DispatchQueue.main.async { + observationEvent?.emit(events) + } + }) + + return observationEvent!; + } + } + + // MARK: Initialization Methods + + /** + Creates an event object + + - parameter name: The name of the event + - parameter description: The description of the event + - parameter location: The location of the event + - parameter start: The start time + - parameter end: End time + */ + public init(name: String, description: String?, location: String?, start: Date, end: Date) { + self.name_in = name + self.description_in = description + self.location_in = location + self.start_in = start + self.end_in = end + self.googleID = nil + self.dirty = true + self.id = nil + } + + /** + Creates the event on the server + + - parameter callback: A function that will be called when the job is done + + - throws: `DALIError.Create` error describing some error encountered + */ + public func create() -> Future { + guard self.id == nil else { + return Future(fail: DALIError.Create.AlreadyCreated) + } + + do { + return try ServerCommunicator.post(url: DALIapi.config.serverURL + "/api/events", json: self.json()).onSuccess(block: { (response) in + if !response.success { + throw response.assertedError + } + }) + } catch { + return Future(fail: error) + } + } + + // MARK: JSON Parsing and Constructing Methods + + /** + Parses a given json object and returns an event object if it can find one + + - parameter object: The JSON object you want parsed + + - returns: `DALIEvent` that was found. Will be nil if object is not event + */ + public class func parse(_ object: JSON) -> DALIEvent? { + guard let dict = object.dictionary else { + return nil + } + + // Get the required parts and guard + guard let name = dict["name"]?.string, + let startString = dict["startTime"]?.string, + let endString = dict["endTime"]?.string else { + return nil + } + + // Get some of the optionals. No need to guard + let description = dict["description"]?.string + let location = dict["location"]?.string + + // Parse the dates + guard let start = DALIEvent.dateFormatter().date(from: startString), + let end: Date = DALIEvent.dateFormatter().date(from: endString) else { + return nil + } + + // Get the rest + guard let id = dict["id"]?.string else { + return nil + } + let googleID = dict["googleID"]?.string + + let event = DALIEvent(name: name, description: description, location: location, start: start, end: end) + event.id = id + event.googleID = googleID + event.dict = dict + + if let votingEvent = VotingEvent(event: event) { + return votingEvent + } + + return event + } + + /** + Get the event in JSON form. Converts all data in the event into a form the API would use + */ + public func json() -> JSON { + if let dict = self.dict { + return JSON(dict) + } + + if let event = VotingEvent(event: self) { + return event.json() + } + + let dict: [String: Any?] = [ + "name": self.name_in, + "startTime": DALIEvent.dateFormatter().string(from: self.start_in), + "endTime": DALIEvent.dateFormatter().string(from: self.end_in), + "description": self.description, + "location": self.location, + "id": self.id, + "votingEnabled": false, + "googleID": self.googleID, + ] + + return JSON(dict) + } + + // MARK: Static Get Methods + + /** + Pulls __all__ the events from the server + + - parameter callback: Function called when done + - parameter events: The events returned by the API + - parameter error: The error encountered (if any) + */ + public static func getAll() -> Future<[DALIEvent]> { + return ServerCommunicator.get(url: "\(DALIapi.config.serverURL)/api/events").onSuccess { (response) -> [DALIEvent] in + guard let array = response.json?.array else { + throw DALIError.General.UnexpectedResponse + } + + return array.compactMap({ (json) -> DALIEvent? in + return DALIEvent.parse(json) + }) + } + } + + /// The socket used to get event updates + internal static var updatesSocket: SocketIOClient! + /// The callbacks for each event type + internal static var updatesCallbacks: [String: ([DALIEvent]?, DALIError.General?) -> Void] = [:] + /// Makes sure the socket is open and waiting for updates + internal static func assertUpdatesSocket() { + if updatesSocket == nil { + + updatesSocket = DALIapi.socketManager.socket(forNamespace: "/eventsReloads") + + updatesSocket.onAny({ (event) in + if let callback = updatesCallbacks[event.event] { + guard let arr = event.items?[0] as? [[String: Any]] else { + DispatchQueue.main.async { + callback(nil, DALIError.General.UnexpectedResponse) + } + return + } + + var events: [DALIEvent] = [] + for obj in arr { + if let event = DALIEvent.parse(JSON(obj)) { + events.append(event) + } + } + + DispatchQueue.main.async { + callback(events, nil) + } + } + }) + + updatesSocket.connect() + updatesSocket.on(clientEvent: .connect, callback: { (data, ack) in + ServerCommunicator.authenticateSocket(socket: updatesSocket) + }) + } + } + + /** + Observes all events. Will call callback every time something changes + + - parameter callback: The function called when the updates occur + - parameter events: The updated events + - parameter error: The error, if any, encountered + */ + public static func observeAll(block: @escaping (_ events: [DALIEvent]?, _ error: Error?) -> Void) -> Observation { + assertUpdatesSocket() + updatesCallbacks["allEvents"] = block + + getAll().onSuccess { (events) in + block(events, nil) + }.onFail { (error) in + block(nil, error) + } + + return Observation(stop: { + removeCallback(forKey: "allEvents") + }, id: "allEventsOberver") + } + + /** + Observes all upcoming events. Will call callback every time something changes + + - parameter callback: The function to call when done + - parameter events: The events + - parameter error: The error, if any, encountered + */ + public static func observeUpcoming(callback: @escaping (_ events: [DALIEvent]?, _ error: Error?) -> Void) -> Observation { + assertUpdatesSocket() + updatesCallbacks["weekEvents"] = callback + + getUpcoming().onSuccess { (events) in + callback(events, nil) + }.onFail { (error) in + callback(nil, error) + } + + return Observation(stop: { + removeCallback(forKey: "weekEvents") + }, id: "weekEventsOberver") + } + + /// Cancels the callback for that key + internal static func removeCallback(forKey key: String) { + updatesCallbacks.removeValue(forKey: key) + + if updatesCallbacks.keys.count == 0 && updatesSocket != nil { + if updatesSocket.status != .disconnected { + updatesSocket.disconnect() + } + updatesSocket = nil + } + } + + /** + Observe future events + + - parameter includeHidden: Include events marked as hidden (admin only) + - parameter callback: The function to call when update happens + - parameter events: The updated events + - parameter error: The error, if any, encountered + */ + public static func observeFuture(includeHidden: Bool = false, callback: @escaping (_ events: [DALIEvent]?, _ error: Error?) -> Void) -> Observation { + assertUpdatesSocket() + updatesCallbacks["futureEvents" + (includeHidden && (DALIapi.config.member?.isAdmin ?? false) ? "Hidden" : "")] = callback + + getFuture(includeHidden: includeHidden && (DALIapi.config.member?.isAdmin ?? false)).onSuccess { (events) in + callback(events, nil) + }.onFail { (error) in + callback(nil, error) + } + + return Observation(stop: { + removeCallback(forKey: "futureEvents" + (includeHidden && (DALIapi.config.member?.isAdmin ?? false) ? "Hidden" : "")) + }, id: "futureEvents" + (includeHidden && (DALIapi.config.member?.isAdmin ?? false) ? "Hidden" : "") + "Oberver") + } + + /** + Observes public events. + + - parameter callback: The function called when the data is updated + - parameter events: The events that have been updated + - parameter error: The error, if any, encountered + */ + public static func observePublicUpcoming(callback: @escaping (_ events: [DALIEvent]?, _ error: Error?) -> Void) -> Observation { + assertUpdatesSocket() + updatesCallbacks["publicEvents"] = callback + + getPublicUpcoming().onSuccess { (events) in + callback(events, nil) + }.onFail { (error) in + callback(nil, error) + } + + return Observation(stop: { + removeCallback(forKey: "publicEvents") + }, id: "publicEventsOberver") + } + + /** + Gets all upcoming events within a week from now + + - parameter callback: Function called when done + - parameter events: The events returned by the API + - parameter error: The error encountered (if any) + */ + public static func getUpcoming() -> Future<[DALIEvent]> { + return ServerCommunicator.get(url: "\(DALIapi.config.serverURL)/api/events/week").onSuccess { (response) -> [DALIEvent] in + guard let array = response.json?.array else { + throw response.assertedError + } + return array.compactMap({ (json) -> DALIEvent? in + return DALIEvent.parse(json) + }) + } + } + + /** + Gets all upcoming events within a week from now that are public + No authorization is needed for this route + + - parameter callback: Function called when done + - parameter events: The events returned by the API + - parameter error: The error encountered (if any) + */ + public static func getPublicUpcoming() -> Future<[DALIEvent]> { + return ServerCommunicator.get(url: "\(DALIapi.config.serverURL)/api/events/public/week").onSuccess { (response) -> [DALIEvent] in + guard let array = response.json?.array else { + throw response.assertedError + } + return array.compactMap({ (json) -> DALIEvent? in + return DALIEvent.parse(json) + }) + } + } + + /** + Gets all events in the future + + - parameter includeHidden: Include events that have been marked hidden (admin only) + - parameter callback: Function called when done + - parameter events: The events returned by the API + - parameter error: The error encountered (if any) + */ + public static func getFuture(includeHidden: Bool = false) -> Future<[DALIEvent]> { + var params = [String:String]() + if includeHidden && (DALIapi.config.member?.isAdmin ?? false) { + params["hidden"] = "true" + } + + return ServerCommunicator.get(url: "\(DALIapi.config.serverURL)/api/events/future", params: params).onSuccess { (response) -> [DALIEvent] in + guard let array = response.json?.array else { + throw response.assertedError + } + return array.compactMap({ (json) -> DALIEvent? in + return DALIEvent.parse(json) + }) + } + } + + // MARK: Voting Conversion Methods + + /** + Enable voting on this event + + ![Admin only](http://icons.iconarchive.com/icons/graphicloads/flat-finance/64/lock-icon.png) + + - parameter numSelected: Number of options the user should select + - parameter ordered: The choices the user makes should be ordered (1st, 2nd, 3rd, ...) + - parameter callback: Function to call when done + - parameter success: Flag to indicate that the event has been properly enabled for voting + - parameter event: The new VotingEvent, if it was successful + - parameter error: The error encountered if it was not successful + */ + public func enableVoting(numSelected: Int, ordered: Bool) -> Future { + guard DALIapi.config.member?.isAdmin ?? false else { + return Future(fail: DALIError.General.Unauthorized) + } + guard let id = self.id else { + return Future(fail: DALIError.General.BadRequest) + } + + let config = VotingEvent.Config(numSelected: numSelected, ordered: ordered) + let url = "\(DALIapi.config.serverURL)/api/voting/admin/\(id)/enable" + + do { + return try ServerCommunicator.post(url: url, json: config.json()).onSuccess(block: { (response) -> VotingEvent in + if response.success { + return VotingEvent(event: self, votingConfig: config, options: nil, resultsReleased: false) + } else { + throw response.assertedError + } + }) + } catch { + return Future(fail: error) + } + } + + // MARK: Check In Methods + + /** + Checks in the current user to whatever event is happening now + + - parameter major: The major value of the bluetooth beacon + - parameter minor: The minor value of the beacon + - parameter callback: Called when done + - parameter success: The operation was a success + - parameter error: The error, if any, encountered + */ + public static func checkIn(major: Int, minor: Int) -> Future { + DALIapi.assertUser(funcName: "checkIn") + let data = ["major": major, "minor": minor] + + do { + return try ServerCommunicator.post(url: "\(DALIapi.config.serverURL)/api/events/checkin", json: JSON(data)).onSuccess(block: { (response) in + if !response.success { + throw response.assertedError + } + }) + } catch { + return Future(fail: error) + } + } + + /** + Enables checkin on the event, and gets back major and minor values to be used when advertizing + + - parameter callback: Called when done + - parameter success: The operation was a success + - parameter major: The major value of the bluetooth beacon + - parameter minor: The minor value of the beacon + - parameter error: The error, if any, encountered + */ + public func enableCheckin() -> Future<(major: Int?, minor: Int?)> { + guard let id = self.id else { + return Future(fail: DALIError.General.BadRequest) + } + + return ServerCommunicator.post(url: "\(DALIapi.config.serverURL)/api/events/\(id)/checkin", data: "".data(using: .utf8)!).onSuccess { (response) -> (Int?, Int?) in + if !response.success { + throw response.assertedError + } + + let dict = response.json?.dictionary + let major: Int? = dict?["major"]?.int + let minor: Int? = dict?["minor"]?.int + return (major, minor) + } + } + + /** + Gets a list of members who have checked in + + - parameter callback: Called when done + - parameter members: The members who have been checked in to the event + - parameter error: The error, if any, encountered + */ + public func getMembersCheckedIn() -> Future<[DALIMember]> { + guard let id = self.id else { + return Future(fail: DALIError.General.BadRequest) + } + + return ServerCommunicator.get(url: "\(DALIapi.config.serverURL)/api/events/\(id)/checkin").onSuccess { (response) -> [DALIMember] in + guard let array = response.json?.array else { + throw response.assertedError + } + + return array.compactMap({ (json) -> DALIMember? in + return DALIMember(json: json) + }) + } + } + + internal var checkinSocket: SocketIOClient? + + /** + Observe the list of members whom have checked in + + - parameter callback: Called when complete + - parameter memebers: The members who have checked in + */ + public func observeMembersCheckedIn(callback: @escaping (_ members: [DALIMember]) -> Void) -> Observation { + if checkinSocket == nil { + self.checkinSocket = DALIapi.socketManager.socket(forNamespace: "/listCheckins") + + let checkinSocket = self.checkinSocket! + + checkinSocket.on(clientEvent: .connect, callback: { (data, ack) in + ServerCommunicator.authenticateSocket(socket: checkinSocket) + }) + + checkinSocket.on("authed", callback: { (data, ack) in + checkinSocket.emit("eventSelect", self.id!) + }) + + checkinSocket.connect() + } + + self.checkinSocket!.on("members", callback: { (data, ack) in + guard let array = data[0] as? [Any] else { + DispatchQueue.main.async { + callback([]) + } + return + } + + var members: [DALIMember] = [] + for memberObj in array { + if let member = DALIMember(json:JSON(memberObj)) { + members.append(member) + } + } + + DispatchQueue.main.async { + DispatchQueue.main.async { + callback(members) + } + } + }) + + return Observation(stop: { + if self.checkinSocket?.status != .disconnected { + self.checkinSocket?.disconnect() + } + self.checkinSocket = nil + }, id: "checkInMembers:\(self.id!)") + } + + internal static func dateFormatter() -> DateFormatter { + let formatter = DateFormatter() + formatter.calendar = Calendar(identifier: .iso8601) + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(secondsFromGMT: 0) + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX" + return formatter + } +} diff --git a/DALI/DALIFood.swift b/DALI/DALIFood.swift new file mode 100644 index 0000000..3f92a86 --- /dev/null +++ b/DALI/DALIFood.swift @@ -0,0 +1,113 @@ +// +// DALIFood.swift +// Pods +// +// Created by John Kotz on 9/6/17. +// +// + +import Foundation +import SwiftyJSON +import SocketIO +import FutureKit + +/** +An interface for getting and setting information about food in the lab +*/ +public class DALIFood { + /// The most recently gathered information on food + public static var current: String? + + /** + Gets the current food for the night + + - parameter callback: The function called when the data has been received + - parameter food: The food tonight + */ + public static func getFood() -> Future { + return ServerCommunicator.get(url: "\(DALIapi.config.serverURL)/api/food").onSuccess { (response) -> String? in + if let error = response.error ?? response.generalError ?? response.jsonError { + throw error + } + return response.json?.string + } + } + + /// The socket to be used for observing + internal static var socket: SocketIOClient? + + /** + Observe the current listing of food + + - parameter callback: Called when complete + - parameter food: The food listed for tonight, if any + */ + public static func observeFood(callback: @escaping (_ food: String?) -> Void) -> Observation { + if socket == nil { + socket = DALIapi.socketManager.socket(forNamespace: "/food") + + socket!.connect() + socket!.on(clientEvent: .connect, callback: { (data, ack) in + ServerCommunicator.authenticateSocket(socket: socket!) + }) + } + + socket!.on("foodUpdate", callback: { (data, ack) in + DispatchQueue.main.async { + callback(data[0] as? String) + } + }) + + return Observation(stop: { + if socket?.status != .disconnected { + socket?.disconnect() + } + socket = nil + }, id: "foodSocket") + } + + /** + Sets the food listing for the night + + ![Admin only](http://icons.iconarchive.com/icons/graphicloads/flat-finance/64/lock-icon.png) + + - parameter food: The food to set the listing to + - parameter callback: Called when complete + - parameter success: Was successful + */ + public static func setFood(food: String) -> Future { + if !(DALIMember.current?.isAdmin ?? false) { + return Future(fail: DALIError.General.Unauthorized) + } + + return ServerCommunicator.post(url: "\(DALIapi.config.serverURL)/api/food", json: JSON(["food": food])).onSuccess(block: { (response) in + if !response.success { + throw response.assertedError + } + }) + } + + /** + Cancels the food listing for tonight + + ![Admin only](http://icons.iconarchive.com/icons/graphicloads/flat-finance/64/lock-icon.png) + + - parameter callback: Called when complete + - parameter success: Was successful + */ + public static func cancelFood() -> Future { + guard DALIMember.current?.isAdmin ?? false else { + return Future(fail: DALIError.General.Unauthorized) + } + + do { + return try ServerCommunicator.post(url: "\(DALIapi.config.serverURL)/api/food", json: JSON([:])).onSuccess(block: { (response) in + if !response.success { + throw response.assertedError + } + }) + } catch { + return Future(fail: error) + } + } +} diff --git a/DALI/DALILights.swift b/DALI/DALILights.swift new file mode 100644 index 0000000..30b4dcf --- /dev/null +++ b/DALI/DALILights.swift @@ -0,0 +1,292 @@ +// +// DALILights.swift +// Pods +// +// Created by John Kotz on 9/17/17. +// +// + +import Foundation +import SwiftyJSON +import SocketIO +import FutureKit + +/** +A class for controlling the lights in the lab +*/ +public class DALILights { + private static var scenesMap: [String:[String]] = [:] + private static var scenesAvgColorMap: [String:[String:String]] = [:] + + /** + A group of lights. This is defined as one of the rooms in DALI, each one a grouping of lights that can be controlled + */ + public struct Group { + /// The name of the group + public let name: String + /// The formatted name of the group for printing + public var formattedName: String { + if name == "tvspace" { + return "TV Space" + } else { + return name.lowercased().replacingOccurrences(of: "pod:", with: "").capitalized + } + } + /// The name the currently set scene + public let scene: String? + /// The formated scene name for printing + public var formattedScene: String? { + return scene?.capitalized + } + /// The current color set for the group + public let color: String? + + /// An average current color. Used for displaying state via color overlay + public var avgColor: String? { + if let color = color { + return color + }else if let scene = scene { + return DALILights.scenesAvgColorMap[self.name]?[scene] + } else { + return nil + } + } + + /// Boolean of current power status + public let isOn: Bool + /// The scenes available for this group + public var scenes: [String] { + if name == "all" { + var allSet: Set? + for entry in scenesMap { + var set = Set() + for scene in entry.value { + set.insert(scene) + } + if allSet != nil { + allSet = allSet!.intersection(set) + } else { + allSet = set + } + } + + return Array(allSet!).sorted(by: { (string1, string2) -> Bool in + return string1 == "default" || string1 < string2 + }) + }else if name == "pods" { + var podsSet: Set? + for entry in scenesMap { + if entry.key.contains("pod") { + var set = Set() + for scene in entry.value { + set.insert(scene) + } + if podsSet != nil { + podsSet = podsSet!.intersection(set) + } else { + podsSet = set + } + } + } + + return Array(podsSet!).sorted(by: { (string1, string2) -> Bool in + return string1 == "default" || string1 < string2 + }) + } + + if let scenes = DALILights.scenesMap[name] { + return scenes.sorted(by: { (string1, string2) -> Bool in + return string1 == "default" || string1 < string2 + }) + } else { + return [] + } + } + + /// Initialize the group + internal init(name: String, scene: String?, color: String?, isOn: Bool) { + self.name = name + self.scene = scene + self.color = color + self.isOn = isOn + } + + /** + Set the scene of the group + + - parameter scene: The scene to set the lights to + - parameter callback: Called when done + - parameter success: Was successful + - parameter error: The error, if any, encountered + */ + public func set(scene: String) -> Future { + return setValue(value: scene) + } + + /// Used to set the value of the lights. The API uses strings to idenitify actions to take on the lights + internal func setValue(value: String) -> Future { + do { + return try ServerCommunicator.post(url: "\(DALIapi.config.serverURL)/api/lights/\(name)", json: JSON(["value":value])).onSuccess(block: { (response) in + if !response.success { + throw response.assertedError + } + }) + } catch { + return Future(fail: error) + } + } + + /** + Set the color of the group + + - parameter color: The color to set the lights + - parameter callback: Called when done + - parameter success: Was successful + - parameter error: The error, if any, encountered + */ + public func set(color: String) -> Future { + return setValue(value: color) + } + + /** + Set the power + + - parameter on: Boolean = the power is on + - parameter callback: Called when done + - parameter success: Was successful + - parameter error: The error, if any, encountered + */ + public func set(on: Bool) -> Future { + return setValue(value: on ? "on" : "off") + } + + /// All the groups + public static internal(set) var all = Group(name: "all", scene: nil, color: nil, isOn: false) + /// The pod groups + public static internal(set) var pods = Group(name: "pods", scene: nil, color: nil, isOn: false) + } + + internal static var updatingSocket: SocketIOClient! + + /** + Observe all the groups + + - parameter callback: Called when done + - parameter groups: The updated groups + */ + public static func oberserveAll(callback: @escaping (_ groups: [Group]) -> Void) -> Observation { + if updatingSocket == nil { + updatingSocket = DALIapi.socketManager.socket(forNamespace: "/lights") + + updatingSocket.connect() + + updatingSocket.on(clientEvent: .connect, callback: { (data, ack) in + guard let updatingSocket = updatingSocket else { + return + } + ServerCommunicator.authenticateSocket(socket: updatingSocket) + }) + } + + updatingSocket.on("state", callback: { (data, ack) in + guard let dict = data[0] as? [String: Any] else { + return + } + + guard let hueDict = dict["hue"] as? [String: Any] else { + return + } + + var groups: [Group] = [] + var allOn = true + var podsOn = true + var allScene: String? + var noAllScene = false + var allColor: String? + var noAllColor = false + var podsScene: String? + var noPodsScene = false + var podsColor: String? + var noPodsColor = false + + for entry in hueDict { + let name = entry.key + if let dict = entry.value as? [String:Any], let isOn = dict["isOn"] as? Bool { + let color = dict["color"] as? String + let scene = dict["scene"] as? String + allOn = allOn && isOn + if name.contains("pod") { + podsOn = isOn + + if podsScene == nil { + podsScene = scene + }else if podsScene != scene { + noPodsScene = true + } + + if podsColor == nil { + podsColor = color + }else if podsColor != color { + noPodsColor = true + } + } + + if allScene == nil { + allScene = scene + }else if allScene != scene { + noAllScene = true + } + + if allColor == nil { + allColor = color + }else if allColor != color { + noAllColor = true + } + + groups.append(Group(name: name, scene: scene, color: color, isOn: isOn)) + } + } + + Group.all = Group(name: "all", scene: noAllScene ? nil : allScene, color: noAllColor ? nil : allColor, isOn: allOn) + Group.pods = Group(name: "pods", scene: noPodsScene ? nil : podsScene, color: noPodsColor ? nil : podsColor, isOn: podsOn) + + DispatchQueue.main.async { + callback(groups) + } + }) + + _ = ServerCommunicator.get(url: "\(DALIapi.config.serverURL)/api/lights/scenes").onSuccess { (response) in + guard let dict = response.json?.dictionary else { + return + } + + var map = [String:[String]]() + var colorMap = [String:[String:String]]() + + for entry in dict { + colorMap[entry.key] = [:] + if let value = entry.value.array { + var array: [String] = [] + for scene in value { + if let sceneDict = scene.dictionary, let scene = sceneDict["name"]?.string { + + array.append(scene) + colorMap[entry.key]![scene] = sceneDict["averageColor"]?.string + } + } + + map[entry.key] = array + } + } + + DALILights.scenesAvgColorMap = colorMap + DALILights.scenesMap = map + } + + return Observation(stop: { + updatingSocket.disconnect() + updatingSocket = nil + }, id: "lights") + } +} + diff --git a/DALI/DALILocation.swift b/DALI/DALILocation.swift new file mode 100644 index 0000000..2404c6d --- /dev/null +++ b/DALI/DALILocation.swift @@ -0,0 +1,316 @@ +// +// DALILocation.swift +// DALIapi +// +// Created by John Kotz on 7/31/17. +// Copyright © 2017 DALI Lab. All rights reserved. +// + +import Foundation +import SwiftyJSON +import SocketIO +import FutureKit + +/** +A static struct that contains all location updates and queries + +## Example: + + DALILocation.Tim.get { (tim, error) in + // ... + if tim.inDALI { + // ... + } else if tim.inOffice { + // ... + } + } +*/ +public class DALILocation { + internal static var sharedCallback: (([DALIMember]?, DALIError.General?) -> Void)? + internal static var lastSharedData: [DALIMember]? + internal static var enterCallback: ((DALIMember) -> Void)? + internal static var timCallback: ((Tim?, DALIError.General?) -> Void)? + internal static var lastTimData: Tim? + internal static var updatingSocket: SocketIOClient! + internal static func assertSocket() { + if updatingSocket == nil { + updatingSocket = DALIapi.socketManager.socket(forNamespace: "/location") + + updatingSocket.on("shared", callback: { (data, ack) in + guard let arr = data[0] as? [Any] else { + if let sharedCallback = sharedCallback { + DispatchQueue.main.async { + sharedCallback(nil, DALIError.General.UnexpectedResponse) + } + } + return + } + + var outputArr: [DALIMember] = [] + for obj in arr { + guard let dict = obj as? [String: Any], let user = dict["user"], let member = DALIMember(json:JSON(user)) else { + if let sharedCallback = sharedCallback { + DispatchQueue.main.async { + sharedCallback(nil, DALIError.General.UnexpectedResponse) + } + } + return + } + + outputArr.append(member) + } + self.lastSharedData = outputArr + + if let sharedCallback = sharedCallback { + DispatchQueue.main.async { + sharedCallback(outputArr, nil) + } + } + }) + + updatingSocket.on("memberEnter", callback: { (data, ack) in + guard let user = data[0] as? [String: Any], let member = DALIMember(json:JSON(user)) else { + return + } + + if let enterCallback = enterCallback { + enterCallback(member) + } + }) + + updatingSocket.on("tim", callback: { (data, ack) in + guard let dict = data[0] as? [String: Any], let inDALI = dict["inDALI"] as? Bool, let inOffice = dict["inOffice"] as? Bool else { + if let timCallback = timCallback { + DispatchQueue.main.async { + timCallback(nil, DALIError.General.UnexpectedResponse) + } + } + return + } + + let tim = Tim(inDALI: inDALI, inOffice: inOffice) + Tim.current = tim + + self.lastTimData = tim + + if let timCallback = timCallback { + DispatchQueue.main.async { + timCallback(tim, nil) + } + } + }) + + updatingSocket.connect() + updatingSocket.on(clientEvent: .connect, callback: { (data, ack) in + ServerCommunicator.authenticateSocket(socket: updatingSocket) + }) + } + } + + /** + A simple struct that holds booleans that indicate Tim's location. Use it wisely 😉 + */ + public struct Tim { + /// The most recent information on tim's location + public internal(set) static var current: Tim? + + /// Tim is in DALI + public private(set) var inDALI: Bool + /// Tim in in his office + public private(set) var inOffice: Bool + + /** + Gets the current data on Tim's Location and returns it. + + - parameter callback: Function to be called when the request is complete + + ## Example: + + DALILocation.Tim.get { (tim, error) in + if tim.inDALI { + // ... + } else if tim.inOffice { + // ... + } + } + */ + public static func get() -> Future { + return ServerCommunicator.get(url: "\(DALIapi.config.serverURL)/api/location/tim").onSuccess { (response) -> Tim in + guard let dict = response.json?.dictionary, + let inDALI = dict["inDALI"]?.bool, + let inOffice = dict["inOffice"]?.bool else { + throw response.assertedError + } + let tim = Tim(inDALI: inDALI, inOffice: inOffice) + self.current = tim + return tim + } + } + + /** + Observe tim's location + + - parameter callback: Function called when an update on tim's location is received + - parameter tim: Tim's updated location + - parameter error: The error, if any, encountered + */ + public static func observe(callback: @escaping (_ tim: Tim?, _ error: DALIError.General?) -> Void) -> Observation { + DALILocation.assertSocket() + DALILocation.timCallback = callback + + if let timData = DALILocation.lastTimData { + callback(timData, nil) + } + + return Observation(stop: { + DALILocation.timCallback = nil + if DALILocation.sharedCallback == nil && DALILocation.enterCallback == nil && DALILocation.updatingSocket != nil { + if DALILocation.updatingSocket.status != .disconnected { + DALILocation.updatingSocket.disconnect() + } + DALILocation.updatingSocket = nil + } + }, id: "timObserver") + } + + /** + Submit information about tim's location. Will generate an error if user is not tim + - important: If you call this without a user will `fatalerror` + + - parameter inDALI: Tim is in DALI + - parameter inOffice: Tim is in his office + - parameter callback: Function called apon completion + */ + public static func submit(inDALI: Bool, inOffice: Bool) -> Future { + DALIapi.assertUser(funcName: "DALILocation.Tim.submit") + + let dict: [String: Any] = [ + "inDALI": inDALI, + "inOffice": inOffice + ] + + do { + return try ServerCommunicator.post(url: "\(DALIapi.config.serverURL)/api/location/tim", json: JSON(dict)).onSuccess(block: { (response) in + if !response.success { + throw response.assertedError + } + }) + } catch { + return Future(fail: error) + } + } + } + + /** + A simple struct that handles getting a list of shared user + */ + public struct Shared { + /** + Get a list of all the people in the lab who are sharing their location + + - parameter callback: Function called apon completion + */ + public static func get() -> Future<[DALIMember]> { + return ServerCommunicator.get(url: "\(DALIapi.config.serverURL)/api/location/shared").onSuccess { (response) -> [DALIMember] in + guard let array = response.json?.array else { + throw DALIError.General.UnexpectedResponse + } + + let outputArr: [DALIMember] = array.compactMap({ (json) -> DALIMember? in + guard let dict = json.dictionary, + let user = dict["user"] else { + return nil + } + return DALIMember(json: user) + }) + return outputArr + } + } + + /** + Observe the shared locations + + - parameter callback: The function called when an update is available + - parameter members: The update members listed in the lab + - parameter error: The error, if any, encountered + */ + public static func observe(callback: @escaping (_ members: [DALIMember]?, _ error: DALIError.General?) -> Void) -> Observation { + DALILocation.assertSocket() + DALILocation.sharedCallback = callback + + if let sharedData = DALILocation.lastSharedData { + callback(sharedData, nil) + } + + return Observation(stop: { + DALILocation.sharedCallback = nil + if DALILocation.timCallback == nil && DALILocation.enterCallback == nil && DALILocation.updatingSocket != nil { + if DALILocation.updatingSocket.status != .disconnected { + DALILocation.updatingSocket.disconnect() + } + DALILocation.updatingSocket = nil + } + }, id: "sharedObserver") + } + + /** + Submit the current location of the user + - important: Do not run this on an API authenticated program. It will fatal error to protect the server! + + - parameter inDALI: The user is in DALI + - parameter entering: The user is entering DALI + - parameter callback: Function that is called when done + */ + public static func submit(inDALI: Bool, entering: Bool) -> Future { + DALIapi.assertUser(funcName: "DALILocation.submit") + + let dict: [String: Any] = [ + "inDALI": inDALI, + "entering": entering, + "sharing": DALILocation.sharing + ] + + do { + return try ServerCommunicator.post(url: "\(DALIapi.config.serverURL)/api/location/shared", json: JSON(dict)).onSuccess(block: { (response) in + if !response.success { + throw response.assertedError + } + }) + } catch { + return Future(fail: error) + } + } + } + + /** + Observe members entering the lab. Will call callback everytime someone is tracked as entering the lab + + - parameter callback: Called when someone enters the lab + - parameter member: The member who entered the lab + */ + public static func observeMemberEnter(callback: @escaping (_ member: DALIMember) -> Void) -> Observation { + DALILocation.assertSocket() + DALILocation.enterCallback = callback + + return Observation(stop: { + DALILocation.enterCallback = nil + if DALILocation.timCallback == nil && DALILocation.sharedCallback == nil && DALILocation.updatingSocket != nil { + if DALILocation.updatingSocket.status != .disconnected { + DALILocation.updatingSocket.disconnect() + } + DALILocation.updatingSocket = nil + } + }, id: "enterObserver") + } + + /// The current user is sharing this device's location + public static var sharing: Bool { + get { + return UserDefaults.standard.value(forKey: "DALIapi:sharing") as? Bool ?? DALIapi.config.sharingDefault + } + set { + UserDefaults.standard.set(newValue, forKey: "DALIapi:sharing") + _ = try? ServerCommunicator.post(url: "\(DALIapi.config.serverURL)/api/location/shared/updatePreference", json: JSON(["sharing": newValue])) + } + } +} diff --git a/DALI/DALIMember.swift b/DALI/DALIMember.swift new file mode 100644 index 0000000..b159ebe --- /dev/null +++ b/DALI/DALIMember.swift @@ -0,0 +1,112 @@ +// +// DALIUser.swift +// DALIapi +// +// Created by John Kotz on 7/29/17. +// Copyright © 2017 DALI Lab. All rights reserved. +// + +import Foundation +import CoreLocation +import SwiftyJSON +import FutureKit + +/** +A member of DALI + +The user object contains as much data as is allowed to a general client by the api + */ +public class DALIMember: Equatable { + // MARK: - Properties + /// The current member + public static var current: DALIMember? { + return DALIapi.config.member + } + + internal var json: JSON + + /// User's full name (eg. John Kotz) + public var name: String + /// User's entered gender + public var gender: String? + /// User's email address + public var email: String + /// URL to the user's photo + public var photoURL: String + /// URL to the user's website + public var website: String? + /// URL to the user's linkedin + public var linkedin: String? + /// User's greeting + public var greeting: String? + /// User's Github username + public var githubUsername: String? + /// URL to user's cover photo + public var coverPhoto: String? + /// URL to user's goolge photo + public var googlePhotoURL: String + /// User's chosen origin location (data used by mappy) + public var location: CLLocation? + /// User's job title + public var jobTitle: String? + /// A list of skills the user has listed for themselves + public var skills: [String]? + + /// The user is an admin + public private(set) var isAdmin: Bool = false + + /// The identifier used by the server + public var id: String + + // MARK: - Functions + + init?(json: JSON) { + guard let dict = json.dictionary else { + return nil + } + + guard let name = dict["fullName"]?.string, + let email = dict["email"]?.string, + let photoURL = dict["photoUrl"]?.string, + let googlePhotoURL = dict["googlePhotoUrl"]?.string else { + return nil + } + + guard let id = dict["id"]?.string else { + return nil + } + + if let location = dict["location"]?.arrayObject as? [Double] { + self.location = CLLocation.init(latitude: location[0], longitude: location[1]) + } + self.json = json + self.name = name + self.gender = dict["gender"]?.string + self.email = email + self.photoURL = photoURL + self.website = dict["website"]?.string + self.linkedin = dict["linkedin"]?.string + self.greeting = dict["greeting"]?.string + self.githubUsername = dict["githubUsername"]?.string + self.coverPhoto = dict["coverPhoto"]?.string + self.googlePhotoURL = googlePhotoURL + self.jobTitle = dict["jobTitle"]?.string + self.skills = dict["skills"]?.arrayObject as? [String] + self.isAdmin = dict["isAdmin"]?.bool ?? false + self.id = id + } + + public static func == (lhs: DALIMember, rhs: DALIMember) -> Bool { + return lhs.id == rhs.id + } + + static func get(id: String) -> Future { + return ServerCommunicator.get(url: "\(DALIapi.config.serverURL)/api/users/\(id)").onSuccess(block: { (response) -> DALIMember in + if let json = response.json, let member = DALIMember(json: json) { + return member + } else { + throw response.assertedError + } + }) + } +} diff --git a/DALI/DALIPhoto.swift b/DALI/DALIPhoto.swift new file mode 100644 index 0000000..d4743fe --- /dev/null +++ b/DALI/DALIPhoto.swift @@ -0,0 +1,34 @@ +// +// DALIPhotos.swift +// DALI +// +// Created by John Kotz on 10/13/17. +// + +import Foundation +import FutureKit + +/** +Photo class for getting a list of all photos from the API +*/ +public class DALIPhoto { + + /** + Gets list of photo urls + + - parameter callback: Function called when the data arrives + - parameter photos: The photos that were retrieved + - parameter error: The error, if any, encountered + */ + public static func get() -> Future<[String]> { + return ServerCommunicator.get(url: "\(DALIapi.config.serverURL)/api/photos").onSuccess { (response) -> [String] in + guard let array = response.json?.array else { + throw response.assertedError + } + + return array.compactMap({ (value) -> String? in + return value.string + }) + } + } +} diff --git a/DALI/DALIProject.swift b/DALI/DALIProject.swift new file mode 100644 index 0000000..2558905 --- /dev/null +++ b/DALI/DALIProject.swift @@ -0,0 +1,19 @@ +// +// DALIProject.swift +// DALIapi +// +// Created by John Kotz on 8/4/17. +// Copyright © 2017 DALI Lab. All rights reserved. +// + +import Foundation +import SwiftyJSON + +/** +A simple struct to store information + +## TODO: Write this 😀 +*/ +public struct DALIProject { + +} diff --git a/DALI/DALIapi.swift b/DALI/DALIapi.swift new file mode 100644 index 0000000..8d16415 --- /dev/null +++ b/DALI/DALIapi.swift @@ -0,0 +1,267 @@ +// +// daliAPI.swift +// +// +// Created by John Kotz on 7/23/17. +// +// + +import UIKit +import SwiftyJSON +import SocketIO +import FutureKit + +/** +Static class to configure and handle general requests for the DALI api framework +*/ +public class DALIapi { + private static var unProtConfig: DALIConfig! + /// The current configuration being used by the framework + public static var config: DALIConfig { + if self.unProtConfig == nil { + guard let data = UserDefaults.standard.data(forKey: "DALIapi-config"), + let config = try? JSONDecoder().decode(DALIConfig.self, from: data) else { + fatalError("DALIapi: Config missing! You are required to have a configuration\n" + + "Run:\nlet config = DALIConfig(dict: NSDictionary(contentsOfFile: filePath))\n" + + "DALIapi.configure(config)\n" + + "before you use it") + } + self.unProtConfig = config + } + return unProtConfig! + } + + internal static let socketManager = SocketManager(socketURL: DALIapi.config.serverURLobject) + + /// Defines if the user is signed in + public static var isSignedIn: Bool { + return config.member != nil + } + + /** + A callback reporting either success or failure in the requested action + + - parameter success: Flag indicating success in the action + - parameter error: Error encountered (if any) + */ + public typealias SuccessCallback = (_ success: Bool, _ error: DALIError.General?) -> Void + + /** + Configures the entire framework + + NOTE: Make sure to run this configure method before using anything on the API + */ + public static func configure(config: DALIConfig) { + self.unProtConfig = config + + let encoder = JSONEncoder() + if let encoded = try? encoder.encode(config) { + UserDefaults.standard.set(encoded, forKey: "DALIapi-config") + } + + if config.enableSockets { + enableSockets() + self.socketManager.config = SocketIOClientConfiguration(arrayLiteral: SocketIOClientConfiguration.Element.forceWebsockets(config.forceWebsockets)) + } + } + + /// Enables the use of sockets + public static func enableSockets() { + NotificationCenter.default.addObserver(self, selector: #selector(self.goingForeground), name: UIApplication.didBecomeActiveNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.goingBackground), name: UIApplication.willResignActiveNotification, object: nil) + } + + /// Disables all sockets used by the API + public static func disableSockets() { + + NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil) + NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil) + + if let eventSocket = DALIEvent.updatesSocket, eventSocket.status != .disconnected { + eventSocket.disconnect() + } + if let locationSocket = DALILocation.updatingSocket, locationSocket.status != .disconnected { + locationSocket.disconnect() + } + if let lightsSocket = DALILights.updatingSocket, lightsSocket.status != .disconnected { + lightsSocket.disconnect() + } + if let socket = DALIFood.socket, socket.status != .disconnected { + socket.disconnect() + } + } + + /// Called when switching to background mode, this function will close sockets of autoswitching is enabled + @objc internal static func goingBackground() { + if config.socketAutoSwitching { + if let eventSocket = DALIEvent.updatesSocket, eventSocket.status != .disconnected { + eventSocket.disconnect() + } + if let locationSocket = DALILocation.updatingSocket, locationSocket.status != .disconnected { + locationSocket.disconnect() + } + if let updatingSocket = DALILights.updatingSocket, updatingSocket.status != .disconnected { + updatingSocket.disconnect() + } + if let socket = DALIFood.socket, socket.status != .disconnected { + socket.disconnect() + } + } + } + + /// Called when switching to forground mode, this function will reconnect sockets of autoswitching is enabled + @objc internal static func goingForeground() { + if config.socketAutoSwitching { + if let eventSocket = DALIEvent.updatesSocket, eventSocket.status == .disconnected { + eventSocket.connect() + } + if let locationSocket = DALILocation.updatingSocket, locationSocket.status == .disconnected { + locationSocket.connect() + } + if let updatingSocket = DALILights.updatingSocket, updatingSocket.status == .disconnected { + updatingSocket.connect() + } + if let socket = DALIFood.socket, socket.status == .disconnected { + socket.connect() + } + } + } + + /** + Signs in on the server using Google Signin provided server auth code + + - parameter authCode: The authCode provided by Google signin + - parameter done: Function called when signin is complete + - parameter success: The signin completed correctly + - parameter error: The error, if any, encountered + */ + public static func signin(authCode: String) -> Future { + // One way or the other are we already authenticated + if (config.token != nil || config.apiKey != nil) { + return Future(fail: DALIError.General.Unauthorized) + } + + return ServerCommunicator.get(url: "\(config.serverURL)/api/auth/google/callback?code=\(authCode)").onSuccess { (response) -> DALIMember in + guard let json = response.json, + let token = json["token"].string, + let member = DALIMember(json: json["user"]) + else { + throw response.assertedError + } + + self.unProtConfig.token = token + self.unProtConfig.member = member + + return member + } + } + + /** + Signs in on the server using access and refresh tokens provided by Google Signin. Will not sign in if already signed in + + - parameter accessToken: The access token provided by Google signin + - parameter refreshToken: The refresh token from Google siginin + - parameter done: Function called when signin is complete + - parameter success: The signin completed correctly + - parameter error: The error, if any, encountered + */ + public static func signin(accessToken: String, refreshToken: String) -> Future { + return self.signin(accessToken: accessToken, refreshToken: refreshToken, forced: false) + } + + /** + Signs in on the server using access and refresh tokens provided by Google Signin + + - parameter accessToken: The access token provided by Google signin + - parameter refreshToken: The refresh token from Google siginin + - parameter forced: Flag forces signin even if there is already a token avialable + - parameter done: Function called when signin is complete + - parameter success: The signin completed correctly + - parameter error: The error, if any, encountered + */ + public static func signin(accessToken: String, refreshToken: String, forced: Bool) -> Future { + // One way or the other are we already authenticated + if ((config.token != nil || config.apiKey != nil) && !forced) { + return Future(fail: DALIError.General.Unauthorized) + } + + let package = ["access_token": accessToken, "refresh_token": refreshToken] + + do { + return try ServerCommunicator.post(url: "\(config.serverURL)/api/signin", json: JSON(package)).onSuccess(block: { (response) -> DALIMember in + guard let json = response.json?.dictionary, + let token = json["token"]?.string, + let userObj = json["user"], + let member = DALIMember(json: userObj) + else { + throw response.assertedError + } + + self.unProtConfig.token = token + self.unProtConfig.member = member + return member + }) + } catch { + return Future(fail: error) + } + } + + /** + Silently updates the current member object from the server + + - parameter callback: A function to be called when the opperation is complete + - parameter member: The updated memeber object + */ + public static func silentMemberUpdate() -> Future { + return ServerCommunicator.get(url: "\(DALIapi.config.serverURL)/users/me").onSuccess { (response) in + guard let data = response.json, let member = DALIMember(json: data) else { + return nil + } + self.unProtConfig.member = member + return member + } + } + + /// Signs out of your account on the API + public static func signOut() { + config.token = nil + config.member = nil + } + + /** + Sends a notification to EVERY device with the given tag set to true + + - parameter title: The title of the notification + - parameter subtitle: The main message to be sent + - parameter tag: The tag that OneSignal will use to identify recipient devices + - parameter callback: The function that iwll be called when the process is done + - parameter success: The notification was sent correctly + - parameter error: The error, if any, encountered + */ + public static func sendSimpleNotification(with title: String, and subtitle: String, to tag: String) -> Future { + let dict: [String: Any] = [ + "title": title, + "subtitle": subtitle, + "tag": tag + ] + + do { + return try ServerCommunicator.post(url: "\(DALIapi.config.serverURL)/api/notify", json: JSON(dict)).onSuccess(block: { (response) in + if !response.success { + throw response.assertedError + } + }) + } catch { + return Future(fail: error) + } + } + + /// Asserts that a member is signed in + internal static func assertUser(funcName: String) { + if (DALIapi.config.member == nil) { + fatalError("API key programs may not modify location records!" + + " Don't use \(funcName) if you configure with an API key." + + " If you are getting this error and you do not configure using an API key, consult John Kotz") + } + } +} diff --git a/DALI/Enumerations.swift b/DALI/Enumerations.swift new file mode 100644 index 0000000..ca8c4f8 --- /dev/null +++ b/DALI/Enumerations.swift @@ -0,0 +1,56 @@ +// +// Enumerations.swift +// Pods +// +// Created by John Kotz on 9/5/17. +// +// + +import Foundation +import EmitterKit + +extension Notification.Name { + enum Custom { + static let SocketsDisabled = Notification.Name(rawValue: "SocketsDisabled") + static let SocketsEnabled = Notification.Name(rawValue: "SocketsEnabled") + } +} + +/** +An object that allows the user to control socket observations. + +You receive an object of this class when you observe some data. + You may use this object to close the observation when you are done. + The observation will automatically be closed when the app terminates, + and the socket will be temporarily suspended when the app goes into the background. +*/ +public struct Observation { + /// A function to cancel an observation + public let stop: () -> Void + /// An identifier of the observation + public let id: String + /// The listener this represents + internal let listener: Listener? + /// The block that will restart the observation (if possible) + internal let restartBlock: (() -> Bool)? + + init(stop: @escaping () -> Void, id: String = "", listener: Listener? = nil, restartBlock: (() -> Bool)? = nil) { + self.stop = stop + self.id = id + self.listener = listener + self.restartBlock = restartBlock + } + + /** + Restart the observation if possible + + - note: Not all classes that use Observation support restarting + + - returns: Whether restart was successful + */ + func restart() -> Bool { + let result = restartBlock?() + return restartBlock != nil && result! + } +} + diff --git a/DALI/Info.plist b/DALI/Info.plist new file mode 100644 index 0000000..e1fe4cf --- /dev/null +++ b/DALI/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/DALI/ServerCommunicator.swift b/DALI/ServerCommunicator.swift new file mode 100644 index 0000000..1e6fc2a --- /dev/null +++ b/DALI/ServerCommunicator.swift @@ -0,0 +1,195 @@ +// +// ServerCommunications.swift +// DALIapi +// +// Created by John Kotz on 7/28/17. +// Copyright © 2017 DALI Lab. All rights reserved. +// + +import Foundation +import SwiftyJSON +import SocketIO +import FutureKit + +class ServerCommunicator { + private static var config: DALIConfig { + return DALIapi.config + } + + static func authenticateSocket(socket: SocketIOClient) { + socket.emit("authenticate", with: [(config.token ?? config.apiKey) as Any]) + } + + // MARK : POST and GET methods + // =========================== + + /** + Makes a GET request on a given url, calling the callback with the response JSON object when its done + + - parameter url: String - The URL you wan to GET from + - parameter callback: (response: Any)->Void - The callback that will be invoked when the task is done + - parameter response: The data response from the server + - parameter code: The code for the response + - parameter error: The error encountered (if any) + */ + static func get(url: String) -> Future { + return get(url: url, params: nil) + } + + static func get(url: String, params: [String:String]?) -> Future { + return doDataRequest(url: url, httpMethod: "GET", params: params, data: nil) + } + + static func delete(url: String, json: JSON) -> Future { + do { + return delete(url: url, data: try json.rawData()) + } catch { + return Future(fail: error) + } + } + + static func delete(url: String, data: Data) -> Future { + return doDataRequest(url: url, httpMethod: "DELETE", params: nil, data: data) + } + + /** + Convenience function for posting JSON data + + - parameter url: The URL you want to post to + - parameter json: A JSON encoded data string to be sent to the server + - parameter callback: A callback that will be invoked when the process is complete + - parameter success: Flag indicating success + - parameter data: The JSON data sent back + - parameter error: The error encountered (if any) + */ + static func post(url: String, json: JSON) -> Future { + do { + return post(url: url, data: try json.rawData()) + } catch { + return Future(fail: error) + } + } + + /** + Makes a POST request to the given url using the given data, using the callback when it is done + + - parameter url: String - The URL you want to post to + - parameter data: Data - A JSON encoded data string to be sent to the server + - parameter callback: A callback that will be invoked when the process is complete + - parameter success: Flag indicating success + - parameter data: The JSON data sent back + - parameter error: The error encountered (if any) + */ + static func post(url: String, data: Data?) -> Future { + return doDataRequest(url: url, httpMethod: "POST", params: nil, data: data) + } + + static func put(url: String, json: JSON) -> Future { + do { + return put(url: url, data: try json.rawData()) + } catch { + return Future(fail: error) + } + } + + static func put(url: String, data: Data?) -> Future { + return doDataRequest(url: url, httpMethod: "PUT", params: nil, data: data) + } + + static func doDataRequest(url: String, httpMethod: String, params: [String:String]?, data: Data?) -> Future { + var urlComps = URLComponents(string: url)! + if let params = params { + urlComps.queryItems = params.keys.map({ (key) -> URLQueryItem in + return URLQueryItem(name: key, value: params[key]) + }) + } + + var request = URLRequest(url: urlComps.url!) + request.httpMethod = httpMethod + request.httpBody = data + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + request.addValue("application/json", forHTTPHeaderField: "Accept") + + if let token = config.token { + request.addValue(token, forHTTPHeaderField: "authorization") + }else if let apiKey = config.apiKey { + request.addValue(apiKey, forHTTPHeaderField: "apiKey") + } + + let promise = Promise() + + URLSession.shared.dataTask(with: request) { data, response, error in + let response = Response(response: response, data: data, error: error) + promise.completeWithSuccess(response) + }.resume() + + return promise.future + } + + struct Response { + let response: URLResponse? + let data: Data? + let error: Error? + + // MARK: - Computed variables + + var success: Bool { + return code == 200 + } + var code: Int? { + return (response as? HTTPURLResponse)?.statusCode + } + + var jsonError: SwiftyJSONError? { + guard let data = data else { + return SwiftyJSONError.notExist + } + + do { + _ = try JSON.init(data: data) + } catch is SwiftyJSONError { + return error as? SwiftyJSONError + } catch {} + return nil + } + + var assertedError: Error { + if error != nil { + return error! + } else if generalError != nil { + return generalError! + } else if jsonError != nil { + return jsonError! + } + return unknownError + } + + var json: JSON? { + guard let data = data else { + return nil + } + return try? JSON.init(data: data) + } + + var generalError: DALIError.General? { + guard let code = code else { + return nil + } + + switch code { + case 200: return nil + case 401: return DALIError.General.Unauthorized + case 403: fatalError("DALIapi: Provided API Key invalid!") + case 422: return DALIError.General.Unprocessable + case 400: return DALIError.General.BadRequest + case 404: return DALIError.General.Unfound + default: return nil + } + } + + var unknownError: DALIError.General { + let text = data == nil ? nil : String(data: data!, encoding: .utf8) + return DALIError.General.UnknownError(error: error, text: text, code: code) + } + } +} diff --git a/DALITests/DALITests.swift b/DALITests/DALITests.swift new file mode 100644 index 0000000..552d5e2 --- /dev/null +++ b/DALITests/DALITests.swift @@ -0,0 +1,34 @@ +// +// DALITests.swift +// DALITests +// +// Created by John Kotz on 7/27/19. +// Copyright © 2019 BrunchLabs. All rights reserved. +// + +import XCTest +@testable import DALI + +class DALITests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/DALITests/Info.plist b/DALITests/Info.plist new file mode 100644 index 0000000..6c40a6c --- /dev/null +++ b/DALITests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Podfile b/Podfile index 2a9a73e..6db4a3e 100644 --- a/Podfile +++ b/Podfile @@ -1,16 +1,24 @@ # Uncomment the next line to define a global platform for your project # platform :ios, '9.0' +def DALIframework_pods + pod 'SwiftyJSON' + pod 'Socket.IO-Client-Swift' + pod 'FutureKit' +end + target 'iOS' do # Comment the next line if you're not using Swift and don't want to use dynamic frameworks use_frameworks! inhibit_all_warnings! + pod 'SwiftyJSON' + pod 'Socket.IO-Client-Swift' + pod 'FutureKit' pod 'Google/SignIn' pod 'SCLAlertView' pod 'Eureka' pod 'Crashlytics' - pod 'DALI', :git => 'https://github.com/dali-lab/DALI-Framework' pod 'Socket.IO-Client-Swift' pod 'ChromaColorPicker' pod 'QRCodeReaderViewController' @@ -35,10 +43,21 @@ target 'iOS' do end end +target 'DALI' do + use_frameworks! + pod 'EmitterKit' + pod 'SwiftyJSON' + pod 'Socket.IO-Client-Swift' + pod 'FutureKit' +end + target 'tvOS' do # Comment the next line if you're not using Swift and don't want to use dynamic frameworks use_frameworks! - pod 'DALI', :git => 'https://github.com/dali-lab/DALI-Framework' + pod 'SwiftyJSON' + pod 'Socket.IO-Client-Swift' + pod 'FutureKit' + pod 'EmitterKit' # Pods for DALI Lab diff --git a/Podfile.lock b/Podfile.lock index def1a0b..ef5f82c 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -3,11 +3,6 @@ PODS: - ChromaColorPicker (1.8.0) - Crashlytics (3.13.4): - Fabric (~> 1.10.2) - - DALI (0.4.5): - - EmitterKit - - FutureKit - - Socket.IO-Client-Swift - - SwiftyJSON - EmitterKit (5.2.2) - Eureka (5.0.0) - Fabric (1.10.2) @@ -55,7 +50,6 @@ DEPENDENCIES: - Alamofire - ChromaColorPicker - Crashlytics - - DALI (from `https://github.com/dali-lab/DALI-Framework`) - EmitterKit - Eureka - FutureKit @@ -67,6 +61,7 @@ DEPENDENCIES: - SCLAlertView - Socket.IO-Client-Swift - SwiftLint + - SwiftyJSON SPEC REPOS: https://github.com/cocoapods/specs.git: @@ -94,16 +89,11 @@ SPEC REPOS: - SwiftyJSON EXTERNAL SOURCES: - DALI: - :git: https://github.com/dali-lab/DALI-Framework RLBAlertsPickers: :branch: master :git: https://github.com/loicgriffie/Alerts-Pickers.git CHECKOUT OPTIONS: - DALI: - :commit: bce045f4f0341a0b6d03580736cbba345c1a1fd9 - :git: https://github.com/dali-lab/DALI-Framework RLBAlertsPickers: :commit: 5ce7637f4c8d5118442f1a9ae781020fa80fccb9 :git: https://github.com/loicgriffie/Alerts-Pickers.git @@ -112,7 +102,6 @@ SPEC CHECKSUMS: Alamofire: ae5c501addb7afdbb13687d7f2f722c78734c2d3 ChromaColorPicker: 2e47f5acb805d53350fb7af1b511bc534b38ee31 Crashlytics: 2dfd686bcb918dc10ee0e76f7f853fe42c7bd552 - DALI: d6adf2a331ce195e5fbee01885ab47e21d20a8b4 EmitterKit: e7a27b37118823d8ad413b8396f75e40d59f8cba Eureka: 0f64f477ec8ef6201c6fec143d60ff5c201cf654 Fabric: 706c8b8098fff96c33c0db69cbf81f9c551d0d74 @@ -127,13 +116,13 @@ SPEC CHECKSUMS: Log: 5e368c9528db07517d18d2d04ff5fe2b6f5a1e21 OneSignal: 0f5ff711d9f25da54885e4ab06ef0abc221a46ef QRCodeReaderViewController: e8f27d035b3e72b1d4b1c61ff66458287e3be0ff - RLBAlertsPickers: 41c85adf62bf535988e31a722c2ff8b13262d9b3 + RLBAlertsPickers: d8dd984ed0a686bd167554eeb4a3af5ae8d82d24 SCLAlertView: 6a77bb2edfc65e04dbe57725546cb4107a506b85 Socket.IO-Client-Swift: 7cb44c0ffb86e158cee32d0642d30ec5fdcf8f61 Starscream: 08172b481e145289c4930cb567230fb55897cfa4 SwiftLint: 79d48a17c6565dc286c37efb8322c7b450f95c67 SwiftyJSON: 36413e04c44ee145039d332b4f4e2d3e8d6c4db7 -PODFILE CHECKSUM: 2665fc6007e0559bdb96047eb20838d6e6042d32 +PODFILE CHECKSUM: 899dd4fcd5d645cebda7d191094f0bfcd9d71e3b -COCOAPODS: 1.7.5 +COCOAPODS: 1.6.1