Merge branch 'master' into bug4478
No files matched your search
@@ -280,19 +280,14 @@
|
||||
7F44095B19F65B5B0021BF22 /* CoreData.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 7F44095919F65B5B0021BF22 /* CoreData.xcdatamodeld */; };
|
||||
7F4D2F541A1C8EE50038E7D1 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7F4D2F531A1C8EE50038E7D1 /* Images.xcassets */; };
|
||||
7F83F15619F154F000C53328 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F83F15519F154F000C53328 /* AppDelegate.swift */; };
|
||||
7F83F15819F154F000C53328 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F83F15719F154F000C53328 /* HomeViewController.swift */; };
|
||||
7F83F16C19F154F000C53328 /* SAPTrackerAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F83F16B19F154F000C53328 /* SAPTrackerAPITests.swift */; };
|
||||
7F83F17619F1574700C53328 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F83F17519F1574700C53328 /* CoreLocation.framework */; };
|
||||
7F87CAA11A2CCE8B0076C617 /* OpenSans-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7F87CA9D1A2CCE8B0076C617 /* OpenSans-Bold.ttf */; };
|
||||
7F87CAA21A2CCE8B0076C617 /* OpenSans-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7F87CA9E1A2CCE8B0076C617 /* OpenSans-Light.ttf */; };
|
||||
7F87CAA31A2CCE8B0076C617 /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7F87CA9F1A2CCE8B0076C617 /* OpenSans-Regular.ttf */; };
|
||||
7F87CAA41A2CCE8B0076C617 /* OpenSans-Semibold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7F87CAA01A2CCE8B0076C617 /* OpenSans-Semibold.ttf */; };
|
||||
7F8B8D3F1A37356C00C34B88 /* TrackingPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F8B8D3E1A37356C00C34B88 /* TrackingPageViewController.swift */; };
|
||||
7F8B8D411A373B0200C34B88 /* SpeedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F8B8D401A373B0200C34B88 /* SpeedViewController.swift */; };
|
||||
7F8B8D431A373D4400C34B88 /* CourseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F8B8D421A373D4400C34B88 /* CourseViewController.swift */; };
|
||||
7F8CC2081A10D47A00C56D7F /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F8CC2071A10D47A00C56D7F /* SettingsViewController.swift */; };
|
||||
7F950A911A35E967003C8C5C /* LeaderboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F950A901A35E967003C8C5C /* LeaderboardViewController.swift */; };
|
||||
7F99DE3C1A126554009B3619 /* CompetitorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F99DE3B1A126554009B3619 /* CompetitorViewController.swift */; };
|
||||
7F99DE3C1A126554009B3619 /* RegattaCompetitorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F99DE3B1A126554009B3619 /* RegattaCompetitorViewController.swift */; };
|
||||
7FA2220B1A25E55400FD364B /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F83F17519F1574700C53328 /* CoreLocation.framework */; };
|
||||
7FA6551A19F6661900A1B92D /* CoreDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FA6551919F6661900A1B92D /* CoreDataManager.swift */; };
|
||||
7FAFA7951A126B5700D230F1 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FAFA7941A126B5700D230F1 /* AboutViewController.swift */; };
|
||||
@@ -305,24 +300,33 @@
|
||||
9E95C3D61C723AB30016632B /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E95C3D51C723AB30016632B /* Constants.swift */; };
|
||||
9ECBDDAE1C9C4F0300F76AEB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9ECBDDAD1C9C4F0300F76AEB /* LaunchScreen.storyboard */; };
|
||||
C8150BA51DCC6FDB0087C9C2 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C8150BA71DCC6FDB0087C9C2 /* InfoPlist.strings */; };
|
||||
C8289F3A1F386D8300A76953 /* SignUpRequestManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8289F391F386D8300A76953 /* SignUpRequestManager.swift */; };
|
||||
C843D47F1EBC54A300250F01 /* CompetitorCheckIn+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C843D4791EBC54A300250F01 /* CompetitorCheckIn+CoreDataProperties.swift */; };
|
||||
C843D4801EBC54A300250F01 /* CompetitorCheckIn.swift in Sources */ = {isa = PBXBuildFile; fileRef = C843D47A1EBC54A300250F01 /* CompetitorCheckIn.swift */; };
|
||||
C843D4811EBC54A300250F01 /* MarkCheckIn+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C843D47B1EBC54A300250F01 /* MarkCheckIn+CoreDataProperties.swift */; };
|
||||
C843D4821EBC54A300250F01 /* MarkCheckIn.swift in Sources */ = {isa = PBXBuildFile; fileRef = C843D47C1EBC54A300250F01 /* MarkCheckIn.swift */; };
|
||||
C843D4831EBC54A300250F01 /* CheckIn+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C843D47D1EBC54A300250F01 /* CheckIn+CoreDataProperties.swift */; };
|
||||
C843D4841EBC54A300250F01 /* CheckIn.swift in Sources */ = {isa = PBXBuildFile; fileRef = C843D47E1EBC54A300250F01 /* CheckIn.swift */; };
|
||||
C843D4861EBC927800250F01 /* MarkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C843D4851EBC927800250F01 /* MarkViewController.swift */; };
|
||||
C843D4861EBC927800250F01 /* RegattaMarkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C843D4851EBC927800250F01 /* RegattaMarkViewController.swift */; };
|
||||
C843D4881EBCADDC00250F01 /* CompetitorSessionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C843D4871EBCADDC00250F01 /* CompetitorSessionController.swift */; };
|
||||
C843D48A1EBCADF500250F01 /* MarkSessionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C843D4891EBCADF500250F01 /* MarkSessionController.swift */; };
|
||||
C843D48C1EBCB36900250F01 /* SessionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C843D48B1EBCB36900250F01 /* SessionViewController.swift */; };
|
||||
C843D48F1EC099EB00250F01 /* CoreData2-3.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = C843D48E1EC099EB00250F01 /* CoreData2-3.xcmappingmodel */; };
|
||||
C843D4911EC0A93600250F01 /* CoreData1-3.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = C843D4901EC0A93600250F01 /* CoreData1-3.xcmappingmodel */; };
|
||||
C843D532201F3050003725F0 /* NewCheckInViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C843D531201F3050003725F0 /* NewCheckInViewController.swift */; };
|
||||
C84936181D40D295000F30E8 /* GPSFixController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C84936171D40D295000F30E8 /* GPSFixController.swift */; };
|
||||
C84936291D47645F000F30E8 /* HomeViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C84936281D47645F000F30E8 /* HomeViewCell.swift */; };
|
||||
C84936291D47645F000F30E8 /* CheckInTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C84936281D47645F000F30E8 /* CheckInTableViewCell.swift */; };
|
||||
C852914F1D48FB91009E9A9D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C85291511D48FB91009E9A9D /* Localizable.strings */; };
|
||||
C852BAA21CF5E1EA0093B0C5 /* RequestManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C852BAA11CF5E1EA0093B0C5 /* RequestManager.swift */; };
|
||||
C852BAA21CF5E1EA0093B0C5 /* CheckInRequestManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C852BAA11CF5E1EA0093B0C5 /* CheckInRequestManager.swift */; };
|
||||
C852BAAB1CF736680093B0C5 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = C852BAAA1CF736680093B0C5 /* Preferences.swift */; };
|
||||
C85DB8251F30B680009559CE /* SignUp.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C85DB8241F30B680009559CE /* SignUp.storyboard */; };
|
||||
C85DB8281F31AAE0009559CE /* SignUpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C85DB8271F31AAE0009559CE /* SignUpViewController.swift */; };
|
||||
C85DB82A1F31AAFD009559CE /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C85DB8291F31AAFD009559CE /* LoginViewController.swift */; };
|
||||
C85DB82C1F31AB1A009559CE /* ForgotPasswordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C85DB82B1F31AB1A009559CE /* ForgotPasswordViewController.swift */; };
|
||||
C85DB82E1F31D804009559CE /* SignUpController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C85DB82D1F31D804009559CE /* SignUpController.swift */; };
|
||||
C8603D511C52892A002B2F28 /* Acknowledgements.markdown in Resources */ = {isa = PBXBuildFile; fileRef = C8603D491C528832002B2F28 /* Acknowledgements.markdown */; };
|
||||
C867BD091F4B0274007E78E0 /* TrainingCompetitorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C867BD081F4B0274007E78E0 /* TrainingCompetitorViewController.swift */; };
|
||||
C867BD0B1F4B0290007E78E0 /* CreateTrainingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C867BD0A1F4B0290007E78E0 /* CreateTrainingViewController.swift */; };
|
||||
C8739E061D2AA2A500A89F3C /* GPSFix+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8739DFC1D2AA2A500A89F3C /* GPSFix+CoreDataProperties.swift */; };
|
||||
C8739E071D2AA2A500A89F3C /* GPSFix.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8739DFD1D2AA2A500A89F3C /* GPSFix.swift */; };
|
||||
C8739E081D2AA2A500A89F3C /* Leaderboard+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8739DFE1D2AA2A500A89F3C /* Leaderboard+CoreDataProperties.swift */; };
|
||||
@@ -335,16 +339,50 @@
|
||||
C8739E161D2BF17900A89F3C /* CompetitorData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8739E151D2BF17900A89F3C /* CompetitorData.swift */; };
|
||||
C8739E181D2BF2DC00A89F3C /* BaseData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8739E171D2BF2DC00A89F3C /* BaseData.swift */; };
|
||||
C8739E1C1D2D5F1700A89F3C /* LocationData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8739E1B1D2D5F1700A89F3C /* LocationData.swift */; };
|
||||
C87A74E11F4EE570003DE396 /* TrainingRequestManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C87A74E01F4EE570003DE396 /* TrainingRequestManager.swift */; };
|
||||
C87A74E31F4F0E34003DE396 /* TrainingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C87A74E21F4F0E34003DE396 /* TrainingController.swift */; };
|
||||
C87A74E81F504EBB003DE396 /* CreateEventData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C87A74E71F504EBB003DE396 /* CreateEventData.swift */; };
|
||||
C87A74EA1F5053AE003DE396 /* RegattaCompetitorCreateAndAddData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C87A74E91F5053AE003DE396 /* RegattaCompetitorCreateAndAddData.swift */; };
|
||||
C87A74EC1F5068D0003DE396 /* CreateTrainingData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C87A74EB1F5068D0003DE396 /* CreateTrainingData.swift */; };
|
||||
C88F6D2B1FEAC4E800A616BE /* LeaderboardAutoCourseData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C88F6D2A1FEAC4E800A616BE /* LeaderboardAutoCourseData.swift */; };
|
||||
C88F6D2D1FEACC5700A616BE /* LeaderboardGroupData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C88F6D2C1FEACC5700A616BE /* LeaderboardGroupData.swift */; };
|
||||
C88F6D2F1FEACD7400A616BE /* RegattaRaceCourseData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C88F6D2E1FEACD7400A616BE /* RegattaRaceCourseData.swift */; };
|
||||
C88F6D311FFBC35F00A616BE /* ErrorHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C88F6D301FFBC35F00A616BE /* ErrorHelper.swift */; };
|
||||
C891886E2057DB4A00E37CC3 /* BoatData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C891886D2057DB4A00E37CC3 /* BoatData.swift */; };
|
||||
C89188712057E7D700E37CC3 /* BoatCheckIn.swift in Sources */ = {isa = PBXBuildFile; fileRef = C891886F2057E7D700E37CC3 /* BoatCheckIn.swift */; };
|
||||
C89188722057E7D700E37CC3 /* BoatCheckIn+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89188702057E7D700E37CC3 /* BoatCheckIn+CoreDataProperties.swift */; };
|
||||
C89188742057F32300E37CC3 /* RegattaBoatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89188732057F32300E37CC3 /* RegattaBoatViewController.swift */; };
|
||||
C89188762057F36300E37CC3 /* BoatSessionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89188752057F36300E37CC3 /* BoatSessionViewController.swift */; };
|
||||
C89188782057F44200E37CC3 /* BoatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89188772057F44200E37CC3 /* BoatViewController.swift */; };
|
||||
C891887A2057F55100E37CC3 /* BoatSessionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89188792057F55100E37CC3 /* BoatSessionController.swift */; };
|
||||
C891887C20580A4100E37CC3 /* TrainingBoatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C891887B20580A4100E37CC3 /* TrainingBoatViewController.swift */; };
|
||||
C89C5FC51D06A289002AAAD2 /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89C5FC41D06A289002AAAD2 /* GradientView.swift */; };
|
||||
C89C5FCA1D06CECD002AAAD2 /* TrackingViewStatusCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89C5FC91D06CECD002AAAD2 /* TrackingViewStatusCell.swift */; };
|
||||
C89C5FCC1D06CF0C002AAAD2 /* TrackingViewGPSAccuracyCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89C5FCB1D06CF0C002AAAD2 /* TrackingViewGPSAccuracyCell.swift */; };
|
||||
C89C5FCE1D06CF26002AAAD2 /* TrackingViewGPSFixesCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89C5FCD1D06CF26002AAAD2 /* TrackingViewGPSFixesCell.swift */; };
|
||||
C89C5FD01D06CF46002AAAD2 /* TrackingViewModeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89C5FCF1D06CF46002AAAD2 /* TrackingViewModeCell.swift */; };
|
||||
C8A4400B1C18153600DF70A0 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = C8A4400A1C18153600DF70A0 /* Settings.bundle */; };
|
||||
C8B3B9BC1F39E64400B3B6BA /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B3B9BB1F39E64400B3B6BA /* Keychain.swift */; };
|
||||
C8B3B9C01F3C3B4400B3B6BA /* SignUp.strings in Resources */ = {isa = PBXBuildFile; fileRef = C8B3B9BF1F3C3B4400B3B6BA /* SignUp.strings */; };
|
||||
C8B3B9C21F3C6B3900B3B6BA /* SignUpTranslation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B3B9C11F3C6B3900B3B6BA /* SignUpTranslation.swift */; };
|
||||
C8BBAC791C52649E004CA551 /* LicenseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8BBAC781C52649E004CA551 /* LicenseViewController.swift */; };
|
||||
C8BFCCC41D51C53500FC1A27 /* TeamData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8BFCCC31D51C53500FC1A27 /* TeamData.swift */; };
|
||||
C8BFCCCC1D523C5D00FC1A27 /* TitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8BFCCCB1D523C5D00FC1A27 /* TitleView.swift */; };
|
||||
C8CF22A31F6194C600A01E0A /* RegattaCheckInController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8CF22A21F6194C600A01E0A /* RegattaCheckInController.swift */; };
|
||||
C8CF22A51F61965C00A01E0A /* TrainingCheckInController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8CF22A41F61965C00A01E0A /* TrainingCheckInController.swift */; };
|
||||
C8CF22A91F62780C00A01E0A /* CheckInDataCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8CF22A81F62780C00A01E0A /* CheckInDataCollector.swift */; };
|
||||
C8CF22AD1F668BAB00A01E0A /* TrainingMarkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8CF22AC1F668BAB00A01E0A /* TrainingMarkViewController.swift */; };
|
||||
C8CF22B41F67C4D800A01E0A /* RegattaRaceColumnAddData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8CF22B31F67C4D800A01E0A /* RegattaRaceColumnAddData.swift */; };
|
||||
C8CF22B81F68133000A01E0A /* TrainingRaceData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8CF22B71F68133000A01E0A /* TrainingRaceData.swift */; };
|
||||
C8CF22BE1F69187800A01E0A /* CompetitorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8CF22BD1F69187800A01E0A /* CompetitorViewController.swift */; };
|
||||
C8CF22C01F6926E500A01E0A /* CompetitorSessionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8CF22BF1F6926E500A01E0A /* CompetitorSessionViewController.swift */; };
|
||||
C8CF22C21F692B5300A01E0A /* MarkSessionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8CF22C11F692B5300A01E0A /* MarkSessionViewController.swift */; };
|
||||
C8CF22C41F692B7700A01E0A /* MarkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8CF22C31F692B7700A01E0A /* MarkViewController.swift */; };
|
||||
C8CF22C61F694EB900A01E0A /* TrainingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8CF22C51F694EB900A01E0A /* TrainingViewController.swift */; };
|
||||
C8D5E81D1D479FA800DA0D61 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C8D5E81C1D479FA800DA0D61 /* Main.storyboard */; };
|
||||
C8E0BD0A1F39A08B002FCCA5 /* FormularViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E0BD091F39A08B002FCCA5 /* FormularViewController.swift */; };
|
||||
C8E0BD0D1F39ADAD002FCCA5 /* KeychainPasswordItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E0BD0C1F39ADAD002FCCA5 /* KeychainPasswordItem.swift */; };
|
||||
C8E0D3131F59475B001EEF5C /* CheckInTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E0D3121F59475B001EEF5C /* CheckInTableViewController.swift */; };
|
||||
C8F1B1E11D488B6D004C3379 /* Translation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8F1B1E01D488B6D004C3379 /* Translation.swift */; };
|
||||
C8F71EDA1D8691F700826DE4 /* AFHTTPRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F71EA61D8691F700826DE4 /* AFHTTPRequestOperation.m */; };
|
||||
C8F71EDB1D8691F700826DE4 /* AFHTTPRequestOperationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F71EA81D8691F700826DE4 /* AFHTTPRequestOperationManager.m */; };
|
||||
@@ -660,21 +698,16 @@
|
||||
7F83F15019F154F000C53328 /* SAPTracker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SAPTracker.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7F83F15419F154F000C53328 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
7F83F15519F154F000C53328 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7F83F15719F154F000C53328 /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = "<group>"; };
|
||||
7F83F16519F154F000C53328 /* SAPTrackerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SAPTrackerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7F83F16A19F154F000C53328 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
7F83F16B19F154F000C53328 /* SAPTrackerAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SAPTrackerAPITests.swift; sourceTree = "<group>"; };
|
||||
7F83F17519F1574700C53328 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; };
|
||||
7F87CA9D1A2CCE8B0076C617 /* OpenSans-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "OpenSans-Bold.ttf"; sourceTree = "<group>"; };
|
||||
7F87CA9E1A2CCE8B0076C617 /* OpenSans-Light.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "OpenSans-Light.ttf"; sourceTree = "<group>"; };
|
||||
7F87CA9F1A2CCE8B0076C617 /* OpenSans-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "OpenSans-Regular.ttf"; sourceTree = "<group>"; };
|
||||
7F87CAA01A2CCE8B0076C617 /* OpenSans-Semibold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "OpenSans-Semibold.ttf"; sourceTree = "<group>"; };
|
||||
7F8B8D3E1A37356C00C34B88 /* TrackingPageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrackingPageViewController.swift; sourceTree = "<group>"; };
|
||||
7F8B8D401A373B0200C34B88 /* SpeedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpeedViewController.swift; sourceTree = "<group>"; };
|
||||
7F8B8D421A373D4400C34B88 /* CourseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseViewController.swift; sourceTree = "<group>"; };
|
||||
7F8CC2071A10D47A00C56D7F /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; };
|
||||
7F950A901A35E967003C8C5C /* LeaderboardViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LeaderboardViewController.swift; sourceTree = "<group>"; };
|
||||
7F99DE3B1A126554009B3619 /* CompetitorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompetitorViewController.swift; sourceTree = "<group>"; };
|
||||
7F99DE3B1A126554009B3619 /* RegattaCompetitorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegattaCompetitorViewController.swift; sourceTree = "<group>"; };
|
||||
7FA6551919F6661900A1B92D /* CoreDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = CoreDataManager.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
|
||||
7FAFA7941A126B5700D230F1 /* AboutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = "<group>"; };
|
||||
7FC5232C19F6AEE800BBFDCC /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
|
||||
@@ -686,25 +719,35 @@
|
||||
9ECBDDAD1C9C4F0300F76AEB /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
C8150BA61DCC6FDB0087C9C2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
C8150BA81DCC6FDD0087C9C2 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
C8289F391F386D8300A76953 /* SignUpRequestManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignUpRequestManager.swift; sourceTree = "<group>"; };
|
||||
C843D4791EBC54A300250F01 /* CompetitorCheckIn+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CompetitorCheckIn+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
C843D47A1EBC54A300250F01 /* CompetitorCheckIn.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompetitorCheckIn.swift; sourceTree = "<group>"; };
|
||||
C843D47B1EBC54A300250F01 /* MarkCheckIn+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MarkCheckIn+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
C843D47C1EBC54A300250F01 /* MarkCheckIn.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkCheckIn.swift; sourceTree = "<group>"; };
|
||||
C843D47D1EBC54A300250F01 /* CheckIn+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CheckIn+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
C843D47E1EBC54A300250F01 /* CheckIn.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckIn.swift; sourceTree = "<group>"; };
|
||||
C843D4851EBC927800250F01 /* MarkViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkViewController.swift; sourceTree = "<group>"; };
|
||||
C843D4851EBC927800250F01 /* RegattaMarkViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegattaMarkViewController.swift; sourceTree = "<group>"; };
|
||||
C843D4871EBCADDC00250F01 /* CompetitorSessionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompetitorSessionController.swift; sourceTree = "<group>"; };
|
||||
C843D4891EBCADF500250F01 /* MarkSessionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkSessionController.swift; sourceTree = "<group>"; };
|
||||
C843D48B1EBCB36900250F01 /* SessionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionViewController.swift; sourceTree = "<group>"; };
|
||||
C843D48E1EC099EB00250F01 /* CoreData2-3.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = "CoreData2-3.xcmappingmodel"; sourceTree = "<group>"; };
|
||||
C843D4901EC0A93600250F01 /* CoreData1-3.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = "CoreData1-3.xcmappingmodel"; sourceTree = "<group>"; };
|
||||
C843D522201F1F17003725F0 /* CoreData 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "CoreData 4.xcdatamodel"; sourceTree = "<group>"; };
|
||||
C843D531201F3050003725F0 /* NewCheckInViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewCheckInViewController.swift; sourceTree = "<group>"; };
|
||||
C84936171D40D295000F30E8 /* GPSFixController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GPSFixController.swift; sourceTree = "<group>"; };
|
||||
C84936281D47645F000F30E8 /* HomeViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewCell.swift; sourceTree = "<group>"; };
|
||||
C84936281D47645F000F30E8 /* CheckInTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckInTableViewCell.swift; sourceTree = "<group>"; };
|
||||
C85291501D48FB91009E9A9D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
C852BAA11CF5E1EA0093B0C5 /* RequestManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestManager.swift; sourceTree = "<group>"; };
|
||||
C852BAA11CF5E1EA0093B0C5 /* CheckInRequestManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckInRequestManager.swift; sourceTree = "<group>"; };
|
||||
C852BAAA1CF736680093B0C5 /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
|
||||
C85DB8241F30B680009559CE /* SignUp.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = SignUp.storyboard; sourceTree = "<group>"; };
|
||||
C85DB8271F31AAE0009559CE /* SignUpViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignUpViewController.swift; sourceTree = "<group>"; };
|
||||
C85DB8291F31AAFD009559CE /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = "<group>"; };
|
||||
C85DB82B1F31AB1A009559CE /* ForgotPasswordViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForgotPasswordViewController.swift; sourceTree = "<group>"; };
|
||||
C85DB82D1F31D804009559CE /* SignUpController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignUpController.swift; sourceTree = "<group>"; };
|
||||
C85FAA501D4F84460087C2A2 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
C8603D491C528832002B2F28 /* Acknowledgements.markdown */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Acknowledgements.markdown; sourceTree = "<group>"; };
|
||||
C867BD081F4B0274007E78E0 /* TrainingCompetitorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrainingCompetitorViewController.swift; sourceTree = "<group>"; };
|
||||
C867BD0A1F4B0290007E78E0 /* CreateTrainingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateTrainingViewController.swift; sourceTree = "<group>"; };
|
||||
C8739D9A1D25317100A89F3C /* CoreData 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "CoreData 2.xcdatamodel"; sourceTree = "<group>"; };
|
||||
C8739DFC1D2AA2A500A89F3C /* GPSFix+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GPSFix+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
C8739DFD1D2AA2A500A89F3C /* GPSFix.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GPSFix.swift; sourceTree = "<group>"; };
|
||||
@@ -718,17 +761,51 @@
|
||||
C8739E151D2BF17900A89F3C /* CompetitorData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompetitorData.swift; sourceTree = "<group>"; };
|
||||
C8739E171D2BF2DC00A89F3C /* BaseData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseData.swift; sourceTree = "<group>"; };
|
||||
C8739E1B1D2D5F1700A89F3C /* LocationData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationData.swift; sourceTree = "<group>"; };
|
||||
C87A74E01F4EE570003DE396 /* TrainingRequestManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrainingRequestManager.swift; sourceTree = "<group>"; };
|
||||
C87A74E21F4F0E34003DE396 /* TrainingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrainingController.swift; sourceTree = "<group>"; };
|
||||
C87A74E71F504EBB003DE396 /* CreateEventData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateEventData.swift; sourceTree = "<group>"; };
|
||||
C87A74E91F5053AE003DE396 /* RegattaCompetitorCreateAndAddData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegattaCompetitorCreateAndAddData.swift; sourceTree = "<group>"; };
|
||||
C87A74EB1F5068D0003DE396 /* CreateTrainingData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateTrainingData.swift; sourceTree = "<group>"; };
|
||||
C88F6D2A1FEAC4E800A616BE /* LeaderboardAutoCourseData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaderboardAutoCourseData.swift; sourceTree = "<group>"; };
|
||||
C88F6D2C1FEACC5700A616BE /* LeaderboardGroupData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaderboardGroupData.swift; sourceTree = "<group>"; };
|
||||
C88F6D2E1FEACD7400A616BE /* RegattaRaceCourseData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegattaRaceCourseData.swift; sourceTree = "<group>"; };
|
||||
C88F6D301FFBC35F00A616BE /* ErrorHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHelper.swift; sourceTree = "<group>"; };
|
||||
C891886D2057DB4A00E37CC3 /* BoatData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoatData.swift; sourceTree = "<group>"; };
|
||||
C891886F2057E7D700E37CC3 /* BoatCheckIn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoatCheckIn.swift; sourceTree = "<group>"; };
|
||||
C89188702057E7D700E37CC3 /* BoatCheckIn+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BoatCheckIn+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
C89188732057F32300E37CC3 /* RegattaBoatViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegattaBoatViewController.swift; sourceTree = "<group>"; };
|
||||
C89188752057F36300E37CC3 /* BoatSessionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoatSessionViewController.swift; sourceTree = "<group>"; };
|
||||
C89188772057F44200E37CC3 /* BoatViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoatViewController.swift; sourceTree = "<group>"; };
|
||||
C89188792057F55100E37CC3 /* BoatSessionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoatSessionController.swift; sourceTree = "<group>"; };
|
||||
C891887B20580A4100E37CC3 /* TrainingBoatViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrainingBoatViewController.swift; sourceTree = "<group>"; };
|
||||
C89C5FC41D06A289002AAAD2 /* GradientView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GradientView.swift; sourceTree = "<group>"; };
|
||||
C89C5FC91D06CECD002AAAD2 /* TrackingViewStatusCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrackingViewStatusCell.swift; sourceTree = "<group>"; };
|
||||
C89C5FCB1D06CF0C002AAAD2 /* TrackingViewGPSAccuracyCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrackingViewGPSAccuracyCell.swift; sourceTree = "<group>"; };
|
||||
C89C5FCD1D06CF26002AAAD2 /* TrackingViewGPSFixesCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrackingViewGPSFixesCell.swift; sourceTree = "<group>"; };
|
||||
C89C5FCF1D06CF46002AAAD2 /* TrackingViewModeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrackingViewModeCell.swift; sourceTree = "<group>"; };
|
||||
C8A4400A1C18153600DF70A0 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
|
||||
C8B3B9BB1F39E64400B3B6BA /* Keychain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; };
|
||||
C8B3B9BF1F3C3B4400B3B6BA /* SignUp.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; path = SignUp.strings; sourceTree = "<group>"; };
|
||||
C8B3B9C11F3C6B3900B3B6BA /* SignUpTranslation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignUpTranslation.swift; sourceTree = "<group>"; };
|
||||
C8BBAC781C52649E004CA551 /* LicenseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LicenseViewController.swift; sourceTree = "<group>"; };
|
||||
C8BFCCC31D51C53500FC1A27 /* TeamData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TeamData.swift; sourceTree = "<group>"; };
|
||||
C8BFCCCB1D523C5D00FC1A27 /* TitleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TitleView.swift; sourceTree = "<group>"; };
|
||||
C8CF22A21F6194C600A01E0A /* RegattaCheckInController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegattaCheckInController.swift; sourceTree = "<group>"; };
|
||||
C8CF22A41F61965C00A01E0A /* TrainingCheckInController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrainingCheckInController.swift; sourceTree = "<group>"; };
|
||||
C8CF22A81F62780C00A01E0A /* CheckInDataCollector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckInDataCollector.swift; sourceTree = "<group>"; };
|
||||
C8CF22AC1F668BAB00A01E0A /* TrainingMarkViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrainingMarkViewController.swift; sourceTree = "<group>"; };
|
||||
C8CF22B31F67C4D800A01E0A /* RegattaRaceColumnAddData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegattaRaceColumnAddData.swift; sourceTree = "<group>"; };
|
||||
C8CF22B71F68133000A01E0A /* TrainingRaceData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrainingRaceData.swift; sourceTree = "<group>"; };
|
||||
C8CF22BD1F69187800A01E0A /* CompetitorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompetitorViewController.swift; sourceTree = "<group>"; };
|
||||
C8CF22BF1F6926E500A01E0A /* CompetitorSessionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompetitorSessionViewController.swift; sourceTree = "<group>"; };
|
||||
C8CF22C11F692B5300A01E0A /* MarkSessionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkSessionViewController.swift; sourceTree = "<group>"; };
|
||||
C8CF22C31F692B7700A01E0A /* MarkViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkViewController.swift; sourceTree = "<group>"; };
|
||||
C8CF22C51F694EB900A01E0A /* TrainingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrainingViewController.swift; sourceTree = "<group>"; };
|
||||
C8D5E81C1D479FA800DA0D61 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
|
||||
C8D938B11EB9AFDB00B1B4BC /* CoreData 3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "CoreData 3.xcdatamodel"; sourceTree = "<group>"; };
|
||||
C8E0BD091F39A08B002FCCA5 /* FormularViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormularViewController.swift; sourceTree = "<group>"; };
|
||||
C8E0BD0C1F39ADAD002FCCA5 /* KeychainPasswordItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainPasswordItem.swift; sourceTree = "<group>"; };
|
||||
C8E0D3121F59475B001EEF5C /* CheckInTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckInTableViewController.swift; sourceTree = "<group>"; };
|
||||
C8F1B1E01D488B6D004C3379 /* Translation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Translation.swift; sourceTree = "<group>"; };
|
||||
C8F71EA51D8691F700826DE4 /* AFHTTPRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFHTTPRequestOperation.h; sourceTree = "<group>"; };
|
||||
C8F71EA61D8691F700826DE4 /* AFHTTPRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFHTTPRequestOperation.m; sourceTree = "<group>"; };
|
||||
@@ -819,6 +896,8 @@
|
||||
7F06590019F7FE750077FF95 /* CoreData */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C891886F2057E7D700E37CC3 /* BoatCheckIn.swift */,
|
||||
C89188702057E7D700E37CC3 /* BoatCheckIn+CoreDataProperties.swift */,
|
||||
C843D47E1EBC54A300250F01 /* CheckIn.swift */,
|
||||
C843D47D1EBC54A300250F01 /* CheckIn+CoreDataProperties.swift */,
|
||||
C843D47A1EBC54A300250F01 /* CompetitorCheckIn.swift */,
|
||||
@@ -841,13 +920,12 @@
|
||||
7F06590119F7FE920077FF95 /* ViewController */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7FAFA7941A126B5700D230F1 /* AboutViewController.swift */,
|
||||
C8BBAC781C52649E004CA551 /* LicenseViewController.swift */,
|
||||
7F8CC2071A10D47A00C56D7F /* SettingsViewController.swift */,
|
||||
7FAFA7941A126B5700D230F1 /* AboutViewController.swift */,
|
||||
7F83F15719F154F000C53328 /* HomeViewController.swift */,
|
||||
C84936281D47645F000F30E8 /* HomeViewCell.swift */,
|
||||
7F0658FC19F7D6700077FF95 /* ScanViewController.swift */,
|
||||
C8CF22B01F669CA700A01E0A /* CheckIn */,
|
||||
C89C60101D0FF036002AAAD2 /* Session */,
|
||||
C85DB8261F31AAAA009559CE /* SignUp */,
|
||||
);
|
||||
name = ViewController;
|
||||
sourceTree = "<group>";
|
||||
@@ -855,12 +933,12 @@
|
||||
7F06590219F7FEA40077FF95 /* DataController */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7F23F0871A1A3DEF00F03F1C /* CheckInController.swift */,
|
||||
C843D4871EBCADDC00250F01 /* CompetitorSessionController.swift */,
|
||||
C84936171D40D295000F30E8 /* GPSFixController.swift */,
|
||||
C843D4891EBCADF500250F01 /* MarkSessionController.swift */,
|
||||
C8739E0E1D2BEE7900A89F3C /* SessionController.swift */,
|
||||
C87A74E51F504CA7003DE396 /* CheckIn */,
|
||||
C8739E101D2BF13200A89F3C /* Data */,
|
||||
C8CF22A71F619B5F00A01E0A /* GPSFix */,
|
||||
C8CF22A61F619B4800A01E0A /* Session */,
|
||||
C87A74E61F504CB4003DE396 /* SignUp */,
|
||||
C87A74E41F504C97003DE396 /* Training */,
|
||||
);
|
||||
name = DataController;
|
||||
sourceTree = "<group>";
|
||||
@@ -1138,10 +1216,10 @@
|
||||
7F83F14719F154F000C53328 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7AC6457280AEA3EC18F5E846 /* Frameworks */,
|
||||
7F83F15119F154F000C53328 /* Products */,
|
||||
7F83F15219F154F000C53328 /* SAPTracker */,
|
||||
7F83F16819F154F000C53328 /* SAPTrackerTests */,
|
||||
7F83F15119F154F000C53328 /* Products */,
|
||||
7AC6457280AEA3EC18F5E846 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -1157,9 +1235,9 @@
|
||||
7F83F15219F154F000C53328 /* SAPTracker */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C8F71EA31D8691F700826DE4 /* Libraries */,
|
||||
7F83F15519F154F000C53328 /* AppDelegate.swift */,
|
||||
C8739DC71D2A653F00A89F3C /* Classes */,
|
||||
C8F71EA31D8691F700826DE4 /* Libraries */,
|
||||
C89C60131D0FF61E002AAAD2 /* Resources */,
|
||||
7F83F15319F154F000C53328 /* Supporting Files */,
|
||||
);
|
||||
@@ -1169,18 +1247,22 @@
|
||||
7F83F15319F154F000C53328 /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C85291511D48FB91009E9A9D /* Localizable.strings */,
|
||||
C8D5E81C1D479FA800DA0D61 /* Main.storyboard */,
|
||||
9ECBDDAD1C9C4F0300F76AEB /* LaunchScreen.storyboard */,
|
||||
7F4D2F531A1C8EE50038E7D1 /* Images.xcassets */,
|
||||
7FD9D3021A1236080058FEAD /* Extensions.swift */,
|
||||
9E95C3D51C723AB30016632B /* Constants.swift */,
|
||||
C8A4400A1C18153600DF70A0 /* Settings.bundle */,
|
||||
7F03FD3F19FA4D7400FA1327 /* bridge.h */,
|
||||
C8603D491C528832002B2F28 /* Acknowledgements.markdown */,
|
||||
7F83F15419F154F000C53328 /* Info.plist */,
|
||||
7F03FD3F19FA4D7400FA1327 /* bridge.h */,
|
||||
C8A4400A1C18153600DF70A0 /* Settings.bundle */,
|
||||
9ECBDDAD1C9C4F0300F76AEB /* LaunchScreen.storyboard */,
|
||||
C8D5E81C1D479FA800DA0D61 /* Main.storyboard */,
|
||||
C85DB8241F30B680009559CE /* SignUp.storyboard */,
|
||||
C8B3B9BF1F3C3B4400B3B6BA /* SignUp.strings */,
|
||||
9E95C3D51C723AB30016632B /* Constants.swift */,
|
||||
7FD9D3021A1236080058FEAD /* Extensions.swift */,
|
||||
C8B3B9C11F3C6B3900B3B6BA /* SignUpTranslation.swift */,
|
||||
C8F1B1E01D488B6D004C3379 /* Translation.swift */,
|
||||
7F4D2F531A1C8EE50038E7D1 /* Images.xcassets */,
|
||||
C8150BA71DCC6FDB0087C9C2 /* InfoPlist.strings */,
|
||||
C85291511D48FB91009E9A9D /* Localizable.strings */,
|
||||
C88F6D301FFBC35F00A616BE /* ErrorHelper.swift */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
@@ -1216,8 +1298,8 @@
|
||||
C843D48D1EC0999D00250F01 /* Migration */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C843D48E1EC099EB00250F01 /* CoreData2-3.xcmappingmodel */,
|
||||
C843D4901EC0A93600250F01 /* CoreData1-3.xcmappingmodel */,
|
||||
C843D48E1EC099EB00250F01 /* CoreData2-3.xcmappingmodel */,
|
||||
);
|
||||
name = Migration;
|
||||
sourceTree = "<group>";
|
||||
@@ -1225,17 +1307,30 @@
|
||||
C852BAA91CF736440093B0C5 /* Preferences */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C8B3B9BB1F39E64400B3B6BA /* Keychain.swift */,
|
||||
C8E0BD0C1F39ADAD002FCCA5 /* KeychainPasswordItem.swift */,
|
||||
C852BAAA1CF736680093B0C5 /* Preferences.swift */,
|
||||
);
|
||||
name = Preferences;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C85DB8261F31AAAA009559CE /* SignUp */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C85DB82B1F31AB1A009559CE /* ForgotPasswordViewController.swift */,
|
||||
C8E0BD091F39A08B002FCCA5 /* FormularViewController.swift */,
|
||||
C85DB8291F31AAFD009559CE /* LoginViewController.swift */,
|
||||
C85DB8271F31AAE0009559CE /* SignUpViewController.swift */,
|
||||
);
|
||||
name = SignUp;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C8739D8B1D23C01500A89F3C /* Managers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7F2C605919F160E400C32D93 /* LocationManager.swift */,
|
||||
7F1B66F81A0CDCF600904A41 /* BatteryManager.swift */,
|
||||
C852BAA11CF5E1EA0093B0C5 /* RequestManager.swift */,
|
||||
7F2C605919F160E400C32D93 /* LocationManager.swift */,
|
||||
C8CF22AE1F669C1A00A01E0A /* Request */,
|
||||
);
|
||||
name = Managers;
|
||||
sourceTree = "<group>";
|
||||
@@ -1256,18 +1351,54 @@
|
||||
C8739E101D2BF13200A89F3C /* Data */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C891886D2057DB4A00E37CC3 /* BoatData.swift */,
|
||||
C8739E171D2BF2DC00A89F3C /* BaseData.swift */,
|
||||
7F0658FE19F7EE910077FF95 /* CheckInData.swift */,
|
||||
C87A74E91F5053AE003DE396 /* RegattaCompetitorCreateAndAddData.swift */,
|
||||
C8739E151D2BF17900A89F3C /* CompetitorData.swift */,
|
||||
C87A74E71F504EBB003DE396 /* CreateEventData.swift */,
|
||||
C87A74EB1F5068D0003DE396 /* CreateTrainingData.swift */,
|
||||
C8739E111D2BF14F00A89F3C /* EventData.swift */,
|
||||
C8739E131D2BF16300A89F3C /* LeaderboardData.swift */,
|
||||
C8739E151D2BF17900A89F3C /* CompetitorData.swift */,
|
||||
C8739E171D2BF2DC00A89F3C /* BaseData.swift */,
|
||||
C8739E1B1D2D5F1700A89F3C /* LocationData.swift */,
|
||||
C8BFCCC31D51C53500FC1A27 /* TeamData.swift */,
|
||||
C8FF5B7B1EB8CF2C00CF9764 /* MarkData.swift */,
|
||||
C8BFCCC31D51C53500FC1A27 /* TeamData.swift */,
|
||||
C8CF22B31F67C4D800A01E0A /* RegattaRaceColumnAddData.swift */,
|
||||
C8CF22B71F68133000A01E0A /* TrainingRaceData.swift */,
|
||||
C88F6D2A1FEAC4E800A616BE /* LeaderboardAutoCourseData.swift */,
|
||||
C88F6D2C1FEACC5700A616BE /* LeaderboardGroupData.swift */,
|
||||
C88F6D2E1FEACD7400A616BE /* RegattaRaceCourseData.swift */,
|
||||
);
|
||||
name = Data;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C87A74E41F504C97003DE396 /* Training */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C87A74E21F4F0E34003DE396 /* TrainingController.swift */,
|
||||
);
|
||||
name = Training;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C87A74E51F504CA7003DE396 /* CheckIn */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7F23F0871A1A3DEF00F03F1C /* CheckInController.swift */,
|
||||
C8CF22A81F62780C00A01E0A /* CheckInDataCollector.swift */,
|
||||
C8CF22A21F6194C600A01E0A /* RegattaCheckInController.swift */,
|
||||
C8CF22A41F61965C00A01E0A /* TrainingCheckInController.swift */,
|
||||
);
|
||||
name = CheckIn;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C87A74E61F504CB4003DE396 /* SignUp */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C85DB82D1F31D804009559CE /* SignUpController.swift */,
|
||||
);
|
||||
name = SignUp;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C89C5FC31D06A269002AAAD2 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1281,10 +1412,10 @@
|
||||
C89C5FC61D06CE62002AAAD2 /* Tracking */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7F8B8D3E1A37356C00C34B88 /* TrackingPageViewController.swift */,
|
||||
7F1875B21A10F1B800C25BE0 /* TrackingViewController.swift */,
|
||||
C89C5FC81D06CEA0002AAAD2 /* Cells */,
|
||||
C89C5FC71D06CE87002AAAD2 /* Pages */,
|
||||
7F1875B21A10F1B800C25BE0 /* TrackingViewController.swift */,
|
||||
7F8B8D3E1A37356C00C34B88 /* TrackingPageViewController.swift */,
|
||||
);
|
||||
name = Tracking;
|
||||
sourceTree = "<group>";
|
||||
@@ -1292,9 +1423,9 @@
|
||||
C89C5FC71D06CE87002AAAD2 /* Pages */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7F193A8E1A39ACF500D71773 /* TimerViewController.swift */,
|
||||
7F8B8D401A373B0200C34B88 /* SpeedViewController.swift */,
|
||||
7F8B8D421A373D4400C34B88 /* CourseViewController.swift */,
|
||||
7F8B8D401A373B0200C34B88 /* SpeedViewController.swift */,
|
||||
7F193A8E1A39ACF500D71773 /* TimerViewController.swift */,
|
||||
);
|
||||
name = Pages;
|
||||
sourceTree = "<group>";
|
||||
@@ -1302,10 +1433,10 @@
|
||||
C89C5FC81D06CEA0002AAAD2 /* Cells */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C89C5FC91D06CECD002AAAD2 /* TrackingViewStatusCell.swift */,
|
||||
C89C5FCB1D06CF0C002AAAD2 /* TrackingViewGPSAccuracyCell.swift */,
|
||||
C89C5FCD1D06CF26002AAAD2 /* TrackingViewGPSFixesCell.swift */,
|
||||
C89C5FCF1D06CF46002AAAD2 /* TrackingViewModeCell.swift */,
|
||||
C89C5FC91D06CECD002AAAD2 /* TrackingViewStatusCell.swift */,
|
||||
);
|
||||
name = Cells;
|
||||
sourceTree = "<group>";
|
||||
@@ -1313,11 +1444,17 @@
|
||||
C89C60101D0FF036002AAAD2 /* Session */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7F99DE3B1A126554009B3619 /* CompetitorViewController.swift */,
|
||||
C8CF22BF1F6926E500A01E0A /* CompetitorSessionViewController.swift */,
|
||||
C8CF22BD1F69187800A01E0A /* CompetitorViewController.swift */,
|
||||
7F950A901A35E967003C8C5C /* LeaderboardViewController.swift */,
|
||||
C843D4851EBC927800250F01 /* MarkViewController.swift */,
|
||||
C8CF22C11F692B5300A01E0A /* MarkSessionViewController.swift */,
|
||||
C8CF22C31F692B7700A01E0A /* MarkViewController.swift */,
|
||||
C843D48B1EBCB36900250F01 /* SessionViewController.swift */,
|
||||
C8CF22B11F669DA700A01E0A /* Regatta */,
|
||||
C89C5FC61D06CE62002AAAD2 /* Tracking */,
|
||||
C8CF22B21F669DB100A01E0A /* Training */,
|
||||
C89188752057F36300E37CC3 /* BoatSessionViewController.swift */,
|
||||
C89188772057F44200E37CC3 /* BoatViewController.swift */,
|
||||
);
|
||||
name = Session;
|
||||
sourceTree = "<group>";
|
||||
@@ -1326,22 +1463,10 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7F14A0031A13B9A6001C1ECB /* Flags */,
|
||||
C89C60141D0FF63C002AAAD2 /* Fonts */,
|
||||
);
|
||||
name = Resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C89C60141D0FF63C002AAAD2 /* Fonts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7F87CA9D1A2CCE8B0076C617 /* OpenSans-Bold.ttf */,
|
||||
7F87CA9E1A2CCE8B0076C617 /* OpenSans-Light.ttf */,
|
||||
7F87CA9F1A2CCE8B0076C617 /* OpenSans-Regular.ttf */,
|
||||
7F87CAA01A2CCE8B0076C617 /* OpenSans-Semibold.ttf */,
|
||||
);
|
||||
name = Fonts;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C8BFCC1B1D50F17800FC1A27 /* SMTWiFiStatus */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1352,6 +1477,76 @@
|
||||
path = ..;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C8CF22A61F619B4800A01E0A /* Session */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C843D4871EBCADDC00250F01 /* CompetitorSessionController.swift */,
|
||||
C843D4891EBCADF500250F01 /* MarkSessionController.swift */,
|
||||
C8739E0E1D2BEE7900A89F3C /* SessionController.swift */,
|
||||
C89188792057F55100E37CC3 /* BoatSessionController.swift */,
|
||||
);
|
||||
name = Session;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C8CF22A71F619B5F00A01E0A /* GPSFix */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C84936171D40D295000F30E8 /* GPSFixController.swift */,
|
||||
);
|
||||
name = GPSFix;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C8CF22AE1F669C1A00A01E0A /* Request */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C852BAA11CF5E1EA0093B0C5 /* CheckInRequestManager.swift */,
|
||||
C8289F391F386D8300A76953 /* SignUpRequestManager.swift */,
|
||||
C87A74E01F4EE570003DE396 /* TrainingRequestManager.swift */,
|
||||
);
|
||||
name = Request;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C8CF22AF1F669C8F00A01E0A /* New */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C843D531201F3050003725F0 /* NewCheckInViewController.swift */,
|
||||
C867BD0A1F4B0290007E78E0 /* CreateTrainingViewController.swift */,
|
||||
7F0658FC19F7D6700077FF95 /* ScanViewController.swift */,
|
||||
);
|
||||
name = New;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C8CF22B01F669CA700A01E0A /* CheckIn */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C84936281D47645F000F30E8 /* CheckInTableViewCell.swift */,
|
||||
C8E0D3121F59475B001EEF5C /* CheckInTableViewController.swift */,
|
||||
C8CF22AF1F669C8F00A01E0A /* New */,
|
||||
);
|
||||
name = CheckIn;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C8CF22B11F669DA700A01E0A /* Regatta */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7F99DE3B1A126554009B3619 /* RegattaCompetitorViewController.swift */,
|
||||
C843D4851EBC927800250F01 /* RegattaMarkViewController.swift */,
|
||||
C89188732057F32300E37CC3 /* RegattaBoatViewController.swift */,
|
||||
);
|
||||
name = Regatta;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C8CF22B21F669DB100A01E0A /* Training */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C867BD081F4B0274007E78E0 /* TrainingCompetitorViewController.swift */,
|
||||
C8CF22AC1F668BAB00A01E0A /* TrainingMarkViewController.swift */,
|
||||
C8CF22C51F694EB900A01E0A /* TrainingViewController.swift */,
|
||||
C891887B20580A4100E37CC3 /* TrainingBoatViewController.swift */,
|
||||
);
|
||||
name = Training;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C8F71EA31D8691F700826DE4 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1494,6 +1689,7 @@
|
||||
CreatedOnToolsVersion = 6.0.1;
|
||||
DevelopmentTeam = RRE8EFVCE3;
|
||||
LastSwiftMigration = 0820;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
7F83F16419F154F000C53328 = {
|
||||
CreatedOnToolsVersion = 6.0.1;
|
||||
@@ -1542,6 +1738,7 @@
|
||||
7F14A12D1A13B9A6001C1ECB /* AZ.png in Resources */,
|
||||
7F14A1D21A13B9A6001C1ECB /* PT.png in Resources */,
|
||||
7F14A1241A13B9A6001C1ECB /* AN.png in Resources */,
|
||||
C85DB8251F30B680009559CE /* SignUp.storyboard in Resources */,
|
||||
7F14A2071A13B9A6001C1ECB /* VN.png in Resources */,
|
||||
7F14A1791A13B9A6001C1ECB /* Hello.txt in Resources */,
|
||||
7F14A1E81A13B9A6001C1ECB /* SS.png in Resources */,
|
||||
@@ -1587,6 +1784,7 @@
|
||||
7F14A1681A13B9A6001C1ECB /* GA.png in Resources */,
|
||||
7F14A10C1A13B9A6001C1ECB /* _british-antarctic-territory.png in Resources */,
|
||||
7F14A1771A13B9A6001C1ECB /* GW.png in Resources */,
|
||||
C8B3B9C01F3C3B4400B3B6BA /* SignUp.strings in Resources */,
|
||||
7F14A1811A13B9A6001C1ECB /* IE.png in Resources */,
|
||||
7F14A14D1A13B9A6001C1ECB /* CR.png in Resources */,
|
||||
7F14A1991A13B9A6001C1ECB /* LB.png in Resources */,
|
||||
@@ -1707,7 +1905,6 @@
|
||||
7F14A1C71A13B9A6001C1ECB /* OM.png in Resources */,
|
||||
7F14A1F21A13B9A6001C1ECB /* TJ.png in Resources */,
|
||||
7F14A12C1A13B9A6001C1ECB /* AX.png in Resources */,
|
||||
7F87CAA11A2CCE8B0076C617 /* OpenSans-Bold.ttf in Resources */,
|
||||
7F14A1951A13B9A6001C1ECB /* KW.png in Resources */,
|
||||
7F14A1C21A13B9A6001C1ECB /* NO.png in Resources */,
|
||||
7F14A16E1A13B9A6001C1ECB /* GI.png in Resources */,
|
||||
@@ -1772,13 +1969,10 @@
|
||||
7F14A10F1A13B9A6001C1ECB /* _gosquared.png in Resources */,
|
||||
7F14A1171A13B9A6001C1ECB /* _scotland.png in Resources */,
|
||||
7F14A1FB1A13B9A6001C1ECB /* TW.png in Resources */,
|
||||
7F87CAA41A2CCE8B0076C617 /* OpenSans-Semibold.ttf in Resources */,
|
||||
7F14A1451A13B9A6001C1ECB /* CG.png in Resources */,
|
||||
7F14A1F11A13B9A6001C1ECB /* TH.png in Resources */,
|
||||
7F14A1AC1A13B9A6001C1ECB /* ML.png in Resources */,
|
||||
7F87CAA31A2CCE8B0076C617 /* OpenSans-Regular.ttf in Resources */,
|
||||
7F14A1AE1A13B9A6001C1ECB /* MN.png in Resources */,
|
||||
7F87CAA21A2CCE8B0076C617 /* OpenSans-Light.ttf in Resources */,
|
||||
7F14A1D41A13B9A6001C1ECB /* PY.png in Resources */,
|
||||
7F14A1E11A13B9A6001C1ECB /* SI.png in Resources */,
|
||||
7F14A1921A13B9A6001C1ECB /* KN.png in Resources */,
|
||||
@@ -1838,60 +2032,86 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C867BD091F4B0274007E78E0 /* TrainingCompetitorViewController.swift in Sources */,
|
||||
C85DB82A1F31AAFD009559CE /* LoginViewController.swift in Sources */,
|
||||
C8F71EDA1D8691F700826DE4 /* AFHTTPRequestOperation.m in Sources */,
|
||||
7F1875B31A10F1B800C25BE0 /* TrackingViewController.swift in Sources */,
|
||||
C8739E081D2AA2A500A89F3C /* Leaderboard+CoreDataProperties.swift in Sources */,
|
||||
C8739E121D2BF14F00A89F3C /* EventData.swift in Sources */,
|
||||
C843D4881EBCADDC00250F01 /* CompetitorSessionController.swift in Sources */,
|
||||
C8F71EDF1D8691F700826DE4 /* AFURLConnectionOperation.m in Sources */,
|
||||
C891887A2057F55100E37CC3 /* BoatSessionController.swift in Sources */,
|
||||
C8F71EE31D8691F700826DE4 /* SVIndefiniteAnimatedView.m in Sources */,
|
||||
C8739E0F1D2BEE7900A89F3C /* SessionController.swift in Sources */,
|
||||
C8739E1C1D2D5F1700A89F3C /* LocationData.swift in Sources */,
|
||||
7F83F15819F154F000C53328 /* HomeViewController.swift in Sources */,
|
||||
C85DB82C1F31AB1A009559CE /* ForgotPasswordViewController.swift in Sources */,
|
||||
7F8B8D431A373D4400C34B88 /* CourseViewController.swift in Sources */,
|
||||
7FAFA7951A126B5700D230F1 /* AboutViewController.swift in Sources */,
|
||||
C8739E161D2BF17900A89F3C /* CompetitorData.swift in Sources */,
|
||||
C8289F3A1F386D8300A76953 /* SignUpRequestManager.swift in Sources */,
|
||||
C8739E0C1D2AA2A500A89F3C /* Event+CoreDataProperties.swift in Sources */,
|
||||
C8739E071D2AA2A500A89F3C /* GPSFix.swift in Sources */,
|
||||
C8E0BD0D1F39ADAD002FCCA5 /* KeychainPasswordItem.swift in Sources */,
|
||||
C891887C20580A4100E37CC3 /* TrainingBoatViewController.swift in Sources */,
|
||||
C87A74E11F4EE570003DE396 /* TrainingRequestManager.swift in Sources */,
|
||||
C89188762057F36300E37CC3 /* BoatSessionViewController.swift in Sources */,
|
||||
C843D4811EBC54A300250F01 /* MarkCheckIn+CoreDataProperties.swift in Sources */,
|
||||
C843D532201F3050003725F0 /* NewCheckInViewController.swift in Sources */,
|
||||
C8F71EEE1D8691F700826DE4 /* UIProgressView+AFNetworking.m in Sources */,
|
||||
C8CF22A91F62780C00A01E0A /* CheckInDataCollector.swift in Sources */,
|
||||
C85DB82E1F31D804009559CE /* SignUpController.swift in Sources */,
|
||||
C8CF22AD1F668BAB00A01E0A /* TrainingMarkViewController.swift in Sources */,
|
||||
7F44095B19F65B5B0021BF22 /* CoreData.xcdatamodeld in Sources */,
|
||||
C89C5FD01D06CF46002AAAD2 /* TrackingViewModeCell.swift in Sources */,
|
||||
C8FF5B7C1EB8CF2C00CF9764 /* MarkData.swift in Sources */,
|
||||
C88F6D311FFBC35F00A616BE /* ErrorHelper.swift in Sources */,
|
||||
C8F71EEA1D8691F700826DE4 /* UIActivityIndicatorView+AFNetworking.m in Sources */,
|
||||
7F1B66F91A0CDCF600904A41 /* BatteryManager.swift in Sources */,
|
||||
7FA6551A19F6661900A1B92D /* CoreDataManager.swift in Sources */,
|
||||
C8F71EE71D8691F700826DE4 /* SVRadialGradientLayer.m in Sources */,
|
||||
C852BAA21CF5E1EA0093B0C5 /* RequestManager.swift in Sources */,
|
||||
C852BAA21CF5E1EA0093B0C5 /* CheckInRequestManager.swift in Sources */,
|
||||
C8F71EE01D8691F700826DE4 /* AFURLRequestSerialization.m in Sources */,
|
||||
C8BBAC791C52649E004CA551 /* LicenseViewController.swift in Sources */,
|
||||
C8BFCCC41D51C53500FC1A27 /* TeamData.swift in Sources */,
|
||||
C843D4911EC0A93600250F01 /* CoreData1-3.xcmappingmodel in Sources */,
|
||||
C89188742057F32300E37CC3 /* RegattaBoatViewController.swift in Sources */,
|
||||
C88F6D2B1FEAC4E800A616BE /* LeaderboardAutoCourseData.swift in Sources */,
|
||||
7F23F0881A1A3DEF00F03F1C /* CheckInController.swift in Sources */,
|
||||
C843D47F1EBC54A300250F01 /* CompetitorCheckIn+CoreDataProperties.swift in Sources */,
|
||||
C8F71EDC1D8691F700826DE4 /* AFHTTPSessionManager.m in Sources */,
|
||||
C8F71EE91D8691F700826DE4 /* AFNetworkActivityIndicatorManager.m in Sources */,
|
||||
C84936291D47645F000F30E8 /* HomeViewCell.swift in Sources */,
|
||||
C84936291D47645F000F30E8 /* CheckInTableViewCell.swift in Sources */,
|
||||
C891886E2057DB4A00E37CC3 /* BoatData.swift in Sources */,
|
||||
7F2C605A19F160E400C32D93 /* LocationManager.swift in Sources */,
|
||||
C89C5FCC1D06CF0C002AAAD2 /* TrackingViewGPSAccuracyCell.swift in Sources */,
|
||||
C89C5FCA1D06CECD002AAAD2 /* TrackingViewStatusCell.swift in Sources */,
|
||||
C843D4861EBC927800250F01 /* MarkViewController.swift in Sources */,
|
||||
C843D4861EBC927800250F01 /* RegattaMarkViewController.swift in Sources */,
|
||||
C8F71EDE1D8691F700826DE4 /* AFSecurityPolicy.m in Sources */,
|
||||
C8F71EDB1D8691F700826DE4 /* AFHTTPRequestOperationManager.m in Sources */,
|
||||
7F8B8D3F1A37356C00C34B88 /* TrackingPageViewController.swift in Sources */,
|
||||
C89188712057E7D700E37CC3 /* BoatCheckIn.swift in Sources */,
|
||||
C843D48F1EC099EB00250F01 /* CoreData2-3.xcmappingmodel in Sources */,
|
||||
C8CF22B81F68133000A01E0A /* TrainingRaceData.swift in Sources */,
|
||||
C84936181D40D295000F30E8 /* GPSFixController.swift in Sources */,
|
||||
7F99DE3C1A126554009B3619 /* CompetitorViewController.swift in Sources */,
|
||||
7F99DE3C1A126554009B3619 /* RegattaCompetitorViewController.swift in Sources */,
|
||||
C89188722057E7D700E37CC3 /* BoatCheckIn+CoreDataProperties.swift in Sources */,
|
||||
C843D4841EBC54A300250F01 /* CheckIn.swift in Sources */,
|
||||
C8BFCCCC1D523C5D00FC1A27 /* TitleView.swift in Sources */,
|
||||
C8CF22C01F6926E500A01E0A /* CompetitorSessionViewController.swift in Sources */,
|
||||
C8F71EF31D86A15F00826DE4 /* SMTWiFiStatus.m in Sources */,
|
||||
7F8CC2081A10D47A00C56D7F /* SettingsViewController.swift in Sources */,
|
||||
C8739E0D1D2AA2A500A89F3C /* Event.swift in Sources */,
|
||||
C87A74EC1F5068D0003DE396 /* CreateTrainingData.swift in Sources */,
|
||||
C8CF22A31F6194C600A01E0A /* RegattaCheckInController.swift in Sources */,
|
||||
7F8B8D411A373B0200C34B88 /* SpeedViewController.swift in Sources */,
|
||||
9E95C3D61C723AB30016632B /* Constants.swift in Sources */,
|
||||
C8CF22A51F61965C00A01E0A /* TrainingCheckInController.swift in Sources */,
|
||||
C89188782057F44200E37CC3 /* BoatViewController.swift in Sources */,
|
||||
C8F71EF01D8691F700826DE4 /* UIWebView+AFNetworking.m in Sources */,
|
||||
C87A74E81F504EBB003DE396 /* CreateEventData.swift in Sources */,
|
||||
7FD9D3031A1236080058FEAD /* Extensions.swift in Sources */,
|
||||
C8739E091D2AA2A500A89F3C /* Leaderboard.swift in Sources */,
|
||||
C8B3B9BC1F39E64400B3B6BA /* Keychain.swift in Sources */,
|
||||
C8739E141D2BF16300A89F3C /* LeaderboardData.swift in Sources */,
|
||||
C89C5FC51D06A289002AAAD2 /* GradientView.swift in Sources */,
|
||||
C8F71EEF1D8691F700826DE4 /* UIRefreshControl+AFNetworking.m in Sources */,
|
||||
@@ -1899,26 +2119,40 @@
|
||||
C843D4821EBC54A300250F01 /* MarkCheckIn.swift in Sources */,
|
||||
C8F71EDD1D8691F700826DE4 /* AFNetworkReachabilityManager.m in Sources */,
|
||||
C843D4801EBC54A300250F01 /* CompetitorCheckIn.swift in Sources */,
|
||||
C8CF22C41F692B7700A01E0A /* MarkViewController.swift in Sources */,
|
||||
C852BAAB1CF736680093B0C5 /* Preferences.swift in Sources */,
|
||||
C843D48A1EBCADF500250F01 /* MarkSessionController.swift in Sources */,
|
||||
C8CF22C21F692B5300A01E0A /* MarkSessionViewController.swift in Sources */,
|
||||
7F0658FD19F7D6700077FF95 /* ScanViewController.swift in Sources */,
|
||||
C843D4831EBC54A300250F01 /* CheckIn+CoreDataProperties.swift in Sources */,
|
||||
C8F71EEC1D8691F700826DE4 /* UIButton+AFNetworking.m in Sources */,
|
||||
C867BD0B1F4B0290007E78E0 /* CreateTrainingViewController.swift in Sources */,
|
||||
C87A74E31F4F0E34003DE396 /* TrainingController.swift in Sources */,
|
||||
C8CF22C61F694EB900A01E0A /* TrainingViewController.swift in Sources */,
|
||||
C8F1B1E11D488B6D004C3379 /* Translation.swift in Sources */,
|
||||
C8F71EE81D8691F700826DE4 /* TSMarkdownParser.m in Sources */,
|
||||
C8739E061D2AA2A500A89F3C /* GPSFix+CoreDataProperties.swift in Sources */,
|
||||
7F193A8F1A39ACF500D71773 /* TimerViewController.swift in Sources */,
|
||||
C88F6D2F1FEACD7400A616BE /* RegattaRaceCourseData.swift in Sources */,
|
||||
C8739E181D2BF2DC00A89F3C /* BaseData.swift in Sources */,
|
||||
C8CF22B41F67C4D800A01E0A /* RegattaRaceColumnAddData.swift in Sources */,
|
||||
7F83F15619F154F000C53328 /* AppDelegate.swift in Sources */,
|
||||
C8B3B9C21F3C6B3900B3B6BA /* SignUpTranslation.swift in Sources */,
|
||||
7F950A911A35E967003C8C5C /* LeaderboardViewController.swift in Sources */,
|
||||
C8F71EED1D8691F700826DE4 /* UIImageView+AFNetworking.m in Sources */,
|
||||
C8F71EEB1D8691F700826DE4 /* UIAlertView+AFNetworking.m in Sources */,
|
||||
C88F6D2D1FEACC5700A616BE /* LeaderboardGroupData.swift in Sources */,
|
||||
C87A74EA1F5053AE003DE396 /* RegattaCompetitorCreateAndAddData.swift in Sources */,
|
||||
C8E0D3131F59475B001EEF5C /* CheckInTableViewController.swift in Sources */,
|
||||
C843D48C1EBCB36900250F01 /* SessionViewController.swift in Sources */,
|
||||
C85DB8281F31AAE0009559CE /* SignUpViewController.swift in Sources */,
|
||||
C89C5FCE1D06CF26002AAAD2 /* TrackingViewGPSFixesCell.swift in Sources */,
|
||||
C8F71EE11D8691F700826DE4 /* AFURLResponseSerialization.m in Sources */,
|
||||
C8F71EE61D8691F700826DE4 /* SVProgressHUD.m in Sources */,
|
||||
7F0658FF19F7EE910077FF95 /* CheckInData.swift in Sources */,
|
||||
7F193A911A39E54600D71773 /* GPSAccuracyView.swift in Sources */,
|
||||
C8E0BD0A1F39A08B002FCCA5 /* FormularViewController.swift in Sources */,
|
||||
C8CF22BE1F69187800A01E0A /* CompetitorViewController.swift in Sources */,
|
||||
C8F71EE41D8691F700826DE4 /* SVProgressAnimatedView.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -2059,15 +2293,16 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_IDENTITY = "iPhone Distribution";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = SAPTracker/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
OTHER_SWIFT_FLAGS = "-D DEBUG";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE = "Q3U792Z354/“SAP Sailing Boat Tracker”";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "Q3U792Z354/“SAP Sailing Boat Tracker”";
|
||||
PROVISIONING_PROFILE = "";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = SAPTracker/bridge.h;
|
||||
SWIFT_VERSION = 3.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -2080,15 +2315,16 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_IDENTITY = "iPhone Distribution";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = SAPTracker/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
OTHER_SWIFT_FLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE = "Q3U792Z354/“SAP Sailing Boat Tracker”";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "Q3U792Z354/“SAP Sailing Boat Tracker”";
|
||||
PROVISIONING_PROFILE = "";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = SAPTracker/bridge.h;
|
||||
SWIFT_VERSION = 3.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -2170,11 +2406,12 @@
|
||||
7F44095919F65B5B0021BF22 /* CoreData.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
C843D522201F1F17003725F0 /* CoreData 4.xcdatamodel */,
|
||||
C8D938B11EB9AFDB00B1B4BC /* CoreData 3.xcdatamodel */,
|
||||
C8739D9A1D25317100A89F3C /* CoreData 2.xcdatamodel */,
|
||||
7F44095A19F65B5B0021BF22 /* CoreData.xcdatamodel */,
|
||||
);
|
||||
currentVersion = C8D938B11EB9AFDB00B1B4BC /* CoreData 3.xcdatamodel */;
|
||||
currentVersion = C843D522201F1F17003725F0 /* CoreData 4.xcdatamodel */;
|
||||
path = CoreData.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
versionGroupType = wrapper.xcdatamodel;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0820"
|
||||
version = "1.3">
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
@@ -73,6 +73,10 @@
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
<LocationScenarioReference
|
||||
identifier = "London, England"
|
||||
referenceType = "1">
|
||||
</LocationScenarioReference>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
||||
@@ -29,11 +29,17 @@ class AboutViewController: UIViewController {
|
||||
// MARK: - Setup
|
||||
|
||||
fileprivate func setup() {
|
||||
setupButtons()
|
||||
setupLocalization()
|
||||
setupNavigationBar()
|
||||
setupVersion()
|
||||
}
|
||||
|
||||
fileprivate func setupButtons() {
|
||||
makeBlue(button: licenseInformationButton)
|
||||
makeBlue(button: termsButton)
|
||||
}
|
||||
|
||||
fileprivate func setupLocalization() {
|
||||
navigationItem.title = Translation.AboutView.Title.String
|
||||
partnershipTextView.text = Translation.AboutView.PartnershipTextView.Text.String
|
||||
@@ -52,11 +58,11 @@ class AboutViewController: UIViewController {
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func doneButtonTapped(_ sender: AnyObject) {
|
||||
@IBAction func doneButtonTapped(_ sender: Any) {
|
||||
presentingViewController!.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@IBAction func termsButtonTapped(_ sender: AnyObject) {
|
||||
@IBAction func termsButtonTapped(_ sender: Any) {
|
||||
UIApplication.shared.openURL(URLs.Terms)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,12 +18,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
logInfo(name: "\(#function)", info: "Background fetch started...")
|
||||
var noData = true
|
||||
var allSuccess = true
|
||||
if let checkIns = CoreDataManager.sharedManager.fetchCheckIns() {
|
||||
if let checkIns = CoreDataManager.shared.fetchCheckIns() {
|
||||
checkIns.forEach({ (checkIn) in
|
||||
if let gpsFixes = checkIn.gpsFixes {
|
||||
if gpsFixes.count > 0 {
|
||||
noData = false
|
||||
let gpsFixController = GPSFixController(checkIn: checkIn)
|
||||
let gpsFixController = GPSFixController(checkIn: checkIn, coreDataManager: CoreDataManager.shared)
|
||||
gpsFixController.sendAll(completion: { (withSuccess) in
|
||||
allSuccess = allSuccess && withSuccess
|
||||
})
|
||||
@@ -54,7 +54,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
||||
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
||||
CoreDataManager.sharedManager.saveContext()
|
||||
CoreDataManager.shared.saveContext()
|
||||
}
|
||||
|
||||
func applicationWillEnterForeground(_ application: UIApplication) {
|
||||
@@ -67,7 +67,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||
CoreDataManager.sharedManager.saveContext()
|
||||
CoreDataManager.shared.saveContext()
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
@@ -91,12 +91,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
}
|
||||
|
||||
fileprivate func setupCoreData() {
|
||||
_ = CoreDataManager.sharedManager // Initialize core data, migrate database if needed, or delete if migration needed but not possible
|
||||
_ = CoreDataManager.shared // Initialize core data, migrate database if needed, or delete if migration needed but not possible
|
||||
}
|
||||
|
||||
fileprivate func setupNavigationBarApperance() {
|
||||
UINavigationBar.appearance().tintColor = Colors.NavigationBarTintColor
|
||||
UINavigationBar.appearance().titleTextAttributes = [NSForegroundColorAttributeName: Colors.NavigationBarTitleColor, NSFontAttributeName: Fonts.OpenSansBold17]
|
||||
}
|
||||
|
||||
fileprivate func setupPageControlApperance() {
|
||||
@@ -105,7 +104,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
}
|
||||
|
||||
fileprivate func setupSVProgressHUD() {
|
||||
SVProgressHUD.setDefaultMaskType(.gradient)
|
||||
SVProgressHUD.setDefaultMaskType(.clear)
|
||||
}
|
||||
|
||||
// MARK: - Helper
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// Copyright © 2016 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
"NSLocationAlwaysUsageDescription" = "In order to track your entire race your phone needs to continue tracking also when you switch off or lock the screen which helps save battery and avoids unintentionally stopping the tracking process.";
|
||||
"NSLocationWhenInUseUsageDescription" = "In order to track your entire race your phone needs to continue tracking also when you switch off or lock the screen which helps save battery and avoids unintentionally stopping the tracking process.";
|
||||
"NSLocationAlwaysAndWhenInUseUsageDescription" = "In order to track your entire race your phone needs to continue tracking also when you switch off or lock the screen which helps save battery and avoids unintentionally stopping the tracking process.";
|
||||
"NSCameraUsageDescription" = "The camera is required to perform a check-in using QR code.";
|
||||
"NSPhotoLibraryUsageDescription" = "To replace the Team Photo the app requires access to the photo library.";
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// BoatCheckIn+CoreDataProperties.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 13.03.18.
|
||||
// Copyright © 2018 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
extension BoatCheckIn {
|
||||
|
||||
@NSManaged public var boatID: String
|
||||
@NSManaged public var color: String
|
||||
@NSManaged public var sailID: String
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// BoatCheckIn+CoreDataClass.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 13.03.18.
|
||||
// Copyright © 2018 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
class BoatCheckIn: CheckIn {
|
||||
|
||||
override func updateWithCheckInData(checkInData: CheckInData) {
|
||||
super.updateWithCheckInData(checkInData: checkInData)
|
||||
boatID = checkInData.boatData.boatID
|
||||
color = checkInData.boatData.color
|
||||
name = checkInData.boatData.name
|
||||
sailID = checkInData.boatData.sailID
|
||||
}
|
||||
|
||||
func displayName() -> String {
|
||||
return name.count > 0 ? name : sailID
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// BoatData.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 13.03.18.
|
||||
// Copyright © 2017 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class BoatData: BaseData {
|
||||
|
||||
fileprivate enum Keys {
|
||||
static let Color = "color"
|
||||
static let ID = "id"
|
||||
static let Name = "name"
|
||||
static let SailID = "sailId"
|
||||
}
|
||||
|
||||
var boatID: String { get { return stringValue(forKey: Keys.ID) } }
|
||||
|
||||
var color: String { get { return stringValue(forKey: Keys.Color) } }
|
||||
|
||||
var name: String { get { return stringValue(forKey: Keys.Name) } }
|
||||
|
||||
var sailID: String { get { return stringValue(forKey: Keys.SailID) } }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// BoatSessionController.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 13.03.18.
|
||||
// Copyright © 2018 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BoatSessionController: SessionController {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// BoatSessionViewController.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 13.03.18.
|
||||
// Copyright © 2018 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BoatSessionViewController: SessionViewController {
|
||||
|
||||
struct BoatSessionSegue {
|
||||
static let EmbedBoat = "EmbedBoat"
|
||||
}
|
||||
|
||||
weak var boatCheckIn: BoatCheckIn!
|
||||
weak var boatCoreDataManager: CoreDataManager!
|
||||
weak var boatViewController: BoatViewController?
|
||||
|
||||
// MARK: - Segues
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
super.prepare(for: segue, sender: sender)
|
||||
if (segue.identifier == BoatSessionSegue.EmbedBoat) {
|
||||
if let boatViewController = segue.destination as? BoatViewController {
|
||||
boatViewController.boatCheckIn = boatCheckIn
|
||||
boatViewController.boatSessionController = boatSessionController
|
||||
boatViewController.boatCoreDataManager = boatCoreDataManager
|
||||
self.boatViewController = boatViewController
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
lazy var boatSessionController: BoatSessionController = {
|
||||
return BoatSessionController(checkIn: self.boatCheckIn, coreDataManager: self.boatCoreDataManager)
|
||||
}()
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// BoatViewController.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 13.03.18.
|
||||
// Copyright © 2018 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BoatViewController: UIViewController {
|
||||
|
||||
weak var boatCheckIn: BoatCheckIn!
|
||||
weak var boatCoreDataManager: CoreDataManager!
|
||||
weak var boatSessionController: BoatSessionController!
|
||||
|
||||
@IBOutlet weak var boatNameView: UIView!
|
||||
@IBOutlet weak var boatNameLabel: UILabel!
|
||||
|
||||
// MARK: - Refresh
|
||||
|
||||
func refresh(_ animated: Bool) {
|
||||
refreshBoatNameLabel(animated)
|
||||
}
|
||||
|
||||
fileprivate func refreshBoatNameLabel(_ animated: Bool) {
|
||||
if (animated) {
|
||||
UIView.animate(withDuration: 0.5) { self.refreshBoatNameLabel() }
|
||||
} else {
|
||||
refreshBoatNameLabel()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func refreshBoatNameLabel() {
|
||||
boatNameLabel.text = boatCheckIn.displayName()
|
||||
if let color = UIColor.init(hexString: boatCheckIn.color) {
|
||||
boatNameView.backgroundColor = color
|
||||
boatNameLabel.textColor = UIColor.init(contrastColorFor: color)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,7 @@ extension CheckIn {
|
||||
|
||||
@NSManaged var serverURL: String
|
||||
@NSManaged var name: String
|
||||
@NSManaged var isTraining: NSNumber
|
||||
@NSManaged var event: Event
|
||||
@NSManaged var gpsFixes: NSSet?
|
||||
@NSManaged var leaderboard: Leaderboard
|
||||
|
||||
@@ -12,12 +12,8 @@ import CoreData
|
||||
@objc(CheckIn)
|
||||
class CheckIn: NSManagedObject {
|
||||
|
||||
func initialize() {
|
||||
event = CoreDataManager.sharedManager.newEvent(checkIn: self)
|
||||
leaderboard = CoreDataManager.sharedManager.newLeaderboard(checkIn: self)
|
||||
}
|
||||
|
||||
func updateWithCheckInData(checkInData: CheckInData) {
|
||||
isTraining = NSNumber(booleanLiteral: checkInData.isTraining)
|
||||
serverURL = checkInData.serverURL
|
||||
event.updateWithEventData(eventData: checkInData.eventData)
|
||||
leaderboard.updateWithLeaderboardData(leaderboardData: checkInData.leaderboardData)
|
||||
|
||||
@@ -10,131 +10,200 @@ import Foundation
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
|
||||
@objc protocol CheckInControllerDelegate {
|
||||
|
||||
func checkInController(_ sender: CheckInController, show alertController: UIAlertController)
|
||||
// MARK: CheckInControllerDelegate
|
||||
|
||||
protocol CheckInControllerDelegate: class {
|
||||
|
||||
func checkInController(
|
||||
_ controller: CheckInController, postBoatCheckIn
|
||||
checkInData: CheckInData,
|
||||
success: @escaping (_ checkIn: CheckIn) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
|
||||
func checkInController(
|
||||
_ controller: CheckInController, postCompetitorCheckIn
|
||||
checkInData: CheckInData,
|
||||
success: @escaping (_ checkIn: CheckIn) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
|
||||
func checkInController(
|
||||
_ controller: CheckInController, postMarkCheckIn
|
||||
checkInData: CheckInData,
|
||||
success: @escaping (_ checkIn: CheckIn) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
|
||||
}
|
||||
|
||||
// MARK: CheckInControllerDelegate (default implementation)
|
||||
|
||||
extension CheckInControllerDelegate {
|
||||
|
||||
func checkInController(
|
||||
_ controller: CheckInController, postBoatCheckIn
|
||||
checkInData: CheckInData,
|
||||
success: @escaping (_ checkIn: CheckIn) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
controller.postCheckIn(checkInData: checkInData, success: success, failure: failure)
|
||||
}
|
||||
|
||||
func checkInController(
|
||||
_ controller: CheckInController, postCompetitorCheckIn
|
||||
checkInData: CheckInData,
|
||||
success: @escaping (_ checkIn: CheckIn) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
controller.postCheckIn(checkInData: checkInData, success: success, failure: failure)
|
||||
}
|
||||
|
||||
func checkInController(
|
||||
_ controller: CheckInController, postMarkCheckIn
|
||||
checkInData: CheckInData,
|
||||
success: @escaping (_ checkIn: CheckIn) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
controller.postCheckIn(checkInData: checkInData, success: success, failure: failure)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class CheckInController : NSObject {
|
||||
|
||||
var delegate: CheckInControllerDelegate?
|
||||
enum CheckInControllerError: Error {
|
||||
case cancelled
|
||||
}
|
||||
|
||||
fileprivate var requestManager = RequestManager()
|
||||
fileprivate unowned let coreDataManager: CoreDataManager
|
||||
|
||||
weak var viewController: UIViewController?
|
||||
weak var delegate: CheckInControllerDelegate?
|
||||
|
||||
init(coreDataManager: CoreDataManager) {
|
||||
self.coreDataManager = coreDataManager
|
||||
super.init()
|
||||
}
|
||||
|
||||
// MARK: - CheckIn
|
||||
|
||||
func checkIn(checkInData: CheckInData, completion: @escaping (_ withSuccess: Bool) -> Void) {
|
||||
SVProgressHUD.show()
|
||||
requestManager = RequestManager(baseURLString: checkInData.serverURL)
|
||||
requestManager.getCheckInData(checkInData: checkInData, success:
|
||||
{ (checkInData) in
|
||||
SVProgressHUD.popActivity()
|
||||
self.checkInSuccess(checkInData: checkInData, completion: completion)
|
||||
}, failure: { (error) in
|
||||
SVProgressHUD.popActivity()
|
||||
self.checkInFailure(error: error, completion: completion)
|
||||
}
|
||||
)
|
||||
func checkInWithViewController(
|
||||
_ viewController: UIViewController?,
|
||||
checkInData: CheckInData,
|
||||
success: @escaping (_ checkIn: CheckIn) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
self.viewController = viewController
|
||||
collectCheckInData(checkInData: checkInData, success: success, failure: failure)
|
||||
}
|
||||
|
||||
fileprivate func checkInSuccess(checkInData: CheckInData, completion: @escaping (_ withSuccess: Bool) -> Void) {
|
||||
// MARK: - CollectCheckInData
|
||||
|
||||
fileprivate func collectCheckInData(
|
||||
checkInData: CheckInData,
|
||||
success: @escaping (_ checkIn: CheckIn) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
SVProgressHUD.show()
|
||||
let checkInDataCollector = CheckInDataCollector(checkInData: checkInData)
|
||||
checkInDataCollector.collect(checkInData: checkInData, success: { [weak self] checkInData in
|
||||
SVProgressHUD.popActivity()
|
||||
self?.collectCheckInDataSuccess(checkInData: checkInData, success: success, failure: failure)
|
||||
}) { [weak self] error in
|
||||
SVProgressHUD.popActivity()
|
||||
self?.didFailCheckIn(withError: error, failure: failure)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func collectCheckInDataSuccess(
|
||||
checkInData: CheckInData,
|
||||
success: @escaping (_ checkIn: CheckIn) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
switch checkInData.type {
|
||||
case .boat:
|
||||
delegate?.checkInController(self, postBoatCheckIn: checkInData, success: success, failure: failure)
|
||||
break
|
||||
case .competitor:
|
||||
let alertController = UIAlertController(
|
||||
title: String(format: Translation.CheckInController.WelcomeAlert.Title.String, checkInData.competitorData.name),
|
||||
message: String(format: Translation.CheckInController.WelcomeAlert.Message.String, checkInData.competitorData.sailID),
|
||||
preferredStyle: .alert
|
||||
)
|
||||
let okAction = UIAlertAction(title: Translation.Common.OK.String, style: .default) { (action) in
|
||||
self.postCheckIn(checkInData: checkInData, completion: completion)
|
||||
}
|
||||
let cancelAction = UIAlertAction(title: Translation.CheckInController.WelcomeAlert.CancelAction.Title.String, style: .cancel) { (action) in
|
||||
self.checkInDidFinish(withSuccess: false, completion: completion)
|
||||
}
|
||||
alertController.addAction(okAction)
|
||||
alertController.addAction(cancelAction)
|
||||
showCheckInAlert(alertController: alertController)
|
||||
delegate?.checkInController(self, postCompetitorCheckIn: checkInData, success: success, failure: failure)
|
||||
break
|
||||
case .mark:
|
||||
self.postCheckIn(checkInData: checkInData, completion: completion)
|
||||
delegate?.checkInController(self, postMarkCheckIn: checkInData, success: success, failure: failure)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func checkInFailure(error: Error, completion: @escaping (_ withSuccess: Bool) -> Void) {
|
||||
let alertController = UIAlertController(
|
||||
title: Translation.Common.Error.String,
|
||||
message: error.localizedDescription,
|
||||
preferredStyle: .alert
|
||||
)
|
||||
let okAction = UIAlertAction(title: Translation.Common.OK.String, style: .default) { (action) in
|
||||
self.checkInDidFinish(withSuccess: false, completion: completion)
|
||||
}
|
||||
alertController.addAction(okAction)
|
||||
showCheckInAlert(alertController: alertController)
|
||||
}
|
||||
|
||||
// MARK: - PostCheckIn
|
||||
|
||||
fileprivate func postCheckIn(checkInData: CheckInData, completion: @escaping (_ withSuccess: Bool) -> Void) {
|
||||
func postCheckIn(
|
||||
checkInData: CheckInData,
|
||||
success: @escaping (_ checkIn: CheckIn) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
SVProgressHUD.show()
|
||||
requestManager.postCheckIn(checkInData: checkInData, success: { () -> Void in
|
||||
SVProgressHUD.popActivity()
|
||||
self.postCheckInSuccess(checkInData: checkInData, completion: completion)
|
||||
}, failure: { (error) -> Void in
|
||||
SVProgressHUD.popActivity()
|
||||
self.postCheckInFailure(error: error, completion: completion)
|
||||
}
|
||||
)
|
||||
let checkInRequestManager = CheckInRequestManager(baseURLString: checkInData.serverURL)
|
||||
checkInRequestManager.postCheckIn(checkInData: checkInData, success: { [weak self] () in
|
||||
SVProgressHUD.popActivity()
|
||||
self?.postCheckInSuccess(checkInData: checkInData, success: success)
|
||||
}) { [weak self] error in
|
||||
SVProgressHUD.popActivity()
|
||||
self?.didFailCheckIn(withError: error, failure: failure)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func postCheckInSuccess(checkInData: CheckInData, completion: (_ withSuccess: Bool) -> Void) {
|
||||
fileprivate func postCheckInSuccess(
|
||||
checkInData: CheckInData,
|
||||
success: (_ checkIn: CheckIn) -> Void)
|
||||
{
|
||||
switch checkInData.type {
|
||||
case .boat:
|
||||
let boatCheckIn = coreDataManager.fetchBoatCheckIn(
|
||||
eventID: checkInData.eventID,
|
||||
leaderboardName: checkInData.leaderboardName,
|
||||
boatID: checkInData.boatID!
|
||||
) ?? coreDataManager.newBoatCheckIn()
|
||||
boatCheckIn.updateWithCheckInData(checkInData: checkInData)
|
||||
coreDataManager.saveContext()
|
||||
didFinishCheckIn(boatCheckIn, success: success)
|
||||
break
|
||||
case .competitor:
|
||||
let competitorCheckIn = CoreDataManager.sharedManager.fetchCompetitorCheckIn(
|
||||
let competitorCheckIn = coreDataManager.fetchCompetitorCheckIn(
|
||||
eventID: checkInData.eventID,
|
||||
leaderboardName: checkInData.leaderboardName,
|
||||
competitorID: checkInData.competitorID!
|
||||
) ?? CoreDataManager.sharedManager.newCompetitorCheckIn()
|
||||
) ?? coreDataManager.newCompetitorCheckIn()
|
||||
competitorCheckIn.updateWithCheckInData(checkInData: checkInData)
|
||||
CoreDataManager.sharedManager.saveContext()
|
||||
checkInDidFinish(withSuccess: true, completion: completion)
|
||||
coreDataManager.saveContext()
|
||||
didFinishCheckIn(competitorCheckIn, success: success)
|
||||
break
|
||||
case .mark:
|
||||
let markCheckIn = CoreDataManager.sharedManager.fetchMarkCheckIn(
|
||||
let markCheckIn = coreDataManager.fetchMarkCheckIn(
|
||||
eventID: checkInData.eventID,
|
||||
leaderboardName: checkInData.leaderboardName,
|
||||
markID: checkInData.markID!
|
||||
) ?? CoreDataManager.sharedManager.newMarkCheckIn()
|
||||
) ?? coreDataManager.newMarkCheckIn()
|
||||
markCheckIn.updateWithCheckInData(checkInData: checkInData)
|
||||
CoreDataManager.sharedManager.saveContext()
|
||||
checkInDidFinish(withSuccess: true, completion: completion)
|
||||
coreDataManager.saveContext()
|
||||
didFinishCheckIn(markCheckIn, success: success)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func postCheckInFailure(error: Error, completion: @escaping (_ withSuccess: Bool) -> Void) {
|
||||
let alertController = UIAlertController(
|
||||
title: Translation.Common.Error.String,
|
||||
message: error.localizedDescription,
|
||||
preferredStyle: .alert
|
||||
)
|
||||
let okAction = UIAlertAction(title: Translation.Common.OK.String, style: .default) { (action) in
|
||||
self.checkInDidFinish(withSuccess: false, completion: completion)
|
||||
// MARK: - Finish, Cancel, Fail
|
||||
|
||||
func didFinishCheckIn(_ checkIn: CheckIn, success:(_ checkIn: CheckIn) -> Void) {
|
||||
success(checkIn)
|
||||
}
|
||||
|
||||
func didCancelCheckIn(failure:(_ error: Error) -> Void) {
|
||||
failure(CheckInControllerError.cancelled)
|
||||
}
|
||||
|
||||
func didFailCheckIn(withError error: Error, failure: @escaping (_ error: Error) -> Void) {
|
||||
let alertController = UIAlertController(title: Translation.Common.Error.String, message: error.localizedDescription, preferredStyle: .alert)
|
||||
let okAction = UIAlertAction(title: Translation.Common.OK.String, style: .default) { action in
|
||||
failure(error)
|
||||
}
|
||||
alertController.addAction(okAction)
|
||||
showCheckInAlert(alertController: alertController)
|
||||
}
|
||||
|
||||
fileprivate func checkInDidFinish(withSuccess success: Bool, completion: (_ withSuccess: Bool) -> Void) {
|
||||
completion(success)
|
||||
}
|
||||
|
||||
// MARK: - Controller
|
||||
|
||||
fileprivate func showCheckInAlert(alertController: UIAlertController) {
|
||||
self.delegate?.checkInController(self, show: alertController)
|
||||
viewController?.present(alertController, animated: true)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,9 +8,10 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
open class CheckInData: NSObject {
|
||||
class CheckInData: NSObject {
|
||||
|
||||
fileprivate struct ItemNames {
|
||||
static let BoatID = "boat_id"
|
||||
static let EventID = "event_id"
|
||||
static let LeaderboardName = "leaderboard_name"
|
||||
static let CompetitorID = "competitor_id"
|
||||
@@ -18,6 +19,7 @@ open class CheckInData: NSObject {
|
||||
}
|
||||
|
||||
public enum CheckInDataType {
|
||||
case boat
|
||||
case competitor
|
||||
case mark
|
||||
}
|
||||
@@ -25,12 +27,15 @@ open class CheckInData: NSObject {
|
||||
let serverURL: String
|
||||
let eventID: String
|
||||
let leaderboardName: String
|
||||
let boatID: String?
|
||||
let competitorID: String?
|
||||
let markID: String?
|
||||
let isTraining: Bool
|
||||
let type: CheckInDataType
|
||||
|
||||
var eventData = EventData()
|
||||
var leaderboardData = LeaderboardData()
|
||||
var boatData = BoatData()
|
||||
var competitorData = CompetitorData()
|
||||
var markData = MarkData()
|
||||
var teamImageURL: String?
|
||||
@@ -38,13 +43,16 @@ open class CheckInData: NSObject {
|
||||
init(serverURL: String,
|
||||
eventID: String,
|
||||
leaderboardName: String,
|
||||
competitorID: String)
|
||||
competitorID: String,
|
||||
isTraining: Bool)
|
||||
{
|
||||
self.boatID = nil
|
||||
self.competitorID = competitorID
|
||||
self.eventID = eventID
|
||||
self.leaderboardName = leaderboardName
|
||||
self.markID = nil
|
||||
self.serverURL = serverURL
|
||||
self.isTraining = isTraining
|
||||
self.type = CheckInDataType.competitor
|
||||
super.init()
|
||||
}
|
||||
@@ -52,23 +60,44 @@ open class CheckInData: NSObject {
|
||||
init(serverURL: String,
|
||||
eventID: String,
|
||||
leaderboardName: String,
|
||||
markID: String)
|
||||
markID: String,
|
||||
isTraining: Bool)
|
||||
{
|
||||
self.boatID = nil
|
||||
self.competitorID = nil
|
||||
self.eventID = eventID
|
||||
self.leaderboardName = leaderboardName
|
||||
self.markID = markID
|
||||
self.serverURL = serverURL
|
||||
self.isTraining = isTraining
|
||||
type = CheckInDataType.mark
|
||||
super.init()
|
||||
}
|
||||
|
||||
init(serverURL: String,
|
||||
eventID: String,
|
||||
leaderboardName: String,
|
||||
boatID: String,
|
||||
isTraining: Bool)
|
||||
{
|
||||
self.boatID = boatID
|
||||
self.competitorID = nil
|
||||
self.eventID = eventID
|
||||
self.leaderboardName = leaderboardName
|
||||
self.markID = nil
|
||||
self.serverURL = serverURL
|
||||
self.isTraining = isTraining
|
||||
type = CheckInDataType.boat
|
||||
super.init()
|
||||
}
|
||||
|
||||
convenience init(competitorCheckIn: CompetitorCheckIn) {
|
||||
self.init(
|
||||
serverURL: competitorCheckIn.serverURL,
|
||||
eventID: competitorCheckIn.event.eventID,
|
||||
leaderboardName: competitorCheckIn.leaderboard.name,
|
||||
competitorID: competitorCheckIn.competitorID
|
||||
competitorID: competitorCheckIn.competitorID,
|
||||
isTraining: competitorCheckIn.isTraining.boolValue
|
||||
)
|
||||
}
|
||||
|
||||
@@ -77,7 +106,8 @@ open class CheckInData: NSObject {
|
||||
serverURL: markCheckIn.serverURL,
|
||||
eventID: markCheckIn.event.eventID,
|
||||
leaderboardName: markCheckIn.leaderboard.name,
|
||||
markID: markCheckIn.markID
|
||||
markID: markCheckIn.markID,
|
||||
isTraining: markCheckIn.isTraining.boolValue
|
||||
)
|
||||
}
|
||||
|
||||
@@ -100,14 +130,24 @@ open class CheckInData: NSObject {
|
||||
serverURL: serverURL,
|
||||
eventID: eventID,
|
||||
leaderboardName: leaderboardName,
|
||||
competitorID: competitorID
|
||||
competitorID: competitorID,
|
||||
isTraining: false
|
||||
)
|
||||
} else if let markID = CheckInData.queryItemValue(queryItems: queryItems, itemName: ItemNames.MarkID) {
|
||||
self.init(
|
||||
serverURL: serverURL,
|
||||
eventID: eventID,
|
||||
leaderboardName: leaderboardName,
|
||||
markID: markID
|
||||
markID: markID,
|
||||
isTraining: false
|
||||
)
|
||||
} else if let boatID = CheckInData.queryItemValue(queryItems: queryItems, itemName: ItemNames.BoatID) {
|
||||
self.init(
|
||||
serverURL: serverURL,
|
||||
eventID: eventID,
|
||||
leaderboardName: leaderboardName,
|
||||
boatID: boatID,
|
||||
isTraining: false
|
||||
)
|
||||
} else {
|
||||
logError(name: "\(#function)", error: "unknown check-in type")
|
||||
@@ -132,7 +172,20 @@ open class CheckInData: NSObject {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
convenience init?(createTrainingData: CreateTrainingData) {
|
||||
guard let eventID = createTrainingData.createEventData?.eventID else { return nil }
|
||||
guard let leaderboardName = createTrainingData.createEventData?.leaderboardName else { return nil }
|
||||
guard let competitorID = createTrainingData.competitorID else { return nil }
|
||||
self.init(
|
||||
serverURL: createTrainingData.serverURL,
|
||||
eventID: eventID,
|
||||
leaderboardName: leaderboardName,
|
||||
competitorID: competitorID,
|
||||
isTraining: true
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Helper
|
||||
|
||||
class fileprivate func queryItemValue(queryItems: [URLQueryItem]?, itemName: String) -> String? {
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
//
|
||||
// CheckInDataCollector.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 08.09.17.
|
||||
// Copyright © 2017 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
enum CheckInDataCollectorError: Error {
|
||||
case checkInDataIsIncomplete
|
||||
}
|
||||
|
||||
extension CheckInDataCollectorError: LocalizedError {
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .checkInDataIsIncomplete:
|
||||
return Translation.CheckInDataCollectorError.CheckInDataIsIncomplete.String
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CheckInDataCollector: NSObject {
|
||||
|
||||
fileprivate let checkInRequestManager: CheckInRequestManager
|
||||
|
||||
init(checkInData: CheckInData) {
|
||||
checkInRequestManager = CheckInRequestManager(baseURLString: checkInData.serverURL)
|
||||
super.init()
|
||||
}
|
||||
|
||||
func collect(
|
||||
checkInData: CheckInData,
|
||||
success: @escaping (_ collector: CheckInData) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
getEventData(collector: checkInData, success: success, failure: failure)
|
||||
}
|
||||
|
||||
// MARK: - Event
|
||||
|
||||
fileprivate func getEventData(
|
||||
collector: CheckInData,
|
||||
success: @escaping (_ collector: CheckInData) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
checkInRequestManager.getEvent(eventID: collector.eventID, success: { (eventData) in
|
||||
collector.eventData = eventData
|
||||
self.getLeaderboardData(collector: collector, success: success, failure: failure)
|
||||
}) { (error) in
|
||||
failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Leaderboard
|
||||
|
||||
fileprivate func getLeaderboardData(
|
||||
collector: CheckInData,
|
||||
success: @escaping (_ collector: CheckInData) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
checkInRequestManager.getLeaderboard(leaderboardName: collector.leaderboardName, success: { (leaderboardData) in
|
||||
collector.leaderboardData = leaderboardData
|
||||
self.getLeaderboardDataSuccess(collector: collector, success: success, failure: failure)
|
||||
}) { (error) in
|
||||
failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func getLeaderboardDataSuccess(
|
||||
collector: CheckInData,
|
||||
success: @escaping (_ collector: CheckInData) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
if let boatID = collector.boatID {
|
||||
getBoatData(collector: collector, boatID: boatID, success: success, failure: failure)
|
||||
} else if let competitorID = collector.competitorID {
|
||||
getCompetitorData(collector: collector, competitorID: competitorID, success: success, failure: failure)
|
||||
} else if let markID = collector.markID {
|
||||
getMarkData(collector: collector, markID: markID, success: success, failure: failure)
|
||||
} else {
|
||||
failure(CheckInDataCollectorError.checkInDataIsIncomplete)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Boat
|
||||
|
||||
fileprivate func getBoatData(
|
||||
collector: CheckInData,
|
||||
boatID: String,
|
||||
success: @escaping (_ collector: CheckInData) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
checkInRequestManager.getBoat(boatID: boatID, success: { (boatData) in
|
||||
collector.boatData = boatData
|
||||
success(collector)
|
||||
}) { error in
|
||||
failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Competitor
|
||||
|
||||
fileprivate func getCompetitorData(
|
||||
collector: CheckInData,
|
||||
competitorID: String,
|
||||
success: @escaping (_ collector: CheckInData) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
checkInRequestManager.getCompetitor(competitorID: competitorID, success: { (competitorData) in
|
||||
collector.competitorData = competitorData
|
||||
self.getCompetitorDataSuccess(
|
||||
collector: collector,
|
||||
competitorID: competitorID,
|
||||
success: success,
|
||||
failure: failure
|
||||
)
|
||||
}) { (error) in
|
||||
failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func getCompetitorDataSuccess(
|
||||
collector: CheckInData,
|
||||
competitorID: String,
|
||||
success: @escaping (_ collector: CheckInData) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
checkInRequestManager.getTeamImageURL(competitorID: competitorID, result: { (imageURL) in
|
||||
collector.teamImageURL = imageURL
|
||||
success(collector)
|
||||
})
|
||||
}
|
||||
|
||||
// MARK: - Mark
|
||||
|
||||
fileprivate func getMarkData(
|
||||
collector: CheckInData,
|
||||
markID: String,
|
||||
success: @escaping (_ collector: CheckInData) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
checkInRequestManager.getMark(leaderboardName: collector.leaderboardName, markID: markID, success: { (markData) in
|
||||
collector.markData = markData
|
||||
success(collector)
|
||||
}) { error in
|
||||
failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// RequestManager.swift
|
||||
// CheckInRequestManager.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 25.05.16.
|
||||
@@ -9,9 +9,9 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
enum RequestManagerError: Error {
|
||||
case checkInDataIsIncomplete
|
||||
enum CheckInRequestManagerError: Error {
|
||||
case communicationFailed
|
||||
case getBoatFailed
|
||||
case getCompetitorFailed
|
||||
case getEventFailed
|
||||
case getLeaderboardFailed
|
||||
@@ -24,42 +24,43 @@ enum RequestManagerError: Error {
|
||||
case teamImageURLIsInvalid
|
||||
}
|
||||
|
||||
extension RequestManagerError: LocalizedError {
|
||||
extension CheckInRequestManagerError: LocalizedError {
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .checkInDataIsIncomplete:
|
||||
return Translation.RequestManagerError.CheckInDataIsIncomplete.String
|
||||
case .communicationFailed:
|
||||
return Translation.RequestManagerError.CommunicationFailed.String
|
||||
return Translation.CheckInRequestManagerError.CommunicationFailed.String
|
||||
case .getBoatFailed:
|
||||
return Translation.CheckInRequestManagerError.GetBoatFailed.String
|
||||
case .getCompetitorFailed:
|
||||
return Translation.RequestManagerError.GetCompetitorFailed.String
|
||||
return Translation.CheckInRequestManagerError.GetCompetitorFailed.String
|
||||
case .getEventFailed:
|
||||
return Translation.RequestManagerError.GetEventFailed.String
|
||||
return Translation.CheckInRequestManagerError.GetEventFailed.String
|
||||
case .getLeaderboardFailed:
|
||||
return Translation.RequestManagerError.GetLeaderboardFailed.String
|
||||
return Translation.CheckInRequestManagerError.GetLeaderboardFailed.String
|
||||
case .getMarkFailed:
|
||||
return Translation.RequestManagerError.GetMarkFailed.String
|
||||
return Translation.CheckInRequestManagerError.GetMarkFailed.String
|
||||
case .getTeamFailed:
|
||||
return Translation.RequestManagerError.GetTeamFailed.String
|
||||
return Translation.CheckInRequestManagerError.GetTeamFailed.String
|
||||
case .postCheckInFailed:
|
||||
return Translation.RequestManagerError.PostCheckInFailed.String
|
||||
return Translation.CheckInRequestManagerError.PostCheckInFailed.String
|
||||
case .postCheckOutFailed:
|
||||
return Translation.RequestManagerError.PostCheckOutFailed.String
|
||||
return Translation.CheckInRequestManagerError.PostCheckOutFailed.String
|
||||
case .postGPSFixFailed:
|
||||
return Translation.RequestManagerError.PostGPSFixFailed.String
|
||||
return Translation.CheckInRequestManagerError.PostGPSFixFailed.String
|
||||
case .postTeamImageFailed:
|
||||
return Translation.RequestManagerError.PostTeamImageFailed.String
|
||||
return Translation.CheckInRequestManagerError.PostTeamImageFailed.String
|
||||
case .teamImageURLIsInvalid:
|
||||
return Translation.RequestManagerError.TeamImageURLIsInvalid.String
|
||||
return Translation.CheckInRequestManagerError.TeamImageURLIsInvalid.String
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RequestManager: NSObject {
|
||||
class CheckInRequestManager: NSObject {
|
||||
|
||||
fileprivate let basePathString = "/sailingserver/api/v1"
|
||||
|
||||
fileprivate enum BodyKeys {
|
||||
static let BoatID = "boatId"
|
||||
static let CompetitorID = "competitorId"
|
||||
static let DeviceType = "deviceType"
|
||||
static let DeviceUUID = "deviceUuid"
|
||||
@@ -91,133 +92,16 @@ class RequestManager: NSObject {
|
||||
self.baseURLString = baseURLString
|
||||
manager = AFHTTPRequestOperationManager(baseURL: URL(string: baseURLString))
|
||||
manager.requestSerializer = AFJSONRequestSerializer() as AFHTTPRequestSerializer
|
||||
manager.requestSerializer.timeoutInterval = Application.RequestTimeout
|
||||
manager.responseSerializer = AFJSONResponseSerializer() as AFHTTPResponseSerializer
|
||||
sessionManager = AFURLSessionManager(sessionConfiguration: URLSessionConfiguration.default)
|
||||
sessionManager.responseSerializer = AFJSONResponseSerializer() as AFHTTPResponseSerializer
|
||||
super.init()
|
||||
}
|
||||
|
||||
// MARK: - CheckIn
|
||||
|
||||
func getCheckInData(
|
||||
checkInData: CheckInData,
|
||||
success: @escaping (_ checkInData: CheckInData) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
getEventData(checkInData: checkInData, success: success, failure: failure)
|
||||
}
|
||||
|
||||
fileprivate func getEventData(
|
||||
checkInData: CheckInData,
|
||||
success: @escaping (_ checkInData: CheckInData) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
getEvent(
|
||||
eventID: checkInData.eventID,
|
||||
success: { (data) in self.getEventDataSuccess(eventData: data, checkInData: checkInData, success: success, failure: failure) },
|
||||
failure: { (error) in failure(error) }
|
||||
)
|
||||
}
|
||||
|
||||
fileprivate func getEventDataSuccess(
|
||||
eventData: EventData,
|
||||
checkInData: CheckInData,
|
||||
success: @escaping (_ checkInData: CheckInData) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
checkInData.eventData = eventData
|
||||
getLeaderboardData(checkInData: checkInData, success: success, failure: failure)
|
||||
}
|
||||
|
||||
fileprivate func getLeaderboardData(
|
||||
checkInData: CheckInData,
|
||||
success: @escaping (_ checkInData: CheckInData) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
getLeaderboard(
|
||||
leaderboardName: checkInData.leaderboardName,
|
||||
success: { (data) in self.getLeaderboardDataSuccess(leaderboardData: data, checkInData: checkInData, success: success, failure: failure) },
|
||||
failure: { (error) in failure(error) }
|
||||
)
|
||||
}
|
||||
|
||||
fileprivate func getLeaderboardDataSuccess(
|
||||
leaderboardData: LeaderboardData,
|
||||
checkInData: CheckInData,
|
||||
success: @escaping (_ checkInData: CheckInData) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
checkInData.leaderboardData = leaderboardData
|
||||
if (checkInData.competitorID != nil) {
|
||||
getCompetitorData(checkInData: checkInData, competitorID: checkInData.competitorID!, success: success, failure: failure)
|
||||
} else if (checkInData.markID != nil) {
|
||||
getMarkData(checkInData: checkInData, markID: checkInData.markID!, success: success, failure: failure)
|
||||
} else {
|
||||
failure(RequestManagerError.checkInDataIsIncomplete)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func getCompetitorData(
|
||||
checkInData: CheckInData,
|
||||
competitorID: String,
|
||||
success: @escaping (_ checkInData: CheckInData) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
getCompetitor(competitorID: competitorID,
|
||||
success: { (data) in
|
||||
self.getCompetitorDataSuccess(
|
||||
competitorData: data,
|
||||
checkInData: checkInData,
|
||||
competitorID: competitorID,
|
||||
success: success,
|
||||
failure: failure
|
||||
)},
|
||||
failure: { (error) in
|
||||
failure(error)
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate func getCompetitorDataSuccess(
|
||||
competitorData: CompetitorData,
|
||||
checkInData: CheckInData,
|
||||
competitorID: String,
|
||||
success: @escaping (_ checkInData: CheckInData) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
checkInData.competitorData = competitorData
|
||||
getTeamImageURL(competitorID: competitorID, result: { (imageURL) in
|
||||
checkInData.teamImageURL = imageURL
|
||||
success(checkInData)
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate func getMarkData(
|
||||
checkInData: CheckInData,
|
||||
markID: String,
|
||||
success: @escaping (_ checkInData: CheckInData) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
getMark(
|
||||
leaderboardName: checkInData.leaderboardName,
|
||||
markID: markID,
|
||||
success: { (data) in self.getMarkDataSuccess(markData: data, checkInData: checkInData, success: success, failure: failure) },
|
||||
failure: { (error) in failure(error) }
|
||||
)
|
||||
}
|
||||
|
||||
fileprivate func getMarkDataSuccess(
|
||||
markData: MarkData,
|
||||
checkInData: CheckInData,
|
||||
success: (_ checkInData: CheckInData) -> Void,
|
||||
failure: (_ error: Error) -> Void)
|
||||
{
|
||||
checkInData.markData = markData
|
||||
success(checkInData)
|
||||
}
|
||||
|
||||
// MARK: - Event
|
||||
|
||||
fileprivate func getEvent(
|
||||
func getEvent(
|
||||
eventID: String,
|
||||
success: @escaping (_ eventData: EventData) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
@@ -239,12 +123,12 @@ class RequestManager: NSObject {
|
||||
|
||||
fileprivate func getEventFailure(error: Error, failure: (_ error: Error) -> Void) -> Void {
|
||||
logError(name: "\(#function)", error: error)
|
||||
failure(RequestManagerError.getEventFailed)
|
||||
failure(CheckInRequestManagerError.getEventFailed)
|
||||
}
|
||||
|
||||
// MARK: - Leaderboard
|
||||
|
||||
fileprivate func getLeaderboard(
|
||||
func getLeaderboard(
|
||||
leaderboardName: String,
|
||||
success: @escaping (_ leaderboardData: LeaderboardData) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
@@ -266,12 +150,39 @@ class RequestManager: NSObject {
|
||||
|
||||
fileprivate func getLeaderboardFailure(error: Error, failure: (_ error: Error) -> Void) {
|
||||
logError(name: "\(#function)", error: error)
|
||||
failure(RequestManagerError.getLeaderboardFailed)
|
||||
failure(CheckInRequestManagerError.getLeaderboardFailed)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Boat
|
||||
|
||||
func getBoat(
|
||||
boatID: String,
|
||||
success: @escaping (_ boatData: BoatData) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
let encodedBoatID = boatID.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlPathAllowed) ?? ""
|
||||
let urlString = "\(basePathString)/boats/\(encodedBoatID)"
|
||||
manager.get(
|
||||
urlString,
|
||||
parameters: nil,
|
||||
success: { (requestOperation, responseObject) in self.getBoatSuccess(responseObject: responseObject, success: success) },
|
||||
failure: { (requestOperation, error) in self.getBoatFailure(error: error, failure: failure) }
|
||||
)
|
||||
}
|
||||
|
||||
fileprivate func getBoatSuccess(responseObject: Any, success: (_ boatData: BoatData) -> Void) {
|
||||
logInfo(name: "\(#function)", info: responseObjectToString(responseObject: responseObject))
|
||||
success(BoatData(dictionary: responseObject as? [String: AnyObject]))
|
||||
}
|
||||
|
||||
fileprivate func getBoatFailure(error: Error, failure: (_ error: Error) -> Void) {
|
||||
logError(name: "\(#function)", error: error)
|
||||
failure(CheckInRequestManagerError.getBoatFailed)
|
||||
}
|
||||
|
||||
// MARK: - Competitor
|
||||
|
||||
fileprivate func getCompetitor(
|
||||
func getCompetitor(
|
||||
competitorID: String,
|
||||
success: @escaping (_ competitorData: CompetitorData) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
@@ -293,12 +204,12 @@ class RequestManager: NSObject {
|
||||
|
||||
fileprivate func getCompetitorFailure(error: Error, failure: (_ error: Error) -> Void) {
|
||||
logError(name: "\(#function)", error: error)
|
||||
failure(RequestManagerError.getCompetitorFailed)
|
||||
failure(CheckInRequestManagerError.getCompetitorFailed)
|
||||
}
|
||||
|
||||
// MARK: - Mark
|
||||
|
||||
fileprivate func getMark(
|
||||
func getMark(
|
||||
leaderboardName: String,
|
||||
markID: String,
|
||||
success: @escaping (_ markData: MarkData) -> Void,
|
||||
@@ -322,12 +233,12 @@ class RequestManager: NSObject {
|
||||
|
||||
fileprivate func getMarkFailure(error: Error, failure: (_ error: Error) -> Void) {
|
||||
logError(name: "\(#function)", error: error)
|
||||
failure(RequestManagerError.getMarkFailed)
|
||||
failure(CheckInRequestManagerError.getMarkFailed)
|
||||
}
|
||||
|
||||
// MARK: - Team
|
||||
|
||||
fileprivate func getTeam(
|
||||
func getTeam(
|
||||
competitorID: String,
|
||||
success: @escaping (_ teamData: TeamData) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
@@ -349,10 +260,10 @@ class RequestManager: NSObject {
|
||||
|
||||
fileprivate func getTeamFailure(error: Error, failure: (_ error: Error) -> Void) {
|
||||
logError(name: "\(#function)", error: error)
|
||||
failure(RequestManagerError.getTeamFailed)
|
||||
failure(CheckInRequestManagerError.getTeamFailed)
|
||||
}
|
||||
|
||||
fileprivate func getTeamImageURL(competitorID: String, result: @escaping (_ imageURL: String?) -> Void) {
|
||||
func getTeamImageURL(competitorID: String, result: @escaping (_ imageURL: String?) -> Void) {
|
||||
getTeam(
|
||||
competitorID: competitorID,
|
||||
success: { (teamData) in result(teamData.imageURL) },
|
||||
@@ -374,6 +285,9 @@ class RequestManager: NSObject {
|
||||
body[BodyKeys.PushDeviceID] = "" as AnyObject?
|
||||
body[BodyKeys.FromMillis] = millisSince1970()
|
||||
switch checkInData.type {
|
||||
case .boat:
|
||||
body[BodyKeys.BoatID] = checkInData.boatData.boatID as AnyObject?
|
||||
break
|
||||
case .competitor:
|
||||
body[BodyKeys.CompetitorID] = checkInData.competitorData.competitorID as AnyObject?
|
||||
break
|
||||
@@ -399,7 +313,7 @@ class RequestManager: NSObject {
|
||||
|
||||
fileprivate func postCheckInFailure(error: Error, failure: (_ error: Error) -> Void) {
|
||||
logError(name: "\(#function)", error: error)
|
||||
failure(RequestManagerError.postCheckInFailed)
|
||||
failure(CheckInRequestManagerError.postCheckInFailed)
|
||||
}
|
||||
|
||||
// MARK: - CheckOut
|
||||
@@ -438,7 +352,7 @@ class RequestManager: NSObject {
|
||||
|
||||
fileprivate func postCheckOutFailure(error: Error, failure: (_ error: Error) -> Void) {
|
||||
logError(name: "\(#function)", error: error)
|
||||
failure(RequestManagerError.postCheckOutFailed)
|
||||
failure(CheckInRequestManagerError.postCheckOutFailed)
|
||||
}
|
||||
|
||||
// MARK: - GPSFixes
|
||||
@@ -479,7 +393,7 @@ class RequestManager: NSObject {
|
||||
|
||||
fileprivate func postGPSFixesFailure(error: Error, failure: (_ error: Error) -> Void) {
|
||||
logError(name: "\(#function)", error: error)
|
||||
failure(RequestManagerError.postGPSFixFailed)
|
||||
failure(CheckInRequestManagerError.postGPSFixFailed)
|
||||
}
|
||||
|
||||
// MARK: - TeamImage
|
||||
@@ -491,7 +405,7 @@ class RequestManager: NSObject {
|
||||
failure: @escaping (_ error: Error) -> Void)
|
||||
{
|
||||
guard let teamImageURL = URL(string: "\(baseURLString)\(basePathString)/competitors/\(competitorID)/team/image") else {
|
||||
failure(RequestManagerError.teamImageURLIsInvalid)
|
||||
failure(CheckInRequestManagerError.teamImageURLIsInvalid)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -529,11 +443,11 @@ class RequestManager: NSObject {
|
||||
failure: (_ error: Error) -> Void)
|
||||
{
|
||||
guard let teamImageDictionary = responseObject as? [String: AnyObject] else {
|
||||
postTeamImageDataFailure(error: RequestManagerError.communicationFailed, failure: failure)
|
||||
postTeamImageDataFailure(error: CheckInRequestManagerError.communicationFailed, failure: failure)
|
||||
return
|
||||
}
|
||||
guard let teamImageURL = teamImageDictionary[TeamImageKeys.TeamImageURL] as? String else {
|
||||
postTeamImageDataFailure(error: RequestManagerError.communicationFailed, failure: failure)
|
||||
postTeamImageDataFailure(error: CheckInRequestManagerError.communicationFailed, failure: failure)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// HomeViewCell.swift
|
||||
// CheckInTableViewCell.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 26.07.16.
|
||||
@@ -8,10 +8,10 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class HomeViewCell: UITableViewCell {
|
||||
|
||||
class CheckInTableViewCell: UITableViewCell {
|
||||
|
||||
@IBOutlet weak var eventLabel: UILabel!
|
||||
@IBOutlet weak var leaderboardLabel: UILabel!
|
||||
@IBOutlet weak var competitorLabel: UILabel!
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,574 @@
|
||||
//
|
||||
// BaseCheckInTableViewController.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 01.09.17.
|
||||
// Copyright © 2017 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
|
||||
class CheckInTableViewController: UIViewController {
|
||||
|
||||
struct CheckInSegue {
|
||||
static let NewCheckIn = "NewCheckIn"
|
||||
static let RegattaBoat = "RegattaBoat"
|
||||
static let RegattaCompetitor = "RegattaCompetitor"
|
||||
static let RegattaMark = "RegattaMark"
|
||||
static let TrainingBoat = "TrainingBoat"
|
||||
static let TrainingCompetitor = "TrainingCompetitor"
|
||||
static let TrainingMark = "TrainingMark"
|
||||
}
|
||||
|
||||
struct CheckInSegues {
|
||||
static let boat = [CheckInSegue.RegattaBoat, CheckInSegue.TrainingBoat]
|
||||
static let competitor = [CheckInSegue.RegattaCompetitor, CheckInSegue.TrainingCompetitor]
|
||||
static let mark = [CheckInSegue.RegattaMark, CheckInSegue.TrainingMark]
|
||||
}
|
||||
|
||||
var segueCheckIn: CheckIn?
|
||||
|
||||
// Strong reference needed to avoid deallocation when not attached to table view
|
||||
@IBOutlet var headerView: UIView!
|
||||
@IBOutlet var footerTextView: UITextView!
|
||||
|
||||
@IBOutlet weak var headerTitleLabel: UILabel!
|
||||
@IBOutlet weak var tableView: UITableView!
|
||||
@IBOutlet weak var footerView: UIView!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
setup()
|
||||
subscribeForTrainingEndpointChangedNotifications()
|
||||
}
|
||||
|
||||
deinit {
|
||||
unsubscribeFromTrainingEndpointChangedNotifications()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
tableViewDeselectRow()
|
||||
titleView.setSubtitle(subtitle: signUpController.userName ?? "")
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
review()
|
||||
subscribeForNewCheckInURLNotifications()
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
unsubscribeFromNewCheckInURLNotifications()
|
||||
super.viewWillDisappear(animated)
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
layout()
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
fileprivate func setup() {
|
||||
setupLocalization()
|
||||
setupNavigationBar()
|
||||
setupTableView()
|
||||
setupTableViewDataSource()
|
||||
setupTableViewHeader()
|
||||
setupTableViewFooter()
|
||||
}
|
||||
|
||||
fileprivate func setupLocalization() {
|
||||
navigationItem.title = Translation.RegattaCheckInListView.Title.String
|
||||
headerTitleLabel.text = Translation.RegattaCheckInListView.HeaderTitleLabel.Text.String
|
||||
footerTextView.text = Translation.RegattaCheckInListView.FooterTextView.Text.String
|
||||
}
|
||||
|
||||
fileprivate func setupNavigationBar() {
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIImageView(image: UIImage(named: "sap_logo")))
|
||||
// TODO: Activate Training Feature -> Use this custom title view:
|
||||
// navigationItem.titleView = titleView
|
||||
navigationController?.navigationBar.setNeedsLayout()
|
||||
}
|
||||
|
||||
fileprivate func setupTableView() {
|
||||
tableView.rowHeight = UITableViewAutomaticDimension
|
||||
tableView.estimatedRowHeight = 140
|
||||
}
|
||||
|
||||
fileprivate func setupTableViewDataSource() {
|
||||
do {
|
||||
try fetchedResultsController?.performFetch()
|
||||
tableView.reloadData()
|
||||
} catch {
|
||||
logError(name: "\(#function)", error: error)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func setupTableViewHeader() {
|
||||
if fetchedResultsController?.sections?[0].numberOfObjects ?? 0 == 0 {
|
||||
tableView.tableHeaderView = nil
|
||||
} else {
|
||||
tableView.tableHeaderView = headerView
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func setupTableViewFooter() {
|
||||
tableView.tableFooterView?.isHidden = false
|
||||
}
|
||||
|
||||
// MARK: - Review
|
||||
|
||||
fileprivate func review() {
|
||||
reviewTerms(completion: { [weak self] in
|
||||
logInfo(name: "\(#function)", info: "Review terms done.")
|
||||
self?.reviewCodeConvention(completion: { [weak self] in
|
||||
logInfo(name: "\(#function)", info: "Review code convention done.")
|
||||
self?.reviewGPSFixes(completion: { [weak self] in
|
||||
logInfo(name: "\(#function)", info: "Review GPS fixes done.")
|
||||
self?.reviewNewCheckIn(completion: { [weak self] (checkIn) in
|
||||
logInfo(name: "\(#function)", info: "Review new check-in done.")
|
||||
self?.performSegue(forCheckIn: checkIn)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// MARK: 1. Review Terms
|
||||
|
||||
fileprivate func reviewTerms(completion: @escaping () -> Void) {
|
||||
guard Preferences.termsAccepted == false else {
|
||||
completion()
|
||||
return
|
||||
}
|
||||
let alertController = UIAlertController(
|
||||
title: Translation.RegattaCheckInListView.TermsAlert.Title.String,
|
||||
message: Translation.RegattaCheckInListView.TermsAlert.Message.String,
|
||||
preferredStyle: .alert
|
||||
)
|
||||
let showTermsAction = UIAlertAction(title: Translation.RegattaCheckInListView.TermsAlert.ShowTermsAction.Title.String, style: .default) { [weak self] (action) in
|
||||
UIApplication.shared.openURL(URLs.Terms)
|
||||
self?.reviewTerms(completion: completion) // Review terms until user accepted terms
|
||||
}
|
||||
let acceptTermsAction = UIAlertAction(title: Translation.RegattaCheckInListView.TermsAlert.AcceptTermsAction.Title.String, style: .default) { (action) in
|
||||
Preferences.termsAccepted = true
|
||||
completion() // Terms accepted
|
||||
}
|
||||
alertController.addAction(showTermsAction)
|
||||
alertController.addAction(acceptTermsAction)
|
||||
present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
// MARK: 2. Review Code Convention
|
||||
|
||||
fileprivate func reviewCodeConvention(completion: @escaping () -> Void) {
|
||||
#if DEBUG
|
||||
guard Preferences.codeConventionRead == false else { completion(); return }
|
||||
let alertController = UIAlertController(
|
||||
title: "Code Convention",
|
||||
message: "Please try to respect the code convention which is used for this project.",
|
||||
preferredStyle: .alert
|
||||
)
|
||||
let showCodeConventionAction = UIAlertAction(title: "Code Convention", style: .default) { [weak self] (action) in
|
||||
UIApplication.shared.openURL(URLs.CodeConvention)
|
||||
self?.reviewCodeConvention(completion: completion)
|
||||
}
|
||||
let okAction = UIAlertAction(title: "OK", style: .default) { (action) in
|
||||
Preferences.codeConventionRead = true
|
||||
completion()
|
||||
}
|
||||
alertController.addAction(showCodeConventionAction)
|
||||
alertController.addAction(okAction)
|
||||
present(alertController, animated: true, completion: nil)
|
||||
#else
|
||||
completion()
|
||||
#endif
|
||||
}
|
||||
|
||||
// MARK: 3. Review GPS Fixes
|
||||
|
||||
fileprivate func reviewGPSFixes(completion: @escaping () -> Void) {
|
||||
SVProgressHUD.show()
|
||||
let checkIns = coreDataManager.fetchCheckIns() ?? []
|
||||
reviewGPSFixes(checkIns: checkIns) { [weak self] in
|
||||
self?.reviewGPSFixesCompleted(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func reviewGPSFixes(checkIns: [CheckIn], completion: @escaping () -> Void) {
|
||||
guard checkIns.count > 0 else { completion(); return }
|
||||
let gpsFixController = GPSFixController.init(checkIn: checkIns[0], coreDataManager: coreDataManager)
|
||||
gpsFixController.sendAll(completion: { [weak self] (withSuccess) in
|
||||
self?.reviewGPSFixes(checkIns: Array(checkIns[1..<checkIns.count]), completion: completion)
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate func reviewGPSFixesCompleted(completion: () -> Void) {
|
||||
SVProgressHUD.popActivity()
|
||||
completion()
|
||||
}
|
||||
|
||||
fileprivate func reviewNewCheckIn(completion: @escaping (_ checkIn: CheckIn?) -> Void) {
|
||||
guard let urlString = Preferences.newCheckInURL else { completion(nil); return }
|
||||
guard let checkInData = CheckInData(urlString: urlString) else { completion(nil); return }
|
||||
regattaCheckInController.checkInWithViewController(self, checkInData: checkInData, success: { (checkIn) in
|
||||
Preferences.newCheckInURL = nil
|
||||
completion(checkIn)
|
||||
}) { (error) in
|
||||
Preferences.newCheckInURL = nil // TODO: Ask user for retry, retry later or dismiss before deleting check-in url
|
||||
completion(nil)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Notifications
|
||||
|
||||
fileprivate func subscribeForNewCheckInURLNotifications() {
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(CheckInTableViewController.newCheckInURLNotification(_:)),
|
||||
name: NSNotification.Name(rawValue: Preferences.NotificationType.NewCheckInURLChanged),
|
||||
object: nil
|
||||
)
|
||||
}
|
||||
|
||||
fileprivate func unsubscribeFromNewCheckInURLNotifications() {
|
||||
NotificationCenter.default.removeObserver(
|
||||
self,
|
||||
name: NSNotification.Name(rawValue: Preferences.NotificationType.NewCheckInURLChanged),
|
||||
object: nil
|
||||
)
|
||||
}
|
||||
|
||||
@objc fileprivate func newCheckInURLNotification(_ notification: Notification) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.reviewNewCheckIn(completion: { checkIn in
|
||||
logInfo(name: "\(#function)", info: "Review new check-in done.")
|
||||
self.performSegue(forCheckIn: checkIn)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate func subscribeForTrainingEndpointChangedNotifications() {
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(trainingEndpointChanged(_:)),
|
||||
name: NSNotification.Name(rawValue: Preferences.NotificationType.TrainingEndpointChanged),
|
||||
object: nil
|
||||
)
|
||||
}
|
||||
|
||||
fileprivate func unsubscribeFromTrainingEndpointChangedNotifications() {
|
||||
NotificationCenter.default.removeObserver(
|
||||
self,
|
||||
name: NSNotification.Name(rawValue: Preferences.NotificationType.TrainingEndpointChanged),
|
||||
object: nil
|
||||
)
|
||||
}
|
||||
|
||||
@objc fileprivate func trainingEndpointChanged(_ notification: Notification) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
guard let trainingEndpoint = notification.userInfo?[Preferences.UserInfo.TrainingEndpoint] as? String else { return }
|
||||
self.signUpController = SignUpController(baseURLString: trainingEndpoint)
|
||||
})
|
||||
}
|
||||
|
||||
// MARK: - Layout
|
||||
|
||||
fileprivate func layout() {
|
||||
layoutFooterView()
|
||||
}
|
||||
|
||||
fileprivate func layoutFooterView() {
|
||||
let height = footerTextView.sizeThatFits(CGSize(width: footerView.frame.width, height: CGFloat.greatestFiniteMagnitude)).height
|
||||
footerView.frame = CGRect(
|
||||
x: footerView.frame.origin.x,
|
||||
y: footerView.frame.origin.y,
|
||||
width: footerView.frame.width,
|
||||
height: height
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - TableView
|
||||
|
||||
fileprivate func tableViewDeselectRow() {
|
||||
if let indexPath = tableView.indexPathForSelectedRow {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Segues
|
||||
|
||||
func performSegue(forCheckIn checkIn: CheckIn?) {
|
||||
segueCheckIn = checkIn
|
||||
guard let checkIn = checkIn else {
|
||||
logInfo(name: "\(#function)", info: "check-in is nil")
|
||||
return
|
||||
}
|
||||
if checkIn is BoatCheckIn {
|
||||
if checkIn.isTraining.boolValue {
|
||||
performSegue(withIdentifier: CheckInSegue.TrainingBoat, sender: self)
|
||||
} else {
|
||||
performSegue(withIdentifier: CheckInSegue.RegattaBoat, sender: self)
|
||||
}
|
||||
} else if checkIn is CompetitorCheckIn {
|
||||
if checkIn.isTraining.boolValue {
|
||||
performSegue(withIdentifier: CheckInSegue.TrainingCompetitor, sender: self)
|
||||
} else {
|
||||
performSegue(withIdentifier: CheckInSegue.RegattaCompetitor, sender: self)
|
||||
}
|
||||
} else if (checkIn is MarkCheckIn) {
|
||||
if checkIn.isTraining.boolValue {
|
||||
performSegue(withIdentifier: CheckInSegue.TrainingMark, sender: self)
|
||||
} else {
|
||||
performSegue(withIdentifier: CheckInSegue.RegattaMark, sender: self)
|
||||
}
|
||||
} else {
|
||||
logInfo(name: "\(#function)", info: "unknown check-in type")
|
||||
}
|
||||
}
|
||||
|
||||
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
||||
if CheckInSegues.boat.contains(identifier) {
|
||||
return segueCheckIn != nil && segueCheckIn is BoatCheckIn
|
||||
} else if CheckInSegues.competitor.contains(identifier) {
|
||||
return segueCheckIn != nil && segueCheckIn is CompetitorCheckIn
|
||||
} else if CheckInSegues.mark.contains(identifier) {
|
||||
return segueCheckIn != nil && segueCheckIn is MarkCheckIn
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
super.prepare(for: segue, sender: sender)
|
||||
if let identifier = segue.identifier {
|
||||
if CheckInSegues.boat.contains(identifier) {
|
||||
if let boatCheckIn = segueCheckIn as? BoatCheckIn {
|
||||
if let regattaBoatVC = segue.destination as? RegattaBoatViewController {
|
||||
regattaBoatVC.boatCheckIn = boatCheckIn
|
||||
regattaBoatVC.boatCoreDataManager = coreDataManager
|
||||
} else if let trainingBoatVC = segue.destination as? TrainingBoatViewController {
|
||||
trainingBoatVC.boatCheckIn = boatCheckIn
|
||||
trainingBoatVC.boatCoreDataManager = coreDataManager
|
||||
}
|
||||
}
|
||||
} else if CheckInSegues.competitor.contains(identifier) {
|
||||
if let competitorCheckIn = segueCheckIn as? CompetitorCheckIn {
|
||||
if let regattaCompetitorVC = segue.destination as? RegattaCompetitorViewController {
|
||||
regattaCompetitorVC.competitorCheckIn = competitorCheckIn
|
||||
regattaCompetitorVC.competitorCoreDataManager = coreDataManager
|
||||
} else if let trainingCompetitorVC = segue.destination as? TrainingCompetitorViewController {
|
||||
trainingCompetitorVC.competitorCheckIn = competitorCheckIn
|
||||
trainingCompetitorVC.competitorCoreDataManager = coreDataManager
|
||||
}
|
||||
}
|
||||
} else if CheckInSegues.mark.contains(identifier) {
|
||||
if let markCheckIn = segueCheckIn as? MarkCheckIn {
|
||||
if let regattaMarkVC = segue.destination as? RegattaMarkViewController {
|
||||
regattaMarkVC.markCheckIn = markCheckIn
|
||||
regattaMarkVC.markCoreDataManager = coreDataManager
|
||||
} else if let trainingMarkVC = segue.destination as? TrainingMarkViewController {
|
||||
trainingMarkVC.markCheckIn = markCheckIn
|
||||
trainingMarkVC.markCoreDataManager = coreDataManager
|
||||
}
|
||||
}
|
||||
} else if (identifier == CheckInSegue.NewCheckIn) {
|
||||
guard let newCheckInNC = segue.destination as? UINavigationController else { return }
|
||||
guard let newCheckInVC = newCheckInNC.viewControllers[0] as? NewCheckInViewController else { return }
|
||||
newCheckInVC.coreDataManager = coreDataManager
|
||||
newCheckInVC.delegate = self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func optionButtonTapped(_ sender: Any) {
|
||||
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
if let popoverController = alertController.popoverPresentationController {
|
||||
popoverController.barButtonItem = sender as? UIBarButtonItem
|
||||
}
|
||||
let settingsAction = UIAlertAction(title: Translation.SettingsView.Title.String, style: .default) { [weak self] action in
|
||||
self?.presentSettingsViewController()
|
||||
}
|
||||
let infoAction = UIAlertAction(title: Translation.Common.Info.String, style: .default) { [weak self] action in
|
||||
self?.presentAboutViewController()
|
||||
}
|
||||
let cancelAction = UIAlertAction(title: Translation.Common.Cancel.String, style: .cancel, handler: nil)
|
||||
alertController.addAction(settingsAction)
|
||||
alertController.addAction(infoAction)
|
||||
alertController.addAction(cancelAction)
|
||||
present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
// TODO: Activate Training Feature -> Set sort button visible in IB
|
||||
@IBAction func sortButtonTapped(_ sender: Any) {
|
||||
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
if let popoverController = alertController.popoverPresentationController {
|
||||
popoverController.barButtonItem = sender as? UIBarButtonItem
|
||||
}
|
||||
let dateAction = UIAlertAction(title: "DATE", style: .default) { [weak self] action in
|
||||
self?.sortByDate()
|
||||
}
|
||||
let leaderboardAction = UIAlertAction(title: "LEADERBOARD", style: .default) { [weak self] action in
|
||||
self?.sortByLeaderboard()
|
||||
}
|
||||
let competitorAction = UIAlertAction(title: "COMPETITOR", style: .default) { [weak self] action in
|
||||
self?.sortByCompetitor()
|
||||
}
|
||||
let cancelAction = UIAlertAction(title: Translation.Common.Cancel.String, style: .cancel, handler: nil)
|
||||
alertController.addAction(dateAction)
|
||||
alertController.addAction(leaderboardAction)
|
||||
alertController.addAction(competitorAction)
|
||||
alertController.addAction(cancelAction)
|
||||
present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func sortByDate() {
|
||||
fetchedResultsController?.fetchRequest.sortDescriptors = [NSSortDescriptor(key: "event.startDate", ascending: true)]
|
||||
setupTableViewDataSource()
|
||||
}
|
||||
|
||||
func sortByLeaderboard() {
|
||||
fetchedResultsController?.fetchRequest.sortDescriptors = [NSSortDescriptor(key: "leaderboard.name", ascending: true)]
|
||||
setupTableViewDataSource()
|
||||
}
|
||||
|
||||
func sortByCompetitor() {
|
||||
fetchedResultsController?.fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
|
||||
setupTableViewDataSource()
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
fileprivate lazy var titleView: TitleView = {
|
||||
return TitleView(
|
||||
title: Translation.TrainingCheckInListView.Title.String,
|
||||
subtitle: self.signUpController.userName ?? ""
|
||||
)
|
||||
}()
|
||||
|
||||
lazy var fetchedResultsController: NSFetchedResultsController<CheckIn>? = {
|
||||
let fetchedResultsController = self.coreDataManager.checkInFetchedResultsController()
|
||||
fetchedResultsController.delegate = self
|
||||
return fetchedResultsController
|
||||
}()
|
||||
|
||||
lazy var regattaCheckInController: RegattaCheckInController = {
|
||||
return RegattaCheckInController(coreDataManager: self.coreDataManager)
|
||||
}()
|
||||
|
||||
lazy var signUpController: SignUpController = {
|
||||
return SignUpController(baseURLString: Preferences.trainingEndpoint)
|
||||
}()
|
||||
|
||||
lazy var coreDataManager: CoreDataManager = {
|
||||
return CoreDataManager(name: "CoreData")
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDataSource
|
||||
|
||||
extension CheckInTableViewController: UITableViewDataSource {
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return fetchedResultsController?.sections?[section].numberOfObjects ?? 0
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") ?? UITableViewCell()
|
||||
if let checkIn = fetchedResultsController?.object(at: indexPath) {
|
||||
configureCell(cell, forCheckIn: checkIn)
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
||||
func configureCell(_ cell: UITableViewCell, forCheckIn checkIn: CheckIn) {
|
||||
guard let regattaCheckInTableViewCell = cell as? CheckInTableViewCell else { return }
|
||||
regattaCheckInTableViewCell.eventLabel.text = checkIn.event.name
|
||||
regattaCheckInTableViewCell.leaderboardLabel.text = checkIn.leaderboard.name
|
||||
regattaCheckInTableViewCell.competitorLabel.textAlignment = .right
|
||||
regattaCheckInTableViewCell.competitorLabel.backgroundColor = nil
|
||||
if let boatCheckIn = checkIn as? BoatCheckIn {
|
||||
regattaCheckInTableViewCell.competitorLabel.text = boatCheckIn.displayName()
|
||||
regattaCheckInTableViewCell.competitorLabel.textAlignment = .center
|
||||
if let color = UIColor.init(hexString: boatCheckIn.color) {
|
||||
regattaCheckInTableViewCell.competitorLabel.backgroundColor = color
|
||||
regattaCheckInTableViewCell.competitorLabel.textColor = UIColor.init(contrastColorFor: color)
|
||||
}
|
||||
} else {
|
||||
regattaCheckInTableViewCell.competitorLabel.text = checkIn.name
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
|
||||
extension CheckInTableViewController: UITableViewDelegate {
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
performSegue(forCheckIn: fetchedResultsController?.object(at: indexPath))
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||
cell.removeSeparatorInset()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - NSFetchedResultsControllerDelegate
|
||||
|
||||
extension CheckInTableViewController: NSFetchedResultsControllerDelegate {
|
||||
|
||||
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
||||
tableView.beginUpdates()
|
||||
}
|
||||
|
||||
func controller(
|
||||
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
|
||||
didChange object: Any,
|
||||
at indexPath: IndexPath?,
|
||||
for type: NSFetchedResultsChangeType,
|
||||
newIndexPath: IndexPath?)
|
||||
{
|
||||
switch type {
|
||||
case .insert:
|
||||
if indexPath == nil { // Workaround for an iOS 8.4 bug https://forums.developer.apple.com/thread/12184
|
||||
tableView.insertRows(at: [newIndexPath!], with: UITableViewRowAnimation.automatic)
|
||||
}
|
||||
case .update:
|
||||
if let cell = tableView.cellForRow(at: indexPath!) {
|
||||
if let checkIn = fetchedResultsController?.object(at: indexPath!) {
|
||||
configureCell(cell, forCheckIn: checkIn)
|
||||
}
|
||||
tableView.reloadRows(at: [indexPath!], with: UITableViewRowAnimation.automatic)
|
||||
}
|
||||
case .move:
|
||||
tableView.deleteRows(at: [indexPath!], with: UITableViewRowAnimation.automatic)
|
||||
tableView.insertRows(at: [newIndexPath!], with: .automatic)
|
||||
case .delete:
|
||||
tableView.deleteRows(at: [indexPath!], with: UITableViewRowAnimation.automatic)
|
||||
}
|
||||
}
|
||||
|
||||
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
||||
tableView.endUpdates()
|
||||
setupTableViewHeader()
|
||||
setupTableViewFooter()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - NewCheckInViewControllerDelegate
|
||||
|
||||
extension CheckInTableViewController: NewCheckInViewControllerDelegate {
|
||||
|
||||
func newCheckInViewController(_ controller: NewCheckInViewController, didCheckIn checkIn: CheckIn) {
|
||||
performSegue(forCheckIn: checkIn)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,7 +18,7 @@ class CompetitorSessionController: SessionController {
|
||||
success: @escaping (_ teamImageURL: String) -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void
|
||||
) {
|
||||
requestManager.postTeamImageData(
|
||||
checkInRequestManager.postTeamImageData(
|
||||
imageData: imageData,
|
||||
competitorID: competitorID,
|
||||
success: success,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// CompetitorSessionViewController.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 13.09.17.
|
||||
// Copyright © 2017 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class CompetitorSessionViewController: SessionViewController {
|
||||
|
||||
struct CompetitorSessionSegue {
|
||||
static let EmbedCompetitor = "EmbedCompetitor"
|
||||
}
|
||||
|
||||
weak var competitorCheckIn: CompetitorCheckIn!
|
||||
weak var competitorCoreDataManager: CoreDataManager!
|
||||
weak var competitorViewController: CompetitorViewController?
|
||||
|
||||
// MARK: - Segues
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
super.prepare(for: segue, sender: sender)
|
||||
if (segue.identifier == CompetitorSessionSegue.EmbedCompetitor) {
|
||||
if let competitorViewController = segue.destination as? CompetitorViewController {
|
||||
competitorViewController.competitorCheckIn = competitorCheckIn
|
||||
competitorViewController.competitorSessionController = competitorSessionController
|
||||
competitorViewController.competitorCoreDataManager = competitorCoreDataManager
|
||||
self.competitorViewController = competitorViewController
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
lazy var competitorSessionController: CompetitorSessionController = {
|
||||
return CompetitorSessionController(checkIn: self.competitorCheckIn, coreDataManager: self.competitorCoreDataManager)
|
||||
}()
|
||||
|
||||
}
|
||||
@@ -2,13 +2,17 @@
|
||||
// CompetitorViewController.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by computing on 10/11/14.
|
||||
// Copyright (c) 2014 com.sap.sailing. All rights reserved.
|
||||
// Created by Raimund Wege on 13.09.17.
|
||||
// Copyright © 2017 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
class CompetitorViewController : SessionViewController, UINavigationControllerDelegate {
|
||||
class CompetitorViewController: UIViewController {
|
||||
|
||||
weak var competitorCheckIn: CompetitorCheckIn!
|
||||
weak var competitorCoreDataManager: CoreDataManager!
|
||||
weak var competitorSessionController: CompetitorSessionController!
|
||||
|
||||
@IBOutlet weak var teamImageView: UIImageView!
|
||||
@IBOutlet weak var teamImageAddButton: UIButton!
|
||||
@@ -16,101 +20,57 @@ class CompetitorViewController : SessionViewController, UINavigationControllerDe
|
||||
@IBOutlet weak var teamImageRetryButton: UIButton!
|
||||
@IBOutlet weak var competitorNameLabel: UILabel!
|
||||
@IBOutlet weak var competitorFlagImageView: UIImageView!
|
||||
@IBOutlet weak var competitorSailLabel: UILabel!
|
||||
@IBOutlet weak var regattaStartLabel: UILabel!
|
||||
@IBOutlet weak var countdownView: UIView!
|
||||
@IBOutlet weak var countdownViewHeight: NSLayoutConstraint!
|
||||
@IBOutlet weak var countdownDaysLabel: UILabel!
|
||||
@IBOutlet weak var countdownDaysTitleLabel: UILabel!
|
||||
@IBOutlet weak var countdownHoursLabel: UILabel!
|
||||
@IBOutlet weak var countdownHoursTitleLabel: UILabel!
|
||||
@IBOutlet weak var countdownMinutesLabel: UILabel!
|
||||
@IBOutlet weak var countdownMinutesTitleLabel: UILabel!
|
||||
@IBOutlet weak var leaderboardButton: UIButton!
|
||||
@IBOutlet weak var eventButton: UIButton!
|
||||
@IBOutlet weak var announcementLabel: UILabel!
|
||||
|
||||
var competitorCheckIn: CompetitorCheckIn!
|
||||
|
||||
var countdownTimer: Timer?
|
||||
@IBOutlet weak var competitorSailIDLabel: UILabel!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
delegate = self
|
||||
setup()
|
||||
update()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
refresh()
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
fileprivate func setup() {
|
||||
setupButtons()
|
||||
setupCountdownTimer()
|
||||
setupLocalization()
|
||||
setupNavigationBar()
|
||||
}
|
||||
|
||||
fileprivate func setupButtons() {
|
||||
eventButton.setBackgroundImage(Images.BlueHighlighted, for: .highlighted)
|
||||
leaderboardButton.setBackgroundImage(Images.BlueHighlighted, for: .highlighted)
|
||||
startTrackingButton.setBackgroundImage(Images.GreenHighlighted, for: .highlighted)
|
||||
}
|
||||
|
||||
fileprivate func setupCountdownTimer() {
|
||||
countdownTimer?.invalidate()
|
||||
countdownTimer = Timer.scheduledTimer(
|
||||
timeInterval: 1,
|
||||
target: self,
|
||||
selector: #selector(CompetitorViewController.countdownTimerTick),
|
||||
userInfo: nil,
|
||||
repeats: true
|
||||
)
|
||||
countdownTimerTick()
|
||||
makeTranslucent(button: teamImageAddButton)
|
||||
makeTranslucent(button: teamImageRetryButton)
|
||||
}
|
||||
|
||||
fileprivate func setupLocalization() {
|
||||
announcementLabel.text = Translation.CompetitorView.AnnouncementLabel.Text.String
|
||||
countdownDaysTitleLabel.text = Translation.CompetitorView.CountdownDaysTitleLabel.Text.String
|
||||
countdownHoursTitleLabel.text = Translation.CompetitorView.CountdownHoursTitleLabel.Text.String
|
||||
countdownMinutesTitleLabel.text = Translation.CompetitorView.CountdownMinutesTitleLabel.Text.String
|
||||
eventButton.setTitle(Translation.CompetitorView.EventButton.Title.String, for: .normal)
|
||||
leaderboardButton.setTitle(Translation.LeaderboardView.Title.String, for: .normal)
|
||||
startTrackingButton.setTitle(Translation.CompetitorView.StartTrackingButton.Title.String, for: .normal)
|
||||
teamImageAddButton.setTitle(Translation.CompetitorView.TeamImageAddButton.Title.String, for: .normal)
|
||||
teamImageRetryButton.setTitle(Translation.CompetitorView.TeamImageUploadRetryButton.Title.String, for: .normal)
|
||||
}
|
||||
|
||||
fileprivate func setupNavigationBar() {
|
||||
navigationItem.titleView = TitleView(title: competitorCheckIn.event.name, subtitle: competitorCheckIn.leaderboard.name)
|
||||
navigationController?.navigationBar.setNeedsLayout()
|
||||
}
|
||||
|
||||
// MARK: - Update
|
||||
|
||||
fileprivate func update() {
|
||||
SVProgressHUD.show()
|
||||
competitorSessionController.update {
|
||||
self.refresh()
|
||||
SVProgressHUD.popActivity()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Refresh
|
||||
|
||||
fileprivate func refresh() {
|
||||
refreshCompetitor()
|
||||
refreshTeamImage()
|
||||
func refresh(_ animated: Bool) {
|
||||
refreshCompetitor(animated)
|
||||
refreshTeamImage(animated)
|
||||
}
|
||||
|
||||
fileprivate func refreshCompetitor(_ animated: Bool) {
|
||||
if (animated) {
|
||||
UIView.animate(withDuration: 0.5) { self.refreshCompetitor() }
|
||||
} else {
|
||||
refreshCompetitor()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func refreshCompetitor() {
|
||||
competitorNameLabel.text = competitorCheckIn.name
|
||||
competitorFlagImageView.image = UIImage(named: competitorCheckIn.countryCode)
|
||||
competitorSailLabel.text = competitorCheckIn.sailID
|
||||
competitorSailIDLabel.text = competitorCheckIn.sailID
|
||||
}
|
||||
|
||||
fileprivate func refreshTeamImage(_ animated: Bool) {
|
||||
if (animated) {
|
||||
UIView.animate(withDuration: 0.5) { self.refreshTeamImage() }
|
||||
} else {
|
||||
refreshTeamImage()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func refreshTeamImage() {
|
||||
@@ -139,7 +99,7 @@ class CompetitorViewController : SessionViewController, UINavigationControllerDe
|
||||
}
|
||||
|
||||
// MARK: - TeamImage
|
||||
|
||||
|
||||
fileprivate func setTeamImageWithURLRequest(urlString: String?, completion: @escaping (_ withSuccess: Bool) -> Void) {
|
||||
setTeamImageWithURLRequest(urlString: urlString, success: { () in
|
||||
completion(true)
|
||||
@@ -151,19 +111,18 @@ class CompetitorViewController : SessionViewController, UINavigationControllerDe
|
||||
fileprivate func setTeamImageWithURLRequest(urlString: String?, success: @escaping () -> Void, failure: @escaping () -> Void) {
|
||||
guard let string = urlString else { failure(); return }
|
||||
guard let url = URL(string: string) else { failure(); return }
|
||||
teamImageView.setImageWith(
|
||||
URLRequest(url: url),
|
||||
placeholderImage: nil,
|
||||
success: { (request, response, image) in self.setTeamImageWithURLSuccess(image: image, success: success) },
|
||||
failure: { (request, response, error) in self.setTeamImageWithURLFailure(error: error, failure: failure) }
|
||||
)
|
||||
teamImageView.setImageWith(URLRequest(url: url), placeholderImage: nil, success: { (request, response, image) in
|
||||
self.setTeamImageWithURLSuccess(image: image, success: success)
|
||||
}) { (request, response, error) in
|
||||
self.setTeamImageWithURLFailure(error: error, failure: failure)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func setTeamImageWithURLSuccess(image: UIImage, success: () -> Void) {
|
||||
teamImageView.image = image
|
||||
competitorCheckIn.teamImageRetry = false
|
||||
competitorCheckIn.teamImageData = UIImageJPEGRepresentation(image, 0.8)
|
||||
CoreDataManager.sharedManager.saveContext()
|
||||
competitorCoreDataManager.saveContext()
|
||||
success()
|
||||
}
|
||||
|
||||
@@ -172,92 +131,36 @@ class CompetitorViewController : SessionViewController, UINavigationControllerDe
|
||||
failure()
|
||||
}
|
||||
|
||||
// MARK: - Timer
|
||||
|
||||
@objc fileprivate func countdownTimerTick() {
|
||||
if competitorCheckIn.event.startDate - Date().timeIntervalSince1970 > 0 {
|
||||
regattaStartLabel.text = Translation.CompetitorView.RegattaStartLabel.Text.BeforeRegattaDidStart.String
|
||||
let duration = competitorCheckIn.event.startDate - Date().timeIntervalSince1970
|
||||
let days = Int(duration / (60 * 60 * 24))
|
||||
let hours = Int(duration / (60 * 60)) - (days * 24)
|
||||
let minutes = Int(duration / 60) - (days * 24 * 60) - (hours * 60)
|
||||
countdownDaysLabel.text = String(format: "%02d", days)
|
||||
countdownHoursLabel.text = String(format: "%02d", hours)
|
||||
countdownMinutesLabel.text = String(format: "%02d", minutes)
|
||||
countdownView.isHidden = false
|
||||
countdownViewHeight.constant = 60
|
||||
} else {
|
||||
regattaStartLabel.text = Translation.CompetitorView.RegattaStartLabel.Text.AfterRegattaDidStart.String
|
||||
countdownView.isHidden = true
|
||||
countdownViewHeight.constant = 0
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func teamImageAddButtonTapped(_ sender: AnyObject) {
|
||||
@IBAction func teamImageAddButtonTapped(_ sender: Any) {
|
||||
showSelectImageAlert()
|
||||
}
|
||||
|
||||
@IBAction func teamImageEditButtonTapped(_ sender: AnyObject) {
|
||||
@IBAction func teamImageEditButtonTapped(_ sender: Any) {
|
||||
showSelectImageAlert()
|
||||
}
|
||||
|
||||
@IBAction func teamImageRetryButtonTapped(_ sender: AnyObject) {
|
||||
@IBAction func teamImageRetryButtonTapped(_ sender: Any) {
|
||||
if let data = competitorCheckIn.teamImageData {
|
||||
uploadTeamImageData(imageData: data)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func eventButtonTapped(_ sender: UIButton) {
|
||||
if let eventURL = competitorCheckIn.eventURL() {
|
||||
UIApplication.shared.openURL(eventURL)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func optionButtonTapped(_ sender: AnyObject) {
|
||||
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
if let popoverController = alertController.popoverPresentationController {
|
||||
popoverController.barButtonItem = sender as? UIBarButtonItem
|
||||
}
|
||||
let settingsAction = UIAlertAction(title: Translation.SettingsView.Title.String, style: .default) { (action) in
|
||||
self.performSegue(withIdentifier: Segue.Settings, sender: self)
|
||||
}
|
||||
let checkOutAction = UIAlertAction(title: Translation.CompetitorView.OptionSheet.CheckOutAction.Title.String, style: .default) { (action) in
|
||||
self.checkOut()
|
||||
}
|
||||
let replaceImageAction = UIAlertAction(title: Translation.CompetitorView.OptionSheet.ReplaceImageAction.Title.String, style: .default) { (action) in
|
||||
self.showSelectImageAlert()
|
||||
}
|
||||
let updateAction = UIAlertAction(title: Translation.CompetitorView.OptionSheet.UpdateAction.Title.String, style: .default) { (action) -> Void in
|
||||
self.update()
|
||||
}
|
||||
let aboutAction = UIAlertAction(title: Translation.Common.Info.String, style: .default) { (action) -> Void in
|
||||
self.performSegue(withIdentifier: Segue.About, sender: alertController)
|
||||
}
|
||||
let cancelAction = UIAlertAction(title: Translation.Common.Cancel.String, style: .cancel, handler: nil)
|
||||
alertController.addAction(settingsAction)
|
||||
alertController.addAction(checkOutAction)
|
||||
alertController.addAction(replaceImageAction)
|
||||
alertController.addAction(updateAction)
|
||||
alertController.addAction(aboutAction)
|
||||
alertController.addAction(cancelAction)
|
||||
present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
// MARK: - Alerts
|
||||
|
||||
fileprivate func showSelectImageAlert() {
|
||||
func showSelectImageAlert() {
|
||||
if UIImagePickerController.isSourceTypeAvailable(.camera) && UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
|
||||
let alertController = UIAlertController(title: Translation.CompetitorView.SelectImageAlert.Title.String,
|
||||
message: Translation.CompetitorView.SelectImageAlert.Message.String,
|
||||
preferredStyle: .alert
|
||||
let alertController = UIAlertController(
|
||||
title: Translation.CompetitorView.SelectImageAlert.Title.String,
|
||||
message: Translation.CompetitorView.SelectImageAlert.Message.String,
|
||||
preferredStyle: .alert
|
||||
)
|
||||
let cameraAction = UIAlertAction(title: Translation.CompetitorView.SelectImageAlert.CameraAction.Title.String, style: .default) { (action) in
|
||||
self.showImagePicker(sourceType: .camera)
|
||||
let cameraAction = UIAlertAction(title: Translation.CompetitorView.SelectImageAlert.CameraAction.Title.String, style: .default) { [weak self] (action) in
|
||||
self?.showImagePicker(sourceType: .camera)
|
||||
}
|
||||
let photoLibraryAction = UIAlertAction(title: Translation.CompetitorView.SelectImageAlert.PhotoLibraryAction.Title.String, style: .default) { (action) in
|
||||
self.showImagePicker(sourceType: .photoLibrary)
|
||||
let photoLibraryAction = UIAlertAction(title: Translation.CompetitorView.SelectImageAlert.PhotoLibraryAction.Title.String, style: .default) { [weak self] (action) in
|
||||
self?.showImagePicker(sourceType: .photoLibrary)
|
||||
}
|
||||
let cancelAction = UIAlertAction(title: Translation.Common.Cancel.String, style: .cancel, handler: nil)
|
||||
alertController.addAction(cameraAction)
|
||||
@@ -271,26 +174,13 @@ class CompetitorViewController : SessionViewController, UINavigationControllerDe
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Segues
|
||||
}
|
||||
|
||||
// MARK: - UINavigationControllerDelegate
|
||||
|
||||
extension CompetitorViewController: UINavigationControllerDelegate {
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
if (segue.identifier == Segue.Tracking) {
|
||||
let trackingNC = segue.destination as! UINavigationController
|
||||
let trackingVC = trackingNC.viewControllers[0] as! TrackingViewController
|
||||
trackingVC.checkIn = competitorCheckIn
|
||||
trackingVC.sessionController = competitorSessionController
|
||||
} else if (segue.identifier == Segue.Leaderboard) {
|
||||
let leaderboardNC = segue.destination as! UINavigationController
|
||||
let leaderboardVC = leaderboardNC.viewControllers[0] as! LeaderboardViewController
|
||||
leaderboardVC.checkIn = competitorCheckIn
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
fileprivate lazy var competitorSessionController: CompetitorSessionController = {
|
||||
return CompetitorSessionController(checkIn: self.competitorCheckIn)
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
@@ -318,7 +208,7 @@ extension CompetitorViewController: UIImagePickerControllerDelegate {
|
||||
fileprivate func pickedImage(image: UIImage) {
|
||||
teamImageView.image = image
|
||||
competitorCheckIn.teamImageData = UIImageJPEGRepresentation(image, 0.8)
|
||||
CoreDataManager.sharedManager.saveContext()
|
||||
competitorCoreDataManager.saveContext()
|
||||
if let data = competitorCheckIn.teamImageData {
|
||||
uploadTeamImageData(imageData: data)
|
||||
}
|
||||
@@ -328,28 +218,25 @@ extension CompetitorViewController: UIImagePickerControllerDelegate {
|
||||
|
||||
fileprivate func uploadTeamImageData(imageData: Data!) {
|
||||
SVProgressHUD.show()
|
||||
competitorSessionController.postTeamImageData(imageData: imageData, competitorID: competitorCheckIn.competitorID,
|
||||
success: { (teamImageURL) in
|
||||
SVProgressHUD.popActivity()
|
||||
self.uploadTeamImageDataSuccess(teamImageURL: teamImageURL)
|
||||
},
|
||||
failure: { (error) in
|
||||
SVProgressHUD.popActivity()
|
||||
self.uploadTeamImageDataFailure(error: error)
|
||||
}
|
||||
)
|
||||
competitorSessionController.postTeamImageData(imageData: imageData, competitorID: competitorCheckIn.competitorID, success: { (teamImageURL) in
|
||||
SVProgressHUD.popActivity()
|
||||
self.uploadTeamImageDataSuccess(teamImageURL: teamImageURL)
|
||||
}) { (error) in
|
||||
SVProgressHUD.popActivity()
|
||||
self.uploadTeamImageDataFailure(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func uploadTeamImageDataSuccess(teamImageURL: String) {
|
||||
competitorCheckIn.teamImageRetry = false
|
||||
competitorCheckIn.teamImageURL = teamImageURL
|
||||
CoreDataManager.sharedManager.saveContext()
|
||||
competitorCoreDataManager.saveContext()
|
||||
refreshTeamImage()
|
||||
}
|
||||
|
||||
fileprivate func uploadTeamImageDataFailure(error: Error) {
|
||||
competitorCheckIn.teamImageRetry = true
|
||||
CoreDataManager.sharedManager.saveContext()
|
||||
competitorCoreDataManager.saveContext()
|
||||
showUploadTeamImageFailureAlert(error: error)
|
||||
refreshTeamImage()
|
||||
}
|
||||
@@ -368,25 +255,3 @@ extension CompetitorViewController: UIImagePickerControllerDelegate {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: SessionViewControllerDelegate
|
||||
|
||||
extension CompetitorViewController: SessionViewControllerDelegate {
|
||||
|
||||
func performCheckOut() {
|
||||
competitorSessionController.checkOut { (withSuccess) in
|
||||
self.performCheckOutCompleted(withSuccess: withSuccess)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func performCheckOutCompleted(withSuccess: Bool) {
|
||||
CoreDataManager.sharedManager.deleteObject(object: competitorCheckIn)
|
||||
CoreDataManager.sharedManager.saveContext()
|
||||
self.navigationController!.popViewController(animated: true)
|
||||
}
|
||||
|
||||
func startTracking() throws {
|
||||
try competitorSessionController.startTracking()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,6 +10,11 @@ import Foundation
|
||||
|
||||
struct Application {
|
||||
static let Title = "SAP Sail InSight"
|
||||
static let RequestTimeout: TimeInterval = 7
|
||||
}
|
||||
|
||||
struct AutoCourse {
|
||||
static let MinGPSFixesNeeded = 10
|
||||
}
|
||||
|
||||
struct URLs {
|
||||
@@ -18,36 +23,150 @@ struct URLs {
|
||||
}
|
||||
|
||||
struct Colors {
|
||||
static let Blue = UIColor(hex:0x009de0)
|
||||
static let BlueHighlighted = UIColor(hex:0x007aad)
|
||||
static let Gray = UIColor(hex:0xf3f3f3)
|
||||
static let GrayHighlighted = UIColor(hex:0xf3f3f3)
|
||||
static let Green = UIColor(hex:0x0e7733)
|
||||
static let GreenHighlighted = UIColor(hex:0x08451e)
|
||||
static let Red = UIColor(hex:0xcd201b)
|
||||
static let RedHighlighted = UIColor(hex:0x991814)
|
||||
static let BlueButton = UIColor(hex:0x009de0)
|
||||
static let BlueButtonHighlighted = UIColor(hex:0x007aad)
|
||||
static let BlueButtonTitleColor = UIColor.white
|
||||
static let GrayButton = UIColor(hex:0xf3f3f3)
|
||||
static let GrayButtonBorder = UIColor(hex:0x999999)
|
||||
static let GrayButtonHighlighted = UIColor(hex:0xbfbfbf)
|
||||
static let GrayButtonTitleColor = UIColor.black
|
||||
static let GreenButton = UIColor(hex:0x0e7733)
|
||||
static let GreenButtonHighlighted = UIColor(hex:0x08451e)
|
||||
static let GreenButtonTitleColor = UIColor.white
|
||||
static let RedButton = UIColor(hex:0xcd201b)
|
||||
static let RedButtonHighlighted = UIColor(hex:0x991814)
|
||||
static let RedButtonTitleColor = UIColor.white
|
||||
static let TranslucentButton = UIColor(hex:0xffffff, alpha: 0.5)
|
||||
static let TranslucentButtonBorder = UIColor(hex:0xffffff)
|
||||
static let TranslucentButtonHighlighted = UIColor(hex:0xffffff, alpha: 0.5)
|
||||
static let TranslucentButtonTitleColor = UIColor.white
|
||||
static let NavigationBarTitleColor = UIColor.black
|
||||
static let NavigationBarTintColor = UIColor(hex: 0x009de0)
|
||||
}
|
||||
|
||||
struct FontNames {
|
||||
static let OpenSans = "OpenSans"
|
||||
static let OpenSansBold = "OpenSans-Bold"
|
||||
}
|
||||
|
||||
struct Fonts {
|
||||
static let OpenSansBold17 = UIFont(name: FontNames.OpenSansBold, size: CGFloat(17.0)) ?? UIFont.systemFont(ofSize: 17.0)
|
||||
static let OpenSansBold13 = UIFont(name: FontNames.OpenSansBold, size: CGFloat(13.0)) ?? UIFont.systemFont(ofSize: 13.0)
|
||||
static let OpenSans10 = UIFont(name: FontNames.OpenSans, size: CGFloat(10.0)) ?? UIFont.systemFont(ofSize: 10.0)
|
||||
}
|
||||
|
||||
struct Images {
|
||||
static let Blue = UIImage(color: Colors.Blue)
|
||||
static let BlueHighlighted = UIImage(color: Colors.BlueHighlighted)
|
||||
static let Gray = UIImage(color: Colors.Gray)
|
||||
static let GrayHighlighted = UIImage(color: Colors.GrayHighlighted)
|
||||
static let Green = UIImage(color: Colors.Green)
|
||||
static let GreenHighlighted = UIImage(color: Colors.GreenHighlighted)
|
||||
static let Red = UIImage(color: Colors.Red)
|
||||
static let RedHighlighted = UIImage(color: Colors.RedHighlighted)
|
||||
static let BlueButton = UIImage(color: Colors.BlueButton)
|
||||
static let BlueButtonHighlighted = UIImage(color: Colors.BlueButtonHighlighted)
|
||||
static let GrayButton = UIImage(color: Colors.GrayButton)
|
||||
static let GrayButtonHighlighted = UIImage(color: Colors.GrayButtonHighlighted)
|
||||
static let GreenButton = UIImage(color: Colors.GreenButton)
|
||||
static let GreenButtonHighlighted = UIImage(color: Colors.GreenButtonHighlighted)
|
||||
static let RedButton = UIImage(color: Colors.RedButton)
|
||||
static let RedButtonHighlighted = UIImage(color: Colors.RedButtonHighlighted)
|
||||
static let TranslucentButton = UIImage(color: Colors.TranslucentButton)
|
||||
static let TranslucentButtonHighlighted = UIImage(color: Colors.TranslucentButtonHighlighted)
|
||||
}
|
||||
|
||||
let BoatClassNames = [
|
||||
"18Footer",
|
||||
"2.4 Meter",
|
||||
"12 Meter",
|
||||
"29er",
|
||||
"49er",
|
||||
"49erFX",
|
||||
"420",
|
||||
"470",
|
||||
"5O5",
|
||||
"5.5mR",
|
||||
"8mR",
|
||||
"A-Catamaran",
|
||||
"Albin Express",
|
||||
"Albin Ballad",
|
||||
"B/ONE",
|
||||
"Cadet",
|
||||
"International Canoe",
|
||||
"Canoe Taifun",
|
||||
"Contender",
|
||||
"C&C 30",
|
||||
"D-One",
|
||||
"Dragon Int.",
|
||||
"Delphia 24",
|
||||
"Dyas",
|
||||
"Extreme 40",
|
||||
"D35",
|
||||
"Elliott 6m",
|
||||
"Europe Int.",
|
||||
"Formula 18",
|
||||
"Farr 30",
|
||||
"Farr 280",
|
||||
"Finn",
|
||||
"Flying Dutchman",
|
||||
"Flying Phantom",
|
||||
"Folkboat",
|
||||
"FUN",
|
||||
"Formula 16",
|
||||
"GC 32",
|
||||
"GP 26",
|
||||
"Hobie 16",
|
||||
"H-Boat",
|
||||
"Hansa 303",
|
||||
"Hobie Tiger",
|
||||
"Hobie Wild Cat",
|
||||
"International 14",
|
||||
"J/22",
|
||||
"J/24",
|
||||
"J/70",
|
||||
"J/80",
|
||||
"J/88",
|
||||
"J/105",
|
||||
"J/111",
|
||||
"Kielzugvogel",
|
||||
"Kite",
|
||||
"Laser 2",
|
||||
"Laser 4.7",
|
||||
"Laser Radial",
|
||||
"Laser Int.",
|
||||
"Laser SB3",
|
||||
"Lago 26",
|
||||
"Longtze",
|
||||
"M32",
|
||||
"Melges 20",
|
||||
"Melges 24",
|
||||
"Mini Transat 6.50",
|
||||
"Musto Skiff",
|
||||
"Nacra 15",
|
||||
"Nacra 17",
|
||||
"Nacra 17 Foiling",
|
||||
"O-Jolle",
|
||||
"OK Dinghy",
|
||||
"O'pen BIC",
|
||||
"Optimist",
|
||||
"Pirate",
|
||||
"Platu 25",
|
||||
"PWA",
|
||||
"RC44",
|
||||
"RS 100",
|
||||
"RS 200",
|
||||
"RS 400",
|
||||
"RS 500",
|
||||
"RS 800",
|
||||
"RS Aero",
|
||||
"RS:X",
|
||||
"RS Feva",
|
||||
"SKUD 18",
|
||||
"Sonar",
|
||||
"Soling",
|
||||
"Splash Blue",
|
||||
"Splash Red",
|
||||
"Splash Green",
|
||||
"Star",
|
||||
"Streamline",
|
||||
"Sunbeam 22",
|
||||
"Swan 45",
|
||||
"Tartan 10",
|
||||
"Techno 293",
|
||||
"Techno 293 Plus",
|
||||
"Teeny",
|
||||
"Tornado Catamaran",
|
||||
"Tom 28 MAX",
|
||||
"Trias",
|
||||
"Viper 640",
|
||||
"VO60",
|
||||
"VX ONE",
|
||||
"Weta",
|
||||
"X-99",
|
||||
"ORC",
|
||||
"ORC Club",
|
||||
"ORC International",
|
||||
"PHRF"
|
||||
].sorted { $0 < $1 }
|
||||
@@ -3,6 +3,6 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>CoreData 3.xcdatamodel</string>
|
||||
<string>CoreData 4.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="13772" systemVersion="16G1212" minimumToolsVersion="Xcode 4.3" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="BoatCheckIn" representedClassName=".BoatCheckIn" parentEntity="CheckIn" syncable="YES">
|
||||
<attribute name="boatID" attributeType="String" syncable="YES"/>
|
||||
<attribute name="color" attributeType="String" syncable="YES"/>
|
||||
<attribute name="sailID" attributeType="String" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="CheckIn" representedClassName="CheckIn" elementID="Regatta" syncable="YES">
|
||||
<attribute name="isTraining" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="serverURL" attributeType="String" elementID="serverUrl" syncable="YES"/>
|
||||
<relationship name="event" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="Event" inverseName="checkIn" inverseEntity="Event" syncable="YES"/>
|
||||
<relationship name="gpsFixes" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="GPSFix" inverseName="checkIn" inverseEntity="GPSFix" syncable="YES"/>
|
||||
<relationship name="leaderboard" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="Leaderboard" inverseName="checkIn" inverseEntity="Leaderboard" elementID="leaderBoard" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="CompetitorCheckIn" representedClassName=".CompetitorCheckIn" parentEntity="CheckIn" syncable="YES">
|
||||
<attribute name="boatClassName" attributeType="String" syncable="YES"/>
|
||||
<attribute name="competitorID" attributeType="String" indexed="YES" elementID="competitorId" syncable="YES"/>
|
||||
<attribute name="countryCode" attributeType="String" syncable="YES"/>
|
||||
<attribute name="nationality" attributeType="String" syncable="YES"/>
|
||||
<attribute name="sailID" attributeType="String" elementID="sailId" syncable="YES"/>
|
||||
<attribute name="teamImageData" optional="YES" attributeType="Binary" elementID="userImage" syncable="YES"/>
|
||||
<attribute name="teamImageRetry" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="teamImageURL" optional="YES" attributeType="String" elementID="imageUrl" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Event" representedClassName="Event" syncable="YES">
|
||||
<attribute name="endDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="eventID" attributeType="String" indexed="YES" elementID="eventId" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="startDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||
<relationship name="checkIn" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CheckIn" inverseName="event" inverseEntity="CheckIn" elementID="regatta" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="GPSFix" representedClassName="GPSFix" syncable="YES">
|
||||
<attribute name="course" attributeType="Double" defaultValueString="0.0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="latitude" attributeType="Double" defaultValueString="0.0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="longitude" attributeType="Double" defaultValueString="0.0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="speed" attributeType="Double" defaultValueString="0.0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="timestamp" attributeType="Double" defaultValueString="0.0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<relationship name="checkIn" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CheckIn" inverseName="gpsFixes" inverseEntity="CheckIn" elementID="regatta" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Leaderboard" representedClassName="Leaderboard" elementID="LeaderBoard" syncable="YES">
|
||||
<attribute name="name" attributeType="String" indexed="YES" syncable="YES"/>
|
||||
<relationship name="checkIn" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CheckIn" inverseName="leaderboard" inverseEntity="CheckIn" elementID="regatta" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="MarkCheckIn" representedClassName=".MarkCheckIn" parentEntity="CheckIn" syncable="YES">
|
||||
<attribute name="markID" attributeType="String" indexed="YES" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="BoatCheckIn" positionX="-164" positionY="216" width="128" height="90"/>
|
||||
<element name="CheckIn" positionX="-299" positionY="6" width="128" height="135"/>
|
||||
<element name="CompetitorCheckIn" positionX="-299" positionY="215" width="128" height="163"/>
|
||||
<element name="Event" positionX="-137" positionY="-63" width="128" height="118"/>
|
||||
<element name="GPSFix" positionX="-470" positionY="0" width="128" height="133"/>
|
||||
<element name="Leaderboard" positionX="-137" positionY="69" width="128" height="73"/>
|
||||
<element name="MarkCheckIn" positionX="-434" positionY="216" width="128" height="58"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -9,9 +9,10 @@
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
open class CoreDataManager: NSObject {
|
||||
class CoreDataManager: NSObject {
|
||||
|
||||
fileprivate enum Entities: String {
|
||||
case BoatCheckIn
|
||||
case CheckIn
|
||||
case CompetitorCheckIn
|
||||
case Event
|
||||
@@ -20,13 +21,20 @@ open class CoreDataManager: NSObject {
|
||||
case MarkCheckIn
|
||||
}
|
||||
|
||||
open class var sharedManager: CoreDataManager {
|
||||
struct Singleton {
|
||||
static let sharedManager = CoreDataManager()
|
||||
}
|
||||
return Singleton.sharedManager
|
||||
}
|
||||
let name: String
|
||||
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
super.init()
|
||||
}
|
||||
|
||||
class var shared: CoreDataManager {
|
||||
struct Singleton {
|
||||
static let shared = CoreDataManager(name: "CoreData")
|
||||
}
|
||||
return Singleton.shared
|
||||
}
|
||||
|
||||
// MARK: - Fetch
|
||||
|
||||
func fetchCheckIns() -> [CheckIn]? {
|
||||
@@ -44,6 +52,12 @@ open class CoreDataManager: NSObject {
|
||||
|
||||
func fetchCheckIn(checkInData: CheckInData) -> CheckIn? {
|
||||
switch checkInData.type {
|
||||
case .boat:
|
||||
return fetchBoatCheckIn(
|
||||
eventID: checkInData.eventID,
|
||||
leaderboardName: checkInData.leaderboardName,
|
||||
boatID: checkInData.boatID!
|
||||
)
|
||||
case .competitor:
|
||||
return fetchCompetitorCheckIn(
|
||||
eventID: checkInData.eventID,
|
||||
@@ -59,6 +73,45 @@ open class CoreDataManager: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
func fetchBoatCheckIn(eventID: String, leaderboardName: String, boatID: String) -> BoatCheckIn? {
|
||||
let fetchRequest = NSFetchRequest<BoatCheckIn>()
|
||||
fetchRequest.entity = NSEntityDescription.entity(
|
||||
forEntityName: Entities.BoatCheckIn.rawValue,
|
||||
in: managedObjectContext
|
||||
)
|
||||
fetchRequest.predicate = NSPredicate(
|
||||
format: "event.eventID = %@ AND leaderboard.name = %@ AND boatID = %@",
|
||||
eventID,
|
||||
leaderboardName,
|
||||
boatID
|
||||
)
|
||||
do {
|
||||
let checkIns = try managedObjectContext.fetch(fetchRequest)
|
||||
if checkIns.count == 0 {
|
||||
return nil
|
||||
} else {
|
||||
return checkIns[0]
|
||||
}
|
||||
} catch {
|
||||
logError(name: "\(#function)", error: error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func fetchCompetitorCheckIn(serverURL: String, boatClassName: String) -> [CompetitorCheckIn]? {
|
||||
let fetchRequest = NSFetchRequest<NSFetchRequestResult>()
|
||||
fetchRequest.entity = NSEntityDescription.entity(forEntityName: Entities.CompetitorCheckIn.rawValue, in: managedObjectContext)
|
||||
fetchRequest.includesSubentities = true
|
||||
fetchRequest.predicate = NSPredicate(format: "serverURL = %@ AND boatClassName = %@", serverURL, boatClassName)
|
||||
var checkIns: [AnyObject]?
|
||||
do {
|
||||
checkIns = try managedObjectContext.fetch(fetchRequest)
|
||||
} catch {
|
||||
logError(name: "\(#function)", error: error)
|
||||
}
|
||||
return checkIns as? [CompetitorCheckIn]
|
||||
}
|
||||
|
||||
func fetchCompetitorCheckIn(eventID: String, leaderboardName: String, competitorID: String) -> CompetitorCheckIn? {
|
||||
let fetchRequest = NSFetchRequest<CompetitorCheckIn>()
|
||||
fetchRequest.entity = NSEntityDescription.entity(
|
||||
@@ -117,16 +170,25 @@ open class CoreDataManager: NSObject {
|
||||
}
|
||||
|
||||
// MARK: - Insert
|
||||
|
||||
|
||||
func newBoatCheckIn() -> BoatCheckIn {
|
||||
let checkIn = NSEntityDescription.insertNewObject(forEntityName: Entities.BoatCheckIn.rawValue, into: managedObjectContext) as! BoatCheckIn
|
||||
checkIn.event = newEvent(checkIn: checkIn)
|
||||
checkIn.leaderboard = newLeaderboard(checkIn: checkIn)
|
||||
return checkIn
|
||||
}
|
||||
|
||||
func newCompetitorCheckIn() -> CompetitorCheckIn {
|
||||
let checkIn = NSEntityDescription.insertNewObject(forEntityName: Entities.CompetitorCheckIn.rawValue, into: managedObjectContext) as! CompetitorCheckIn
|
||||
checkIn.initialize()
|
||||
checkIn.event = newEvent(checkIn: checkIn)
|
||||
checkIn.leaderboard = newLeaderboard(checkIn: checkIn)
|
||||
return checkIn
|
||||
}
|
||||
|
||||
func newMarkCheckIn() -> MarkCheckIn {
|
||||
let checkIn = NSEntityDescription.insertNewObject(forEntityName: Entities.MarkCheckIn.rawValue, into: managedObjectContext) as! MarkCheckIn
|
||||
checkIn.initialize()
|
||||
checkIn.event = newEvent(checkIn: checkIn)
|
||||
checkIn.leaderboard = newLeaderboard(checkIn: checkIn)
|
||||
return checkIn
|
||||
}
|
||||
|
||||
@@ -173,7 +235,7 @@ open class CoreDataManager: NSObject {
|
||||
|
||||
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
|
||||
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
|
||||
let url = self.applicationDocumentsDirectory.appendingPathComponent("CoreData.sqlite")
|
||||
let url = self.applicationDocumentsDirectory.appendingPathComponent("\(self.name).sqlite")
|
||||
let options = [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true]
|
||||
do {
|
||||
logInfo(name: "\(#function)", info: "Connecting to database...")
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// CreateEventData.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 25.08.17.
|
||||
// Copyright © 2017 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class CreateEventData: BaseData {
|
||||
|
||||
fileprivate struct Keys {
|
||||
static let EndDate = "eventenddate"
|
||||
static let ID = "eventid"
|
||||
static let LeaderboardName = "leaderboard"
|
||||
static let LeaderboardGroupID = "leaderboardgroupid"
|
||||
static let Name = "eventname"
|
||||
static let Regatta = "regatta"
|
||||
static let StartDate = "eventstartdate"
|
||||
}
|
||||
|
||||
var endDate: Double { get { return doubleValue(forKey: Keys.EndDate) / 1000 } }
|
||||
var eventID: String { get { return stringValue(forKey: Keys.ID) } }
|
||||
var name: String { get { return stringValue(forKey: Keys.Name) } }
|
||||
var leaderboardName: String { get { return stringValue(forKey: Keys.LeaderboardName) } }
|
||||
var leaderboardGroupID: String { get { return stringValue(forKey: Keys.LeaderboardGroupID) } }
|
||||
var regatta: String { get { return stringValue(forKey: Keys.Regatta) } }
|
||||
var startDate: Double { get { return doubleValue(forKey: Keys.StartDate) / 1000 } }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// CreateTrainingData.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 25.08.17.
|
||||
// Copyright © 2017 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class CreateTrainingData: NSObject {
|
||||
|
||||
let serverURL: String
|
||||
let boatClassName: String
|
||||
let sailID: String
|
||||
let nationality: String
|
||||
|
||||
var createEventData: CreateEventData?
|
||||
var competitorID: String?
|
||||
|
||||
init(serverURL: String, boatClassName: String, sailID: String, nationality: String) {
|
||||
self.serverURL = serverURL
|
||||
self.boatClassName = boatClassName
|
||||
self.sailID = sailID
|
||||
self.nationality = nationality
|
||||
super.init()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
//
|
||||
// CreateTrainingViewController.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 21.08.17.
|
||||
// Copyright © 2017 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol CreateTrainingViewControllerDelegate: class {
|
||||
|
||||
func createTrainingViewController(_ controller: CreateTrainingViewController, didCheckIn checkIn: CheckIn)
|
||||
|
||||
}
|
||||
|
||||
class CreateTrainingViewController: UIViewController {
|
||||
|
||||
weak var delegate: CreateTrainingViewControllerDelegate?
|
||||
|
||||
weak var coreDataManager: CoreDataManager!
|
||||
|
||||
@IBOutlet var boatClassPickerView: UIPickerView!
|
||||
@IBOutlet var boatClassNameLabel: UILabel!
|
||||
@IBOutlet var createTrainingButton: UIButton!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
setup()
|
||||
login()
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
fileprivate func setup() {
|
||||
setupBoatClassPickerView()
|
||||
setupButtons()
|
||||
setupLocalization()
|
||||
}
|
||||
|
||||
fileprivate func setupBoatClassPickerView() {
|
||||
if let row = BoatClassNames.index(of: Preferences.boatClassName) {
|
||||
boatClassPickerView.selectRow(row, inComponent: 0, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func setupButtons() {
|
||||
makeBlue(button: createTrainingButton)
|
||||
}
|
||||
|
||||
fileprivate func setupLocalization() {
|
||||
navigationItem.title = Translation.CreateTrainingView.Title.String
|
||||
boatClassNameLabel.text = Translation.CreateTrainingView.BoatClassNameLabel.Text.String
|
||||
createTrainingButton.setTitle(Translation.CreateTrainingView.CreateTrainingButton.Title.String, for: .normal)
|
||||
}
|
||||
|
||||
// MARK: - Login
|
||||
|
||||
fileprivate func login() {
|
||||
SVProgressHUD.show()
|
||||
signUpController.login(success: { (userName) in
|
||||
SVProgressHUD.dismiss()
|
||||
}) { (error, message) in
|
||||
SVProgressHUD.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func cancelButtonTapped(_ sender: Any) {
|
||||
presentingViewController?.dismiss(animated: true)
|
||||
}
|
||||
|
||||
@IBAction func createTrainingButtonTapped(_ sender: Any) {
|
||||
// TODO: sailID and nationality shouldn't be static
|
||||
createTrainingAndPerformCheckIn(
|
||||
forBoatClassName: BoatClassNames[boatClassPickerView.selectedRow(inComponent: 0)],
|
||||
sailID: "",
|
||||
nationality: "GER"
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - CreateTraining
|
||||
|
||||
fileprivate func createTrainingAndPerformCheckIn(
|
||||
forBoatClassName boatClassName: String,
|
||||
sailID: String,
|
||||
nationality: String)
|
||||
{
|
||||
SVProgressHUD.show()
|
||||
self.trainingController.createTraining(forBoatClassName: boatClassName, sailID: sailID, nationality: nationality, success: { checkInData in
|
||||
Preferences.boatClassName = boatClassName
|
||||
self.trainingCheckInController.checkInWithViewController(self, checkInData: checkInData, success: { [weak self] (checkIn) in
|
||||
SVProgressHUD.dismiss()
|
||||
if let strongSelf = self {
|
||||
strongSelf.dismiss(animated: true) {
|
||||
strongSelf.delegate?.createTrainingViewController(strongSelf, didCheckIn: checkIn)
|
||||
}
|
||||
}
|
||||
}) { [weak self] (error) in
|
||||
SVProgressHUD.dismiss()
|
||||
self?.handle(error: error)
|
||||
}
|
||||
}) { [weak self] (error) in
|
||||
SVProgressHUD.dismiss()
|
||||
self?.handle(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Propeties
|
||||
|
||||
fileprivate lazy var signUpController: SignUpController = {
|
||||
return SignUpController(baseURLString: self.trainingController.baseURLString)
|
||||
}()
|
||||
|
||||
fileprivate lazy var trainingCheckInController: TrainingCheckInController = {
|
||||
return TrainingCheckInController(coreDataManager: self.coreDataManager)
|
||||
}()
|
||||
|
||||
fileprivate lazy var trainingController: TrainingController = {
|
||||
return TrainingController(coreDataManager: self.coreDataManager, baseURLString: Preferences.trainingEndpoint)
|
||||
}()
|
||||
|
||||
// MARK: - Helper
|
||||
|
||||
fileprivate func handle(error: Error) {
|
||||
if ErrorHelper.isResponseUnauthorized(error: error as NSError) {
|
||||
self.signUpController.loginWithViewController(self)
|
||||
} else {
|
||||
self.showAlert(forError: error)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UIPickerViewDataSource
|
||||
|
||||
extension CreateTrainingViewController: UIPickerViewDataSource {
|
||||
|
||||
func numberOfComponents(in pickerView: UIPickerView) -> Int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
|
||||
return BoatClassNames.count
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UIPickerViewDelegate
|
||||
|
||||
extension CreateTrainingViewController: UIPickerViewDelegate {
|
||||
|
||||
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
|
||||
return BoatClassNames[row]
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// ResponseErrorHelper.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 02.01.18.
|
||||
// Copyright © 2018 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class ErrorHelper: NSObject {
|
||||
|
||||
static func isResponseUnauthorized(error: NSError) -> Bool {
|
||||
if let response = error.userInfo[AFNetworkingOperationFailingURLResponseErrorKey] as? HTTPURLResponse {
|
||||
return response.statusCode == 401
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,21 +26,73 @@ func logError(name: String, error: String) {
|
||||
#endif
|
||||
}
|
||||
|
||||
// MARK: - UIButton
|
||||
|
||||
extension UIButton {
|
||||
|
||||
func updateLayer(cornerRadius: CGFloat) {
|
||||
layer.cornerRadius = cornerRadius
|
||||
layer.masksToBounds = true
|
||||
}
|
||||
|
||||
func updateLayer(cornerRadius: CGFloat, borderColor: UIColor) {
|
||||
updateLayer(cornerRadius: cornerRadius)
|
||||
layer.borderWidth = 1
|
||||
layer.borderColor = borderColor.cgColor
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UIColor
|
||||
|
||||
extension UIColor { // Set color to RGB hex value. See http://stackoverflow.com/a/24263296
|
||||
extension UIColor {
|
||||
|
||||
// Set color to RGB hex value
|
||||
// See http://stackoverflow.com/a/24263296
|
||||
// See https://cocoacasts.com/from-hex-to-uicolor-and-back-in-swift/
|
||||
|
||||
convenience init(red: Int, green: Int, blue: Int) {
|
||||
assert(red >= 0 && red <= 255, "Invalid red component")
|
||||
assert(green >= 0 && green <= 255, "Invalid green component")
|
||||
assert(blue >= 0 && blue <= 255, "Invalid blue component")
|
||||
self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1.0)
|
||||
convenience init(hex: Int) {
|
||||
self.init(hex: hex, alpha: 1)
|
||||
}
|
||||
|
||||
convenience init(hex: Int, alpha: CGFloat) {
|
||||
let red = CGFloat((hex >> 16) & 0xff) / 255
|
||||
let green = CGFloat((hex >> 8) & 0xff) / 255
|
||||
let blue = CGFloat(hex & 0xff) / 255
|
||||
self.init(red: red, green: green, blue: blue, alpha: alpha)
|
||||
}
|
||||
|
||||
convenience init(hex:Int) {
|
||||
self.init(red:(hex >> 16) & 0xff, green:(hex >> 8) & 0xff, blue:hex & 0xff)
|
||||
convenience init?(hexString: String) {
|
||||
var hexSanitized = hexString.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
|
||||
var rgb: UInt32 = 0
|
||||
var r: CGFloat = 0.0
|
||||
var g: CGFloat = 0.0
|
||||
var b: CGFloat = 0.0
|
||||
var a: CGFloat = 1.0
|
||||
let length = hexSanitized.count
|
||||
guard Scanner(string: hexSanitized).scanHexInt32(&rgb) else { return nil }
|
||||
if length == 6 {
|
||||
r = CGFloat((rgb & 0xFF0000) >> 16) / 255.0
|
||||
g = CGFloat((rgb & 0x00FF00) >> 8) / 255.0
|
||||
b = CGFloat(rgb & 0x0000FF) / 255.0
|
||||
} else if length == 8 {
|
||||
r = CGFloat((rgb & 0xFF000000) >> 24) / 255.0
|
||||
g = CGFloat((rgb & 0x00FF0000) >> 16) / 255.0
|
||||
b = CGFloat((rgb & 0x0000FF00) >> 8) / 255.0
|
||||
a = CGFloat(rgb & 0x000000FF) / 255.0
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
self.init(red: r, green: g, blue: b, alpha: a)
|
||||
}
|
||||
|
||||
|
||||
convenience init(contrastColorFor color: UIColor) {
|
||||
var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
|
||||
color.getHue(&h, saturation: &s, brightness: &b, alpha: &a)
|
||||
self.init(hue: h, saturation: s, brightness: 1 - b, alpha: a)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UIImage
|
||||
@@ -71,3 +123,85 @@ extension UITableViewCell {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UIViewController
|
||||
|
||||
extension UIViewController {
|
||||
|
||||
func makeRoundWithShadow(view: UIView) {
|
||||
|
||||
// Corner radius
|
||||
view.layer.cornerRadius = view.frame.height / 2
|
||||
|
||||
// Shadow
|
||||
view.layer.shadowColor = UIColor.black.cgColor
|
||||
view.layer.shadowOffset = CGSize(width: 3, height: 3)
|
||||
view.layer.shadowOpacity = 0.5
|
||||
view.layer.shadowRadius = 4.0
|
||||
}
|
||||
|
||||
func makeRound(view: UIView, cornerRadius: CGFloat) {
|
||||
view.layer.cornerRadius = cornerRadius
|
||||
view.layer.masksToBounds = true
|
||||
}
|
||||
|
||||
func makeBlue(button: UIButton) {
|
||||
button.setTitleColor(Colors.BlueButtonTitleColor, for: .normal)
|
||||
button.setBackgroundImage(Images.BlueButton, for: .normal)
|
||||
button.setBackgroundImage(Images.BlueButtonHighlighted, for: .highlighted)
|
||||
button.updateLayer(cornerRadius: 4)
|
||||
}
|
||||
|
||||
func makeGray(button: UIButton) {
|
||||
button.setTitleColor(Colors.GrayButtonTitleColor, for: .normal)
|
||||
button.setBackgroundImage(Images.GrayButton, for: .normal)
|
||||
button.setBackgroundImage(Images.GrayButtonHighlighted, for: .highlighted)
|
||||
button.updateLayer(cornerRadius: 4, borderColor: Colors.GrayButtonBorder)
|
||||
}
|
||||
|
||||
func makeGreen(button: UIButton) {
|
||||
button.setTitleColor(Colors.GreenButtonTitleColor, for: .normal)
|
||||
button.setBackgroundImage(Images.GreenButton, for: .normal)
|
||||
button.setBackgroundImage(Images.GreenButtonHighlighted, for: .highlighted)
|
||||
button.updateLayer(cornerRadius: 4)
|
||||
}
|
||||
|
||||
func makeRed(button: UIButton) {
|
||||
button.setTitleColor(Colors.RedButtonTitleColor, for: .normal)
|
||||
button.setBackgroundImage(Images.RedButton, for: .normal)
|
||||
button.setBackgroundImage(Images.RedButtonHighlighted, for: .highlighted)
|
||||
button.updateLayer(cornerRadius: 4)
|
||||
}
|
||||
|
||||
func makeTranslucent(button: UIButton) {
|
||||
button.setTitleColor(Colors.TranslucentButtonTitleColor, for: .normal)
|
||||
button.setBackgroundImage(Images.TranslucentButton, for: .normal)
|
||||
button.setBackgroundImage(Images.TranslucentButtonHighlighted, for: .highlighted)
|
||||
button.updateLayer(cornerRadius: 4, borderColor: Colors.TranslucentButtonBorder)
|
||||
}
|
||||
|
||||
func presentAboutViewController() {
|
||||
let storyboard = UIStoryboard(name: "Main", bundle: nil)
|
||||
guard let aboutNC = storyboard.instantiateViewController(withIdentifier: "AboutNavigationController") as? UINavigationController else { return }
|
||||
present(aboutNC, animated: true)
|
||||
}
|
||||
|
||||
func presentSettingsViewController() {
|
||||
let storyboard = UIStoryboard(name: "Main", bundle: nil)
|
||||
guard let aboutNC = storyboard.instantiateViewController(withIdentifier: "SettingsNavigationController") as? UINavigationController else { return }
|
||||
present(aboutNC, animated: true)
|
||||
|
||||
}
|
||||
|
||||
func showAlert(forError error: Error) {
|
||||
let alertController = UIAlertController(
|
||||
title: Translation.Common.Error.String,
|
||||
message: error.localizedDescription,
|
||||
preferredStyle: .alert
|
||||
)
|
||||
let okAction = UIAlertAction(title: Translation.Common.OK.String, style: .default, handler: nil)
|
||||
alertController.addAction(okAction)
|
||||
present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// ForgotPasswordViewController.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 02.08.17.
|
||||
// Copyright © 2017 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol ForgotPasswordViewControllerDelegate {
|
||||
|
||||
func forgotPasswordViewController(_ controller: ForgotPasswordViewController, willChangePasswordForEmail email: String)
|
||||
|
||||
func forgotPasswordViewController(_ controller: ForgotPasswordViewController, willChangePasswordForUserName userName: String)
|
||||
|
||||
}
|
||||
|
||||
class ForgotPasswordViewController: FormularViewController {
|
||||
|
||||
weak var signUpController: SignUpController?
|
||||
|
||||
@IBOutlet weak var infoLabel: UILabel!
|
||||
@IBOutlet weak var emailLabel: UILabel!
|
||||
@IBOutlet weak var emailTextField: UITextField!
|
||||
@IBOutlet weak var userNameLabel: UILabel!
|
||||
@IBOutlet weak var userNameTextField: UITextField!
|
||||
@IBOutlet weak var resetPasswordButton: UIButton!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
textFields.append(contentsOf: [emailTextField, userNameTextField])
|
||||
setup()
|
||||
}
|
||||
|
||||
fileprivate func setup() {
|
||||
setupButtons()
|
||||
setupLocalization()
|
||||
}
|
||||
|
||||
fileprivate func setupButtons() {
|
||||
makeBlue(button: resetPasswordButton)
|
||||
}
|
||||
|
||||
fileprivate func setupLocalization() {
|
||||
navigationItem.title = SignUpTranslation.ForgotPasswordView.Title.String
|
||||
infoLabel.text = SignUpTranslation.ForgotPasswordView.InfoLabel.Text.String
|
||||
emailLabel.text = SignUpTranslation.Common.Email.String
|
||||
userNameLabel.text = SignUpTranslation.Common.UserName.String
|
||||
resetPasswordButton.setTitle(SignUpTranslation.ForgotPasswordView.ResetPasswordButton.Title.String, for: .normal)
|
||||
}
|
||||
|
||||
@IBAction func resetPasswordButtonTapped(_ sender: Any) {
|
||||
if let email = emailTextField.text {
|
||||
if !email.isEmpty {
|
||||
signUpController?.forgotPasswordViewController(self, willChangePasswordForEmail: email)
|
||||
return
|
||||
}
|
||||
}
|
||||
if let userName = userNameTextField.text {
|
||||
if !userName.isEmpty {
|
||||
signUpController?.forgotPasswordViewController(self, willChangePasswordForUserName: userName)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// FormularViewController.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 08.08.17.
|
||||
// Copyright © 2017 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class FormularViewController: UIViewController {
|
||||
|
||||
var textFields: [UITextField] = []
|
||||
|
||||
@IBOutlet weak var scrollView: UIScrollView!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
subscribeForNotifications()
|
||||
}
|
||||
|
||||
deinit {
|
||||
unsubscribeFromNotifications()
|
||||
}
|
||||
|
||||
// MARK: - Notifications
|
||||
|
||||
fileprivate func subscribeForNotifications() {
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(keyboardWillShowNotification(_:)),
|
||||
name: .UIKeyboardWillShow,
|
||||
object: nil
|
||||
)
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(keyboardWillHideNotification(_:)),
|
||||
name: .UIKeyboardWillHide,
|
||||
object: nil
|
||||
)
|
||||
}
|
||||
|
||||
fileprivate func unsubscribeFromNotifications() {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
@objc fileprivate func keyboardWillShowNotification(_ notification: Notification) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
if let keyboardSize = notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? CGRect {
|
||||
let contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardSize.height, right: 0)
|
||||
self.scrollView.contentInset = contentInsets
|
||||
self.scrollView.scrollIndicatorInsets = contentInsets
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@objc fileprivate func keyboardWillHideNotification(_ notification: Notification) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
let contentInsets = UIEdgeInsets.zero
|
||||
self.scrollView.contentInset = contentInsets
|
||||
self.scrollView.scrollIndicatorInsets = contentInsets
|
||||
})
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func viewTapped(_ sender: Any) {
|
||||
textFields.forEach { (textField) in
|
||||
textField.resignFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UITextFieldDelegate
|
||||
|
||||
extension FormularViewController: UITextFieldDelegate {
|
||||
|
||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
if let nextResponder = textField.superview?.viewWithTag(textField.tag + 1) {
|
||||
nextResponder.becomeFirstResponder()
|
||||
} else {
|
||||
textField.resignFirstResponder()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,10 +12,15 @@ class GPSFixController: NSObject {
|
||||
|
||||
struct NotificationType {
|
||||
static let ModeChanged = "GPSFixController.ModeChanged"
|
||||
static let SentGPSFixes = "GPSFixController.SentGPSFixes"
|
||||
}
|
||||
|
||||
struct UserInfo {
|
||||
static let Mode = "Mode"
|
||||
static let Sent = "Sent"
|
||||
struct SentKey {
|
||||
static let Count = "Count"
|
||||
}
|
||||
}
|
||||
|
||||
struct GPSFixSending {
|
||||
@@ -39,10 +44,12 @@ class GPSFixController: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
let checkIn: CheckIn
|
||||
weak var checkIn: CheckIn!
|
||||
weak var coreDataManager: CoreDataManager!
|
||||
|
||||
init(checkIn: CheckIn) {
|
||||
init(checkIn: CheckIn, coreDataManager: CoreDataManager) {
|
||||
self.checkIn = checkIn
|
||||
self.coreDataManager = coreDataManager
|
||||
}
|
||||
|
||||
// MARK: - Send All
|
||||
@@ -112,11 +119,11 @@ class GPSFixController: NSObject {
|
||||
failure: @escaping (_ error: Error, _ gpsFixesLeft: Set<GPSFix>) -> Void)
|
||||
{
|
||||
log(info: "\(gpsFixes.count) GPS fixes will be sent and \(gpsFixesLeft.count) will be left")
|
||||
requestManager.postGPSFixes(
|
||||
gpsFixes: gpsFixes,
|
||||
success: { () in self.sendSliceSuccess(gpsFixes: gpsFixes, gpsFixesLeft: gpsFixesLeft, success: success) },
|
||||
failure: { (error) in self.sendSliceFailure(error: error, gpsFixesLeft: gpsFixesLeft, failure: failure) }
|
||||
)
|
||||
checkInRequestManager.postGPSFixes(gpsFixes: gpsFixes, success: { () in
|
||||
self.sendSliceSuccess(gpsFixes: gpsFixes, gpsFixesLeft: gpsFixesLeft, success: success)
|
||||
}) { (error) in
|
||||
self.sendSliceFailure(error: error, gpsFixesLeft: gpsFixesLeft, failure: failure)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func sendSliceSuccess(
|
||||
@@ -127,8 +134,9 @@ class GPSFixController: NSObject {
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.log(info: "Sending \(gpsFixes.count) GPS fixes was successful")
|
||||
self.postModeChangedNotification(mode: BatteryManager.sharedManager.batterySaving ? .BatterySaving : .Online)
|
||||
CoreDataManager.sharedManager.deleteObjects(objects: gpsFixes)
|
||||
CoreDataManager.sharedManager.saveContext()
|
||||
self.postSentGPSFixes(count: gpsFixes.count)
|
||||
self.coreDataManager.deleteObjects(objects: gpsFixes)
|
||||
self.coreDataManager.saveContext()
|
||||
self.log(info: "\(gpsFixes.count) GPS fixes deleted")
|
||||
success(gpsFixesLeft)
|
||||
})
|
||||
@@ -151,14 +159,19 @@ class GPSFixController: NSObject {
|
||||
fileprivate func postModeChangedNotification(mode: Mode) {
|
||||
let userInfo = [UserInfo.Mode: mode.rawValue]
|
||||
let notification = Notification(name: Notification.Name(rawValue: NotificationType.ModeChanged), object: self, userInfo: userInfo)
|
||||
NotificationQueue.default.enqueue(notification, postingStyle: NotificationQueue.PostingStyle.asap)
|
||||
NotificationQueue.default.enqueue(notification, postingStyle: .asap)
|
||||
}
|
||||
|
||||
|
||||
fileprivate func postSentGPSFixes(count: Int) {
|
||||
let userInfo = [UserInfo.Sent: [UserInfo.SentKey.Count: count]]
|
||||
let notification = Notification(name: Notification.Name(rawValue: NotificationType.SentGPSFixes), object: self, userInfo: userInfo)
|
||||
NotificationQueue.default.enqueue(notification, postingStyle: .asap)
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
fileprivate lazy var requestManager: RequestManager = {
|
||||
let requestManager = RequestManager(baseURLString: self.checkIn.serverURL)
|
||||
return requestManager
|
||||
fileprivate lazy var checkInRequestManager: CheckInRequestManager = {
|
||||
return CheckInRequestManager(baseURLString: self.checkIn.serverURL)
|
||||
}()
|
||||
|
||||
// MARK: - Helper
|
||||
|
||||
@@ -1,464 +0,0 @@
|
||||
//
|
||||
// ViewController.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 17/10/14.
|
||||
// Copyright (c) 2014 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
|
||||
class HomeViewController: UIViewController {
|
||||
|
||||
fileprivate struct Segue {
|
||||
static let About = "About"
|
||||
static let Competitor = "Competitor"
|
||||
static let Mark = "Mark"
|
||||
static let Scan = "Scan"
|
||||
static let Settings = "Settings"
|
||||
}
|
||||
|
||||
var selectedCheckIn: CheckIn?
|
||||
|
||||
@IBOutlet var headerView: UIView! // Strong reference needed to avoid deallocation when not attached to table view
|
||||
|
||||
@IBOutlet weak var headerTitleLabel: UILabel!
|
||||
@IBOutlet weak var tableView: UITableView!
|
||||
@IBOutlet weak var scanCodeButton: UIButton!
|
||||
@IBOutlet weak var noCodeButton: UIButton!
|
||||
@IBOutlet weak var infoCodeLabel: UILabel!
|
||||
@IBOutlet weak var footerView: UIView!
|
||||
@IBOutlet weak var footerTextView: UITextView!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
setup()
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
review()
|
||||
subscribeForNewCheckInURLNotifications()
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
unsubscribeFromNewCheckInURLNotifications()
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
layout()
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
fileprivate func setup() {
|
||||
setupButtons()
|
||||
setupLocalization()
|
||||
setupNavigationBar()
|
||||
setupTableView()
|
||||
setupTableViewDataSource()
|
||||
setupTableViewHeader()
|
||||
}
|
||||
|
||||
fileprivate func setupButtons() {
|
||||
scanCodeButton.setBackgroundImage(Images.BlueHighlighted, for: .highlighted)
|
||||
noCodeButton.setBackgroundImage(Images.GrayHighlighted, for: .highlighted)
|
||||
}
|
||||
|
||||
fileprivate func setupLocalization() {
|
||||
headerTitleLabel.text = Translation.HomeView.HeaderTitleLabel.Text.String
|
||||
scanCodeButton.setTitle(Translation.ScanView.Title.String, for: .normal)
|
||||
noCodeButton.setTitle(Translation.HomeView.NoCodeAlert.Title.String, for: .normal)
|
||||
infoCodeLabel.text = Translation.HomeView.InfoCodeLabel.Text.String
|
||||
footerTextView.text = Translation.HomeView.FooterTextView.Text.String
|
||||
}
|
||||
|
||||
fileprivate func setupNavigationBar() {
|
||||
navigationItem.title = Application.Title
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIImageView(image: UIImage(named: "sap_logo")))
|
||||
}
|
||||
|
||||
fileprivate func setupTableView() {
|
||||
tableView.rowHeight = UITableViewAutomaticDimension
|
||||
tableView.estimatedRowHeight = 140
|
||||
}
|
||||
|
||||
fileprivate func setupTableViewDataSource() {
|
||||
do {
|
||||
try fetchedResultsController.performFetch()
|
||||
} catch {
|
||||
logError(name: "\(#function)", error: error)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func setupTableViewHeader() {
|
||||
if fetchedResultsController.sections?[0].numberOfObjects ?? 0 == 0 {
|
||||
tableView.tableHeaderView = nil
|
||||
} else {
|
||||
tableView.tableHeaderView = headerView
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Layout
|
||||
|
||||
fileprivate func layout() {
|
||||
self.layoutFooterView()
|
||||
}
|
||||
|
||||
fileprivate func layoutFooterView() {
|
||||
let height = footerTextView.sizeThatFits(CGSize(width: footerView.frame.width, height: CGFloat.greatestFiniteMagnitude)).height
|
||||
footerView.frame = CGRect(
|
||||
x: footerView.frame.origin.x,
|
||||
y: footerView.frame.origin.y,
|
||||
width: footerView.frame.width,
|
||||
height: height
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Review
|
||||
|
||||
fileprivate func review() {
|
||||
self.reviewTerms(completion: {
|
||||
logInfo(name: "\(#function)", info: "Review terms done.")
|
||||
self.reviewCodeConvention(completion: {
|
||||
logInfo(name: "\(#function)", info: "Review code convention done.")
|
||||
self.reviewGPSFixes(completion: {
|
||||
logInfo(name: "\(#function)", info: "Review GPS fixes done.")
|
||||
self.reviewNewCheckIn(completion: {
|
||||
logInfo(name: "\(#function)", info: "Review new check-in done.")
|
||||
self.reviewSelectedCheckIn(completion: {
|
||||
logInfo(name: "\(#function)", info: "Review selected check-in done.")
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// MARK: 1. Review Terms
|
||||
|
||||
fileprivate func reviewTerms(completion: @escaping () -> Void) {
|
||||
guard Preferences.termsAccepted == false else { completion(); return }
|
||||
let alertController = UIAlertController(
|
||||
title: Translation.HomeView.TermsAlert.Title.String,
|
||||
message: Translation.HomeView.TermsAlert.Message.String,
|
||||
preferredStyle: .alert
|
||||
)
|
||||
let showTermsAction = UIAlertAction(title: Translation.HomeView.TermsAlert.ShowTermsAction.Title.String, style: .default) { action in
|
||||
UIApplication.shared.openURL(URLs.Terms)
|
||||
self.reviewTerms(completion: completion) // Review terms until user accepted terms
|
||||
}
|
||||
let acceptTermsAction = UIAlertAction(title: Translation.HomeView.TermsAlert.AcceptTermsAction.Title.String, style: .default) { action in
|
||||
Preferences.termsAccepted = true
|
||||
completion() // Terms accepted
|
||||
}
|
||||
alertController.addAction(showTermsAction)
|
||||
alertController.addAction(acceptTermsAction)
|
||||
present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
// MARK: 2. Review Code Convention
|
||||
|
||||
fileprivate func reviewCodeConvention(completion: @escaping () -> Void) {
|
||||
#if DEBUG
|
||||
guard Preferences.codeConventionRead == false else { completion(); return }
|
||||
let alertController = UIAlertController(
|
||||
title: "Code Convention",
|
||||
message: "Please try to respect the code convention which is used for this project.",
|
||||
preferredStyle: .alert
|
||||
)
|
||||
let showCodeConventionAction = UIAlertAction(title: "Code Convention", style: .default) { action in
|
||||
UIApplication.shared.openURL(URLs.CodeConvention)
|
||||
self.reviewCodeConvention(completion: completion)
|
||||
}
|
||||
let okAction = UIAlertAction(title: "OK", style: .default) { action in
|
||||
Preferences.codeConventionRead = true
|
||||
completion()
|
||||
}
|
||||
alertController.addAction(showCodeConventionAction)
|
||||
alertController.addAction(okAction)
|
||||
present(alertController, animated: true, completion: nil)
|
||||
#else
|
||||
completion()
|
||||
#endif
|
||||
}
|
||||
|
||||
// MARK: 3. Review GPS Fixes
|
||||
|
||||
fileprivate func reviewGPSFixes(completion: @escaping () -> Void) {
|
||||
SVProgressHUD.show()
|
||||
fetchedResultsController.delegate = nil
|
||||
let checkIns = CoreDataManager.sharedManager.fetchCheckIns() ?? []
|
||||
reviewGPSFixes(checkIns: checkIns) {
|
||||
self.reviewGPSFixesCompleted(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func reviewGPSFixes(checkIns: [CheckIn], completion: @escaping () -> Void) {
|
||||
guard checkIns.count > 0 else { completion(); return }
|
||||
let gpsFixController = GPSFixController.init(checkIn: checkIns[0])
|
||||
gpsFixController.sendAll(completion: { (withSuccess) in
|
||||
self.reviewGPSFixes(checkIns: Array(checkIns[1..<checkIns.count]), completion: completion)
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate func reviewGPSFixesCompleted(completion: () -> Void) {
|
||||
SVProgressHUD.popActivity()
|
||||
fetchedResultsController.delegate = self
|
||||
completion()
|
||||
}
|
||||
|
||||
// MARK: 4. Review New Check-In
|
||||
|
||||
fileprivate func reviewNewCheckIn(completion: @escaping () -> Void) {
|
||||
guard let urlString = Preferences.newCheckInURL else { completion(); return }
|
||||
guard let checkInData = CheckInData(urlString: urlString) else { completion(); return }
|
||||
checkInController.checkIn(checkInData: checkInData, completion: { (withSuccess) in
|
||||
Preferences.newCheckInURL = nil
|
||||
self.selectedCheckIn = CoreDataManager.sharedManager.fetchCheckIn(checkInData: checkInData)
|
||||
completion()
|
||||
})
|
||||
}
|
||||
|
||||
// MARK: 5. Review Selected Check-In
|
||||
|
||||
fileprivate func reviewSelectedCheckIn(completion: () -> Void) {
|
||||
if shouldPerformCompetitorSegue() {
|
||||
performSegue(withIdentifier: Segue.Competitor, sender: self)
|
||||
} else if shouldPerformMarkSegue() {
|
||||
performSegue(withIdentifier: Segue.Mark, sender: self)
|
||||
}
|
||||
completion()
|
||||
}
|
||||
|
||||
// MARK: - Notifications
|
||||
|
||||
fileprivate func subscribeForNewCheckInURLNotifications() {
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(HomeViewController.newCheckInURLNotification(_:)),
|
||||
name: NSNotification.Name(rawValue: Preferences.NotificationType.NewCheckInURLChanged),
|
||||
object: nil
|
||||
)
|
||||
}
|
||||
|
||||
fileprivate func unsubscribeFromNewCheckInURLNotifications() {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
@objc fileprivate func newCheckInURLNotification(_ notification: Notification) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.reviewNewCheckIn(completion: {
|
||||
logInfo(name: "\(#function)", info: "Review new check-in done.")
|
||||
self.reviewSelectedCheckIn(completion: {
|
||||
logInfo(name: "\(#function)", info: "Review selected check-in done.")
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func optionButtonTapped(_ sender: AnyObject) {
|
||||
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
if let popoverController = alertController.popoverPresentationController {
|
||||
popoverController.barButtonItem = sender as? UIBarButtonItem
|
||||
}
|
||||
let settingsAction = UIAlertAction(title: Translation.SettingsView.Title.String, style: .default) { (action) -> Void in
|
||||
self.performSegue(withIdentifier: Segue.Settings, sender: alertController)
|
||||
}
|
||||
let aboutAction = UIAlertAction(title: Translation.Common.Info.String, style: .default) { (action) -> Void in
|
||||
self.performSegue(withIdentifier: Segue.About, sender: alertController)
|
||||
}
|
||||
let cancelAction = UIAlertAction(title: Translation.Common.Cancel.String, style: .cancel, handler: nil)
|
||||
alertController.addAction(settingsAction)
|
||||
alertController.addAction(aboutAction)
|
||||
alertController.addAction(cancelAction)
|
||||
present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@IBAction func scanButtonTapped(_ sender: AnyObject) {
|
||||
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera) {
|
||||
performSegue(withIdentifier: Segue.Scan, sender: sender)
|
||||
} else {
|
||||
showNoCameraAlert()
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func noCodeButtonTapped(_ sender: AnyObject) {
|
||||
showNoCodeAlert()
|
||||
}
|
||||
|
||||
// MARK: - Alerts
|
||||
|
||||
fileprivate func showNoCameraAlert() {
|
||||
let alertController = UIAlertController(
|
||||
title: Translation.Common.Error.String,
|
||||
message: Translation.HomeView.NoCameraAlert.Message.String,
|
||||
preferredStyle: .alert
|
||||
)
|
||||
let okAction = UIAlertAction(title: Translation.Common.OK.String, style: .default, handler: nil)
|
||||
alertController.addAction(okAction)
|
||||
present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
fileprivate func showNoCodeAlert() {
|
||||
let alertController = UIAlertController(
|
||||
title: Translation.HomeView.NoCodeAlert.Title.String,
|
||||
message: Translation.HomeView.NoCodeAlert.Message.String,
|
||||
preferredStyle: .alert
|
||||
)
|
||||
let okAction = UIAlertAction(title: Translation.Common.OK.String, style: .default, handler: nil)
|
||||
alertController.addAction(okAction)
|
||||
present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
// MARK: - Segues
|
||||
|
||||
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
||||
if (identifier == Segue.Competitor) {
|
||||
return shouldPerformCompetitorSegue()
|
||||
} else if (identifier == Segue.Mark) {
|
||||
return shouldPerformMarkSegue()
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func shouldPerformCompetitorSegue() -> Bool {
|
||||
return selectedCheckIn != nil && selectedCheckIn is CompetitorCheckIn
|
||||
}
|
||||
|
||||
fileprivate func shouldPerformMarkSegue() -> Bool {
|
||||
return selectedCheckIn != nil && selectedCheckIn is MarkCheckIn
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
if (segue.identifier == Segue.Competitor) {
|
||||
guard let competitorVC = segue.destination as? CompetitorViewController else { return }
|
||||
guard let competitorCheckIn = selectedCheckIn as? CompetitorCheckIn else { return }
|
||||
competitorVC.competitorCheckIn = competitorCheckIn
|
||||
selectedCheckIn = nil
|
||||
} else if (segue.identifier == Segue.Mark) {
|
||||
guard let markVC = segue.destination as? MarkViewController else { return }
|
||||
guard let markCheckIn = selectedCheckIn as? MarkCheckIn else { return }
|
||||
markVC.markCheckIn = markCheckIn
|
||||
selectedCheckIn = nil
|
||||
} else if (segue.identifier == Segue.Scan) {
|
||||
guard let scanVC = segue.destination as? ScanViewController else { return }
|
||||
scanVC.homeViewController = self
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
fileprivate lazy var checkInController: CheckInController = {
|
||||
let checkInController = CheckInController()
|
||||
checkInController.delegate = self
|
||||
return checkInController
|
||||
}()
|
||||
|
||||
fileprivate lazy var fetchedResultsController: NSFetchedResultsController<CheckIn> = {
|
||||
let fetchedResultsController = CoreDataManager.sharedManager.checkInFetchedResultsController()
|
||||
fetchedResultsController.delegate = self
|
||||
return fetchedResultsController
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDataSource
|
||||
|
||||
extension HomeViewController: UITableViewDataSource {
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return fetchedResultsController.sections?[section].numberOfObjects ?? 0
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") ?? UITableViewCell()
|
||||
self.configureCell(cell: cell, atIndexPath: indexPath)
|
||||
return cell
|
||||
}
|
||||
|
||||
func configureCell(cell: UITableViewCell, atIndexPath indexPath: IndexPath) {
|
||||
guard let homeViewCell = cell as? HomeViewCell else { return }
|
||||
let checkIn = fetchedResultsController.object(at: indexPath)
|
||||
homeViewCell.eventLabel.text = checkIn.event.name
|
||||
homeViewCell.leaderboardLabel.text = checkIn.leaderboard.name
|
||||
homeViewCell.competitorLabel.text = checkIn.name
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
|
||||
extension HomeViewController: UITableViewDelegate {
|
||||
|
||||
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
||||
selectedCheckIn = fetchedResultsController.object(at: indexPath)
|
||||
return indexPath
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
reviewSelectedCheckIn {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||
cell.removeSeparatorInset()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - NSFetchedResultsControllerDelegate
|
||||
|
||||
extension HomeViewController: NSFetchedResultsControllerDelegate {
|
||||
|
||||
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
||||
tableView.beginUpdates()
|
||||
}
|
||||
|
||||
func controller(
|
||||
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
|
||||
didChange object: Any,
|
||||
at indexPath: IndexPath?,
|
||||
for type: NSFetchedResultsChangeType,
|
||||
newIndexPath: IndexPath?)
|
||||
{
|
||||
switch type {
|
||||
case .insert:
|
||||
tableView.insertRows(at: [newIndexPath!], with: UITableViewRowAnimation.automatic)
|
||||
case .update:
|
||||
let cell = tableView.cellForRow(at: indexPath!)
|
||||
if cell != nil {
|
||||
configureCell(cell: cell!, atIndexPath: indexPath!)
|
||||
tableView.reloadRows(at: [indexPath!], with: UITableViewRowAnimation.automatic)
|
||||
}
|
||||
case .move:
|
||||
tableView.deleteRows(at: [indexPath!], with: UITableViewRowAnimation.automatic)
|
||||
tableView.insertRows(at: [newIndexPath!], with: .automatic)
|
||||
case .delete:
|
||||
tableView.deleteRows(at: [indexPath!], with: UITableViewRowAnimation.automatic)
|
||||
}
|
||||
}
|
||||
|
||||
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
||||
tableView.endUpdates()
|
||||
setupTableViewHeader()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - CheckInControllerDelegate
|
||||
|
||||
extension HomeViewController: CheckInControllerDelegate {
|
||||
|
||||
func checkInController(_ sender: CheckInController, show alertController: UIAlertController) {
|
||||
present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -97,6 +97,11 @@
|
||||
"idiom" : "ipad",
|
||||
"filename" : "167_iPhone_6Plus_App_Icon@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"size" : "1024x1024",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
@@ -2,12 +2,13 @@
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : " ic_edit.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x",
|
||||
"filename" : "edit@3x.png"
|
||||
"filename" : " ic_edit@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
|
||||
|
Before Width: | Height: | Size: 794 B |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : " ic_edit_pressed.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : " ic_edit_pressed@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "grey600.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "grey600@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "grey600@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 299 B |
|
After Width: | Height: | Size: 655 B |
|
After Width: | Height: | Size: 819 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
@@ -2,11 +2,12 @@
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : " FAB@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "retry@2x.png",
|
||||
"filename" : " FAB@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : " FAB_pressed@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : " FAB_pressed@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "grey600.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "grey600@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "grey600@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 383 B |
|
After Width: | Height: | Size: 804 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 726 B |
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : " ic_refresh@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : " ic_refresh@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Photo_1.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Photo_1@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 285 KiB |
|
Before Width: | Height: | Size: 870 B |
|
After Width: | Height: | Size: 970 B |
|
After Width: | Height: | Size: 2.2 KiB |
@@ -2,17 +2,17 @@
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : " ic_more_vert@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x",
|
||||
"filename" : "settings-44.png"
|
||||
"filename" : " ic_more_vert@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x",
|
||||
"filename" : "settings@3x.png"
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 777 B |
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Photo_2.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Photo_2@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 350 KiB |
@@ -48,13 +48,6 @@
|
||||
</dict>
|
||||
<key>NSLocationAlwaysUsageDescription</key>
|
||||
<string></string>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>OpenSans-Bold.ttf</string>
|
||||
<string>OpenSans-Light.ttf</string>
|
||||
<string>OpenSans-Regular.ttf</string>
|
||||
<string>OpenSans-Semibold.ttf</string>
|
||||
</array>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>fetch</string>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Keychain.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 08.08.17.
|
||||
// Copyright © 2017 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class Keychain: NSObject {
|
||||
|
||||
fileprivate enum Keychain {
|
||||
static let UserNameAccount = "user.name"
|
||||
static let UserPasswordAccount = "user.password"
|
||||
static let UserAccessTokenAccount = "user.access_token"
|
||||
}
|
||||
|
||||
static func userName(forService service: String) -> KeychainPasswordItem {
|
||||
return KeychainPasswordItem.init(service: service, account: Keychain.UserNameAccount)
|
||||
}
|
||||
|
||||
static func userPassword(forService service: String) -> KeychainPasswordItem {
|
||||
return KeychainPasswordItem.init(service: service, account: Keychain.UserPasswordAccount)
|
||||
}
|
||||
|
||||
static func userAccessToken(forService service: String) -> KeychainPasswordItem {
|
||||
return KeychainPasswordItem.init(service: service, account: Keychain.UserAccessTokenAccount)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
Copyright (C) 2016 Apple Inc. All Rights Reserved.
|
||||
See LICENSE.txt for this sample’s licensing information
|
||||
|
||||
Abstract:
|
||||
A struct for accessing generic password keychain items.
|
||||
*/
|
||||
|
||||
// Source: https://developer.apple.com/library/content/samplecode/GenericKeychain/Listings/GenericKeychain_KeychainPasswordItem_swift.html#//apple_ref/doc/uid/DTS40007797-GenericKeychain_KeychainPasswordItem_swift-DontLinkElementID_7
|
||||
|
||||
import Foundation
|
||||
|
||||
struct KeychainPasswordItem {
|
||||
|
||||
// MARK: Types
|
||||
|
||||
enum KeychainError: Error {
|
||||
case noPassword
|
||||
case unexpectedPasswordData
|
||||
case unexpectedItemData
|
||||
case unhandledError(status: OSStatus)
|
||||
}
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
let service: String
|
||||
|
||||
private(set) var account: String
|
||||
|
||||
let accessGroup: String?
|
||||
|
||||
// MARK: Intialization
|
||||
|
||||
init(service: String, account: String, accessGroup: String? = nil) {
|
||||
self.service = service
|
||||
self.account = account
|
||||
self.accessGroup = accessGroup
|
||||
}
|
||||
|
||||
// MARK: Keychain access
|
||||
|
||||
func readPassword() throws -> String {
|
||||
/*
|
||||
Build a query to find the item that matches the service, account and
|
||||
access group.
|
||||
*/
|
||||
var query = KeychainPasswordItem.keychainQuery(withService: service, account: account, accessGroup: accessGroup)
|
||||
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
||||
query[kSecReturnAttributes as String] = kCFBooleanTrue
|
||||
query[kSecReturnData as String] = kCFBooleanTrue
|
||||
|
||||
// Try to fetch the existing keychain item that matches the query.
|
||||
var queryResult: AnyObject?
|
||||
let status = withUnsafeMutablePointer(to: &queryResult) {
|
||||
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
|
||||
}
|
||||
|
||||
// Check the return status and throw an error if appropriate.
|
||||
guard status != errSecItemNotFound else { throw KeychainError.noPassword }
|
||||
guard status == noErr else { throw KeychainError.unhandledError(status: status) }
|
||||
|
||||
// Parse the password string from the query result.
|
||||
guard let existingItem = queryResult as? [String : AnyObject],
|
||||
let passwordData = existingItem[kSecValueData as String] as? Data,
|
||||
let password = String(data: passwordData, encoding: String.Encoding.utf8)
|
||||
else {
|
||||
throw KeychainError.unexpectedPasswordData
|
||||
}
|
||||
|
||||
return password
|
||||
}
|
||||
|
||||
func savePassword(_ password: String) throws {
|
||||
// Encode the password into an Data object.
|
||||
let encodedPassword = password.data(using: String.Encoding.utf8)!
|
||||
|
||||
do {
|
||||
// Check for an existing item in the keychain.
|
||||
try _ = readPassword()
|
||||
|
||||
// Update the existing item with the new password.
|
||||
var attributesToUpdate = [String : AnyObject]()
|
||||
attributesToUpdate[kSecValueData as String] = encodedPassword as AnyObject?
|
||||
|
||||
let query = KeychainPasswordItem.keychainQuery(withService: service, account: account, accessGroup: accessGroup)
|
||||
let status = SecItemUpdate(query as CFDictionary, attributesToUpdate as CFDictionary)
|
||||
|
||||
// Throw an error if an unexpected status was returned.
|
||||
guard status == noErr else { throw KeychainError.unhandledError(status: status) }
|
||||
}
|
||||
catch KeychainError.noPassword {
|
||||
/*
|
||||
No password was found in the keychain. Create a dictionary to save
|
||||
as a new keychain item.
|
||||
*/
|
||||
var newItem = KeychainPasswordItem.keychainQuery(withService: service, account: account, accessGroup: accessGroup)
|
||||
newItem[kSecValueData as String] = encodedPassword as AnyObject?
|
||||
|
||||
// Add a the new item to the keychain.
|
||||
let status = SecItemAdd(newItem as CFDictionary, nil)
|
||||
|
||||
// Throw an error if an unexpected status was returned.
|
||||
guard status == noErr else { throw KeychainError.unhandledError(status: status) }
|
||||
}
|
||||
}
|
||||
|
||||
mutating func renameAccount(_ newAccountName: String) throws {
|
||||
// Try to update an existing item with the new account name.
|
||||
var attributesToUpdate = [String : AnyObject]()
|
||||
attributesToUpdate[kSecAttrAccount as String] = newAccountName as AnyObject?
|
||||
|
||||
let query = KeychainPasswordItem.keychainQuery(withService: service, account: self.account, accessGroup: accessGroup)
|
||||
let status = SecItemUpdate(query as CFDictionary, attributesToUpdate as CFDictionary)
|
||||
|
||||
// Throw an error if an unexpected status was returned.
|
||||
guard status == noErr || status == errSecItemNotFound else { throw KeychainError.unhandledError(status: status) }
|
||||
|
||||
self.account = newAccountName
|
||||
}
|
||||
|
||||
func deleteItem() throws {
|
||||
// Delete the existing item from the keychain.
|
||||
let query = KeychainPasswordItem.keychainQuery(withService: service, account: account, accessGroup: accessGroup)
|
||||
let status = SecItemDelete(query as CFDictionary)
|
||||
|
||||
// Throw an error if an unexpected status was returned.
|
||||
guard status == noErr || status == errSecItemNotFound else { throw KeychainError.unhandledError(status: status) }
|
||||
}
|
||||
|
||||
static func passwordItems(forService service: String, accessGroup: String? = nil) throws -> [KeychainPasswordItem] {
|
||||
// Build a query for all items that match the service and access group.
|
||||
var query = KeychainPasswordItem.keychainQuery(withService: service, accessGroup: accessGroup)
|
||||
query[kSecMatchLimit as String] = kSecMatchLimitAll
|
||||
query[kSecReturnAttributes as String] = kCFBooleanTrue
|
||||
query[kSecReturnData as String] = kCFBooleanFalse
|
||||
|
||||
// Fetch matching items from the keychain.
|
||||
var queryResult: AnyObject?
|
||||
let status = withUnsafeMutablePointer(to: &queryResult) {
|
||||
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
|
||||
}
|
||||
|
||||
// If no items were found, return an empty array.
|
||||
guard status != errSecItemNotFound else { return [] }
|
||||
|
||||
// Throw an error if an unexpected status was returned.
|
||||
guard status == noErr else { throw KeychainError.unhandledError(status: status) }
|
||||
|
||||
// Cast the query result to an array of dictionaries.
|
||||
guard let resultData = queryResult as? [[String : AnyObject]] else { throw KeychainError.unexpectedItemData }
|
||||
|
||||
// Create a `KeychainPasswordItem` for each dictionary in the query result.
|
||||
var passwordItems = [KeychainPasswordItem]()
|
||||
for result in resultData {
|
||||
guard let account = result[kSecAttrAccount as String] as? String else { throw KeychainError.unexpectedItemData }
|
||||
|
||||
let passwordItem = KeychainPasswordItem(service: service, account: account, accessGroup: accessGroup)
|
||||
passwordItems.append(passwordItem)
|
||||
}
|
||||
|
||||
return passwordItems
|
||||
}
|
||||
|
||||
// MARK: Convenience
|
||||
|
||||
private static func keychainQuery(withService service: String, account: String? = nil, accessGroup: String? = nil) -> [String : AnyObject] {
|
||||
var query = [String : AnyObject]()
|
||||
query[kSecClass as String] = kSecClassGenericPassword
|
||||
query[kSecAttrService as String] = service as AnyObject?
|
||||
|
||||
if let account = account {
|
||||
query[kSecAttrAccount as String] = account as AnyObject?
|
||||
}
|
||||
|
||||
if let accessGroup = accessGroup {
|
||||
query[kSecAttrAccessGroup as String] = accessGroup as AnyObject?
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11762" systemVersion="15G1421" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<customFonts key="customFonts">
|
||||
<array key="OpenSans-Regular.ttf">
|
||||
<string>OpenSans</string>
|
||||
</array>
|
||||
</customFonts>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
@@ -28,7 +23,7 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Ct1-2l-YCw" userLabel="Center View">
|
||||
<rect key="frame" x="33.5" y="256" width="308" height="155"/>
|
||||
<rect key="frame" x="33.5" y="259" width="308" height="149.5"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="applogo" translatesAutoresizingMaskIntoConstraints="NO" id="XA8-DM-z3j">
|
||||
<rect key="frame" x="0.0" y="0.0" width="308" height="81"/>
|
||||
@@ -37,8 +32,8 @@
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SAP Sail InSight" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZGa-Zx-evx">
|
||||
<rect key="frame" x="0.0" y="111" width="308" height="44"/>
|
||||
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="32"/>
|
||||
<rect key="frame" x="0.0" y="111" width="308" height="38.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="32"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// LeaderboardAutoCourseData.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 20.12.17.
|
||||
// Copyright © 2017 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class LeaderboardAutoCourseData: BaseData {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// LeaderboardGroupData.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 20.12.17.
|
||||
// Copyright © 2017 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class LeaderboardGroupData: BaseData {
|
||||
|
||||
func raceName(trainingRaceData: TrainingRaceData) -> String? {
|
||||
var raceName: String?
|
||||
if let leaderboards = dictionary["leaderboards"] as? [[String: AnyObject]] {
|
||||
|
||||
// Leaderboards
|
||||
leaderboards.forEach({ (leaderboard) in
|
||||
if leaderboard["name"] as? String == trainingRaceData.leaderboardName {
|
||||
|
||||
// Series
|
||||
if let series = leaderboard["series"] as? [[String: AnyObject]] {
|
||||
series.forEach({ (serie) in
|
||||
|
||||
// Fleets
|
||||
if let fleets = serie["fleets"] as? [[String: AnyObject]] {
|
||||
fleets.forEach({ (fleet) in
|
||||
if fleet["name"] as? String == trainingRaceData.fleetName {
|
||||
|
||||
// Races
|
||||
if let races = fleet["races"] as? [[String: AnyObject]] {
|
||||
races.forEach({ (race) in
|
||||
if race["name"] as? String == trainingRaceData.raceColumnName {
|
||||
raceName = race["trackedRaceName"] as? String
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return raceName
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import Foundation
|
||||
|
||||
class LeaderboardViewController: UIViewController {
|
||||
|
||||
var checkIn: CheckIn!
|
||||
weak var checkIn: CheckIn!
|
||||
|
||||
@IBOutlet weak var webView: UIWebView!
|
||||
@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
|
||||
@@ -24,6 +24,7 @@ class LeaderboardViewController: UIViewController {
|
||||
|
||||
fileprivate func setup() {
|
||||
setupLocalization()
|
||||
setupNavigationBar()
|
||||
setupWebView()
|
||||
}
|
||||
|
||||
@@ -31,6 +32,10 @@ class LeaderboardViewController: UIViewController {
|
||||
navigationItem.title = Translation.LeaderboardView.Title.String
|
||||
}
|
||||
|
||||
fileprivate func setupNavigationBar() {
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIImageView(image: UIImage(named: "sap_logo")))
|
||||
}
|
||||
|
||||
fileprivate func setupWebView() {
|
||||
guard let url = checkIn.leaderboardURL() else { return }
|
||||
webView.loadRequest(URLRequest(url: url))
|
||||
@@ -38,7 +43,7 @@ class LeaderboardViewController: UIViewController {
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func doneButtonTapped(_ sender: AnyObject) {
|
||||
@IBAction func doneButtonTapped(_ sender: Any) {
|
||||
presentingViewController!.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@@ -55,8 +60,8 @@ extension LeaderboardViewController: UIWebViewDelegate {
|
||||
func webView(_ webView: UIWebView, didFailLoadWithError error: Error) {
|
||||
activityIndicator.stopAnimating()
|
||||
let alertController = UIAlertController(title: error.localizedDescription, message: nil, preferredStyle: .alert)
|
||||
let okAction = UIAlertAction(title: Translation.Common.OK.String, style: .default) { (action) in
|
||||
self.presentingViewController!.dismiss(animated: true, completion: nil)
|
||||
let okAction = UIAlertAction(title: Translation.Common.OK.String, style: .default) { [weak self] action in
|
||||
self?.presentingViewController!.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
alertController.addAction(okAction)
|
||||
present(alertController, animated: true, completion: nil)
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// LoginViewController.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 02.08.17.
|
||||
// Copyright © 2017 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol LoginViewControllerDelegate {
|
||||
|
||||
func loginViewController(_ controller: LoginViewController, willLoginWithUserName userName: String, password: String)
|
||||
|
||||
func loginViewControllerWillCancel(_ controller: LoginViewController)
|
||||
|
||||
}
|
||||
|
||||
class LoginViewController: FormularViewController {
|
||||
|
||||
var signUpController: SignUpController?
|
||||
|
||||
@IBOutlet var endpointLabel: UILabel!
|
||||
@IBOutlet var endpointValueLabel: UILabel!
|
||||
@IBOutlet var userNameLabel: UILabel!
|
||||
@IBOutlet var userNameTextField: UITextField!
|
||||
@IBOutlet var passwordLabel: UILabel!
|
||||
@IBOutlet var passwordTextField: UITextField!
|
||||
@IBOutlet var forgotPasswordButton: UIButton!
|
||||
@IBOutlet var signUpButton: UIButton!
|
||||
@IBOutlet var loginButton: UIButton!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
textFields.append(contentsOf: [userNameTextField, passwordTextField])
|
||||
setup()
|
||||
}
|
||||
|
||||
fileprivate func setup() {
|
||||
setupButtons()
|
||||
setupLocalization()
|
||||
setupNavigationBar()
|
||||
setupTextFields()
|
||||
}
|
||||
|
||||
fileprivate func setupButtons() {
|
||||
makeGray(button: signUpButton)
|
||||
makeBlue(button: loginButton)
|
||||
}
|
||||
|
||||
fileprivate func setupLocalization() {
|
||||
navigationItem.title = SignUpTranslation.LoginView.Title.String
|
||||
endpointLabel.text = SignUpTranslation.Common.Server.String
|
||||
endpointValueLabel.text = signUpController?.baseURLString
|
||||
userNameLabel.text = SignUpTranslation.Common.UserName.String
|
||||
passwordLabel.text = SignUpTranslation.Common.Password.String
|
||||
forgotPasswordButton.setTitle(SignUpTranslation.ForgotPasswordView.Title.String, for: .normal)
|
||||
signUpButton.setTitle(SignUpTranslation.SignUpView.Title.String, for: .normal)
|
||||
loginButton.setTitle(SignUpTranslation.LoginView.LoginButton.Title.String, for: .normal)
|
||||
}
|
||||
|
||||
fileprivate func setupNavigationBar() {
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIImageView(image: UIImage(named: "sap_logo")))
|
||||
}
|
||||
|
||||
fileprivate func setupTextFields() {
|
||||
userNameTextField.text = signUpController?.userName
|
||||
passwordTextField.text = signUpController?.userPassword
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func cancelButtonTapped(_ sender: Any) {
|
||||
signUpController?.loginViewControllerWillCancel(self)
|
||||
}
|
||||
|
||||
@IBAction func loginButtonTapped(_ sender: Any) {
|
||||
signUpController?.loginViewController(
|
||||
self,
|
||||
willLoginWithUserName: userNameTextField.text!,
|
||||
password: passwordTextField.text!
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Segues
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
super.prepare(for: segue, sender: sender)
|
||||
if let signUpVC = segue.destination as? SignUpViewController {
|
||||
signUpVC.signUpController = signUpController
|
||||
}
|
||||
if let forgotPasswordVC = segue.destination as? ForgotPasswordViewController {
|
||||
forgotPasswordVC.signUpController = signUpController
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// MarkSessionViewController.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 13.09.17.
|
||||
// Copyright © 2017 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class MarkSessionViewController: SessionViewController {
|
||||
|
||||
struct MarkSessionSegue {
|
||||
static let EmbedMark = "EmbedMark"
|
||||
}
|
||||
|
||||
weak var markCheckIn: MarkCheckIn!
|
||||
weak var markCoreDataManager: CoreDataManager!
|
||||
weak var markViewController: MarkViewController?
|
||||
|
||||
// MARK: - Segues
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
super.prepare(for: segue, sender: sender)
|
||||
if (segue.identifier == MarkSessionSegue.EmbedMark) {
|
||||
if let markViewController = segue.destination as? MarkViewController {
|
||||
markViewController.markCheckIn = markCheckIn
|
||||
markViewController.markSessionController = markSessionController
|
||||
markViewController.markCoreDataManager = markCoreDataManager
|
||||
self.markViewController = markViewController
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
lazy var markSessionController: MarkSessionController = {
|
||||
return MarkSessionController(checkIn: self.markCheckIn, coreDataManager: self.markCoreDataManager)
|
||||
}()
|
||||
|
||||
}
|
||||
@@ -2,132 +2,36 @@
|
||||
// MarkViewController.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 05.05.17.
|
||||
// Created by Raimund Wege on 13.09.17.
|
||||
// Copyright © 2017 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class MarkViewController: SessionViewController {
|
||||
|
||||
class MarkViewController: UIViewController {
|
||||
|
||||
weak var markCheckIn: MarkCheckIn!
|
||||
weak var markCoreDataManager: CoreDataManager!
|
||||
weak var markSessionController: MarkSessionController!
|
||||
|
||||
@IBOutlet weak var markNameLabel: UILabel!
|
||||
|
||||
var markCheckIn: MarkCheckIn!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
delegate = self
|
||||
setup()
|
||||
update()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
refresh()
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
fileprivate func setup() {
|
||||
setupButtons()
|
||||
setupLocalization()
|
||||
setupNavigationBar()
|
||||
}
|
||||
|
||||
fileprivate func setupButtons() {
|
||||
startTrackingButton.setBackgroundImage(Images.GreenHighlighted, for: .highlighted)
|
||||
}
|
||||
|
||||
fileprivate func setupLocalization() {
|
||||
startTrackingButton.setTitle(Translation.CompetitorView.StartTrackingButton.Title.String, for: .normal)
|
||||
}
|
||||
|
||||
fileprivate func setupNavigationBar() {
|
||||
navigationItem.titleView = TitleView(title: markCheckIn.event.name, subtitle: markCheckIn.leaderboard.name)
|
||||
navigationController?.navigationBar.setNeedsLayout()
|
||||
}
|
||||
|
||||
// MARK: - Update
|
||||
|
||||
fileprivate func update() {
|
||||
SVProgressHUD.show()
|
||||
markSessionController.update {
|
||||
self.refresh()
|
||||
SVProgressHUD.popActivity()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Refresh
|
||||
|
||||
fileprivate func refresh() {
|
||||
func refresh(_ animated: Bool) {
|
||||
refreshMarkNameLabel(animated)
|
||||
}
|
||||
|
||||
fileprivate func refreshMarkNameLabel(_ animated: Bool) {
|
||||
if (animated) {
|
||||
UIView.animate(withDuration: 0.5) { self.refreshMarkNameLabel() }
|
||||
} else {
|
||||
refreshMarkNameLabel()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func refreshMarkNameLabel() {
|
||||
markNameLabel.text = markCheckIn.name
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func optionButtonTapped(_ sender: AnyObject) {
|
||||
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
if let popoverController = alertController.popoverPresentationController {
|
||||
popoverController.barButtonItem = sender as? UIBarButtonItem
|
||||
}
|
||||
let settingsAction = UIAlertAction(title: Translation.SettingsView.Title.String, style: .default) { (action) in
|
||||
self.performSegue(withIdentifier: Segue.Settings, sender: self)
|
||||
}
|
||||
let checkOutAction = UIAlertAction(title: Translation.CompetitorView.OptionSheet.CheckOutAction.Title.String, style: .default) { (action) in
|
||||
self.checkOut()
|
||||
}
|
||||
let updateAction = UIAlertAction(title: Translation.CompetitorView.OptionSheet.UpdateAction.Title.String, style: .default) { (action) -> Void in
|
||||
self.update()
|
||||
}
|
||||
let aboutAction = UIAlertAction(title: Translation.Common.Info.String, style: .default) { (action) -> Void in
|
||||
self.performSegue(withIdentifier: Segue.About, sender: alertController)
|
||||
}
|
||||
let cancelAction = UIAlertAction(title: Translation.Common.Cancel.String, style: .cancel, handler: nil)
|
||||
alertController.addAction(settingsAction)
|
||||
alertController.addAction(checkOutAction)
|
||||
alertController.addAction(updateAction)
|
||||
alertController.addAction(aboutAction)
|
||||
alertController.addAction(cancelAction)
|
||||
present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
// MARK: - Segues
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
if (segue.identifier == Segue.Tracking) {
|
||||
let trackingNC = segue.destination as! UINavigationController
|
||||
let trackingVC = trackingNC.viewControllers[0] as! TrackingViewController
|
||||
trackingVC.checkIn = markCheckIn
|
||||
trackingVC.sessionController = markSessionController
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
fileprivate lazy var markSessionController: MarkSessionController = {
|
||||
return MarkSessionController(checkIn: self.markCheckIn)
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
// MARK: SessionViewControllerDelegate
|
||||
|
||||
extension MarkViewController: SessionViewControllerDelegate {
|
||||
|
||||
func performCheckOut() {
|
||||
markSessionController.checkOut { (withSuccess) in
|
||||
self.performCheckOutCompleted(withSuccess: withSuccess)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func performCheckOutCompleted(withSuccess: Bool) {
|
||||
CoreDataManager.sharedManager.deleteObject(object: markCheckIn)
|
||||
CoreDataManager.sharedManager.saveContext()
|
||||
self.navigationController!.popViewController(animated: true)
|
||||
}
|
||||
|
||||
func startTracking() throws {
|
||||
try markSessionController.startTracking()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
//
|
||||
// NewCheckInViewController.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 29.01.18.
|
||||
// Copyright © 2018 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol NewCheckInViewControllerDelegate: class {
|
||||
|
||||
func newCheckInViewController(_ controller: NewCheckInViewController, didCheckIn checkIn: CheckIn)
|
||||
|
||||
}
|
||||
|
||||
class NewCheckInViewController: UIViewController {
|
||||
|
||||
fileprivate struct Segue {
|
||||
static let CreateTraining = "CreateTraining"
|
||||
static let Scan = "Scan"
|
||||
}
|
||||
|
||||
weak var coreDataManager: CoreDataManager!
|
||||
weak var delegate: NewCheckInViewControllerDelegate?
|
||||
|
||||
@IBOutlet var scanCodeButton: UIButton!
|
||||
@IBOutlet var noCodeButton: UIButton!
|
||||
@IBOutlet var infoCodeLabel: UILabel!
|
||||
@IBOutlet var createTrainingButton: UIButton!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
setup()
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
fileprivate func setup() {
|
||||
setupButtons()
|
||||
setupLocalization()
|
||||
setupNavigationBar()
|
||||
}
|
||||
|
||||
fileprivate func setupButtons() {
|
||||
makeBlue(button: scanCodeButton)
|
||||
makeGray(button: noCodeButton)
|
||||
makeBlue(button: createTrainingButton)
|
||||
}
|
||||
|
||||
fileprivate func setupLocalization() {
|
||||
// navigationItem.title = Translation.RegattaCheckInListView.Title.String
|
||||
scanCodeButton.setTitle(Translation.ScanView.Title.String, for: .normal)
|
||||
noCodeButton.setTitle(Translation.RegattaCheckInListView.NoCodeAlert.Title.String, for: .normal)
|
||||
infoCodeLabel.text = Translation.RegattaCheckInListView.InfoCodeLabel.Text.String
|
||||
}
|
||||
|
||||
fileprivate func setupNavigationBar() {
|
||||
navigationItem.title = Application.Title
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIImageView(image: UIImage(named: "sap_logo")))
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func cancelButtonTapped(_ sender: Any) {
|
||||
presentingViewController?.dismiss(animated: true)
|
||||
}
|
||||
|
||||
@IBAction func scanButtonTapped(_ sender: Any) {
|
||||
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera) {
|
||||
performSegue(withIdentifier: Segue.Scan, sender: sender)
|
||||
} else {
|
||||
showNoCameraAlert()
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func noCodeButtonTapped(_ sender: Any) {
|
||||
showNoCodeAlert()
|
||||
}
|
||||
|
||||
// MARK: - Alerts
|
||||
|
||||
fileprivate func showNoCameraAlert() {
|
||||
let alertController = UIAlertController(
|
||||
title: Translation.Common.Error.String,
|
||||
message: Translation.RegattaCheckInListView.NoCameraAlert.Message.String,
|
||||
preferredStyle: .alert
|
||||
)
|
||||
let okAction = UIAlertAction(title: Translation.Common.OK.String, style: .default, handler: nil)
|
||||
alertController.addAction(okAction)
|
||||
present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
fileprivate func showNoCodeAlert() {
|
||||
let alertController = UIAlertController(
|
||||
title: Translation.RegattaCheckInListView.NoCodeAlert.Title.String,
|
||||
message: Translation.RegattaCheckInListView.NoCodeAlert.Message.String,
|
||||
preferredStyle: .alert
|
||||
)
|
||||
let okAction = UIAlertAction(title: Translation.Common.OK.String, style: .default, handler: nil)
|
||||
alertController.addAction(okAction)
|
||||
present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
// MARK: - Segues
|
||||
|
||||
// TODO: Activate Training Feature -> Show training view in IB
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
super.prepare(for: segue, sender: sender)
|
||||
if segue.identifier == Segue.Scan {
|
||||
guard let scanVC = segue.destination as? ScanViewController else { return }
|
||||
scanVC.coreDataManager = coreDataManager
|
||||
scanVC.delegate = self
|
||||
} else if segue.identifier == Segue.CreateTraining {
|
||||
guard let createTrainingVC = segue.destination as? CreateTrainingViewController else { return }
|
||||
createTrainingVC.coreDataManager = coreDataManager
|
||||
createTrainingVC.delegate = self
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - ScanViewControllerDelegate
|
||||
|
||||
extension NewCheckInViewController: ScanViewControllerDelegate {
|
||||
|
||||
func scanViewController(_ controller: ScanViewController, didCheckIn checkIn: CheckIn) {
|
||||
delegate?.newCheckInViewController(self, didCheckIn: checkIn)
|
||||
dismiss(animated: true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - CreateTrainingViewControllerDelegate
|
||||
|
||||
extension NewCheckInViewController: CreateTrainingViewControllerDelegate {
|
||||
|
||||
func createTrainingViewController(_ controller: CreateTrainingViewController, didCheckIn checkIn: CheckIn) {
|
||||
delegate?.newCheckInViewController(self, didCheckIn: checkIn)
|
||||
dismiss(animated: true)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,23 +13,47 @@ class Preferences: NSObject {
|
||||
struct NotificationType {
|
||||
static let BatterySavingChanged = "BatterySavingChanged"
|
||||
static let NewCheckInURLChanged = "NewCheckInURLChanged"
|
||||
static let TrainingEndpointChanged = "TrainingEndpointChanged"
|
||||
}
|
||||
|
||||
struct UserInfo {
|
||||
static let BatterySaving = "BatterySaving"
|
||||
static let CheckInURL = "CheckInURL"
|
||||
static let TrainingEndpoint = "TrainingEndpoint"
|
||||
}
|
||||
|
||||
struct PreferenceKey {
|
||||
static let ActiveTrainingRaceData = "ActiveTrainingRaceData"
|
||||
static let CodeConventionRead = "CodeConventionRead"
|
||||
static let BatterySaving = "BatterySaving"
|
||||
static let BoatClassName = "BoatClassName"
|
||||
static let NewCheckInURL = "NewCheckInURL"
|
||||
static let TrainingEndpoint = "TrainingEndpoint"
|
||||
static let TermsAccepted = "TermsAccepted"
|
||||
static let UUID = "udid"
|
||||
}
|
||||
|
||||
fileprivate static let preferences = UserDefaults.standard
|
||||
|
||||
// MARK: - ActiveTrainingRaceData
|
||||
|
||||
class var activeTrainingRaceData: TrainingRaceData? {
|
||||
get {
|
||||
guard let data = preferences.data(forKey: PreferenceKey.ActiveTrainingRaceData) else { return nil }
|
||||
guard let trainingRaceData = NSKeyedUnarchiver.unarchiveObject(with: data) as? TrainingRaceData else { return nil }
|
||||
return trainingRaceData
|
||||
}
|
||||
set (value) {
|
||||
if let rootObject = value {
|
||||
let data = NSKeyedArchiver.archivedData(withRootObject: rootObject)
|
||||
preferences.set(data, forKey: PreferenceKey.ActiveTrainingRaceData)
|
||||
} else {
|
||||
preferences.set(nil, forKey: PreferenceKey.ActiveTrainingRaceData)
|
||||
}
|
||||
preferences.synchronize()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - CodeConventionRead
|
||||
|
||||
class var codeConventionRead: Bool {
|
||||
@@ -97,5 +121,43 @@ class Preferences: NSObject {
|
||||
NotificationQueue.default.enqueue(notification, postingStyle: NotificationQueue.PostingStyle.asap)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - BoatClassName
|
||||
|
||||
class var boatClassName: String {
|
||||
get {
|
||||
return preferences.string(forKey: PreferenceKey.BoatClassName) ?? ""
|
||||
}
|
||||
set(value) {
|
||||
preferences.set(value, forKey: PreferenceKey.BoatClassName)
|
||||
preferences.synchronize()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - TrainingEndpoint
|
||||
|
||||
class var trainingEndpoint: String {
|
||||
get {
|
||||
if let url = URL(string: preferences.string(forKey: PreferenceKey.TrainingEndpoint) ?? "") {
|
||||
return url.absoluteString
|
||||
} else {
|
||||
return Translation.Endpoint.Training.String
|
||||
}
|
||||
}
|
||||
set (value) {
|
||||
if let url = URL(string: value) {
|
||||
preferences.set(url.absoluteString, forKey: PreferenceKey.TrainingEndpoint)
|
||||
} else {
|
||||
preferences.set(Translation.Endpoint.Training.String, forKey: PreferenceKey.TrainingEndpoint)
|
||||
}
|
||||
preferences.synchronize()
|
||||
|
||||
// Send notification
|
||||
var userInfo = [String: AnyObject]()
|
||||
userInfo[UserInfo.TrainingEndpoint] = value as AnyObject?
|
||||
let notification = Notification(name: Notification.Name(rawValue: NotificationType.TrainingEndpointChanged), object: self, userInfo: userInfo)
|
||||
NotificationQueue.default.enqueue(notification, postingStyle: NotificationQueue.PostingStyle.asap)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// RegattaBoatViewController.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 13.03.18.
|
||||
// Copyright © 2018 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class RegattaBoatViewController: BoatSessionViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
delegate = self
|
||||
setup()
|
||||
updateOptimistic()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
refresh(false)
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
fileprivate func setup() {
|
||||
setupButtons()
|
||||
setupLocalization()
|
||||
setupNavigationBar()
|
||||
}
|
||||
|
||||
fileprivate func setupButtons() {
|
||||
makeGreen(button: startTrackingButton)
|
||||
}
|
||||
|
||||
fileprivate func setupLocalization() {
|
||||
startTrackingButton.setTitle(Translation.RegattaView.StartTrackingButton.Title.String, for: .normal)
|
||||
}
|
||||
|
||||
fileprivate func setupNavigationBar() {
|
||||
navigationItem.titleView = TitleView(title: boatCheckIn.event.name, subtitle: boatCheckIn.leaderboard.name)
|
||||
navigationController?.navigationBar.setNeedsLayout()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: SessionViewControllerDelegate
|
||||
|
||||
extension RegattaBoatViewController: SessionViewControllerDelegate {
|
||||
|
||||
var checkIn: CheckIn { get { return boatCheckIn } }
|
||||
|
||||
var checkOutActionTitle: String { get { return Translation.RegattaView.OptionSheet.CheckOutAction.Title.String } }
|
||||
|
||||
var checkOutAlertMessage: String { get { return Translation.RegattaView.CheckOutAlert.Message.String } }
|
||||
|
||||
var coreDataManager: CoreDataManager { get { return boatCoreDataManager } }
|
||||
|
||||
var sessionController: SessionController { get { return boatSessionController } }
|
||||
|
||||
func makeOptionSheet() -> UIAlertController {
|
||||
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
if let popoverController = alertController.popoverPresentationController {
|
||||
popoverController.barButtonItem = self.optionButton
|
||||
}
|
||||
alertController.addAction(self.makeActionCheckOut())
|
||||
alertController.addAction(self.makeActionSettings())
|
||||
alertController.addAction(self.makeActionInfo())
|
||||
alertController.addAction(self.makeActionCancel())
|
||||
return alertController
|
||||
}
|
||||
|
||||
func refresh(_ animated: Bool) {
|
||||
boatViewController?.refresh(animated)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// RegattaCheckInController.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 07.09.17.
|
||||
// Copyright © 2017 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class RegattaCheckInController: CheckInController {
|
||||
|
||||
override init(coreDataManager: CoreDataManager) {
|
||||
super.init(coreDataManager: coreDataManager)
|
||||
delegate = self
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension RegattaCheckInController: CheckInControllerDelegate {
|
||||
|
||||
func checkInController(
|
||||
_ controller: CheckInController,
|
||||
postCompetitorCheckIn checkInData: CheckInData,
|
||||
success: @escaping (CheckIn) -> Void,
|
||||
failure: @escaping (Error) -> Void)
|
||||
{
|
||||
let title = String(format: Translation.RegattaCheckInController.WelcomeAlert.Title.String, checkInData.competitorData.name)
|
||||
let message = String(format: Translation.RegattaCheckInController.WelcomeAlert.Message.String, checkInData.competitorData.sailID)
|
||||
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
let okAction = UIAlertAction(title: Translation.Common.OK.String, style: .default) { (action) in
|
||||
controller.postCheckIn(checkInData: checkInData, success: success, failure: failure)
|
||||
}
|
||||
let cancelAction = UIAlertAction(title: Translation.RegattaCheckInController.WelcomeAlert.CancelAction.Title.String, style: .cancel) { (action) in
|
||||
controller.didCancelCheckIn(failure: failure)
|
||||
}
|
||||
alertController.addAction(okAction)
|
||||
alertController.addAction(cancelAction)
|
||||
viewController?.present(alertController, animated: true)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// RegattaCompetitorCreateAndAddData.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 25.08.17.
|
||||
// Copyright © 2017 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class RegattaCompetitorCreateAndAddData: BaseData {
|
||||
|
||||
fileprivate struct Keys {
|
||||
static let ID = "id"
|
||||
}
|
||||
|
||||
var competitorID: String { get { return stringValue(forKey: Keys.ID) } }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
//
|
||||
// RegattaCompetitorViewController.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by computing on 10/11/14.
|
||||
// Copyright (c) 2014 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class RegattaCompetitorViewController : CompetitorSessionViewController {
|
||||
|
||||
@IBOutlet weak var regattaStartLabel: UILabel!
|
||||
@IBOutlet weak var countdownView: UIView!
|
||||
@IBOutlet weak var countdownViewHeight: NSLayoutConstraint!
|
||||
@IBOutlet weak var countdownDaysLabel: UILabel!
|
||||
@IBOutlet weak var countdownDaysTitleLabel: UILabel!
|
||||
@IBOutlet weak var countdownHoursLabel: UILabel!
|
||||
@IBOutlet weak var countdownHoursTitleLabel: UILabel!
|
||||
@IBOutlet weak var countdownMinutesLabel: UILabel!
|
||||
@IBOutlet weak var countdownMinutesTitleLabel: UILabel!
|
||||
@IBOutlet weak var leaderboardButton: UIButton!
|
||||
@IBOutlet weak var eventButton: UIButton!
|
||||
@IBOutlet weak var announcementLabel: UILabel!
|
||||
|
||||
weak var countdownTimer: Timer?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
delegate = self
|
||||
setup()
|
||||
updateOptimistic()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
validateTimer()
|
||||
refresh(false)
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
invalidateTimer()
|
||||
}
|
||||
|
||||
// MARK: - Timer
|
||||
|
||||
fileprivate func validateTimer() {
|
||||
tickTimer()
|
||||
countdownTimer = Timer.scheduledTimer(
|
||||
timeInterval: 1,
|
||||
target: self,
|
||||
selector: #selector(tickTimer),
|
||||
userInfo: nil,
|
||||
repeats: true
|
||||
)
|
||||
}
|
||||
|
||||
fileprivate func invalidateTimer() {
|
||||
countdownTimer?.invalidate()
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
fileprivate func setup() {
|
||||
setupButtons()
|
||||
setupLocalization()
|
||||
setupNavigationBar()
|
||||
}
|
||||
|
||||
fileprivate func setupButtons() {
|
||||
makeBlue(button: eventButton)
|
||||
makeBlue(button: leaderboardButton)
|
||||
makeGreen(button: startTrackingButton)
|
||||
}
|
||||
|
||||
fileprivate func setupLocalization() {
|
||||
announcementLabel.text = Translation.RegattaView.AnnouncementLabel.Text.String
|
||||
countdownDaysTitleLabel.text = Translation.RegattaView.CountdownDaysTitleLabel.Text.String
|
||||
countdownHoursTitleLabel.text = Translation.RegattaView.CountdownHoursTitleLabel.Text.String
|
||||
countdownMinutesTitleLabel.text = Translation.RegattaView.CountdownMinutesTitleLabel.Text.String
|
||||
eventButton.setTitle(Translation.RegattaView.EventButton.Title.String, for: .normal)
|
||||
leaderboardButton.setTitle(Translation.LeaderboardView.Title.String, for: .normal)
|
||||
startTrackingButton.setTitle(Translation.RegattaView.StartTrackingButton.Title.String, for: .normal)
|
||||
}
|
||||
|
||||
fileprivate func setupNavigationBar() {
|
||||
navigationItem.titleView = TitleView(title: competitorCheckIn.event.name, subtitle: competitorCheckIn.leaderboard.name)
|
||||
navigationController?.navigationBar.setNeedsLayout()
|
||||
}
|
||||
|
||||
// MARK: - Timer
|
||||
|
||||
@objc fileprivate func tickTimer() {
|
||||
guard competitorCheckIn != nil else { return }
|
||||
if competitorCheckIn.event.startDate - Date().timeIntervalSince1970 > 0 {
|
||||
regattaStartLabel.text = Translation.RegattaView.StartLabel.Text.BeforeRegattaDidStart.String
|
||||
let duration = competitorCheckIn.event.startDate - Date().timeIntervalSince1970
|
||||
let days = Int(duration / (60 * 60 * 24))
|
||||
let hours = Int(duration / (60 * 60)) - (days * 24)
|
||||
let minutes = Int(duration / 60) - (days * 24 * 60) - (hours * 60)
|
||||
countdownDaysLabel.text = String(format: "%02d", days)
|
||||
countdownHoursLabel.text = String(format: "%02d", hours)
|
||||
countdownMinutesLabel.text = String(format: "%02d", minutes)
|
||||
countdownView.isHidden = false
|
||||
countdownViewHeight.constant = 60
|
||||
} else {
|
||||
regattaStartLabel.text = Translation.RegattaView.StartLabel.Text.AfterRegattaDidStart.String
|
||||
countdownView.isHidden = true
|
||||
countdownViewHeight.constant = 0
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: SessionViewControllerDelegate
|
||||
|
||||
extension RegattaCompetitorViewController: SessionViewControllerDelegate {
|
||||
|
||||
var checkIn: CheckIn { get { return competitorCheckIn } }
|
||||
|
||||
var checkOutActionTitle: String { get { return Translation.RegattaView.OptionSheet.CheckOutAction.Title.String } }
|
||||
|
||||
var checkOutAlertMessage: String { get { return Translation.RegattaView.CheckOutAlert.Message.String } }
|
||||
|
||||
var coreDataManager: CoreDataManager { get { return competitorCoreDataManager } }
|
||||
|
||||
var sessionController: SessionController { get { return competitorSessionController } }
|
||||
|
||||
func makeOptionSheet() -> UIAlertController {
|
||||
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
if let popoverController = alertController.popoverPresentationController {
|
||||
popoverController.barButtonItem = self.optionButton
|
||||
}
|
||||
alertController.addAction(self.makeActionCheckOut())
|
||||
alertController.addAction(self.makeActionSettings())
|
||||
alertController.addAction(self.makeActionInfo())
|
||||
alertController.addAction(self.makeActionCancel())
|
||||
return alertController
|
||||
}
|
||||
|
||||
func refresh(_ animated: Bool) {
|
||||
competitorViewController?.refresh(animated)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// RegattaMarkViewController.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 05.05.17.
|
||||
// Copyright © 2017 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class RegattaMarkViewController: MarkSessionViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
delegate = self
|
||||
setup()
|
||||
updateOptimistic()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
refresh(false)
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
fileprivate func setup() {
|
||||
setupButtons()
|
||||
setupLocalization()
|
||||
setupNavigationBar()
|
||||
}
|
||||
|
||||
fileprivate func setupButtons() {
|
||||
makeGreen(button: startTrackingButton)
|
||||
}
|
||||
|
||||
fileprivate func setupLocalization() {
|
||||
startTrackingButton.setTitle(Translation.RegattaView.StartTrackingButton.Title.String, for: .normal)
|
||||
}
|
||||
|
||||
fileprivate func setupNavigationBar() {
|
||||
navigationItem.titleView = TitleView(title: markCheckIn.event.name, subtitle: markCheckIn.leaderboard.name)
|
||||
navigationController?.navigationBar.setNeedsLayout()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: SessionViewControllerDelegate
|
||||
|
||||
extension RegattaMarkViewController: SessionViewControllerDelegate {
|
||||
|
||||
var checkIn: CheckIn { get { return markCheckIn } }
|
||||
|
||||
var checkOutActionTitle: String { get { return Translation.RegattaView.OptionSheet.CheckOutAction.Title.String } }
|
||||
|
||||
var checkOutAlertMessage: String { get { return Translation.RegattaView.CheckOutAlert.Message.String } }
|
||||
|
||||
var coreDataManager: CoreDataManager { get { return markCoreDataManager } }
|
||||
|
||||
var sessionController: SessionController { get { return markSessionController } }
|
||||
|
||||
func makeOptionSheet() -> UIAlertController {
|
||||
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
if let popoverController = alertController.popoverPresentationController {
|
||||
popoverController.barButtonItem = self.optionButton
|
||||
}
|
||||
alertController.addAction(self.makeActionCheckOut())
|
||||
alertController.addAction(self.makeActionSettings())
|
||||
alertController.addAction(self.makeActionInfo())
|
||||
alertController.addAction(self.makeActionCancel())
|
||||
return alertController
|
||||
}
|
||||
|
||||
func refresh(_ animated: Bool) {
|
||||
markViewController?.refresh(animated)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// RegattaRaceColumnAddData.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 12.09.17.
|
||||
// Copyright © 2017 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class RegattaRaceColumnAddData: BaseData {
|
||||
|
||||
fileprivate struct Keys {
|
||||
static let FleetName = "seriesname"
|
||||
static let RaceColumnName = "racename"
|
||||
}
|
||||
|
||||
var fleetName: String { get { return stringValue(forKey: Keys.FleetName) } }
|
||||
var raceColumnName: String { get { return stringValue(forKey: Keys.RaceColumnName) } }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// RegattaRaceCourseData.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by Raimund Wege on 20.12.17.
|
||||
// Copyright © 2017 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class RegattaRaceCourseData: BaseData {
|
||||
|
||||
func isEmpty() -> Bool {
|
||||
guard let waypoints = dictionary["waypoints"] as? [[String: AnyObject]] else { return true }
|
||||
return waypoints.count == 0
|
||||
}
|
||||
|
||||
func isAutoCourse() -> Bool {
|
||||
return dictionary["name"] as? String == "Auto-Course"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,9 +9,16 @@
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
|
||||
protocol ScanViewControllerDelegate: class {
|
||||
|
||||
func scanViewController(_ controller: ScanViewController, didCheckIn checkIn: CheckIn)
|
||||
|
||||
}
|
||||
|
||||
class ScanViewController: UIViewController {
|
||||
|
||||
weak var homeViewController: HomeViewController?
|
||||
weak var coreDataManager: CoreDataManager!
|
||||
weak var delegate: ScanViewControllerDelegate?
|
||||
|
||||
@IBOutlet weak var previewView: UIView!
|
||||
@IBOutlet weak var targetImageView: UIImageView!
|
||||
@@ -105,14 +112,14 @@ class ScanViewController: UIViewController {
|
||||
message: error.localizedFailureReason,
|
||||
preferredStyle: .alert
|
||||
)
|
||||
let settingsAction = UIAlertAction(title: Translation.Common.Settings.String, style: .default) { (action) in
|
||||
let settingsAction = UIAlertAction(title: Translation.Common.Settings.String, style: .default) { [weak self] action in
|
||||
if let settingsURL = URL(string: UIApplicationOpenSettingsURLString) {
|
||||
UIApplication.shared.openURL(settingsURL)
|
||||
}
|
||||
_ = self.navigationController?.popViewController(animated: true)
|
||||
_ = self?.navigationController?.popViewController(animated: true)
|
||||
}
|
||||
let cancelAction = UIAlertAction(title: Translation.Common.Cancel.String, style: .cancel) { (action) in
|
||||
_ = self.navigationController?.popViewController(animated: true)
|
||||
let cancelAction = UIAlertAction(title: Translation.Common.Cancel.String, style: .cancel) { [weak self] action in
|
||||
_ = self?.navigationController?.popViewController(animated: true)
|
||||
}
|
||||
alertController.addAction(settingsAction)
|
||||
alertController.addAction(cancelAction)
|
||||
@@ -136,10 +143,8 @@ class ScanViewController: UIViewController {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
fileprivate lazy var checkInController: CheckInController = {
|
||||
let checkInController = CheckInController()
|
||||
checkInController.delegate = self
|
||||
return checkInController
|
||||
fileprivate lazy var regattaCheckInController: RegattaCheckInController = {
|
||||
return RegattaCheckInController(coreDataManager: self.coreDataManager)
|
||||
}()
|
||||
|
||||
// MARK: - Helper
|
||||
@@ -156,16 +161,6 @@ class ScanViewController: UIViewController {
|
||||
|
||||
}
|
||||
|
||||
// MARK: - CheckInControllerDelegate
|
||||
|
||||
extension ScanViewController: CheckInControllerDelegate {
|
||||
|
||||
func checkInController(_ sender: CheckInController, show alertController: UIAlertController) {
|
||||
present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - AVCaptureMetadataOutputObjectsDelegate
|
||||
|
||||
extension ScanViewController: AVCaptureMetadataOutputObjectsDelegate {
|
||||
@@ -191,33 +186,37 @@ extension ScanViewController: AVCaptureMetadataOutputObjectsDelegate {
|
||||
}
|
||||
|
||||
fileprivate func captureOutputSuccess(checkInData: CheckInData) {
|
||||
checkInController.checkIn(checkInData: checkInData, completion: { (withSuccess) in
|
||||
if withSuccess {
|
||||
self.homeViewController?.selectedCheckIn = CoreDataManager.sharedManager.fetchCheckIn(checkInData: checkInData)
|
||||
_ = self.navigationController?.popViewController(animated: true)
|
||||
} else {
|
||||
self.startScanning()
|
||||
}
|
||||
})
|
||||
checkIn(withCheckInData: checkInData)
|
||||
}
|
||||
|
||||
fileprivate func captureOutputFailure() {
|
||||
showIncorrectCodeAlert()
|
||||
}
|
||||
|
||||
// MARK: - Alerts
|
||||
|
||||
fileprivate func showIncorrectCodeAlert() {
|
||||
let alertController = UIAlertController(
|
||||
title: Translation.Common.Error.String,
|
||||
message: Translation.ScanView.IncorrectCodeAlert.Message.String,
|
||||
preferredStyle: .alert
|
||||
)
|
||||
let okAction = UIAlertAction(title: Translation.Common.OK.String, style: .default) { (action) in
|
||||
self.startScanning()
|
||||
let title = Translation.Common.Error.String
|
||||
let message = Translation.ScanView.IncorrectCodeAlert.Message.String
|
||||
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
let okAction = UIAlertAction(title: Translation.Common.OK.String, style: .default) { [weak self] action in
|
||||
self?.startScanning()
|
||||
}
|
||||
alertController.addAction(okAction)
|
||||
present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
// MARK: - CheckIn
|
||||
|
||||
fileprivate func checkIn(withCheckInData checkInData: CheckInData) {
|
||||
regattaCheckInController.checkInWithViewController(self, checkInData: checkInData, success: { [weak self] (checkIn) in
|
||||
self?.checkInSuccess(checkIn: checkIn)
|
||||
}) { [weak self] (error) in
|
||||
self?.checkInFailure(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func checkInSuccess(checkIn: CheckIn) {
|
||||
_ = navigationController?.popViewController(animated: true)
|
||||
delegate?.scanViewController(self, didCheckIn: checkIn)
|
||||
}
|
||||
|
||||
fileprivate func checkInFailure(error: Error) {
|
||||
startScanning()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,17 +8,23 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
enum SessionControllerError: Error {
|
||||
case checkInDataIsIncomplete
|
||||
}
|
||||
|
||||
class SessionController: NSObject {
|
||||
|
||||
let checkIn: CheckIn
|
||||
|
||||
weak var checkIn: CheckIn!
|
||||
weak var coreDataManager: CoreDataManager!
|
||||
|
||||
var sendingBackgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
|
||||
var sendingDate: Date = Date()
|
||||
|
||||
fileprivate (set) var isTracking: Bool = false
|
||||
|
||||
init(checkIn: CheckIn) {
|
||||
init(checkIn: CheckIn, coreDataManager: CoreDataManager) {
|
||||
self.checkIn = checkIn
|
||||
self.coreDataManager = coreDataManager
|
||||
super.init()
|
||||
subscribeForNotifications()
|
||||
}
|
||||
@@ -53,9 +59,9 @@ class SessionController: NSObject {
|
||||
guard self.isTracking else { return }
|
||||
guard let locationData = notification.userInfo?[LocationManager.UserInfo.LocationData] as? LocationData else { return }
|
||||
guard locationData.isValid else { return }
|
||||
let gpsFix = CoreDataManager.sharedManager.newGPSFix(checkIn: self.checkIn)
|
||||
let gpsFix = self.coreDataManager.newGPSFix(checkIn: self.checkIn)
|
||||
gpsFix.updateWithLocationData(locationData: locationData)
|
||||
CoreDataManager.sharedManager.saveContext()
|
||||
self.coreDataManager.saveContext()
|
||||
if self.sendingDate.compare(Date()) == .orderedAscending {
|
||||
self.sendingDate = Date().addingTimeInterval(BatteryManager.sharedManager.sendingPeriod)
|
||||
self.beginGPSFixSendingInBackgroundTask()
|
||||
@@ -92,25 +98,27 @@ class SessionController: NSObject {
|
||||
|
||||
// MARK: - Update
|
||||
|
||||
func update(completion: @escaping () -> Void) {
|
||||
guard let checkInData = CheckInData(checkIn: checkIn) else { updateFailure(completion: completion); return }
|
||||
requestManager.getCheckInData(
|
||||
checkInData: checkInData,
|
||||
success: { (checkInData) in self.updateSuccess(checkInData: checkInData, completion: completion) },
|
||||
failure: { (error) in self.updateFailure(completion: completion) }
|
||||
)
|
||||
func update(
|
||||
success: @escaping () -> Void,
|
||||
failure: @escaping (_ error: Error) -> Void) throws
|
||||
{
|
||||
guard let checkInData = CheckInData(checkIn: checkIn) else {
|
||||
throw SessionControllerError.checkInDataIsIncomplete
|
||||
}
|
||||
let checkInDataCollector = CheckInDataCollector(checkInData: checkInData)
|
||||
checkInDataCollector.collect(checkInData: checkInData, success: { [weak self] (checkInData) in
|
||||
self?.updateSuccess(checkInData: checkInData, success: success)
|
||||
}) { (error) in
|
||||
failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
func updateSuccess(checkInData: CheckInData, completion: () -> Void) {
|
||||
fileprivate func updateSuccess(checkInData: CheckInData, success: () -> Void) {
|
||||
checkIn.event.updateWithEventData(eventData: checkInData.eventData)
|
||||
checkIn.leaderboard.updateWithLeaderboardData(leaderboardData: checkInData.leaderboardData)
|
||||
checkIn.updateWithCheckInData(checkInData: checkInData)
|
||||
CoreDataManager.sharedManager.saveContext()
|
||||
completion()
|
||||
}
|
||||
|
||||
func updateFailure(completion: () -> Void) {
|
||||
completion()
|
||||
coreDataManager.saveContext()
|
||||
success()
|
||||
}
|
||||
|
||||
// MARK: - Tracking
|
||||
@@ -129,7 +137,7 @@ class SessionController: NSObject {
|
||||
// MARK: - CheckOut
|
||||
|
||||
func checkOut(completion: @escaping (_ withSuccess: Bool) -> Void) {
|
||||
requestManager.postCheckOut(
|
||||
checkInRequestManager.postCheckOut(
|
||||
checkIn,
|
||||
success: { () in completion(true) },
|
||||
failure: { (error) in completion(false) }
|
||||
@@ -139,13 +147,11 @@ class SessionController: NSObject {
|
||||
// MARK: - Properties
|
||||
|
||||
lazy var gpsFixController: GPSFixController = {
|
||||
let gpsFixController = GPSFixController(checkIn: self.checkIn)
|
||||
return gpsFixController
|
||||
return GPSFixController(checkIn: self.checkIn, coreDataManager: self.coreDataManager)
|
||||
}()
|
||||
|
||||
lazy var requestManager: RequestManager = {
|
||||
let requestManager = RequestManager(baseURLString: self.checkIn.serverURL)
|
||||
return requestManager
|
||||
lazy var checkInRequestManager: CheckInRequestManager = {
|
||||
return CheckInRequestManager(baseURLString: self.checkIn.serverURL)
|
||||
}()
|
||||
|
||||
}
|
||||
@@ -8,44 +8,41 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol SessionViewControllerDelegate {
|
||||
protocol SessionViewControllerDelegate: class {
|
||||
|
||||
var checkIn: CheckIn { get }
|
||||
|
||||
func performCheckOut()
|
||||
var checkOutActionTitle: String { get }
|
||||
|
||||
func startTracking() throws
|
||||
var checkOutAlertMessage: String { get }
|
||||
|
||||
var coreDataManager: CoreDataManager { get }
|
||||
|
||||
var sessionController: SessionController { get }
|
||||
|
||||
func makeOptionSheet() -> UIAlertController
|
||||
|
||||
func refresh(_ animated: Bool)
|
||||
|
||||
}
|
||||
|
||||
class SessionViewController: UIViewController {
|
||||
|
||||
struct Segue {
|
||||
static let About = "About"
|
||||
static let Leaderboard = "Leaderboard"
|
||||
static let Settings = "Settings"
|
||||
|
||||
struct SessionSegue {
|
||||
static let Tracking = "Tracking"
|
||||
}
|
||||
|
||||
@IBOutlet weak var startTrackingButton: UIButton!
|
||||
|
||||
var delegate: SessionViewControllerDelegate!
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func startTrackingButtonTapped(_ sender: AnyObject) {
|
||||
// TODO: Add or add not WiFi Alert?
|
||||
//if SMTWiFiStatus.wifiStatus() == WiFiStatus.On && !AFNetworkReachabilityManager.sharedManager().reachableViaWiFi {
|
||||
// showStartTrackingWiFiAlert()
|
||||
//} else {
|
||||
startTracking()
|
||||
//}
|
||||
}
|
||||
|
||||
@IBOutlet weak var optionButton: UIBarButtonItem!
|
||||
@IBOutlet weak var startTrackingButton: UIButton!
|
||||
|
||||
weak var delegate: SessionViewControllerDelegate!
|
||||
|
||||
// MARK: - Tracking
|
||||
|
||||
|
||||
fileprivate func startTracking() {
|
||||
do {
|
||||
try delegate.startTracking()
|
||||
performSegue(withIdentifier: Segue.Tracking, sender: self)
|
||||
try delegate.sessionController.startTracking()
|
||||
performSegue(withIdentifier: SessionSegue.Tracking, sender: self)
|
||||
} catch let error as LocationManager.LocationManagerError {
|
||||
showStartTrackingFailureAlert(message: error.description)
|
||||
} catch {
|
||||
@@ -53,42 +50,161 @@ class SessionViewController: UIViewController {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Update
|
||||
|
||||
func updateOptimistic() {
|
||||
update(success: {
|
||||
// ...
|
||||
}) { (error) in
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
func updatePessimistic() {
|
||||
SVProgressHUD.show()
|
||||
update(success: {
|
||||
SVProgressHUD.dismiss()
|
||||
}) { [weak self] (error) in
|
||||
SVProgressHUD.dismiss()
|
||||
self?.showAlert(forError: error)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func update(
|
||||
success: @escaping (() -> Void),
|
||||
failure: @escaping ((_ error: Error) -> Void))
|
||||
{
|
||||
do {
|
||||
try delegate.sessionController.update(success: { [weak self] in
|
||||
success()
|
||||
self?.delegate.refresh(true)
|
||||
}) { (error) in
|
||||
failure(error)
|
||||
}
|
||||
} catch {
|
||||
failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - CheckOut
|
||||
|
||||
func checkOut() {
|
||||
showCheckOutAlert()
|
||||
}
|
||||
|
||||
// MARK: - Alerts
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func optionButtonTapped(_ sender: Any) {
|
||||
present(delegate.makeOptionSheet(), animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func makeActionCancel() -> UIAlertAction {
|
||||
return UIAlertAction(title: Translation.Common.Cancel.String, style: .cancel, handler: nil)
|
||||
}
|
||||
|
||||
func makeActionCheckOut() -> UIAlertAction {
|
||||
return UIAlertAction(title: delegate?.checkOutActionTitle, style: .default) { [weak self] action in
|
||||
self?.checkOut()
|
||||
}
|
||||
}
|
||||
|
||||
func makeActionInfo() -> UIAlertAction {
|
||||
return UIAlertAction(title: Translation.Common.Info.String, style: .default) { [weak self] action in
|
||||
self?.presentAboutViewController()
|
||||
}
|
||||
}
|
||||
|
||||
func makeActionSettings() -> UIAlertAction {
|
||||
return UIAlertAction(title: Translation.SettingsView.Title.String, style: .default) { [weak self] action in
|
||||
self?.presentSettingsViewController()
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func refreshButtonTapped(_ sender: Any) {
|
||||
updatePessimistic()
|
||||
}
|
||||
|
||||
@IBAction func startTrackingButtonTapped(_ sender: Any) {
|
||||
// TODO: Add or add not WiFi Alert?
|
||||
//if SMTWiFiStatus.wifiStatus() == WiFiStatus.On && !AFNetworkReachabilityManager.sharedManager().reachableViaWiFi {
|
||||
// showStartTrackingWiFiAlert()
|
||||
//} else {
|
||||
startTracking()
|
||||
//}
|
||||
}
|
||||
|
||||
@IBAction func eventButtonTapped(_ sender: UIButton) {
|
||||
if let eventURL = delegate.checkIn.eventURL() {
|
||||
UIApplication.shared.openURL(eventURL)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func leaderboardButtonTapped(_ sender: Any) {
|
||||
presentLeaderboardViewController()
|
||||
}
|
||||
|
||||
// MARK: - Segues
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
super.prepare(for: segue, sender: sender)
|
||||
if (segue.identifier == SessionSegue.Tracking) {
|
||||
let trackingNC = segue.destination as! UINavigationController
|
||||
let trackingVC = trackingNC.viewControllers[0] as! TrackingViewController
|
||||
trackingVC.checkIn = delegate.checkIn
|
||||
trackingVC.sessionController = delegate.sessionController
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func presentLeaderboardViewController() {
|
||||
let storyboard = UIStoryboard(name: "Main", bundle: nil)
|
||||
guard let leaderboardNC = storyboard.instantiateViewController(withIdentifier: "LeaderboardNavigationController") as? UINavigationController else { return }
|
||||
guard let leaderboardVC = leaderboardNC.childViewControllers.first as? LeaderboardViewController else { return }
|
||||
leaderboardVC.checkIn = delegate.checkIn
|
||||
present(leaderboardNC, animated: true)
|
||||
}
|
||||
|
||||
// MARK: - Alerts
|
||||
|
||||
fileprivate func showCheckOutAlert() {
|
||||
let alertController = UIAlertController(
|
||||
title: Translation.Common.Warning.String,
|
||||
message: Translation.CompetitorView.CheckOutAlert.Message.String,
|
||||
message: delegate?.checkOutAlertMessage,
|
||||
preferredStyle: .alert
|
||||
)
|
||||
let yesAction = UIAlertAction(title: Translation.Common.Yes.String, style: .default) { (action) in
|
||||
self.delegate.performCheckOut()
|
||||
let yesAction = UIAlertAction(title: Translation.Common.Yes.String, style: .default) { [weak self] action in
|
||||
self?.performCheckOut()
|
||||
}
|
||||
let noAction = UIAlertAction(title: Translation.Common.No.String, style: .cancel, handler: nil)
|
||||
alertController.addAction(yesAction)
|
||||
alertController.addAction(noAction)
|
||||
present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
|
||||
fileprivate func performCheckOut() {
|
||||
delegate.sessionController.checkOut { [weak self] (withSuccess) in
|
||||
self?.performCheckOutCompleted(withSuccess: withSuccess)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func performCheckOutCompleted(withSuccess: Bool) {
|
||||
delegate.coreDataManager.deleteObject(object: delegate.checkIn)
|
||||
delegate.coreDataManager.saveContext()
|
||||
navigationController?.popViewController(animated: true)
|
||||
}
|
||||
|
||||
fileprivate func showStartTrackingWiFiAlert() {
|
||||
let alertController = UIAlertController(
|
||||
title: "INFO",
|
||||
message: "WIFI IS ON BUT NOT CONNECTED",
|
||||
preferredStyle: .alert
|
||||
)
|
||||
let settingsAction = UIAlertAction(title: Translation.Common.Settings.String, style: .default) { (action) in
|
||||
let settingsAction = UIAlertAction(title: Translation.Common.Settings.String, style: .default) { action in
|
||||
if let settingsURL = URL(string: "prefs:root=WIFI") {
|
||||
UIApplication.shared.openURL(settingsURL)
|
||||
}
|
||||
}
|
||||
let okAction = UIAlertAction(title: Translation.Common.OK.String, style: .default) { (action) in
|
||||
self.startTracking()
|
||||
let okAction = UIAlertAction(title: Translation.Common.OK.String, style: .default) { [weak self] action in
|
||||
self?.startTracking()
|
||||
}
|
||||
let cancelAction = UIAlertAction(title: Translation.Common.Cancel.String, style: .cancel, handler: nil)
|
||||
alertController.addAction(settingsAction)
|
||||
@@ -103,7 +219,7 @@ class SessionViewController: UIViewController {
|
||||
message: message,
|
||||
preferredStyle: .alert
|
||||
)
|
||||
let settingsAction = UIAlertAction(title: Translation.Common.Settings.String, style: .default) { (action) in
|
||||
let settingsAction = UIAlertAction(title: Translation.Common.Settings.String, style: .default) { action in
|
||||
if let locationServiceURL = URL(string: "prefs:root=LOCATION_SERVICES") {
|
||||
UIApplication.shared.openURL(locationServiceURL)
|
||||
}
|
||||
@@ -113,5 +229,5 @@ class SessionViewController: UIViewController {
|
||||
alertController.addAction(cancelAction)
|
||||
present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||