diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..569d9dd --- /dev/null +++ b/.gitignore @@ -0,0 +1,83 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ +.DS_Store + +## Other +*.moved-aside +*.xccheckout +*.xcscmblueprint + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +Pods/ +# +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..4854fb8 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Simon Christian Krüger + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Podfile b/Podfile new file mode 100644 index 0000000..8223f0b --- /dev/null +++ b/Podfile @@ -0,0 +1,11 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +target 'caffeine' do + # Comment this line if you're not using Swift and don't want to use dynamic frameworks + use_frameworks! + + # Pods for caffeine + inherit! :search_paths + pod 'Spring', :git => 'https://github.com/MengTo/Spring.git' +end diff --git a/Podfile.lock b/Podfile.lock new file mode 100644 index 0000000..9ddee44 --- /dev/null +++ b/Podfile.lock @@ -0,0 +1,21 @@ +PODS: + - Spring (1.0.6) + +DEPENDENCIES: + - Spring (from `https://github.com/MengTo/Spring.git`) + +EXTERNAL SOURCES: + Spring: + :git: https://github.com/MengTo/Spring.git + +CHECKOUT OPTIONS: + Spring: + :commit: 50d92a5b9e08848387ae95bf44c6ad20834f7083 + :git: https://github.com/MengTo/Spring.git + +SPEC CHECKSUMS: + Spring: 3d113f14575ef79aec9c5f906dfa4f5001c03254 + +PODFILE CHECKSUM: 02db9cfd46bb75d696ea5fcf5ba1b997cc110895 + +COCOAPODS: 1.9.3 diff --git a/README.md b/README.md new file mode 100644 index 0000000..7a85d0e --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# caffe:ne +The project of this app is one of my first publications on the Apple AppStore in 2015. When I closed my account in 2017 the code was forgotten and the project was left in the bottom shelf of my drawer. With corona knocking on our door I finally found the time to read through my old code and refactor it a bit. It's far from perfect and lacks a clean architectural concept though it was always dear to me. Therefore I felt obliged to get an AppStore account again and publish it for free. At the same time I would like others to learn from my mistakes. Therefore the whole codebase is open source. + +Depending on my availability I will maybe publish some additional updates here and on the AppStore. diff --git a/caffeine.xcodeproj/project.pbxproj b/caffeine.xcodeproj/project.pbxproj new file mode 100644 index 0000000..dea6c69 --- /dev/null +++ b/caffeine.xcodeproj/project.pbxproj @@ -0,0 +1,844 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 600647DA1BAE0A0E00101295 /* Coffee.plist in Resources */ = {isa = PBXBuildFile; fileRef = 600647D91BAE0A0E00101295 /* Coffee.plist */; }; + 600647DC1BAE0A1B00101295 /* Milk.plist in Resources */ = {isa = PBXBuildFile; fileRef = 600647DB1BAE0A1B00101295 /* Milk.plist */; }; + 600647DE1BAE0A2700101295 /* Sugar.plist in Resources */ = {isa = PBXBuildFile; fileRef = 600647DD1BAE0A2700101295 /* Sugar.plist */; }; + 600647E01BAE0A3D00101295 /* Size.plist in Resources */ = {isa = PBXBuildFile; fileRef = 600647DF1BAE0A3D00101295 /* Size.plist */; }; + 600D1C381BE7C47C00DC9500 /* UICaffeineInputButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 600D1C371BE7C47C00DC9500 /* UICaffeineInputButtonView.swift */; }; + 601336D11BF389E6006A67D3 /* UICaffeineStatisticsTimeSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 601336D01BF389E6006A67D3 /* UICaffeineStatisticsTimeSelectorView.swift */; }; + 601EB15A1C00CF9A00F56A2A /* UICaffeineConsumptionRecentButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 601EB1591C00CF9A00F56A2A /* UICaffeineConsumptionRecentButton.swift */; }; + 601FAA291BAC52D200553555 /* InputViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 601FAA271BAC52D200553555 /* InputViewController.swift */; }; + 601FAA2A1BAC52D200553555 /* ConsumptionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 601FAA281BAC52D200553555 /* ConsumptionViewController.swift */; }; + 601FAA2C1BAC52E300553555 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 601FAA2B1BAC52E300553555 /* AppDelegate.swift */; }; + 602E64B21BAC1BDF00E25D52 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 602E64B01BAC1BDF00E25D52 /* Main.storyboard */; }; + 602E64B41BAC1BDF00E25D52 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 602E64B31BAC1BDF00E25D52 /* Assets.xcassets */; }; + 602E64B71BAC1BDF00E25D52 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 602E64B51BAC1BDF00E25D52 /* LaunchScreen.storyboard */; }; + 602E64E51BAC3C6C00E25D52 /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 602E64E41BAC3C6C00E25D52 /* HealthKit.framework */; }; + 603DEE0424AB7C640079A79C /* HealthStoreService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 603DEE0324AB7C640079A79C /* HealthStoreService.swift */; }; + 603DEE0924AB7FE10079A79C /* swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = 603DEE0824AB7FE00079A79C /* swiftlint.yml */; }; + 603DEE0D24AB87030079A79C /* UserSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 603DEE0C24AB87030079A79C /* UserSettings.swift */; }; + 60481E1624AB71D8008DE168 /* CaffeineColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60481E1524AB71D8008DE168 /* CaffeineColors.swift */; }; + 60481E1924AB76C8008DE168 /* Consumable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60481E1824AB76C8008DE168 /* Consumable.swift */; }; + 60481E1B24AB7860008DE168 /* ConsumableProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60481E1A24AB7860008DE168 /* ConsumableProperties.swift */; }; + 60496F2B1BD011CA00A72757 /* UISliderPopupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60496F2A1BD011CA00A72757 /* UISliderPopupView.swift */; }; + 60496F2D1BD015CE00A72757 /* UICaffeineSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60496F2C1BD015CE00A72757 /* UICaffeineSlider.swift */; }; + 6062DFF11BDAA05E003EF136 /* UICaffeineInputButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6062DFF01BDAA05E003EF136 /* UICaffeineInputButton.swift */; }; + 60BE64AE24AF620D000F26C4 /* caffeineUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60BE64AD24AF620D000F26C4 /* caffeineUITests.swift */; }; + 60BE64B624AF634E000F26C4 /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60BE64B524AF634E000F26C4 /* SnapshotHelper.swift */; }; + 60CA813D24AC6D8200DDED35 /* PeriodicConsumption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60CA813C24AC6D8200DDED35 /* PeriodicConsumption.swift */; }; + 60CA813F24AC781100DDED35 /* CaffeineDataService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60CA813E24AC781100DDED35 /* CaffeineDataService.swift */; }; + 60CA814124ADC04800DDED35 /* DrinksService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60CA814024ADC04800DDED35 /* DrinksService.swift */; }; + 60CA814624ADC39D00DDED35 /* Consumption.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 60CA814424ADC39D00DDED35 /* Consumption.xcdatamodeld */; }; + 60CA814824ADC45500DDED35 /* PersistenceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60CA814724ADC45500DDED35 /* PersistenceService.swift */; }; + 60CA814A24ADC48B00DDED35 /* DatastoreProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60CA814924ADC48B00DDED35 /* DatastoreProvider.swift */; }; + 60CA814C24ADC4A700DDED35 /* ContextProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60CA814B24ADC4A700DDED35 /* ContextProvider.swift */; }; + 60CA814E24ADC57500DDED35 /* ConsumptionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60CA814D24ADC57500DDED35 /* ConsumptionStore.swift */; }; + 60CA815024ADC59B00DDED35 /* ConsumptionDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60CA814F24ADC59B00DDED35 /* ConsumptionDataProvider.swift */; }; + 60D3BAF41BC12CC100CAC7DE /* StatisticsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60D3BAF31BC12CC100CAC7DE /* StatisticsViewController.swift */; }; + 60D3BAF61BC12D2000CAC7DE /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60D3BAF51BC12D2000CAC7DE /* SettingsViewController.swift */; }; + 60E25B911C03418D007205C3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 60E25B931C03418D007205C3 /* Localizable.strings */; }; + 60E76D2B1BEA183300670CEA /* UICaffeineInputOKButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60E76D2A1BEA183300670CEA /* UICaffeineInputOKButton.swift */; }; + 60E76D2D1BEB608800670CEA /* UICaffeineStatisticsChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60E76D2C1BEB608800670CEA /* UICaffeineStatisticsChartView.swift */; }; + 95B669E5BD1E01661467093F /* Pods_caffeine.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AC0D4BC68B85877491EC9915 /* Pods_caffeine.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 60BE64B024AF620D000F26C4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 602E649F1BAC1BDF00E25D52 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 602E64A61BAC1BDF00E25D52; + remoteInfo = caffeine; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 41D1ABB40FE4971EAEC727F3 /* Pods-caffeineUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-caffeineUITests.release.xcconfig"; path = "Pods/Target Support Files/Pods-caffeineUITests/Pods-caffeineUITests.release.xcconfig"; sourceTree = ""; }; + 59916642B9BECC76E54897AF /* Pods-caffeineUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-caffeineUITests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-caffeineUITests/Pods-caffeineUITests.debug.xcconfig"; sourceTree = ""; }; + 600647D91BAE0A0E00101295 /* Coffee.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Coffee.plist; path = "Supporting Files/Consumption/Coffee.plist"; sourceTree = ""; }; + 600647DB1BAE0A1B00101295 /* Milk.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Milk.plist; path = "Supporting Files/Consumption/Milk.plist"; sourceTree = ""; }; + 600647DD1BAE0A2700101295 /* Sugar.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Sugar.plist; path = "Supporting Files/Consumption/Sugar.plist"; sourceTree = ""; }; + 600647DF1BAE0A3D00101295 /* Size.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Size.plist; path = "Supporting Files/Consumption/Size.plist"; sourceTree = ""; }; + 600D1C371BE7C47C00DC9500 /* UICaffeineInputButtonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UICaffeineInputButtonView.swift; path = View/UICaffeineInputButtonView.swift; sourceTree = ""; }; + 601336D01BF389E6006A67D3 /* UICaffeineStatisticsTimeSelectorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UICaffeineStatisticsTimeSelectorView.swift; path = View/UICaffeineStatisticsTimeSelectorView.swift; sourceTree = ""; }; + 601EB1591C00CF9A00F56A2A /* UICaffeineConsumptionRecentButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UICaffeineConsumptionRecentButton.swift; path = View/UICaffeineConsumptionRecentButton.swift; sourceTree = ""; }; + 601FAA271BAC52D200553555 /* InputViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InputViewController.swift; path = ViewController/InputViewController.swift; sourceTree = ""; }; + 601FAA281BAC52D200553555 /* ConsumptionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ConsumptionViewController.swift; path = ViewController/ConsumptionViewController.swift; sourceTree = ""; }; + 601FAA2B1BAC52E300553555 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 602E64A71BAC1BDF00E25D52 /* caffeine.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = caffeine.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 602E64B11BAC1BDF00E25D52 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 602E64B31BAC1BDF00E25D52 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 602E64B61BAC1BDF00E25D52 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 602E64B81BAC1BDF00E25D52 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 602E64E41BAC3C6C00E25D52 /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = System/Library/Frameworks/HealthKit.framework; sourceTree = SDKROOT; }; + 602E64E61BAC3C6C00E25D52 /* caffeine.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = caffeine.entitlements; sourceTree = ""; }; + 603DEE0324AB7C640079A79C /* HealthStoreService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthStoreService.swift; sourceTree = ""; }; + 603DEE0824AB7FE00079A79C /* swiftlint.yml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.yaml; path = swiftlint.yml; sourceTree = SOURCE_ROOT; }; + 603DEE0C24AB87030079A79C /* UserSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettings.swift; sourceTree = ""; }; + 60481E1524AB71D8008DE168 /* CaffeineColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaffeineColors.swift; sourceTree = ""; }; + 60481E1824AB76C8008DE168 /* Consumable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Consumable.swift; sourceTree = ""; }; + 60481E1A24AB7860008DE168 /* ConsumableProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsumableProperties.swift; sourceTree = ""; }; + 60496F2A1BD011CA00A72757 /* UISliderPopupView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UISliderPopupView.swift; path = View/UISliderPopupView.swift; sourceTree = ""; }; + 60496F2C1BD015CE00A72757 /* UICaffeineSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UICaffeineSlider.swift; path = View/UICaffeineSlider.swift; sourceTree = ""; }; + 6062DFF01BDAA05E003EF136 /* UICaffeineInputButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UICaffeineInputButton.swift; path = View/UICaffeineInputButton.swift; sourceTree = ""; }; + 60BE64AB24AF620D000F26C4 /* caffeineUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = caffeineUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 60BE64AD24AF620D000F26C4 /* caffeineUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = caffeineUITests.swift; sourceTree = ""; }; + 60BE64AF24AF620D000F26C4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 60BE64B524AF634E000F26C4 /* SnapshotHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SnapshotHelper.swift; path = fastlane/SnapshotHelper.swift; sourceTree = SOURCE_ROOT; }; + 60CA813C24AC6D8200DDED35 /* PeriodicConsumption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeriodicConsumption.swift; sourceTree = ""; }; + 60CA813E24AC781100DDED35 /* CaffeineDataService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaffeineDataService.swift; sourceTree = ""; }; + 60CA814024ADC04800DDED35 /* DrinksService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrinksService.swift; sourceTree = ""; }; + 60CA814524ADC39D00DDED35 /* Consumption.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Consumption.xcdatamodel; sourceTree = ""; }; + 60CA814724ADC45500DDED35 /* PersistenceService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceService.swift; sourceTree = ""; }; + 60CA814924ADC48B00DDED35 /* DatastoreProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatastoreProvider.swift; sourceTree = ""; }; + 60CA814B24ADC4A700DDED35 /* ContextProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextProvider.swift; sourceTree = ""; }; + 60CA814D24ADC57500DDED35 /* ConsumptionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsumptionStore.swift; sourceTree = ""; }; + 60CA814F24ADC59B00DDED35 /* ConsumptionDataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsumptionDataProvider.swift; sourceTree = ""; }; + 60D3BAF31BC12CC100CAC7DE /* StatisticsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StatisticsViewController.swift; path = ViewController/StatisticsViewController.swift; sourceTree = ""; }; + 60D3BAF51BC12D2000CAC7DE /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SettingsViewController.swift; path = ViewController/SettingsViewController.swift; sourceTree = ""; }; + 60D941701BC8623100C203C6 /* MindModellDesctiption.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; name = MindModellDesctiption.rtf; path = "Supporting Files/MindModellDesctiption.rtf"; sourceTree = ""; }; + 60D941721BC8659C00C203C6 /* CaffeineDataDescription.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; name = CaffeineDataDescription.rtf; path = "Supporting Files/CaffeineDataDescription.rtf"; sourceTree = ""; }; + 60E25B921C03418D007205C3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; + 60E25B941C048E19007205C3 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Main.strings; sourceTree = ""; }; + 60E25B951C048E19007205C3 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/LaunchScreen.strings; sourceTree = ""; }; + 60E25B961C048E19007205C3 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + 60E76D2A1BEA183300670CEA /* UICaffeineInputOKButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UICaffeineInputOKButton.swift; path = View/UICaffeineInputOKButton.swift; sourceTree = ""; }; + 60E76D2C1BEB608800670CEA /* UICaffeineStatisticsChartView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UICaffeineStatisticsChartView.swift; path = View/UICaffeineStatisticsChartView.swift; sourceTree = ""; }; + 6943F8CA7228E6BA43EF1D52 /* Pods-caffeine.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-caffeine.release.xcconfig"; path = "Pods/Target Support Files/Pods-caffeine/Pods-caffeine.release.xcconfig"; sourceTree = ""; }; + 6A7D83803EA44D18A8ACA433 /* Pods-caffeineTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-caffeineTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-caffeineTests/Pods-caffeineTests.debug.xcconfig"; sourceTree = ""; }; + 6B0FA68D033FC175428005D5 /* Pods-caffeineTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-caffeineTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-caffeineTests/Pods-caffeineTests.release.xcconfig"; sourceTree = ""; }; + 805C0FA2158610E631948B9C /* libPods-caffeineTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-caffeineTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + AC0D4BC68B85877491EC9915 /* Pods_caffeine.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_caffeine.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BA3B96DF380F91729B1B1847 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D62277E8878BF98CB27A8302 /* libPods-caffeineUITests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-caffeineUITests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + DE4BBE3566B0763A7FAAAF50 /* Pods-caffeine.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-caffeine.debug.xcconfig"; path = "Pods/Target Support Files/Pods-caffeine/Pods-caffeine.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 602E64A41BAC1BDF00E25D52 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 602E64E51BAC3C6C00E25D52 /* HealthKit.framework in Frameworks */, + 95B669E5BD1E01661467093F /* Pods_caffeine.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 60BE64A824AF620D000F26C4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 600647D81BAE09CD00101295 /* Consumption */ = { + isa = PBXGroup; + children = ( + 600647D91BAE0A0E00101295 /* Coffee.plist */, + 600647DB1BAE0A1B00101295 /* Milk.plist */, + 600647DD1BAE0A2700101295 /* Sugar.plist */, + 600647DF1BAE0A3D00101295 /* Size.plist */, + ); + name = Consumption; + sourceTree = ""; + }; + 601FAA2D1BAC532300553555 /* View */ = { + isa = PBXGroup; + children = ( + 60496F2A1BD011CA00A72757 /* UISliderPopupView.swift */, + 60496F2C1BD015CE00A72757 /* UICaffeineSlider.swift */, + 601EB1591C00CF9A00F56A2A /* UICaffeineConsumptionRecentButton.swift */, + 6062DFF01BDAA05E003EF136 /* UICaffeineInputButton.swift */, + 60E76D2A1BEA183300670CEA /* UICaffeineInputOKButton.swift */, + 600D1C371BE7C47C00DC9500 /* UICaffeineInputButtonView.swift */, + 60E76D2C1BEB608800670CEA /* UICaffeineStatisticsChartView.swift */, + 601336D01BF389E6006A67D3 /* UICaffeineStatisticsTimeSelectorView.swift */, + ); + name = View; + sourceTree = ""; + }; + 602E649E1BAC1BDF00E25D52 = { + isa = PBXGroup; + children = ( + 602E64A91BAC1BDF00E25D52 /* caffeine */, + 60BE64AC24AF620D000F26C4 /* caffeineUITests */, + 658C97C1AE0B060543CE9128 /* Frameworks */, + 602E64A81BAC1BDF00E25D52 /* Products */, + 94E80EB2AEC9C94EF41E466C /* Pods */, + ); + sourceTree = ""; + }; + 602E64A81BAC1BDF00E25D52 /* Products */ = { + isa = PBXGroup; + children = ( + 602E64A71BAC1BDF00E25D52 /* caffeine.app */, + 60BE64AB24AF620D000F26C4 /* caffeineUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 602E64A91BAC1BDF00E25D52 /* caffeine */ = { + isa = PBXGroup; + children = ( + 601FAA2B1BAC52E300553555 /* AppDelegate.swift */, + 60481E1724AB76A5008DE168 /* Data */, + 60481E1424AB71BE008DE168 /* Styling */, + 603DEE0224AB7C2D0079A79C /* Services */, + 601FAA2D1BAC532300553555 /* View */, + 602E64DB1BAC1BF400E25D52 /* ViewController */, + 602E64DD1BAC1C0600E25D52 /* Supporting Files */, + ); + path = caffeine; + sourceTree = ""; + }; + 602E64DB1BAC1BF400E25D52 /* ViewController */ = { + isa = PBXGroup; + children = ( + 601FAA281BAC52D200553555 /* ConsumptionViewController.swift */, + 601FAA271BAC52D200553555 /* InputViewController.swift */, + 60D3BAF31BC12CC100CAC7DE /* StatisticsViewController.swift */, + 60D3BAF51BC12D2000CAC7DE /* SettingsViewController.swift */, + ); + name = ViewController; + sourceTree = ""; + }; + 602E64DC1BAC1BFC00E25D52 /* Storyboards */ = { + isa = PBXGroup; + children = ( + 602E64B01BAC1BDF00E25D52 /* Main.storyboard */, + 602E64B51BAC1BDF00E25D52 /* LaunchScreen.storyboard */, + ); + name = Storyboards; + sourceTree = ""; + }; + 602E64DD1BAC1C0600E25D52 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 600647D81BAE09CD00101295 /* Consumption */, + 602E64DC1BAC1BFC00E25D52 /* Storyboards */, + 602E64B31BAC1BDF00E25D52 /* Assets.xcassets */, + 602E64E61BAC3C6C00E25D52 /* caffeine.entitlements */, + 60E25B931C03418D007205C3 /* Localizable.strings */, + 60D941701BC8623100C203C6 /* MindModellDesctiption.rtf */, + 60D941721BC8659C00C203C6 /* CaffeineDataDescription.rtf */, + 602E64B81BAC1BDF00E25D52 /* Info.plist */, + 603DEE0824AB7FE00079A79C /* swiftlint.yml */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 603DEE0224AB7C2D0079A79C /* Services */ = { + isa = PBXGroup; + children = ( + 60CA814324ADC38400DDED35 /* ConsumptionStore */, + 60CA815324ADC83700DDED35 /* HealthKit */, + 60CA814024ADC04800DDED35 /* DrinksService.swift */, + ); + path = Services; + sourceTree = ""; + }; + 60481E1424AB71BE008DE168 /* Styling */ = { + isa = PBXGroup; + children = ( + 60481E1524AB71D8008DE168 /* CaffeineColors.swift */, + ); + path = Styling; + sourceTree = ""; + }; + 60481E1724AB76A5008DE168 /* Data */ = { + isa = PBXGroup; + children = ( + 60481E1824AB76C8008DE168 /* Consumable.swift */, + 60481E1A24AB7860008DE168 /* ConsumableProperties.swift */, + 60CA813C24AC6D8200DDED35 /* PeriodicConsumption.swift */, + 603DEE0C24AB87030079A79C /* UserSettings.swift */, + ); + path = Data; + sourceTree = ""; + }; + 60BE64AC24AF620D000F26C4 /* caffeineUITests */ = { + isa = PBXGroup; + children = ( + 60BE64AD24AF620D000F26C4 /* caffeineUITests.swift */, + 60BE64B524AF634E000F26C4 /* SnapshotHelper.swift */, + 60BE64AF24AF620D000F26C4 /* Info.plist */, + ); + path = caffeineUITests; + sourceTree = ""; + }; + 60CA814324ADC38400DDED35 /* ConsumptionStore */ = { + isa = PBXGroup; + children = ( + 60CA814424ADC39D00DDED35 /* Consumption.xcdatamodeld */, + 60CA814F24ADC59B00DDED35 /* ConsumptionDataProvider.swift */, + 60CA814D24ADC57500DDED35 /* ConsumptionStore.swift */, + 60CA814724ADC45500DDED35 /* PersistenceService.swift */, + 60CA814924ADC48B00DDED35 /* DatastoreProvider.swift */, + 60CA814B24ADC4A700DDED35 /* ContextProvider.swift */, + ); + path = ConsumptionStore; + sourceTree = ""; + }; + 60CA815324ADC83700DDED35 /* HealthKit */ = { + isa = PBXGroup; + children = ( + 60CA813E24AC781100DDED35 /* CaffeineDataService.swift */, + 603DEE0324AB7C640079A79C /* HealthStoreService.swift */, + ); + path = HealthKit; + sourceTree = ""; + }; + 658C97C1AE0B060543CE9128 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 602E64E41BAC3C6C00E25D52 /* HealthKit.framework */, + BA3B96DF380F91729B1B1847 /* Pods.framework */, + AC0D4BC68B85877491EC9915 /* Pods_caffeine.framework */, + 805C0FA2158610E631948B9C /* libPods-caffeineTests.a */, + D62277E8878BF98CB27A8302 /* libPods-caffeineUITests.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 94E80EB2AEC9C94EF41E466C /* Pods */ = { + isa = PBXGroup; + children = ( + DE4BBE3566B0763A7FAAAF50 /* Pods-caffeine.debug.xcconfig */, + 6943F8CA7228E6BA43EF1D52 /* Pods-caffeine.release.xcconfig */, + 6A7D83803EA44D18A8ACA433 /* Pods-caffeineTests.debug.xcconfig */, + 6B0FA68D033FC175428005D5 /* Pods-caffeineTests.release.xcconfig */, + 59916642B9BECC76E54897AF /* Pods-caffeineUITests.debug.xcconfig */, + 41D1ABB40FE4971EAEC727F3 /* Pods-caffeineUITests.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 602E64A61BAC1BDF00E25D52 /* caffeine */ = { + isa = PBXNativeTarget; + buildConfigurationList = 602E64D11BAC1BDF00E25D52 /* Build configuration list for PBXNativeTarget "caffeine" */; + buildPhases = ( + A4AE9D0B6EDC84E5FC899167 /* [CP] Check Pods Manifest.lock */, + 603DEE0524AB7F700079A79C /* Swiftlint */, + 602E64A31BAC1BDF00E25D52 /* Sources */, + 602E64A41BAC1BDF00E25D52 /* Frameworks */, + 602E64A51BAC1BDF00E25D52 /* Resources */, + B931EF7F89DA3D474234C581 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = caffeine; + productName = caffeine; + productReference = 602E64A71BAC1BDF00E25D52 /* caffeine.app */; + productType = "com.apple.product-type.application"; + }; + 60BE64AA24AF620D000F26C4 /* caffeineUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 60BE64B424AF620D000F26C4 /* Build configuration list for PBXNativeTarget "caffeineUITests" */; + buildPhases = ( + 60BE64A724AF620D000F26C4 /* Sources */, + 60BE64A824AF620D000F26C4 /* Frameworks */, + 60BE64A924AF620D000F26C4 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 60BE64B124AF620D000F26C4 /* PBXTargetDependency */, + ); + name = caffeineUITests; + productName = caffeineUITests; + productReference = 60BE64AB24AF620D000F26C4 /* caffeineUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 602E649F1BAC1BDF00E25D52 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1150; + LastUpgradeCheck = 1150; + ORGANIZATIONNAME = kayos; + TargetAttributes = { + 602E64A61BAC1BDF00E25D52 = { + CreatedOnToolsVersion = 7.0; + DevelopmentTeam = T2MU43SWN2; + LastSwiftMigration = 0800; + ProvisioningStyle = Manual; + SystemCapabilities = { + com.apple.ApplicationGroups.iOS = { + enabled = 1; + }; + com.apple.BackgroundModes = { + enabled = 0; + }; + com.apple.HealthKit = { + enabled = 1; + }; + }; + }; + 60BE64AA24AF620D000F26C4 = { + CreatedOnToolsVersion = 11.5; + DevelopmentTeam = C5HCUYE57C; + ProvisioningStyle = Automatic; + TestTargetID = 602E64A61BAC1BDF00E25D52; + }; + }; + }; + buildConfigurationList = 602E64A21BAC1BDF00E25D52 /* Build configuration list for PBXProject "caffeine" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + de, + ); + mainGroup = 602E649E1BAC1BDF00E25D52; + productRefGroup = 602E64A81BAC1BDF00E25D52 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 602E64A61BAC1BDF00E25D52 /* caffeine */, + 60BE64AA24AF620D000F26C4 /* caffeineUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 602E64A51BAC1BDF00E25D52 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 602E64B71BAC1BDF00E25D52 /* LaunchScreen.storyboard in Resources */, + 600647E01BAE0A3D00101295 /* Size.plist in Resources */, + 602E64B41BAC1BDF00E25D52 /* Assets.xcassets in Resources */, + 600647DA1BAE0A0E00101295 /* Coffee.plist in Resources */, + 603DEE0924AB7FE10079A79C /* swiftlint.yml in Resources */, + 602E64B21BAC1BDF00E25D52 /* Main.storyboard in Resources */, + 60E25B911C03418D007205C3 /* Localizable.strings in Resources */, + 600647DC1BAE0A1B00101295 /* Milk.plist in Resources */, + 600647DE1BAE0A2700101295 /* Sugar.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 60BE64A924AF620D000F26C4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 603DEE0524AB7F700079A79C /* Swiftlint */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = Swiftlint; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ -x \"$(command -v swiftlint)\" ]; then\n swiftlint autocorrect --config swiftlint.yml\n #swiftlint --config swiftlint.yml\n echo \"Hello world\"\nelse\n echo \"An error occured. Swiftlint should be installed.\"\n exit 1\nfi\n\n"; + }; + A4AE9D0B6EDC84E5FC899167 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-caffeine-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + B931EF7F89DA3D474234C581 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-caffeine/Pods-caffeine-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/Spring/Spring.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Spring.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-caffeine/Pods-caffeine-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 602E64A31BAC1BDF00E25D52 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 60CA814824ADC45500DDED35 /* PersistenceService.swift in Sources */, + 60CA813F24AC781100DDED35 /* CaffeineDataService.swift in Sources */, + 600D1C381BE7C47C00DC9500 /* UICaffeineInputButtonView.swift in Sources */, + 60CA814C24ADC4A700DDED35 /* ContextProvider.swift in Sources */, + 603DEE0D24AB87030079A79C /* UserSettings.swift in Sources */, + 601EB15A1C00CF9A00F56A2A /* UICaffeineConsumptionRecentButton.swift in Sources */, + 601FAA2C1BAC52E300553555 /* AppDelegate.swift in Sources */, + 6062DFF11BDAA05E003EF136 /* UICaffeineInputButton.swift in Sources */, + 603DEE0424AB7C640079A79C /* HealthStoreService.swift in Sources */, + 60CA815024ADC59B00DDED35 /* ConsumptionDataProvider.swift in Sources */, + 60CA814E24ADC57500DDED35 /* ConsumptionStore.swift in Sources */, + 60CA814A24ADC48B00DDED35 /* DatastoreProvider.swift in Sources */, + 60CA814624ADC39D00DDED35 /* Consumption.xcdatamodeld in Sources */, + 60E76D2B1BEA183300670CEA /* UICaffeineInputOKButton.swift in Sources */, + 601FAA2A1BAC52D200553555 /* ConsumptionViewController.swift in Sources */, + 601FAA291BAC52D200553555 /* InputViewController.swift in Sources */, + 60481E1624AB71D8008DE168 /* CaffeineColors.swift in Sources */, + 601336D11BF389E6006A67D3 /* UICaffeineStatisticsTimeSelectorView.swift in Sources */, + 60496F2D1BD015CE00A72757 /* UICaffeineSlider.swift in Sources */, + 60D3BAF41BC12CC100CAC7DE /* StatisticsViewController.swift in Sources */, + 60481E1924AB76C8008DE168 /* Consumable.swift in Sources */, + 60CA814124ADC04800DDED35 /* DrinksService.swift in Sources */, + 60496F2B1BD011CA00A72757 /* UISliderPopupView.swift in Sources */, + 60481E1B24AB7860008DE168 /* ConsumableProperties.swift in Sources */, + 60D3BAF61BC12D2000CAC7DE /* SettingsViewController.swift in Sources */, + 60CA813D24AC6D8200DDED35 /* PeriodicConsumption.swift in Sources */, + 60E76D2D1BEB608800670CEA /* UICaffeineStatisticsChartView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 60BE64A724AF620D000F26C4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 60BE64AE24AF620D000F26C4 /* caffeineUITests.swift in Sources */, + 60BE64B624AF634E000F26C4 /* SnapshotHelper.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 60BE64B124AF620D000F26C4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 602E64A61BAC1BDF00E25D52 /* caffeine */; + targetProxy = 60BE64B024AF620D000F26C4 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 602E64B01BAC1BDF00E25D52 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 602E64B11BAC1BDF00E25D52 /* Base */, + 60E25B941C048E19007205C3 /* de */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 602E64B51BAC1BDF00E25D52 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 602E64B61BAC1BDF00E25D52 /* Base */, + 60E25B951C048E19007205C3 /* de */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; + 60E25B931C03418D007205C3 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 60E25B921C03418D007205C3 /* Base */, + 60E25B961C048E19007205C3 /* de */, + ); + name = Localizable.strings; + path = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 602E64CF1BAC1BDF00E25D52 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/**"; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 602E64D01BAC1BDF00E25D52 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/**"; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 5.0; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 602E64D21BAC1BDF00E25D52 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DE4BBE3566B0763A7FAAAF50 /* Pods-caffeine.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = caffeine/caffeine.entitlements; + CODE_SIGN_IDENTITY = "Apple Distribution: Simon Krüger (T2MU43SWN2)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Distribution: Simon Krüger (T2MU43SWN2)"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = T2MU43SWN2; + FRAMEWORK_SEARCH_PATHS = "$(inherited)/**"; + INFOPLIST_FILE = caffeine/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 1.1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.cr0ss.caffeine; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = "Caffeine AppStore"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 602E64D31BAC1BDF00E25D52 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6943F8CA7228E6BA43EF1D52 /* Pods-caffeine.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = caffeine/caffeine.entitlements; + CODE_SIGN_IDENTITY = "Apple Distribution: Simon Krüger (T2MU43SWN2)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Distribution: Simon Krüger (T2MU43SWN2)"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = T2MU43SWN2; + FRAMEWORK_SEARCH_PATHS = "$(inherited)/**"; + INFOPLIST_FILE = caffeine/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 1.1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.cr0ss.caffeine; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = "Caffeine AppStore"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 60BE64B224AF620D000F26C4 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = C5HCUYE57C; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = caffeineUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.cr0ss.caffeineUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = caffeine; + }; + name = Debug; + }; + 60BE64B324AF620D000F26C4 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = C5HCUYE57C; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = caffeineUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.cr0ss.caffeineUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = caffeine; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 602E64A21BAC1BDF00E25D52 /* Build configuration list for PBXProject "caffeine" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 602E64CF1BAC1BDF00E25D52 /* Debug */, + 602E64D01BAC1BDF00E25D52 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 602E64D11BAC1BDF00E25D52 /* Build configuration list for PBXNativeTarget "caffeine" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 602E64D21BAC1BDF00E25D52 /* Debug */, + 602E64D31BAC1BDF00E25D52 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 60BE64B424AF620D000F26C4 /* Build configuration list for PBXNativeTarget "caffeineUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 60BE64B224AF620D000F26C4 /* Debug */, + 60BE64B324AF620D000F26C4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCVersionGroup section */ + 60CA814424ADC39D00DDED35 /* Consumption.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + 60CA814524ADC39D00DDED35 /* Consumption.xcdatamodel */, + ); + currentVersion = 60CA814524ADC39D00DDED35 /* Consumption.xcdatamodel */; + path = Consumption.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; +/* End XCVersionGroup section */ + }; + rootObject = 602E649F1BAC1BDF00E25D52 /* Project object */; +} diff --git a/caffeine.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/caffeine.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..c935d83 --- /dev/null +++ b/caffeine.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/caffeine.xcodeproj/xcshareddata/xcschemes/caffeine.xcscheme b/caffeine.xcodeproj/xcshareddata/xcschemes/caffeine.xcscheme new file mode 100644 index 0000000..fd43f23 --- /dev/null +++ b/caffeine.xcodeproj/xcshareddata/xcschemes/caffeine.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/caffeine.xcodeproj/xcshareddata/xcschemes/caffeineUITests.xcscheme b/caffeine.xcodeproj/xcshareddata/xcschemes/caffeineUITests.xcscheme new file mode 100644 index 0000000..3b2907a --- /dev/null +++ b/caffeine.xcodeproj/xcshareddata/xcschemes/caffeineUITests.xcscheme @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/caffeine.xcworkspace/contents.xcworkspacedata b/caffeine.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..0786a33 --- /dev/null +++ b/caffeine.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/caffeine.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/caffeine.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/caffeine.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/caffeine/AppDelegate.swift b/caffeine/AppDelegate.swift new file mode 100644 index 0000000..e2705fc --- /dev/null +++ b/caffeine/AppDelegate.swift @@ -0,0 +1,101 @@ +import UIKit + +// Statistics Setup +internal let intersectDistance: [Int] = [4, 1, 5, 61] +internal let catmullRomSelection: [Bool] = [true, false, true, false] + +enum ShortcutItem: String { + case first = "caffeine.shortcut.first" + case second = "caffeine.shortcut.second" + case third = "caffeine.shortcut.third" + + var consumable: Consumable { + let recentDrinks = DrinksService.shared.getRecentDrinks() + switch self { + case .first: + return recentDrinks[0] + case .second: + return recentDrinks[1] + case .third: + return recentDrinks[2] + } + } + + var shortcutItem: UIMutableApplicationShortcutItem? { + return .init( + type: self.rawValue, + localizedTitle: consumable.coffee.localizedTitle, + localizedSubtitle: "\(consumable.milk.localizedTitle), \(consumable.sugar.localizedTitle)", + icon: UIApplicationShortcutIcon(templateImageName: consumable.size.imageName), + userInfo: [:] as [String: NSSecureCoding] + ) + } +} + +// MARK: Begin AppDelegate +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + internal var window: UIWindow? + + /// A storage provider to be accessed by core data relevant classes. + lazy internal var datastoreProvider: DatastoreProvider = { + return DatastoreProvider() + }() + + /// A context provider to be accessed by core data relevant classes. + lazy internal var contextProvider: ContextProvider = { + return ContextProvider(with: datastoreProvider) + }() + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + setShortcutItems(application) + return true + } + + func application( + _ application: UIApplication, + performActionFor shortcutItem: UIApplicationShortcutItem, + completionHandler: @escaping (Bool) -> Void + ) { + let handledShortCutItem = handleShortCutItem(shortcutItem) + completionHandler(handledShortCutItem) + } + + func handleShortCutItem( + _ shortcutItem: UIApplicationShortcutItem + ) -> Bool { + guard let shortcutItem = ShortcutItem(rawValue: shortcutItem.type) else { return false } + + let alertController = UIAlertController( + title: "\(NSLocalizedString("ShortcutHeading", comment: ""))", + message: NSLocalizedString("ShortcutText", comment: ""), + preferredStyle: .alert + ) + let okAction = UIAlertAction( + title: NSLocalizedString("ShortcutOK", comment: ""), + style: .default, + handler: nil + ) + alertController.addAction(okAction) + + DrinksService.shared.drink(shortcutItem.consumable) + window?.rootViewController?.present( + alertController, + animated: true, + completion: nil + ) + return true + } + + internal func setShortcutItems(_ application: UIApplication) { + application.shortcutItems = [ + ShortcutItem.first.shortcutItem, + ShortcutItem.second.shortcutItem, + ShortcutItem.third.shortcutItem + ].compactMap { $0 } + } +} diff --git a/caffeine/Assets.xcassets/AppIcon.appiconset/1024upscale.png b/caffeine/Assets.xcassets/AppIcon.appiconset/1024upscale.png new file mode 100644 index 0000000..bc5aa1b Binary files /dev/null and b/caffeine/Assets.xcassets/AppIcon.appiconset/1024upscale.png differ diff --git a/caffeine/Assets.xcassets/AppIcon.appiconset/29@2x.png b/caffeine/Assets.xcassets/AppIcon.appiconset/29@2x.png new file mode 100644 index 0000000..335d77b Binary files /dev/null and b/caffeine/Assets.xcassets/AppIcon.appiconset/29@2x.png differ diff --git a/caffeine/Assets.xcassets/AppIcon.appiconset/29@3x.png b/caffeine/Assets.xcassets/AppIcon.appiconset/29@3x.png new file mode 100644 index 0000000..718278f Binary files /dev/null and b/caffeine/Assets.xcassets/AppIcon.appiconset/29@3x.png differ diff --git a/caffeine/Assets.xcassets/AppIcon.appiconset/40@2x.png b/caffeine/Assets.xcassets/AppIcon.appiconset/40@2x.png new file mode 100644 index 0000000..d177eca Binary files /dev/null and b/caffeine/Assets.xcassets/AppIcon.appiconset/40@2x.png differ diff --git a/caffeine/Assets.xcassets/AppIcon.appiconset/40@3x.png b/caffeine/Assets.xcassets/AppIcon.appiconset/40@3x.png new file mode 100644 index 0000000..faac9b6 Binary files /dev/null and b/caffeine/Assets.xcassets/AppIcon.appiconset/40@3x.png differ diff --git a/caffeine/Assets.xcassets/AppIcon.appiconset/60@2x.png b/caffeine/Assets.xcassets/AppIcon.appiconset/60@2x.png new file mode 100644 index 0000000..faac9b6 Binary files /dev/null and b/caffeine/Assets.xcassets/AppIcon.appiconset/60@2x.png differ diff --git a/caffeine/Assets.xcassets/AppIcon.appiconset/60@3x.png b/caffeine/Assets.xcassets/AppIcon.appiconset/60@3x.png new file mode 100644 index 0000000..5e3ff03 Binary files /dev/null and b/caffeine/Assets.xcassets/AppIcon.appiconset/60@3x.png differ diff --git a/caffeine/Assets.xcassets/AppIcon.appiconset/Contents.json b/caffeine/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..e9fcca8 --- /dev/null +++ b/caffeine/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,60 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "29@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "29@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "40@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "40@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "60@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "60@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "1024upscale.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/caffeine/Assets.xcassets/Contents.json b/caffeine/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/caffeine/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Input/Contents.json b/caffeine/Assets.xcassets/Input/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/caffeine/Assets.xcassets/Input/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Input/Cups/Contents.json b/caffeine/Assets.xcassets/Input/Cups/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/caffeine/Assets.xcassets/Input/Cups/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Input/Cups/cupLarge.imageset/Contents.json b/caffeine/Assets.xcassets/Input/Cups/cupLarge.imageset/Contents.json new file mode 100644 index 0000000..9ee5a1f --- /dev/null +++ b/caffeine/Assets.xcassets/Input/Cups/cupLarge.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "cupLarge.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Input/Cups/cupLarge.imageset/cupLarge.pdf b/caffeine/Assets.xcassets/Input/Cups/cupLarge.imageset/cupLarge.pdf new file mode 100644 index 0000000..5687b5d Binary files /dev/null and b/caffeine/Assets.xcassets/Input/Cups/cupLarge.imageset/cupLarge.pdf differ diff --git a/caffeine/Assets.xcassets/Input/Cups/cupMiddle.imageset/Contents.json b/caffeine/Assets.xcassets/Input/Cups/cupMiddle.imageset/Contents.json new file mode 100644 index 0000000..0fb6df6 --- /dev/null +++ b/caffeine/Assets.xcassets/Input/Cups/cupMiddle.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "cupMiddle.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Input/Cups/cupMiddle.imageset/cupMiddle.pdf b/caffeine/Assets.xcassets/Input/Cups/cupMiddle.imageset/cupMiddle.pdf new file mode 100644 index 0000000..c04a2b7 Binary files /dev/null and b/caffeine/Assets.xcassets/Input/Cups/cupMiddle.imageset/cupMiddle.pdf differ diff --git a/caffeine/Assets.xcassets/Input/Cups/cupSmall.imageset/Contents.json b/caffeine/Assets.xcassets/Input/Cups/cupSmall.imageset/Contents.json new file mode 100644 index 0000000..0ba00b3 --- /dev/null +++ b/caffeine/Assets.xcassets/Input/Cups/cupSmall.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "cupSmall.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Input/Cups/cupSmall.imageset/cupSmall.pdf b/caffeine/Assets.xcassets/Input/Cups/cupSmall.imageset/cupSmall.pdf new file mode 100644 index 0000000..e337629 Binary files /dev/null and b/caffeine/Assets.xcassets/Input/Cups/cupSmall.imageset/cupSmall.pdf differ diff --git a/caffeine/Assets.xcassets/Input/Milk/Contents.json b/caffeine/Assets.xcassets/Input/Milk/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/caffeine/Assets.xcassets/Input/Milk/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Input/Milk/milkBottleCow.imageset/Contents.json b/caffeine/Assets.xcassets/Input/Milk/milkBottleCow.imageset/Contents.json new file mode 100644 index 0000000..6a3d7c9 --- /dev/null +++ b/caffeine/Assets.xcassets/Input/Milk/milkBottleCow.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "milkBottleCow.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Input/Milk/milkBottleCow.imageset/milkBottleCow.pdf b/caffeine/Assets.xcassets/Input/Milk/milkBottleCow.imageset/milkBottleCow.pdf new file mode 100644 index 0000000..9880619 Binary files /dev/null and b/caffeine/Assets.xcassets/Input/Milk/milkBottleCow.imageset/milkBottleCow.pdf differ diff --git a/caffeine/Assets.xcassets/Input/Milk/milkBottleLactoseFree.imageset/Contents.json b/caffeine/Assets.xcassets/Input/Milk/milkBottleLactoseFree.imageset/Contents.json new file mode 100644 index 0000000..d2c0c0e --- /dev/null +++ b/caffeine/Assets.xcassets/Input/Milk/milkBottleLactoseFree.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "milkBottleLactoseFree.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Input/Milk/milkBottleLactoseFree.imageset/milkBottleLactoseFree.pdf b/caffeine/Assets.xcassets/Input/Milk/milkBottleLactoseFree.imageset/milkBottleLactoseFree.pdf new file mode 100644 index 0000000..2f12248 Binary files /dev/null and b/caffeine/Assets.xcassets/Input/Milk/milkBottleLactoseFree.imageset/milkBottleLactoseFree.pdf differ diff --git a/caffeine/Assets.xcassets/Input/Milk/milkBottleSoy.imageset/Contents.json b/caffeine/Assets.xcassets/Input/Milk/milkBottleSoy.imageset/Contents.json new file mode 100644 index 0000000..0e88cbb --- /dev/null +++ b/caffeine/Assets.xcassets/Input/Milk/milkBottleSoy.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "milkBottleSoy.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Input/Milk/milkBottleSoy.imageset/milkBottleSoy.pdf b/caffeine/Assets.xcassets/Input/Milk/milkBottleSoy.imageset/milkBottleSoy.pdf new file mode 100644 index 0000000..183180e Binary files /dev/null and b/caffeine/Assets.xcassets/Input/Milk/milkBottleSoy.imageset/milkBottleSoy.pdf differ diff --git a/caffeine/Assets.xcassets/Input/Shots/1-shot.imageset/Contents.json b/caffeine/Assets.xcassets/Input/Shots/1-shot.imageset/Contents.json new file mode 100644 index 0000000..c556d1e --- /dev/null +++ b/caffeine/Assets.xcassets/Input/Shots/1-shot.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "oneshot.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Input/Shots/1-shot.imageset/oneshot.pdf b/caffeine/Assets.xcassets/Input/Shots/1-shot.imageset/oneshot.pdf new file mode 100644 index 0000000..ea2ccbc Binary files /dev/null and b/caffeine/Assets.xcassets/Input/Shots/1-shot.imageset/oneshot.pdf differ diff --git a/caffeine/Assets.xcassets/Input/Shots/2-shots.imageset/2-shots.pdf b/caffeine/Assets.xcassets/Input/Shots/2-shots.imageset/2-shots.pdf new file mode 100644 index 0000000..d50ba1f Binary files /dev/null and b/caffeine/Assets.xcassets/Input/Shots/2-shots.imageset/2-shots.pdf differ diff --git a/caffeine/Assets.xcassets/Input/Shots/2-shots.imageset/Contents.json b/caffeine/Assets.xcassets/Input/Shots/2-shots.imageset/Contents.json new file mode 100644 index 0000000..e5a55bb --- /dev/null +++ b/caffeine/Assets.xcassets/Input/Shots/2-shots.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "2-shots.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Input/Shots/3-shots.imageset/3-shots.pdf b/caffeine/Assets.xcassets/Input/Shots/3-shots.imageset/3-shots.pdf new file mode 100644 index 0000000..5eaf849 Binary files /dev/null and b/caffeine/Assets.xcassets/Input/Shots/3-shots.imageset/3-shots.pdf differ diff --git a/caffeine/Assets.xcassets/Input/Shots/3-shots.imageset/Contents.json b/caffeine/Assets.xcassets/Input/Shots/3-shots.imageset/Contents.json new file mode 100644 index 0000000..d6a02df --- /dev/null +++ b/caffeine/Assets.xcassets/Input/Shots/3-shots.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "3-shots.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Input/Shots/Contents.json b/caffeine/Assets.xcassets/Input/Shots/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/caffeine/Assets.xcassets/Input/Shots/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Input/Sugar/1-sugar.imageset/1-sugar.pdf b/caffeine/Assets.xcassets/Input/Sugar/1-sugar.imageset/1-sugar.pdf new file mode 100644 index 0000000..72211f3 Binary files /dev/null and b/caffeine/Assets.xcassets/Input/Sugar/1-sugar.imageset/1-sugar.pdf differ diff --git a/caffeine/Assets.xcassets/Input/Sugar/1-sugar.imageset/Contents.json b/caffeine/Assets.xcassets/Input/Sugar/1-sugar.imageset/Contents.json new file mode 100644 index 0000000..7459a56 --- /dev/null +++ b/caffeine/Assets.xcassets/Input/Sugar/1-sugar.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "1-sugar.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Input/Sugar/2-sugars.imageset/2-sugars.pdf b/caffeine/Assets.xcassets/Input/Sugar/2-sugars.imageset/2-sugars.pdf new file mode 100644 index 0000000..ee6f69b Binary files /dev/null and b/caffeine/Assets.xcassets/Input/Sugar/2-sugars.imageset/2-sugars.pdf differ diff --git a/caffeine/Assets.xcassets/Input/Sugar/2-sugars.imageset/Contents.json b/caffeine/Assets.xcassets/Input/Sugar/2-sugars.imageset/Contents.json new file mode 100644 index 0000000..d1b781c --- /dev/null +++ b/caffeine/Assets.xcassets/Input/Sugar/2-sugars.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "2-sugars.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Input/Sugar/3-sugars.imageset/3-sugars.pdf b/caffeine/Assets.xcassets/Input/Sugar/3-sugars.imageset/3-sugars.pdf new file mode 100644 index 0000000..4206ccb Binary files /dev/null and b/caffeine/Assets.xcassets/Input/Sugar/3-sugars.imageset/3-sugars.pdf differ diff --git a/caffeine/Assets.xcassets/Input/Sugar/3-sugars.imageset/Contents.json b/caffeine/Assets.xcassets/Input/Sugar/3-sugars.imageset/Contents.json new file mode 100644 index 0000000..b81260a --- /dev/null +++ b/caffeine/Assets.xcassets/Input/Sugar/3-sugars.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "3-sugars.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Input/Sugar/Contents.json b/caffeine/Assets.xcassets/Input/Sugar/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/caffeine/Assets.xcassets/Input/Sugar/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Quick Launch/Contents.json b/caffeine/Assets.xcassets/Quick Launch/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/caffeine/Assets.xcassets/Quick Launch/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Quick Launch/quickLarge.imageset/Contents.json b/caffeine/Assets.xcassets/Quick Launch/quickLarge.imageset/Contents.json new file mode 100644 index 0000000..9a02e43 --- /dev/null +++ b/caffeine/Assets.xcassets/Quick Launch/quickLarge.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "quickLarge.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Quick Launch/quickLarge.imageset/quickLarge.png b/caffeine/Assets.xcassets/Quick Launch/quickLarge.imageset/quickLarge.png new file mode 100644 index 0000000..f63812a Binary files /dev/null and b/caffeine/Assets.xcassets/Quick Launch/quickLarge.imageset/quickLarge.png differ diff --git a/caffeine/Assets.xcassets/Quick Launch/quickLargePressed.imageset/Contents.json b/caffeine/Assets.xcassets/Quick Launch/quickLargePressed.imageset/Contents.json new file mode 100644 index 0000000..7369fa5 --- /dev/null +++ b/caffeine/Assets.xcassets/Quick Launch/quickLargePressed.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "quickLargePressed.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Quick Launch/quickLargePressed.imageset/quickLargePressed.png b/caffeine/Assets.xcassets/Quick Launch/quickLargePressed.imageset/quickLargePressed.png new file mode 100644 index 0000000..c1f9b80 Binary files /dev/null and b/caffeine/Assets.xcassets/Quick Launch/quickLargePressed.imageset/quickLargePressed.png differ diff --git a/caffeine/Assets.xcassets/Quick Launch/quickMedium.imageset/Contents.json b/caffeine/Assets.xcassets/Quick Launch/quickMedium.imageset/Contents.json new file mode 100644 index 0000000..d1e1215 --- /dev/null +++ b/caffeine/Assets.xcassets/Quick Launch/quickMedium.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "quickMedium.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Quick Launch/quickMedium.imageset/quickMedium.png b/caffeine/Assets.xcassets/Quick Launch/quickMedium.imageset/quickMedium.png new file mode 100644 index 0000000..b68d701 Binary files /dev/null and b/caffeine/Assets.xcassets/Quick Launch/quickMedium.imageset/quickMedium.png differ diff --git a/caffeine/Assets.xcassets/Quick Launch/quickMediumPressed.imageset/Contents.json b/caffeine/Assets.xcassets/Quick Launch/quickMediumPressed.imageset/Contents.json new file mode 100644 index 0000000..00284b5 --- /dev/null +++ b/caffeine/Assets.xcassets/Quick Launch/quickMediumPressed.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "quickMediumPressed.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Quick Launch/quickMediumPressed.imageset/quickMediumPressed.png b/caffeine/Assets.xcassets/Quick Launch/quickMediumPressed.imageset/quickMediumPressed.png new file mode 100644 index 0000000..9c93444 Binary files /dev/null and b/caffeine/Assets.xcassets/Quick Launch/quickMediumPressed.imageset/quickMediumPressed.png differ diff --git a/caffeine/Assets.xcassets/Quick Launch/quickShot.imageset/Contents.json b/caffeine/Assets.xcassets/Quick Launch/quickShot.imageset/Contents.json new file mode 100644 index 0000000..42cfd59 --- /dev/null +++ b/caffeine/Assets.xcassets/Quick Launch/quickShot.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "QuickShot.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Quick Launch/quickShot.imageset/QuickShot.png b/caffeine/Assets.xcassets/Quick Launch/quickShot.imageset/QuickShot.png new file mode 100644 index 0000000..14639a7 Binary files /dev/null and b/caffeine/Assets.xcassets/Quick Launch/quickShot.imageset/QuickShot.png differ diff --git a/caffeine/Assets.xcassets/Quick Launch/quickShotPressed.imageset/Contents.json b/caffeine/Assets.xcassets/Quick Launch/quickShotPressed.imageset/Contents.json new file mode 100644 index 0000000..407fc43 --- /dev/null +++ b/caffeine/Assets.xcassets/Quick Launch/quickShotPressed.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "quickShotPressed.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Quick Launch/quickShotPressed.imageset/quickShotPressed.png b/caffeine/Assets.xcassets/Quick Launch/quickShotPressed.imageset/quickShotPressed.png new file mode 100644 index 0000000..bcecc4f Binary files /dev/null and b/caffeine/Assets.xcassets/Quick Launch/quickShotPressed.imageset/quickShotPressed.png differ diff --git a/caffeine/Assets.xcassets/Quick Launch/quickSmall.imageset/Contents.json b/caffeine/Assets.xcassets/Quick Launch/quickSmall.imageset/Contents.json new file mode 100644 index 0000000..fd86098 --- /dev/null +++ b/caffeine/Assets.xcassets/Quick Launch/quickSmall.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "quickSmall.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Quick Launch/quickSmall.imageset/quickSmall.png b/caffeine/Assets.xcassets/Quick Launch/quickSmall.imageset/quickSmall.png new file mode 100644 index 0000000..be17a80 Binary files /dev/null and b/caffeine/Assets.xcassets/Quick Launch/quickSmall.imageset/quickSmall.png differ diff --git a/caffeine/Assets.xcassets/Quick Launch/quickSmallPressed.imageset/Contents.json b/caffeine/Assets.xcassets/Quick Launch/quickSmallPressed.imageset/Contents.json new file mode 100644 index 0000000..19123bc --- /dev/null +++ b/caffeine/Assets.xcassets/Quick Launch/quickSmallPressed.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "quickSmallPressed.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Quick Launch/quickSmallPressed.imageset/quickSmallPressed.png b/caffeine/Assets.xcassets/Quick Launch/quickSmallPressed.imageset/quickSmallPressed.png new file mode 100644 index 0000000..1fb21d3 Binary files /dev/null and b/caffeine/Assets.xcassets/Quick Launch/quickSmallPressed.imageset/quickSmallPressed.png differ diff --git a/caffeine/Assets.xcassets/TabBar/Contents.json b/caffeine/Assets.xcassets/TabBar/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/caffeine/Assets.xcassets/TabBar/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/TabBar/consumption.imageset/Contents.json b/caffeine/Assets.xcassets/TabBar/consumption.imageset/Contents.json new file mode 100644 index 0000000..8d49ee1 --- /dev/null +++ b/caffeine/Assets.xcassets/TabBar/consumption.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "consumption@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "conumption@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "conumption@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/TabBar/consumption.imageset/consumption@1x.png b/caffeine/Assets.xcassets/TabBar/consumption.imageset/consumption@1x.png new file mode 100644 index 0000000..124ce39 Binary files /dev/null and b/caffeine/Assets.xcassets/TabBar/consumption.imageset/consumption@1x.png differ diff --git a/caffeine/Assets.xcassets/TabBar/consumption.imageset/conumption@2x.png b/caffeine/Assets.xcassets/TabBar/consumption.imageset/conumption@2x.png new file mode 100644 index 0000000..1ad269b Binary files /dev/null and b/caffeine/Assets.xcassets/TabBar/consumption.imageset/conumption@2x.png differ diff --git a/caffeine/Assets.xcassets/TabBar/consumption.imageset/conumption@3x.png b/caffeine/Assets.xcassets/TabBar/consumption.imageset/conumption@3x.png new file mode 100644 index 0000000..5d705d3 Binary files /dev/null and b/caffeine/Assets.xcassets/TabBar/consumption.imageset/conumption@3x.png differ diff --git a/caffeine/Assets.xcassets/TabBar/input.imageset/Contents.json b/caffeine/Assets.xcassets/TabBar/input.imageset/Contents.json new file mode 100644 index 0000000..39b026c --- /dev/null +++ b/caffeine/Assets.xcassets/TabBar/input.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "input@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "input@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "input@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/TabBar/input.imageset/input@1x.png b/caffeine/Assets.xcassets/TabBar/input.imageset/input@1x.png new file mode 100644 index 0000000..96a7bff Binary files /dev/null and b/caffeine/Assets.xcassets/TabBar/input.imageset/input@1x.png differ diff --git a/caffeine/Assets.xcassets/TabBar/input.imageset/input@2x.png b/caffeine/Assets.xcassets/TabBar/input.imageset/input@2x.png new file mode 100644 index 0000000..affca10 Binary files /dev/null and b/caffeine/Assets.xcassets/TabBar/input.imageset/input@2x.png differ diff --git a/caffeine/Assets.xcassets/TabBar/input.imageset/input@3x.png b/caffeine/Assets.xcassets/TabBar/input.imageset/input@3x.png new file mode 100644 index 0000000..c992a9c Binary files /dev/null and b/caffeine/Assets.xcassets/TabBar/input.imageset/input@3x.png differ diff --git a/caffeine/Assets.xcassets/TabBar/settings.imageset/Contents.json b/caffeine/Assets.xcassets/TabBar/settings.imageset/Contents.json new file mode 100644 index 0000000..3ae81ca --- /dev/null +++ b/caffeine/Assets.xcassets/TabBar/settings.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "settings@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "settings@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "settings@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/TabBar/settings.imageset/settings@1x.png b/caffeine/Assets.xcassets/TabBar/settings.imageset/settings@1x.png new file mode 100644 index 0000000..bcb3d6e Binary files /dev/null and b/caffeine/Assets.xcassets/TabBar/settings.imageset/settings@1x.png differ diff --git a/caffeine/Assets.xcassets/TabBar/settings.imageset/settings@2x.png b/caffeine/Assets.xcassets/TabBar/settings.imageset/settings@2x.png new file mode 100644 index 0000000..b52c3b2 Binary files /dev/null and b/caffeine/Assets.xcassets/TabBar/settings.imageset/settings@2x.png differ diff --git a/caffeine/Assets.xcassets/TabBar/settings.imageset/settings@3x.png b/caffeine/Assets.xcassets/TabBar/settings.imageset/settings@3x.png new file mode 100644 index 0000000..4f03bc5 Binary files /dev/null and b/caffeine/Assets.xcassets/TabBar/settings.imageset/settings@3x.png differ diff --git a/caffeine/Assets.xcassets/TabBar/statistics.imageset/Contents.json b/caffeine/Assets.xcassets/TabBar/statistics.imageset/Contents.json new file mode 100644 index 0000000..0df2635 --- /dev/null +++ b/caffeine/Assets.xcassets/TabBar/statistics.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "statistics@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "statistics@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "statistics@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/TabBar/statistics.imageset/statistics@1x.png b/caffeine/Assets.xcassets/TabBar/statistics.imageset/statistics@1x.png new file mode 100644 index 0000000..ee13b1a Binary files /dev/null and b/caffeine/Assets.xcassets/TabBar/statistics.imageset/statistics@1x.png differ diff --git a/caffeine/Assets.xcassets/TabBar/statistics.imageset/statistics@2x.png b/caffeine/Assets.xcassets/TabBar/statistics.imageset/statistics@2x.png new file mode 100644 index 0000000..57f9188 Binary files /dev/null and b/caffeine/Assets.xcassets/TabBar/statistics.imageset/statistics@2x.png differ diff --git a/caffeine/Assets.xcassets/TabBar/statistics.imageset/statistics@3x.png b/caffeine/Assets.xcassets/TabBar/statistics.imageset/statistics@3x.png new file mode 100644 index 0000000..dc2f70a Binary files /dev/null and b/caffeine/Assets.xcassets/TabBar/statistics.imageset/statistics@3x.png differ diff --git a/caffeine/Assets.xcassets/Total/Contents.json b/caffeine/Assets.xcassets/Total/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/caffeine/Assets.xcassets/Total/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Total/totalCupsIcon.imageset/Contents.json b/caffeine/Assets.xcassets/Total/totalCupsIcon.imageset/Contents.json new file mode 100644 index 0000000..a82ace1 --- /dev/null +++ b/caffeine/Assets.xcassets/Total/totalCupsIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "totalCupsIcon.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Total/totalCupsIcon.imageset/totalCupsIcon.pdf b/caffeine/Assets.xcassets/Total/totalCupsIcon.imageset/totalCupsIcon.pdf new file mode 100644 index 0000000..5e88ec3 Binary files /dev/null and b/caffeine/Assets.xcassets/Total/totalCupsIcon.imageset/totalCupsIcon.pdf differ diff --git a/caffeine/Assets.xcassets/Total/totalMilkIcon.imageset/Contents.json b/caffeine/Assets.xcassets/Total/totalMilkIcon.imageset/Contents.json new file mode 100644 index 0000000..111d496 --- /dev/null +++ b/caffeine/Assets.xcassets/Total/totalMilkIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "totalMilkIcon.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Total/totalMilkIcon.imageset/totalMilkIcon.pdf b/caffeine/Assets.xcassets/Total/totalMilkIcon.imageset/totalMilkIcon.pdf new file mode 100644 index 0000000..bb647d3 Binary files /dev/null and b/caffeine/Assets.xcassets/Total/totalMilkIcon.imageset/totalMilkIcon.pdf differ diff --git a/caffeine/Assets.xcassets/Total/totalSugarIcon.imageset/Contents.json b/caffeine/Assets.xcassets/Total/totalSugarIcon.imageset/Contents.json new file mode 100644 index 0000000..5ca2640 --- /dev/null +++ b/caffeine/Assets.xcassets/Total/totalSugarIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "totalSugarIcon.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/Total/totalSugarIcon.imageset/totalSugarIcon.pdf b/caffeine/Assets.xcassets/Total/totalSugarIcon.imageset/totalSugarIcon.pdf new file mode 100644 index 0000000..0effbae Binary files /dev/null and b/caffeine/Assets.xcassets/Total/totalSugarIcon.imageset/totalSugarIcon.pdf differ diff --git a/caffeine/Assets.xcassets/disclosure.imageset/Contents.json b/caffeine/Assets.xcassets/disclosure.imageset/Contents.json new file mode 100644 index 0000000..f692b56 --- /dev/null +++ b/caffeine/Assets.xcassets/disclosure.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "disclosure.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "disclosure@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "disclosure@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/disclosure.imageset/disclosure.png b/caffeine/Assets.xcassets/disclosure.imageset/disclosure.png new file mode 100644 index 0000000..86189e8 Binary files /dev/null and b/caffeine/Assets.xcassets/disclosure.imageset/disclosure.png differ diff --git a/caffeine/Assets.xcassets/disclosure.imageset/disclosure@2x.png b/caffeine/Assets.xcassets/disclosure.imageset/disclosure@2x.png new file mode 100644 index 0000000..a8dab61 Binary files /dev/null and b/caffeine/Assets.xcassets/disclosure.imageset/disclosure@2x.png differ diff --git a/caffeine/Assets.xcassets/disclosure.imageset/disclosure@3x.png b/caffeine/Assets.xcassets/disclosure.imageset/disclosure@3x.png new file mode 100644 index 0000000..c0e8f4b Binary files /dev/null and b/caffeine/Assets.xcassets/disclosure.imageset/disclosure@3x.png differ diff --git a/caffeine/Assets.xcassets/launchIcon.imageset/Contents.json b/caffeine/Assets.xcassets/launchIcon.imageset/Contents.json new file mode 100644 index 0000000..091a003 --- /dev/null +++ b/caffeine/Assets.xcassets/launchIcon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "launch.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "launch@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "launch@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/launchIcon.imageset/launch.png b/caffeine/Assets.xcassets/launchIcon.imageset/launch.png new file mode 100644 index 0000000..54fdf5d Binary files /dev/null and b/caffeine/Assets.xcassets/launchIcon.imageset/launch.png differ diff --git a/caffeine/Assets.xcassets/launchIcon.imageset/launch@2x.png b/caffeine/Assets.xcassets/launchIcon.imageset/launch@2x.png new file mode 100644 index 0000000..16eff69 Binary files /dev/null and b/caffeine/Assets.xcassets/launchIcon.imageset/launch@2x.png differ diff --git a/caffeine/Assets.xcassets/launchIcon.imageset/launch@3x.png b/caffeine/Assets.xcassets/launchIcon.imageset/launch@3x.png new file mode 100644 index 0000000..cafb9c4 Binary files /dev/null and b/caffeine/Assets.xcassets/launchIcon.imageset/launch@3x.png differ diff --git a/caffeine/Assets.xcassets/selectIcon-green.imageset/Contents.json b/caffeine/Assets.xcassets/selectIcon-green.imageset/Contents.json new file mode 100644 index 0000000..dcd1baf --- /dev/null +++ b/caffeine/Assets.xcassets/selectIcon-green.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "selectIcon-green.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/selectIcon-green.imageset/selectIcon-green.pdf b/caffeine/Assets.xcassets/selectIcon-green.imageset/selectIcon-green.pdf new file mode 100644 index 0000000..6111475 Binary files /dev/null and b/caffeine/Assets.xcassets/selectIcon-green.imageset/selectIcon-green.pdf differ diff --git a/caffeine/Assets.xcassets/vektor.imageset/Contents.json b/caffeine/Assets.xcassets/vektor.imageset/Contents.json new file mode 100644 index 0000000..bc7ee19 --- /dev/null +++ b/caffeine/Assets.xcassets/vektor.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "vektor@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "vektor@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "vektor@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/caffeine/Assets.xcassets/vektor.imageset/vektor@1x.png b/caffeine/Assets.xcassets/vektor.imageset/vektor@1x.png new file mode 100644 index 0000000..a09b8d0 Binary files /dev/null and b/caffeine/Assets.xcassets/vektor.imageset/vektor@1x.png differ diff --git a/caffeine/Assets.xcassets/vektor.imageset/vektor@2x.png b/caffeine/Assets.xcassets/vektor.imageset/vektor@2x.png new file mode 100644 index 0000000..71381db Binary files /dev/null and b/caffeine/Assets.xcassets/vektor.imageset/vektor@2x.png differ diff --git a/caffeine/Assets.xcassets/vektor.imageset/vektor@3x.png b/caffeine/Assets.xcassets/vektor.imageset/vektor@3x.png new file mode 100644 index 0000000..dc02677 Binary files /dev/null and b/caffeine/Assets.xcassets/vektor.imageset/vektor@3x.png differ diff --git a/caffeine/Base.lproj/LaunchScreen.storyboard b/caffeine/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..a690a55 --- /dev/null +++ b/caffeine/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/caffeine/Base.lproj/Main.storyboard b/caffeine/Base.lproj/Main.storyboard new file mode 100644 index 0000000..9f96e85 --- /dev/null +++ b/caffeine/Base.lproj/Main.storyboard @@ -0,0 +1,3981 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/caffeine/Data/Consumable.swift b/caffeine/Data/Consumable.swift new file mode 100644 index 0000000..8ffea0b --- /dev/null +++ b/caffeine/Data/Consumable.swift @@ -0,0 +1,137 @@ +import Foundation + +struct Consumable: Codable, Equatable { + let coffee: Coffee + let milk: Milk + let size: Size + let sugar: Sugar + let date: Date + + static let defaultRecentDrinks: [Consumable] = [ + .init( + coffee: .doubleShot, + milk: .fullFat, + size: .small, + sugar: .twoPieces, + date: .init() + ), .init( + coffee: .singleShot, + milk: .soyMilk, + size: .medium, + sugar:. singlePiece, + date: .init() + ), .init( + coffee: .tripleShot, + milk: .lactoseFree, + size: .large, + sugar: .noSugar, + date: .init() + ) + ] +} + +enum Coffee: Int, Codable { + case noShot + case singleShot + case doubleShot + case tripleShot + + var localizedTitle: String { + switch(self) { + case .noShot: + return NSLocalizedString("noshot", comment: "") + case .singleShot: + return NSLocalizedString("singleshot", comment: "") + case .doubleShot: + return NSLocalizedString("doubleshot", comment: "") + case .tripleShot: + return NSLocalizedString("tripleshot", comment: "") + } + } +} + +enum Milk: Int, Codable { + case black + case lactoseFree + case fullFat + case soyMilk + + var localizedTitle: String { + switch(self) { + case .black: + return NSLocalizedString("black", comment: "") + case .lactoseFree: + return NSLocalizedString("lactosefree", comment: "") + case .fullFat: + return NSLocalizedString("fullfat", comment: "") + case .soyMilk: + return NSLocalizedString("soymilk", comment: "") + } + } +} + +enum Size: Int, Codable { + case noSize + case small + case medium + case large + + var localizedTitle: String { + switch(self) { + case .noSize: + return NSLocalizedString("nosize", comment: "") + case .small: + return NSLocalizedString("small", comment: "") + case .medium: + return NSLocalizedString("medium", comment: "") + case .large: + return NSLocalizedString("large", comment: "") + } + } + + var imageName: String { + switch(self) { + case .noSize: + return "quickShot" + case .small: + return "quickSmall" + case .medium: + return "quickMedium" + case .large: + return "quickLarge" + } + } + + var imageNameSelected: String { + switch(self) { + case .noSize: + return "quickShotPressed" + case .small: + return "quickSmallPressed" + case .medium: + return "quickMediumPressed" + case .large: + return "quickLargePressed" + } + } +} + +enum Sugar: Int, Codable { + case noSugar + case singlePiece + case twoPieces + case threePieces + + var localizedTitle: String { + switch(self) { + case .noSugar: + return NSLocalizedString("nosugar", comment: "") + case .singlePiece: + return NSLocalizedString("singlepiece", comment: "") + case .twoPieces: + return NSLocalizedString("twopiece", comment: "") + case .threePieces: + return NSLocalizedString("threepiece", comment: "") + } + } +} diff --git a/caffeine/Data/ConsumableProperties.swift b/caffeine/Data/ConsumableProperties.swift new file mode 100644 index 0000000..4ce811a --- /dev/null +++ b/caffeine/Data/ConsumableProperties.swift @@ -0,0 +1,149 @@ +import Foundation + +/* +* +* A consumption object represents a Drink which can be consumed +* The Nutrition Data is loaded from essential .plist Files +* Call calculateHKValuesForSetup(coffee: Coffee, milk: Milk, size: Size, sugar: Sugar) +* to setup the Object with all Nutritionfacts +*/ +struct ConsumableProperties { + + let title = NSLocalizedString("ConsumptionTitle", comment: "Caffeine Companion") + + private(set) var volume: Double = 0.0 + private(set) var caffeine: Double = 0.0 + private(set) var energyCalories: Double = 0.0 + private(set) var fat: Double = 0.0 + private(set) var saturatedFat: Double = 0.0 + private(set) var unsaturatedFat: Double = 0.0 + private(set) var sodium: Double = 0.0 + private(set) var potassium: Double = 0.0 + private(set) var carbs: Double = 0.0 + private(set) var sugar: Double = 0.0 + private(set) var protein: Double = 0.0 + private(set) var amountOfMilk: Double = 0.0 + + init(_ consumable: Consumable) { + loadDataForCoffee(consumable.coffee) + loadDataForMilk(consumable.milk, size: consumable.size) + loadDataForSugar(consumable.sugar) + } + + // MARK: Loading From plist Files + + /* + * + * Loads nutrition Data for Coffee depending on the + * kind of coffee + * + */ + private mutating func loadDataForCoffee(_ coffee: Coffee) { + let path = Bundle.main.path(forResource: "Coffee", ofType: "plist") + let coffeeArray = NSArray(contentsOfFile: path!) + var data: Dictionary + let modifier: Double = 1.0 + + switch(coffee) { + case .noShot: + data = coffeeArray?.object(at: 0) as! Dictionary + case .singleShot: + data = (coffeeArray?.object(at: 1))! as! Dictionary + case .doubleShot: + data = coffeeArray?.object(at: 2) as! Dictionary + case .tripleShot: + data = coffeeArray?.object(at: 3) as! Dictionary + } + loadNutritionDataWithModifier(data, modifier: modifier) + } + + /* + * + * Loads nutrition Data for Milk depending on the + * kind of milk and the size of the mug into the vars + * + */ + private mutating func loadDataForMilk(_ milk: Milk, size: Size) { + let path = Bundle.main.path(forResource: "Milk", ofType: "plist") + let milkArray = NSArray(contentsOfFile: path!) + var data: Dictionary = Dictionary() + + switch(milk) { + case .black: + data = milkArray?.object(at: 0) as! Dictionary + case .lactoseFree: + data = milkArray?.object(at: 1) as! Dictionary + case .fullFat: + data = milkArray?.object(at: 2) as! Dictionary + case .soyMilk: + data = milkArray?.object(at: 3) as! Dictionary + } + + let sizePath = Bundle.main.path(forResource: "Size", ofType: "plist") + let sizeArray = NSArray(contentsOfFile: sizePath!) + var sizeDict: Dictionary = Dictionary() + var modifier: Double = 1.0 + + switch(size) { + case .noSize: + sizeDict = sizeArray?.object(at: 0) as! Dictionary + case .small: + sizeDict = sizeArray?.object(at: 1) as! Dictionary + case .medium: + sizeDict = sizeArray?.object(at: 2) as! Dictionary + case .large: + sizeDict = sizeArray?.object(at: 3) as! Dictionary + } + modifier = (sizeDict["volume"] as! Double - volume) / 100 + amountOfMilk = data["volume"] as! Double * modifier + loadNutritionDataWithModifier(data, modifier: modifier) + } + + /* + * + * Loads nutrition Data for Sugar depending on the + * selected number into the vars + * + */ + private mutating func loadDataForSugar(_ sugar: Sugar) { + let path = Bundle.main.path(forResource: "Sugar", ofType: "plist") + let sugarArray = NSArray(contentsOfFile: path!) + var data: Dictionary = Dictionary() + let modifier: Double = 1.0 + + switch(sugar) { + case .noSugar: + data = sugarArray?.object(at: 0) as! Dictionary + case .singlePiece: + data = sugarArray?.object(at: 1) as! Dictionary + case .twoPieces: + data = sugarArray?.object(at: 2) as! Dictionary + case .threePieces: + data = sugarArray?.object(at: 3) as! Dictionary + } + loadNutritionDataWithModifier(data, modifier: modifier) + } + + /* + * + * Outsourcing of the Interpretation for all Load-Functions + * modifies the class Variables with Data and Modifier + * + */ + private mutating func loadNutritionDataWithModifier( + _ data: Dictionary, + modifier: Double + ) { + volume = volume + (data["volume"] as! Double) * modifier + caffeine = caffeine + (data["hkcaffeine"] as! Double) * modifier + energyCalories = energyCalories + (data["hkenergyCalories"] as! Double) * modifier + fat = fat + (data["hkfat"] as! Double) * modifier + saturatedFat = saturatedFat + (data["hksaturatedFat"] as! Double) * modifier + unsaturatedFat = unsaturatedFat + (data["hkunsaturatedFat"] as! Double) * modifier + sodium = sodium + (data["hksodium"] as! Double) * modifier + potassium = potassium + (data["hkpotassium"] as! Double) * modifier + carbs = carbs + (data["hkcarbs"] as! Double) * modifier + sugar = sugar + (data["hksugar"] as! Double) * modifier + protein = protein + (data["hkprotein"] as! Double) * modifier + } +} diff --git a/caffeine/Data/PeriodicConsumption.swift b/caffeine/Data/PeriodicConsumption.swift new file mode 100644 index 0000000..6a0e4e3 --- /dev/null +++ b/caffeine/Data/PeriodicConsumption.swift @@ -0,0 +1,110 @@ +import Foundation + +enum Period { + case day + case week + case month + case year + + init(with selectorState: SelectorState) { + switch selectorState { + case .day: + self = .day + case .week: + self = .week + case .month: + self = .month + case .year: + self = .year + } + } + + var iterations: Int { + switch self { + case .day: + return 24 + case .week: + return 7 + case .month: + return 31 + case .year: + return 365 + } + } + + var inSeconds: Double { + let minute = 60.0 + let hour = minute * 60.0 + + switch self { + case .day: + return hour * Double(self.iterations) + case .week: + return Period.day.inSeconds * Double(self.iterations) + case .month: + return Period.day.inSeconds * Double(self.iterations) + case .year: + return Period.day.inSeconds * Double(self.iterations) + } + } +} + +struct PeriodicConsumption { + let period: Period + + private let rawData: [Consumable] + + private var rawDataProperties: [ConsumableProperties] { + return rawData.map { ConsumableProperties($0) } + } + + var periodicData: [[Consumable]] { + var periodicData: [[Consumable]] = .init(repeating: [], count: period.iterations) + + switch period { + case .day: + rawData.forEach { + let hour = Calendar.current.component(.hour, from: $0.date) + periodicData[hour].append($0) + } + case .week: + rawData.forEach { + let weekday = Calendar.current.component(.weekday, from: $0.date) + periodicData[weekday - 1].append($0) + } + case .month: + rawData.forEach { + let day = Calendar.current.ordinality(of: .day, in: .month, for: $0.date) ?? 0 + periodicData[day - 1].append($0) + } + case .year: + rawData.forEach { + let month = Calendar.current.ordinality(of: .day, in: .year, for: $0.date) ?? 0 + periodicData[month - 1].append($0) + } + } + + return periodicData + } + + var shots: Int { + return rawData.reduce(0, { $0 + $1.coffee.rawValue }) + } + + var caffeine: Double { + return rawDataProperties.reduce(0.0, { $0 + Double($1.caffeine) }) + } + + var milk: Double { + return rawDataProperties.reduce(0.0, { $0 + Double($1.amountOfMilk) }) + } + + var sugar: Double { + rawDataProperties.reduce(0.0, { $0 + Double($1.sugar) }) + } + + init(_ period: Period, _ consumables: [Consumable]) { + self.period = period + self.rawData = consumables + } +} diff --git a/caffeine/Data/UserSettings.swift b/caffeine/Data/UserSettings.swift new file mode 100644 index 0000000..916ff0c --- /dev/null +++ b/caffeine/Data/UserSettings.swift @@ -0,0 +1,129 @@ +import Foundation + +let defaults = UserDefaults(suiteName: "caffeine") + +@propertyWrapper +struct UserDefault { + let key: String + + init(_ key: String) { + self.key = key + } + + var wrappedValue: T? { + get { + return defaults?.object(forKey: key) as? T + } + set { + defaults?.set(newValue, forKey: key) + } + } +} + +/// The Settings object can be used to easily access the standard +/// UserDefaults. This way we can ensure that identifier are stored +/// in a centralised way. +struct UserSettings { + + // swiftlint:disable let_var_whitespace + @UserDefault("caffeine.age") + private static var age: Int? + + @UserDefault("caffeine.weight") + private static var weight: Int? + + @UserDefault("caffeine.height") + private static var height: Int? + + @UserDefault("caffeine.sensibility") + private static var sensibility: Int? + + @UserDefault("caffeine.sex") + private static var sex: Int? + + @UserDefault("caffeine.sex") + private static var steroids: Bool? + // swiftlint:enable let_var_whitespace + + static var userAge: Int { + get { + guard let age = UserSettings.age else { + UserSettings.age = 18 + return 18 + } + return age + } + set { + UserSettings.age = newValue + } + } + + static var userWeight: Int { + get { + guard let weight = UserSettings.weight else { + UserSettings.weight = 100 + return 100 + } + return weight + } + set { + UserSettings.weight = newValue + } + } + + static var userHeight: Int { + get { + guard let height = UserSettings.height else { + UserSettings.height = 100 + return 100 + } + return height + } + set { + UserSettings.height = newValue + } + } + + static var userSensibility: Int { + get { + guard let sensibility = UserSettings.sensibility else { + UserSettings.sensibility = 50 + return 50 + } + return sensibility + } + set { + UserSettings.sensibility = newValue + } + } + + static var userSex: Int { + get { + guard let sex = UserSettings.sex else { + UserSettings.sex = 50 + return 50 + } + return sex + } + set { + UserSettings.sex = newValue + } + } + + static var userSteroids: Bool { + get { + guard let steroids = UserSettings.steroids else { + UserSettings.steroids = false + return false + } + return steroids + } + set { + UserSettings.steroids = newValue + } + } + + static var userBmi: Double { + return Double(userWeight) / pow(Double(userHeight), 2) + } +} diff --git a/caffeine/Info.plist b/caffeine/Info.plist new file mode 100644 index 0000000..76c696e --- /dev/null +++ b/caffeine/Info.plist @@ -0,0 +1,39 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + caffe:ne + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHealthShareUsageDescription + Caffe:ne needs to share Data with Healthkit to work properly. + NSHealthUpdateUsageDescription + Caffe:ne needs to access Data from Healthkit to work properly. + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + healthkit + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + + diff --git a/caffeine/Services/ConsumptionStore/Consumption.xcdatamodeld/Consumption.xcdatamodel/contents b/caffeine/Services/ConsumptionStore/Consumption.xcdatamodeld/Consumption.xcdatamodel/contents new file mode 100644 index 0000000..2df7b43 --- /dev/null +++ b/caffeine/Services/ConsumptionStore/Consumption.xcdatamodeld/Consumption.xcdatamodel/contents @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/caffeine/Services/ConsumptionStore/ConsumptionDataProvider.swift b/caffeine/Services/ConsumptionStore/ConsumptionDataProvider.swift new file mode 100644 index 0000000..b41d1ee --- /dev/null +++ b/caffeine/Services/ConsumptionStore/ConsumptionDataProvider.swift @@ -0,0 +1,14 @@ +import Foundation + +/// A class conforming to this protocol provides stored lexicon data. +protocol ConsumptionDataProvider: class { + + func add(consumable: Consumable) + + func getRecentEntries() -> [Consumable] + + func getEntries(for period: Period) -> [Consumable] + + func clear() throws + +} diff --git a/caffeine/Services/ConsumptionStore/ConsumptionStore.swift b/caffeine/Services/ConsumptionStore/ConsumptionStore.swift new file mode 100644 index 0000000..88740a3 --- /dev/null +++ b/caffeine/Services/ConsumptionStore/ConsumptionStore.swift @@ -0,0 +1,187 @@ +import Foundation +import CoreData + +/// A data store for lexicon entries. This can be used to locally persist any type of entries. +/// The `ConsumptionStore` follows the singleton pattern and should be accessed +/// via the .shared property. +final class ConsumptionStore: NSObject { + + /// A static singleton reference. + public static var shared: ConsumptionStore = .init() + + /// The context in which we are manipulating the local database + private var mainContextInstance: NSManagedObjectContext + + /// Private initialiser to enforce the singleton pattern. + private override init() { + mainContextInstance = PersistenceService.shared.getMainContextInstance() + } +} + +// MARK: - Sessions + +extension ConsumptionStore: ConsumptionDataProvider { + + func add(consumable: Consumable) { + let minionManagedObjectContextWorker: NSManagedObjectContext = NSManagedObjectContext( + concurrencyType: .privateQueueConcurrencyType + ) + minionManagedObjectContextWorker.parent = mainContextInstance + minionManagedObjectContextWorker.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy + + minionManagedObjectContextWorker.performAndWait { + let newEntry = Consumption(context: minionManagedObjectContextWorker) + + newEntry.date = consumable.date + newEntry.coffee = .init(consumable.coffee.rawValue) + newEntry.milk = .init(consumable.milk.rawValue) + newEntry.size = .init(consumable.size.rawValue) + newEntry.sugar = .init(consumable.sugar.rawValue) + + PersistenceService.shared.saveWorkerContext(minionManagedObjectContextWorker) + PersistenceService.shared.mergeWithMainContext() + } + } + + func getRecentEntries() -> [Consumable] { + let fetchRequest: NSFetchRequest = Consumption.fetchRequest() + fetchRequest.fetchLimit = 3 + fetchRequest.sortDescriptors = [.init(key: "date", ascending: false)] + + guard let entries = try? mainContextInstance.fetch(fetchRequest) + .sorted(by: { $0.date ?? .init() < $1.date ?? .init() }) + else { + return [] + } + + return entries.compactMap { + guard + let coffee = Coffee(rawValue: Int($0.coffee)), + let milk = Milk(rawValue: Int($0.milk)), + let size = Size(rawValue: Int($0.size)), + let sugar = Sugar(rawValue: Int($0.sugar)), + let date = $0.date + else { + return nil + } + return Consumable( + coffee: coffee, + milk: milk, + size: size, + sugar: sugar, + date: date + ) + }.reversed() + } + + func getEntries(for period: Period) -> [Consumable] { + let fetchRequest: NSFetchRequest = Consumption.fetchRequest() + fetchRequest.sortDescriptors = [.init(key: "date", ascending: true)] + + switch period { + case .day: + let startOfDay = Calendar.current.startOfDay(for: .init()) + let endOfDay = startOfDay.addingTimeInterval(Period.day.inSeconds) + fetchRequest.predicate = NSPredicate( + format: "date >= %@ && date <= %@", + startOfDay as NSDate, + endOfDay as NSDate + ) + + break + case .week: + guard let startOfWeek = Calendar.current.date( + from: Calendar.current.dateComponents( + [.yearForWeekOfYear, .weekOfYear], + from: .init() + ) + ) else { return [] } + let endOfWeek = startOfWeek.addingTimeInterval(Period.week.inSeconds) + fetchRequest.predicate = NSPredicate( + format: "date >= %@ && date <= %@", + startOfWeek as NSDate, + endOfWeek as NSDate + ) + + break + case .month: + guard let startOfMonth = Calendar.current.date( + from: Calendar.current.dateComponents( + [.year, .month], + from: .init() + ) + ) else { return [] } + let endOfMonth = startOfMonth.addingTimeInterval(Period.month.inSeconds) + fetchRequest.predicate = NSPredicate( + format: "date >= %@ && date <= %@", + startOfMonth as NSDate, + endOfMonth as NSDate + ) + + break + case .year: + guard let startOfYear = Calendar.current.date( + from: Calendar.current.dateComponents( + [.year], + from: .init() + ) + ) else { return [] } + let endOfYear = startOfYear.addingTimeInterval(Period.year.inSeconds) + fetchRequest.predicate = NSPredicate( + format: "date >= %@ && date <= %@", + startOfYear as NSDate, + endOfYear as NSDate + ) + + break + } + + guard let entries = try? mainContextInstance.fetch(fetchRequest) else { + return [] + } + + return entries.compactMap { + guard + let coffee = Coffee(rawValue: Int($0.coffee)), + let milk = Milk(rawValue: Int($0.milk)), + let size = Size(rawValue: Int($0.size)), + let sugar = Sugar(rawValue: Int($0.sugar)), + let date = $0.date + else { + return nil + } + return Consumable( + coffee: coffee, + milk: milk, + size: size, + sugar: sugar, + date: date + ) + } + } + + func clear() throws { + var throwingError: Error? + + let minionManagedObjectContextWorker: NSManagedObjectContext = NSManagedObjectContext( + concurrencyType: .privateQueueConcurrencyType + ) + minionManagedObjectContextWorker.parent = mainContextInstance + minionManagedObjectContextWorker.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy + + minionManagedObjectContextWorker.performAndWait { + let batchDeleteion = NSBatchDeleteRequest(fetchRequest: Consumption.fetchRequest()) + do { + try minionManagedObjectContextWorker.execute(batchDeleteion) + } catch { + throwingError = error + } + PersistenceService.shared.saveWorkerContext(minionManagedObjectContextWorker) + PersistenceService.shared.mergeWithMainContext() + } + + if let error = throwingError { + throw error + } + } +} diff --git a/caffeine/Services/ConsumptionStore/ContextProvider.swift b/caffeine/Services/ConsumptionStore/ContextProvider.swift new file mode 100644 index 0000000..19bb8b4 --- /dev/null +++ b/caffeine/Services/ConsumptionStore/ContextProvider.swift @@ -0,0 +1,65 @@ +import Foundation +import CoreData +// import LogService + +/// Provides Managed object context's to interact with the database provided by Core Data. +final class ContextProvider: NSObject { + + let datastoreProvider: DatastoreProvider? + + init(with datastoreProvider: DatastoreProvider) { + self.datastoreProvider = datastoreProvider + super.init() + } + + /// A master context reference, with PrivateQueueConcurrency Type. + lazy var masterManagedObjectContextInstance: NSManagedObjectContext = { + var masterManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) + masterManagedObjectContext.persistentStoreCoordinator = datastoreProvider?.persistentStoreCoordinator + masterManagedObjectContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy + return masterManagedObjectContext + }() + + /// A main context reference, with MainQueueuConcurrency Type. + lazy var mainManagedObjectContextInstance: NSManagedObjectContext = { + var mainManagedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) + mainManagedObjectContext.persistentStoreCoordinator = datastoreProvider?.persistentStoreCoordinator + mainManagedObjectContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy + return mainManagedObjectContext + }() + + // MARK: - Core Data Saving support + + /// Saves changes from the Main Context to the Master Managed Object Context. + func saveContext() { + defer { + do { + try masterManagedObjectContextInstance.save() + } catch let masterMocSaveError as NSError { + // LogService.print("Master Managed Object Context save error: \(masterMocSaveError.localizedDescription)") + } catch { + // LogService.print("Master Managed Object Context save error.") + } + } + + if mainManagedObjectContextInstance.hasChanges { + mergeChangesFromMainContext() + } + } + + /// Merge Changes on the Main Context to the Master Context. + private func mergeChangesFromMainContext() { + DispatchQueue.main.async( + execute: { + do { + try self.mainManagedObjectContextInstance.save() + } catch let mocSaveError as NSError { + // LogService.print("Master Managed Object Context error: \(mocSaveError.localizedDescription)") + } catch { + // LogService.print("Master Managed Object Context error.") + } + } + ) + } + +} diff --git a/caffeine/Services/ConsumptionStore/DatastoreProvider.swift b/caffeine/Services/ConsumptionStore/DatastoreProvider.swift new file mode 100644 index 0000000..18935b3 --- /dev/null +++ b/caffeine/Services/ConsumptionStore/DatastoreProvider.swift @@ -0,0 +1,47 @@ +import Foundation +import CoreData + +/// Holds the data store, which is stored in the document's directory of the application. +final class DatastoreProvider: NSObject { + + private let objectModelName = "Consumption" + private let objectModelExtension = "momd" + private let dbFilename = "Consumption.sqlite" + + // MARK: - Core Data stack + + lazy var applicationDocumentsDirectory: URL = { + let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) + + return urls[urls.count - 1] + }() + + lazy var managedObjectModel: NSManagedObjectModel = { + let bundle = Bundle(for: type(of: self)) + // swiftlint:disable force_unwrapping + let modelURL = bundle.url( + forResource: objectModelName, + withExtension: objectModelExtension + )! + return NSManagedObjectModel(contentsOf: modelURL)! + // swiftlint:enable force_unwrapping + }() + + lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator? = { + let coordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel) + let url = applicationDocumentsDirectory.appendingPathComponent(dbFilename) + + do { + try coordinator.addPersistentStore( + ofType: NSSQLiteStoreType, + configurationName: nil, + at: url, + options: nil + ) + } catch { + abort() + } + + return coordinator + }() +} diff --git a/caffeine/Services/ConsumptionStore/PersistenceService.swift b/caffeine/Services/ConsumptionStore/PersistenceService.swift new file mode 100644 index 0000000..9fb850a --- /dev/null +++ b/caffeine/Services/ConsumptionStore/PersistenceService.swift @@ -0,0 +1,61 @@ +import Foundation +import UIKit +import CoreData +// import LogService + +/// A service class that supports the storage and merge process of multiple managed object contexts. +final class PersistenceService: NSObject { + + private var mainContextInstance: NSManagedObjectContext + + static var shared: PersistenceService = .init() + + private override init() { + mainContextInstance = (UIApplication.shared.delegate as? AppDelegate)! + .contextProvider.mainManagedObjectContextInstance + super.init() + } + + /// Get a reference to the Main Context Instance + /// + /// - Returns: Main NSmanagedObjectContext + func getMainContextInstance() -> NSManagedObjectContext { + return mainContextInstance + } + + /// Save the current work/changes done on the worker contexts (the minion workers). + /// + /// - Parameter workerContext: NSManagedObjectContext The Minion worker Context that has to be saved. + func saveWorkerContext(_ workerContext: NSManagedObjectContext) { + //Persist new Event to datastore (via Managed Object Context Layer). + do { + if workerContext.hasChanges { + try workerContext.save() + } + } catch let saveError as NSError { + // LogService.dump(saveError) + + // TODO: Not cool either. + Swift.assertionFailure(saveError.localizedDescription) + } catch { + // LogService.dump(error) + } + } + + /// Save and merge the current work/changes done on the minion workers with Main context. + func mergeWithMainContext() { + do { + if mainContextInstance.hasChanges { + try mainContextInstance.save() + } + } catch let saveError as NSError { + // LogService.dump(saveError) + + // TODO: clear the core data model and download again. + Swift.fatalError() + } catch { + // LogService.dump(error) + } + } + +} diff --git a/caffeine/Services/DrinksDataService.swift b/caffeine/Services/DrinksDataService.swift new file mode 100644 index 0000000..abdd64d --- /dev/null +++ b/caffeine/Services/DrinksDataService.swift @@ -0,0 +1,34 @@ +import Foundation + +class DrinksService { + static var sharedInstance: DrinksService = .init() + + private var recentDrinksURL: URL? { + guard let documentDirectoryUrl = FileManager.default.urls( + for: .documentDirectory, + in: .userDomainMask + ).first else { return nil } + return documentDirectoryUrl.appendingPathComponent("recent_drinks.json") + } + + func drink(_ consumable: Consumable) { + ConsumptionStore.shared.add(consumable: consumable) + HealthStoreService.shared.storeConsumedObjectIntoHealth( + .init(consumable) + ) + } + + internal func getRecentDrinks() -> [Consumable] { + var recent = ConsumptionStore.shared.getRecentEntries() + + // If there are too few recent drinks we can take them from + // the default recent drinks in the consumable object + for index in 0.. [Consumable] { + var recent = ConsumptionStore.shared.getRecentEntries() + + // If there are too few recent drinks we can take them from + // the default recent drinks in the consumable object + for index in 0.. PeriodicConsumption { + let entries = ConsumptionStore.shared.getEntries(for: period) + return .init(period, entries) + } +} diff --git a/caffeine/Services/HealthKit/CaffeineDataService.swift b/caffeine/Services/HealthKit/CaffeineDataService.swift new file mode 100644 index 0000000..8552e41 --- /dev/null +++ b/caffeine/Services/HealthKit/CaffeineDataService.swift @@ -0,0 +1,187 @@ +import Foundation +import HealthKit + +class CaffeineDataService { + static var shared: CaffeineDataService = .init() + + // Value for Gender Manipulation [3000 .. 3600] + // Women have an up to 1h longer half-life time of caffeine in blood (3000sec. + 600sec) + private let genderModifier: Double = 3000.0 + private let sensibilityGenderModifier: Double = 600.0 + // Value for Contraceptive Steroids Manipulation [5800 .. 7200] + private let steroidsModifier: Double = 5800.0 + private let sensibilitySteroidsModifier: Double = 1400.0 + // SafeLimits + private let safeLimitAdult = 350.0 + private let safeLimitModifierAdult = 100.0 + + private init() { } + + /* + * + * Uses Age and Weight to calculate the Healthy Ammount of Caffeine + * Returns the Caffeine Value in the Completion Block + * Does not include Body Data yet. + * + */ + func healthyAmount() -> Double? { + HealthStoreService.shared.refreshAge() + + let expectedSensibility = Double(UserSettings.userSensibility) / 100.0 + var safeLimit = 0.0 + var calculationAge: Int { + if UserSettings.userAge >= 0 { + return UserSettings.userAge + } else { + return 18 + } + } + + if calculationAge <= 18 { + safeLimit = (pow(M_E, 0.3222 * Double(calculationAge)) + 21.6243) + safeLimit += (1.0 - expectedSensibility) * (pow(M_E, 0.2521 * Double(calculationAge)) + 9.3297) + } else { + safeLimit = safeLimitAdult + safeLimitModifierAdult * (1.0 - expectedSensibility) + } + + return safeLimit + } + + func getCaffeineConsumption( + in period: Period = .day, + completion: @escaping (Bool, Double?) -> Void + ) { + var caffeine: Double = 0.0 + HealthStoreService.shared.loadConsumedData(for: period) { (success, data) in + guard success else { + return completion(false, nil) + } + + let caffeineUnit: HKUnit = .gramUnit(with: .milli) + + for quantitySample in data { + guard + let quantity: HKQuantity = (quantitySample as? HKQuantitySample)?.quantity + else { + continue + } + caffeine += quantity.doubleValue(for: caffeineUnit) + } + completion(true, caffeine) + } + } + + /* + * + * The Half Life of Caffeine in healthy adults is about 5.7 hours + * Source[1]: http://www.ncbi.nlm.nih.gov/pubmed/7361718?dopt=Abstract + * See default Value + * + * "However, at higher doses (e.g. 250–500mg single dose), the clearance of caffeine is + * significantly reduced and its elimination half-life is prolonged, indicating non-linearity" + * Source[2]: http://www.militaryenergygum.com/wp-content/uploads/Multiple-Dose-Pharmacokinetics.pdf + * See Switch-Case + * + * "The t1/2 (beta) was significantly prolonged in women on OCS (10.7 +/- 3.0 hr vs. 6.2 +/- 1.6)" + * Source[3]: http://www.ncbi.nlm.nih.gov/pubmed/7359014 + * See contraceptiveSteroidsTimeModifier + * + * Concentration is unrelated to weight and healthy situation + * + * This is where most of the Magic happens. Sparkle! + * + */ + func getCaffeineConcentration( + _ completion: @escaping (Bool, Double?) -> Void + ) { + // TODO: Migrate to coredata based information + HealthStoreService.shared.loadConsumedData(for: .day) { [weak self] (success, data) in + guard success else { + return completion(false, nil) + } + + guard + let genderModifier = self?.genderModifier, + let sensibilityGenderModifier = self?.sensibilityGenderModifier, + let steroidsModifier = self?.steroidsModifier, + let sensibilitySteroidsModifier = self?.sensibilitySteroidsModifier + else { + return + } + + var caffeine: Double = 0.0 + let caffeineUnit: HKUnit = HKUnit.gramUnit(with: .milli) + + for quantitySample: HKSample in data { + let quantity: HKQuantity = (quantitySample as! HKQuantitySample).quantity + let caffeineInQuantity: Double = quantity.doubleValue(for: caffeineUnit) + var halfLifeSeconds: Double = 0.0 + + // Linear Interpolation of non Linear Values from [2] + // and average divergence for simplification by Sensibility from [2] + halfLifeSeconds = (0.0224 * caffeineInQuantity + 4.4) * 3600 + let isSensible: Bool = UserSettings.userSensibility > 50 ? true : false + var sensibility: Double = 0.0 + var sensibilityByValue: Double = 0.0 + + if (isSensible) { + sensibility = (Double(UserSettings.userSensibility) - 50.0) / 100.0 + sensibilityByValue = (0.0019 * caffeineInQuantity + 2.63) * + (1.0 - sensibility) + } else { + sensibility = Double(UserSettings.userSensibility) / 100.0 + sensibilityByValue = (-0.0019 * caffeineInQuantity - 2.63) * + (1.0 - sensibility) + } + + halfLifeSeconds = halfLifeSeconds + sensibilityByValue + sensibility = Double(UserSettings.userSensibility) / 100.0 + + let expectedGenderValue: Double = Double(UserSettings.userSex) / 100.0 + let genderTimeModifier: Double = genderModifier + + (sensibilityGenderModifier * (1.0 - sensibility)) + let genderModifier: Double = genderTimeModifier * + expectedGenderValue + halfLifeSeconds += genderModifier + + let contraceptiveSteroids: Bool = UserSettings.userSteroids + let contraceptiveSteroidsTimeModifier: Double = steroidsModifier + (sensibilitySteroidsModifier * + (1.0 - sensibility)) + if contraceptiveSteroids { + halfLifeSeconds += contraceptiveSteroidsTimeModifier + } + + let elapsedTime = Date().timeIntervalSince(quantitySample.startDate) + let caffeineConcentrationOfSample = quantity.doubleValue(for: caffeineUnit) * + pow(0.5, (elapsedTime / halfLifeSeconds)) + caffeine = caffeine + caffeineConcentrationOfSample + } + + completion(true, caffeine) + } + } + +} + +extension CaffeineDataService { + + /// Data for a longer period (e.g. week, month, year) is historically passed as an array of arrays. + /// This function is a wrapper for `calculateCaffeineData` which maps the data onto + /// An array of double. + private func calculateCaffeinePeriodData(_ data: [[HKSample]?]) -> [Double] { + return data.map { + guard let data = $0 else { return 0.0 } + return CaffeineDataService.calculateCaffeineData(data) + } + } + + private static func calculateCaffeineData(_ data: [HKSample]) -> Double { + return data + .compactMap({ + ($0 as? HKQuantitySample)? + .quantity + .doubleValue(for: .gramUnit(with: .milli)) + } + ).reduce(0.0, +) + } +} diff --git a/caffeine/Services/HealthKit/HealthStoreService.swift b/caffeine/Services/HealthKit/HealthStoreService.swift new file mode 100644 index 0000000..95d9ac1 --- /dev/null +++ b/caffeine/Services/HealthKit/HealthStoreService.swift @@ -0,0 +1,152 @@ +import Foundation +import HealthKit + +class HealthStoreService { + static var shared: HealthStoreService = .init() + private let healthStore: HKHealthStore = .init() + + /// Readable data types from HealthKit + private var readDataTypes: Set { + if let caffeine = HKObjectType.quantityType(forIdentifier: .dietaryCaffeine), + let dateOfBirth = HKObjectType.characteristicType(forIdentifier: .dateOfBirth) { + + return .init([caffeine, dateOfBirth]) + } else { + return .init() + } + } + + /// Writable data types from HealthKit + private var writeDataTypes: Set { + if let caffeine = HKObjectType.quantityType(forIdentifier: .dietaryCaffeine) { + return .init([caffeine]) + } else { + return .init() + } + } + + private init() { } + + func requestPermission(completion: (() -> Void)? = nil) { + // Request permissions for the HKHealthStore object + HealthStoreService.shared.healthStore.requestAuthorization( + toShare: writeDataTypes, + read: readDataTypes + ) { [weak self] (success, error) in + guard success else { + print("You didn't allow HealthKit to access these read/write data types.") + print("In your app, try to handle this error gracefully when a user decides not to provide access.") + print("The error was: \(error?.localizedDescription ?? "unknown"). If you're using a simulator, try it on a device.") + return + } + + self?.refreshAge() + completion?() + } + } + + func refreshAge() { + do { + guard let birthday = try HealthStoreService.shared.healthStore + .dateOfBirthComponents().date else { + return + } + let ageComponents = Calendar.current.dateComponents( + [.year], + from: birthday, + to: .init() + ) + guard let age = ageComponents.year else { + return + } + + UserSettings.userAge = age + } catch { + print(NSLocalizedString("LoadAgeError", comment: "Something went wrong!")) + } + } + + /* + * + * Get a Consumption Object and save its Data as Quantity Sample + * into the Health Application. + * + */ + func storeConsumedObjectIntoHealth(_ consumption: ConsumableProperties) { + + let now: Date = Date() + let metaData = [HKMetadataKeyFoodType: consumption.title] + + guard let hkcaffeine: HKQuantityType = .quantityType( + forIdentifier: HKQuantityTypeIdentifier.dietaryCaffeine + ) else { + return + } + + if healthStore.authorizationStatus(for: hkcaffeine) == HKAuthorizationStatus.sharingAuthorized + && consumption.caffeine > 0.0 { + + let hkcaffeineQuantity: HKQuantity = .init( + unit: .gramUnit(with: .milli), + doubleValue: consumption.caffeine + ) + let hkcaffeineSample: HKQuantitySample = .init( + type: hkcaffeine, + quantity: hkcaffeineQuantity, + start: now, + end: now, + metadata: metaData + ) + healthStore.save( + hkcaffeineSample, + withCompletion: { _, _ in } + ) + } + } + + internal func loadConsumedData( + for period: Period, + completion: @escaping (Bool, [HKSample]) -> Void + ) { + loadConsumedData( + since: .init(timeIntervalSinceNow: -period.inSeconds), + completion: completion + ) + } + + private func loadConsumedData( + since startDate: Date, + until endDate: Date = .init(), + completion: @escaping (Bool, [HKSample]) -> Void + ) { + guard let caffeineType = HKQuantityType.quantityType(forIdentifier: .dietaryCaffeine) else { + return completion(false, []) + } + + let timeSortDescriptor: NSSortDescriptor = NSSortDescriptor( + key: HKSampleSortIdentifierEndDate, + ascending: false + ) + + let predicate = HKQuery.predicateForSamples( + withStart: startDate, + end: endDate, + options: HKQueryOptions() + ) + + let query = HKSampleQuery( + sampleType: caffeineType, + predicate: predicate, + limit: 0, + sortDescriptors: [timeSortDescriptor], + resultsHandler: { (_, result, error) in + guard error == nil, let result = result else { + return completion(false, []) + } + + completion(true, result) + } + ) + healthStore.execute(query) + } +} diff --git a/caffeine/Styling/CaffeineColors.swift b/caffeine/Styling/CaffeineColors.swift new file mode 100644 index 0000000..e720765 --- /dev/null +++ b/caffeine/Styling/CaffeineColors.swift @@ -0,0 +1,66 @@ +import UIKit + +enum CaffeineColors { + case consumptionButton + case graph + case inputButton + case selectIcon + case statsSeparatorStart + case statsSeparatorEnd + + var color: UIColor { + switch self { + case .consumptionButton: + return ColorScheme.primary.color + case .graph: + return ColorScheme.primary.color + case .inputButton: + return ColorScheme.secondary.color + case .selectIcon: + return ColorScheme.secondary.color + case .statsSeparatorStart: + return UIColor(red: 48.0 / 255.0, green: 48.0 / 255.0, blue: 48.0 / 255.0, alpha: 1.0) + case .statsSeparatorEnd: + return UIColor(red: 30.0 / 255.0, green: 29.0 / 255.0, blue: 34.0 / 255.0, alpha: 1.0) + } + } +} + +private enum ColorScheme { + case primary + case secondary + case tertiary + case quaternary + case quinary + case background + + var color: UIColor { + switch self { + case .primary: + return HexColors.gold.color + case .secondary: + return HexColors.green.color + case .tertiary: + return HexColors.blue.color + case .quaternary: + return HexColors.pink.color + case .quinary: + return HexColors.purple.color + case .background: + return HexColors.darkGray.color + } + } +} + +private enum HexColors: String { + case gold = "FEEBB3" + case green = "36D193" + case blue = "B3FEFB" + case pink = "FEB3F3" + case purple = "B3B8FE" + case darkGray = "35363A" + + var color: UIColor { + return .init(hex: self.rawValue) + } +} diff --git a/caffeine/Supporting Files/Base.lproj/Localizable.strings b/caffeine/Supporting Files/Base.lproj/Localizable.strings new file mode 100644 index 0000000..a197d4d --- /dev/null +++ b/caffeine/Supporting Files/Base.lproj/Localizable.strings @@ -0,0 +1,79 @@ +/* + * + * Copyright (C) Kayos UG (haftungsbeschränkt) - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + * + * NOTICE: All information contained herein is, and remains + * the property of Kayos UG (haftungsbeschränkt) and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Kayos UG (haftungsbeschränkt) + * and its suppliers and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Kayos UG (haftungsbeschränkt). + * + * Written by Simon Christian Krüger , October 2015 + * + */ + + +// App Strings +"ConsumptionTitle" = "A nice hot cup of coffee"; + +// Modell Strings +"SensibilityAlertTitle" = "Sensibility Updated"; +"SensibilityAlertBody" = "Please check your statistics and upate your personal data."; +"LoadAgeError" = "An Error occurd during Age loading. Please check your Health authorizations!"; + +// Shortcut Allert +"ShortcutHeading" = "Coffee Saved!"; +"ShortcutText" = "We updated your Statistics. Take a look."; +"ShortcutOK" = "OK"; + +// Days +"Monday" = "MO"; +"Tuesday" = "TU"; +"Wednesday" = "WE"; +"Thursday" = "TH"; +"Friday" = "FR"; +"Saturday" = "SA"; +"Sunday" = "SU"; + +// Months +"January" = "JAN"; +"February" = "FEB"; +"March" = "MAR"; +"April" = "APR"; +"May" = "MAY"; +"June" = "JUNE"; +"July" = "JULY"; +"August" = "AUG"; +"September" = "SEPT"; +"October" = "OCT"; +"November" = "NOV"; +"December" = "DEC"; + +// Shots +"noshot" = "No Coffee"; +"singleshot" = "1 Shot"; +"doubleshot" = "2 Shots"; +"tripleshot" = "3 Shots"; + +// Milk +"black" = "No Milk"; +"lactosefree" = "Lactose Free"; +"fullfat" = "Whole Milk"; +"soymilk" = "Soy Milk"; + +// Size +"nosize" = "Espresso Cup"; +"small" = "Small Cup"; +"medium" = "Medium Cup"; +"large" = "Large Cup"; + +// Sugar +"nosugar" = "No Sugar"; +"singlepiece" = "1 Sugar"; +"twopiece" = "2 Sugar"; +"threepiece" = "3 Sugar"; diff --git a/caffeine/Supporting Files/CaffeineDataDescription.rtf b/caffeine/Supporting Files/CaffeineDataDescription.rtf new file mode 100644 index 0000000..b1a1dec --- /dev/null +++ b/caffeine/Supporting Files/CaffeineDataDescription.rtf @@ -0,0 +1,21 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf470 +{\fonttbl\f0\fnil\fcharset0 Menlo-Regular;\f1\fnil\fcharset0 Menlo-Bold;} +{\colortbl;\red255\green255\blue255;} +\margl1440\margr1440\vieww9000\viewh8400\viewkind0 +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 + +\f0\fs22 \cf0 "The Half Life of Caffeine in healthy adults is about 5.7 hours"[1].\ +"However, at higher doses (e.g. 250\'96500mg single dose), the clearance of caffeine is significantly reduced and its elimination half-life is prolonged, indicating non-linearity"[2]\ +"The t1/2 (beta) was significantly prolonged in women on OCS (10.7 +/- 3.0 hr vs. 6.2 +/- 1.6)"[3]\ +\ + +\f1\b These three Predicates are the base of our caffeine calculation.\ +\ + +\f0\b0 First of all we have to determine the amount of ingested caffeine. According to Source[2] the t1/2 can be described by a non linear function. At the Moment my solution is indeed deterministic, but not reliable at all. The current calculation returns a value between 20270 seconds and 32000 seconds to halve a ingested dose of caffeine.\ +\ +\ +Sources:\ +[1]: {\field{\*\fldinst{HYPERLINK "http://www.ncbi.nlm.nih.gov/pubmed/7361718?dopt=Abstract"}}{\fldrslt http://www.ncbi.nlm.nih.gov/pubmed/7361718?dopt=Abstract}}\ +[2]: {\field{\*\fldinst{HYPERLINK "http://www.militaryenergygum.com/wp-content/uploads/Multiple-Dose-Pharmacokinetics.pdf"}}{\fldrslt http://www.militaryenergygum.com/wp-content/uploads/Multiple-Dose-Pharmacokinetics.pdf}}\ +[3]: {\field{\*\fldinst{HYPERLINK "http://www.ncbi.nlm.nih.gov/pubmed/7359014"}}{\fldrslt http://www.ncbi.nlm.nih.gov/pubmed/7359014}}} \ No newline at end of file diff --git a/caffeine/Supporting Files/Consumption/Coffee.plist b/caffeine/Supporting Files/Consumption/Coffee.plist new file mode 100644 index 0000000..df4eb1d --- /dev/null +++ b/caffeine/Supporting Files/Consumption/Coffee.plist @@ -0,0 +1,110 @@ + + + + + + title + noShot + volume + 0 + hkcaffeine + 0 + hkenergyCalories + 0 + hkfat + 0 + hksaturatedFat + 0 + hkunsaturatedFat + 0 + hksodium + 0 + hkpotassium + 0 + hkcarbs + 0 + hksugar + 0 + hkprotein + 0 + + + title + singleShot + volume + 33 + hkcaffeine + 70.66 + hkenergyCalories + 3 + hkfat + 0.06 + hksaturatedFat + 0.03 + hkunsaturatedFat + 0.03 + hksodium + 4.67 + hkpotassium + 38.3 + hkcarbs + 0.5600000000000001 + hksugar + 0 + hkprotein + 0.033 + + + title + doubleShot + volume + 66 + hkcaffeine + 141.33 + hkenergyCalories + 6 + hkfat + 0.13 + hksaturatedFat + 0.065 + hkunsaturatedFat + 0.065 + hksodium + 9.33 + hkpotassium + 76.59999999999999 + hkcarbs + 1.13 + hksugar + 0 + hkprotein + 0.067 + + + title + tripleShot + volume + 100 + hkcaffeine + 212 + hkenergyCalories + 9 + hkfat + 0.2 + hksaturatedFat + 0.1 + hkunsaturatedFat + 0.1 + hksodium + 14 + hkpotassium + 115 + hkcarbs + 1.7 + hksugar + 0 + hkprotein + 0.1 + + + diff --git a/caffeine/Supporting Files/Consumption/Milk.plist b/caffeine/Supporting Files/Consumption/Milk.plist new file mode 100644 index 0000000..61d5d7d --- /dev/null +++ b/caffeine/Supporting Files/Consumption/Milk.plist @@ -0,0 +1,110 @@ + + + + + + title + black + volume + 0 + hkcaffeine + 0 + hkenergyCalories + 0 + hkfat + 0 + hksaturatedFat + 0 + hkunsaturatedFat + 0 + hksodium + 0 + hkpotassium + 0 + hkcarbs + 0 + hksugar + 0 + hkprotein + 0 + + + title + lactoseFree + volume + 100 + hkcaffeine + 0 + hkenergyCalories + 47 + hkfat + 1.5 + hksaturatedFat + 0.9 + hkunsaturatedFat + 0 + hksodium + 0.05 + hkpotassium + 0 + hkcarbs + 4.9 + hksugar + 4.9 + hkprotein + 3.4 + + + title + fullFat + volume + 100 + hkcaffeine + 0 + hkenergyCalories + 64 + hkfat + 3.5 + hksaturatedFat + 2.1 + hkunsaturatedFat + 0 + hksodium + 50 + hkpotassium + 150 + hkcarbs + 4.8 + hksugar + 2.8 + hkprotein + 3.3 + + + title + soyMilk + volume + 100 + hkcaffeine + 0 + hkenergyCalories + 54 + hkfat + 1.8 + hksaturatedFat + 0.2 + hkunsaturatedFat + 1 + hksodium + 51 + hkpotassium + 118 + hkcarbs + 6 + hksugar + 4 + hkprotein + 3.3 + + + diff --git a/caffeine/Supporting Files/Consumption/Size.plist b/caffeine/Supporting Files/Consumption/Size.plist new file mode 100644 index 0000000..47f0cfb --- /dev/null +++ b/caffeine/Supporting Files/Consumption/Size.plist @@ -0,0 +1,30 @@ + + + + + + title + noSize + volume + 0 + + + title + small + volume + 150 + + + title + medium + volume + 200 + + + title + large + volume + 250 + + + diff --git a/caffeine/Supporting Files/Consumption/Sugar.plist b/caffeine/Supporting Files/Consumption/Sugar.plist new file mode 100644 index 0000000..77f695b --- /dev/null +++ b/caffeine/Supporting Files/Consumption/Sugar.plist @@ -0,0 +1,110 @@ + + + + + + title + noSugar + volume + 0 + hkcaffeine + 0 + hkenergyCalories + 0 + hkfat + 0 + hksaturatedFat + 0 + hkunsaturatedFat + 0 + hksodium + 0 + hkpotassium + 0 + hkcarbs + 0 + hksugar + 0 + hkprotein + 0 + + + title + singlePiece + volume + 8 + hkcaffeine + 0 + hkenergyCalories + 32.25 + hkfat + 0 + hksaturatedFat + 0 + hkunsaturatedFat + 0 + hksodium + 0.08 + hkpotassium + 0.17 + hkcarbs + 8 + hksugar + 8 + hkprotein + 0 + + + title + twoPieces + volume + 16 + hkcaffeine + 0 + hkenergyCalories + 64.5 + hkfat + 0 + hksaturatedFat + 0 + hkunsaturatedFat + 0 + hksodium + 0.17 + hkpotassium + 0.33 + hkcarbs + 17 + hksugar + 17 + hkprotein + 0 + + + title + threePieces + volume + 24 + hkcaffeine + 0 + hkenergyCalories + 96.75 + hkfat + 0 + hksaturatedFat + 0 + hkunsaturatedFat + 0 + hksodium + 0.25 + hkpotassium + 0.5 + hkcarbs + 25 + hksugar + 25 + hkprotein + 0 + + + diff --git a/caffeine/Supporting Files/MindModellDesctiption.rtf b/caffeine/Supporting Files/MindModellDesctiption.rtf new file mode 100644 index 0000000..d638a0e --- /dev/null +++ b/caffeine/Supporting Files/MindModellDesctiption.rtf @@ -0,0 +1,44 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf130 +{\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fnil\fcharset0 LucidaGrande;} +{\colortbl;\red255\green255\blue255;} +{\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{hyphen\}}{\leveltext\leveltemplateid1\'01\uc0\u8259 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{hyphen\}}{\leveltext\leveltemplateid2\'01\uc0\u8259 ;}{\levelnumbers;}\fi-360\li1440\lin1440 }{\listname ;}\listid1}} +{\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}} +\margl1440\margr1440\vieww9000\viewh8400\viewkind0 +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 + +\f0\b\fs24 \cf0 Use the MindModell Class to setup a basic self minded decision system.\ + +\b0 \ +Relevant Data:\ + +\f1 \uc0\u9642 +\f0 Consumed Caffeine during the last week\ + +\f1 \uc0\u9642 +\f0 Blood / Caffeine Concentration during the last week\ + +\f1 \uc0\u9642 +\f0 How often did the user exceed the personal limit\ + +\f1 \uc0\u9642 +\f0 Current Sensibility Level\ + +\f1 \uc0\u9642 +\f0 Number of Entries using Caffeine\ +\ +1.) If no entries were made during the last 7 days give the user a bright reminder to use Caffeine for their data.\ +\pard\tx940\tx1440\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li1440\fi-1440\pardirnatural\partightenfactor0 +\ls1\ilvl1\cf0 \uc0\u8259 suggest a higher sensibility, if the user does not consume any caffeine\ + - use as a reminder to manage caffeine data in our app \ +2.) The user consumed regularly caffeine products but don't usually exceed the limits\ + \uc0\u8259 don't change any Sensibility\ +3.) The user consumed irregularly caffeine products and exceeded the limits most of the time\ + \uc0\u8259 decrease the Sensibility just a bit\ + \uc0\u8259 use as a reminder to check life-work balance\ +4.) The user exceeded the limits regularly\ + - decrease the Sensibility\ + - try to approach the regularly consumed caffeine level\ +\ +\pard\tx940\tx1440\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li1440\fi-1440\pardirnatural\partightenfactor0 +\ls1\ilvl1 +\b \cf0 Maybe the calculation has to be slightly adjusted to fit into these new requirements.} \ No newline at end of file diff --git a/caffeine/Supporting Files/de.lproj/Localizable.strings b/caffeine/Supporting Files/de.lproj/Localizable.strings new file mode 100644 index 0000000..b910050 --- /dev/null +++ b/caffeine/Supporting Files/de.lproj/Localizable.strings @@ -0,0 +1,79 @@ +/* + * + * Copyright (C) Kayos UG (haftungsbeschränkt) - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + * + * NOTICE: All information contained herein is, and remains + * the property of Kayos UG (haftungsbeschränkt) and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Kayos UG (haftungsbeschränkt) + * and its suppliers and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Kayos UG (haftungsbeschränkt). + * + * Written by Simon Christian Krüger , October 2015 + * + */ + + +// App Strings +"ConsumptionTitle" = "Eine schöne heiße Tasse Kaffee."; + +// Modell Strings +"SensibilityAlertTitle" = "Empfindlichkeit aktualisiert"; +"SensibilityAlertBody" = "Bitte überprüf deine Statistik und passe deine persönlichen Daten an."; +"LoadAgeError" = "Während des Ladeprozesses ist ein Fehler aufgetreten. Bitte überprüf deine Health Berechtigungen!"; + +// Shortcut Allert +"ShortcutHeading" = "Kaffee gespeichert!"; +"ShortcutText" = "Deine Statistik wurde aktualisiert. Schau mal rein."; +"ShortcutOK" = "OK"; + +// Days +"Monday" = "MO"; +"Tuesday" = "DI"; +"Wednesday" = "MI"; +"Thursday" = "DO"; +"Friday" = "FR"; +"Saturday" = "SA"; +"Sunday" = "SO"; + +// Months +"January" = "JAN"; +"February" = "FEB"; +"March" = "MÄR"; +"April" = "APR"; +"May" = "MAI"; +"June" = "JUNI"; +"July" = "JULI"; +"August" = "AUG"; +"September" = "SEPT"; +"October" = "OKT"; +"November" = "NOV"; +"December" = "DEZ"; + +// Shots +"noshot" = "Kein Kaffee"; +"singleshot" = "1 Shot"; +"doubleshot" = "2 Shots"; +"tripleshot" = "3 Shots"; + +// Milk +"black" = "Keine Milch"; +"lactosefree" = "Laktosefrei"; +"fullfat" = "Vollmilch"; +"soymilk" = "Sojamilch"; + +// Size +"nosize" = "Espresso Tasse"; +"small" = "Kleine Tasse"; +"medium" = "Mittlere Tasse"; +"large" = "Große Tasse"; + +// Sugar +"nosugar" = "Kein Zucker"; +"singlepiece" = "1 Zucker"; +"twopiece" = "2 Zucker"; +"threepiece" = "3 Zucker"; diff --git a/caffeine/View/UICaffeineConsumptionRecentButton.swift b/caffeine/View/UICaffeineConsumptionRecentButton.swift new file mode 100644 index 0000000..ff03afb --- /dev/null +++ b/caffeine/View/UICaffeineConsumptionRecentButton.swift @@ -0,0 +1,73 @@ +import Foundation +import UIKit + +class UICaffeineConsumptionRecentButton: UIButton { + @IBOutlet private weak var cupIcon: UIImageView! + @IBOutlet private weak var mainLabel: UILabel! + @IBOutlet private weak var subtitleLabel: UILabel! + + internal var shotState: Coffee = .noShot + internal var milkState: Milk = .black + internal var cupState: Size = .noSize + internal var sugarState: Sugar = .noSugar + + override init (frame: CGRect) { + super.init(frame: frame) + setUpView() + } + + convenience init () { + self.init(frame: CGRect.zero) + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setUpView() + } + + fileprivate func setUpView() { + self.layer.cornerRadius = 8.0 + self.layer.borderWidth = 0.0 + self.layer.borderColor = CaffeineColors.consumptionButton.color.cgColor + } + + func setConsumable(_ consumable: Consumable) { + shotState = consumable.coffee + milkState = consumable.milk + cupState = consumable.size + sugarState = consumable.sugar + + setRecentButtonAppearance() + } + + fileprivate func setRecentButtonAppearance() { + self.mainLabel.text = "\(shotState.localizedTitle), \(cupState.localizedTitle)" + self.subtitleLabel.text = "\(milkState.localizedTitle), \(sugarState.localizedTitle)" + self.cupIcon.image = UIImage(named: cupState.imageName) + } + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) + self.mainLabel.textColor = UIColor.darkGray + self.subtitleLabel.textColor = UIColor.darkGray + self.cupIcon.image = UIImage(named: cupState.imageNameSelected) + self.layer.borderColor = UIColor.darkGray.cgColor + } + + override func touchesEnded(_ touches: Set, with event: UIEvent?) { + super.touchesEnded(touches, with: event) + self.mainLabel.textColor = CaffeineColors.consumptionButton.color + self.subtitleLabel.textColor = UIColor.white + self.cupIcon.image = UIImage(named: cupState.imageName) + self.layer.borderColor = CaffeineColors.consumptionButton.color.cgColor + } + + override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + super.touchesCancelled(touches, with: event) + self.mainLabel.textColor = CaffeineColors.consumptionButton.color + self.subtitleLabel.textColor = UIColor.white + self.cupIcon.image = UIImage(named: cupState.imageName) + self.layer.borderColor = CaffeineColors.consumptionButton.color.cgColor + } + +} diff --git a/caffeine/View/UICaffeineInputButton.swift b/caffeine/View/UICaffeineInputButton.swift new file mode 100644 index 0000000..7a96309 --- /dev/null +++ b/caffeine/View/UICaffeineInputButton.swift @@ -0,0 +1,51 @@ +import Foundation +import UIKit +import Spring + +struct UICaffeineInputButtonViewModel { + let inputIconImage: UIImage + let selectIconImage: UIImage + let descriptionLabelText: String? +} + +class UICaffeineInputButton: UIButton { + @IBOutlet private weak var inputIcon: DesignableImageView? + @IBOutlet private weak var selectIcon: SpringImageView? + @IBOutlet private weak var descriptionLabel: UILabel? + + func tapAnimation(_ animated: Bool) { + guard + let selectIcon = self.selectIcon, + let inputIcon = self.inputIcon + else { + return + } + + selectIcon.isHidden = !selectIcon.isHidden + backgroundColor = selectIcon.isHidden ? UIColor( + red: 0.16, + green: 0.16, + blue: 0.17, + alpha: 0 + ) : UIColor( + red: 0.16, + green: 0.16, + blue: 0.17, + alpha: 1 + ) + + if animated == true { + inputIcon.scaleX = 1.5 + inputIcon.scaleY = 1.5 + inputIcon.curve = "spring" + inputIcon.duration = 1.2 + inputIcon.animate() + + selectIcon.animation = "fadeIn" + selectIcon.curve = "spring" + + selectIcon.duration = 1.3 + selectIcon.animate() + } + } +} diff --git a/caffeine/View/UICaffeineInputButtonView.swift b/caffeine/View/UICaffeineInputButtonView.swift new file mode 100644 index 0000000..87a132f --- /dev/null +++ b/caffeine/View/UICaffeineInputButtonView.swift @@ -0,0 +1,23 @@ +import Foundation +import UIKit + +class UICaffeineInputButtonView: UIView { + + override init (frame: CGRect) { + super.init(frame: frame) + setUpView() + } + + convenience init () { + self.init(frame: CGRect.zero) + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setUpView() + } + + func setUpView () { + + } +} diff --git a/caffeine/View/UICaffeineInputOKButton.swift b/caffeine/View/UICaffeineInputOKButton.swift new file mode 100644 index 0000000..7e4bbab --- /dev/null +++ b/caffeine/View/UICaffeineInputOKButton.swift @@ -0,0 +1,50 @@ +import Foundation +import UIKit + +extension UIColor { + func image(_ size: CGSize = CGSize(width: 1, height: 1)) -> UIImage { + return UIGraphicsImageRenderer(size: size).image { rendererContext in + self.setFill() + rendererContext.fill(CGRect(origin: .zero, size: size)) + } + } +} + +class UICaffeineInputOKButton: UIButton { + + override init (frame: CGRect) { + super.init(frame: frame) + setUpView() + } + + convenience init () { + self.init(frame: CGRect.zero) + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setUpView() + } + + fileprivate func setUpView() { + layer.cornerRadius = 10.0 + layer.borderWidth = 1.0 + + setTitleColor(UIColor.lightGray, for: .disabled) + setTitleColor(CaffeineColors.inputButton.color, for: .highlighted) + setBackgroundImage(UIColor.clear.image(), for: .disabled) + setBackgroundImage(UIColor.clear.image(), for: .highlighted) + + makeButtonNotSelectable() + } + + internal func makeButtonSelectable() { + self.layer.borderColor = CaffeineColors.inputButton.color.cgColor + self.isEnabled = true + } + + internal func makeButtonNotSelectable() { + self.layer.borderColor = UIColor.lightGray.cgColor + self.isEnabled = false + } +} diff --git a/caffeine/View/UICaffeineSlider.swift b/caffeine/View/UICaffeineSlider.swift new file mode 100644 index 0000000..cfaf505 --- /dev/null +++ b/caffeine/View/UICaffeineSlider.swift @@ -0,0 +1,30 @@ +import Foundation +import UIKit + +class UICaffeineSlider: UISlider { + @IBOutlet private(set) weak var sliderPopupView: UISliderPopupView? + + internal var sliderType: SliderType? { + didSet { + minimumValue = sliderType?.sliderMin ?? 0.0 + maximumValue = sliderType?.sliderMax ?? 100.0 + } + } + + internal func updateSliderPopup() { + let formatter = NumberFormatter() + formatter.numberStyle = NumberFormatter.Style.decimal + formatter.maximumFractionDigits = 0 + guard let text = formatter.string(from: .init(value: value)) else { return } + + if (sliderType == SliderType.weight) { + sliderPopupView?.setLabel(text: "\(text) kg") + + } else if (sliderType == SliderType.height) { + sliderPopupView?.setLabel(text: "\(text) cm") + + } else if (sliderType == SliderType.sensibility) { + sliderPopupView?.setLabel(text: "\(text) %") + } + } +} diff --git a/caffeine/View/UICaffeineStatisticsChartView.swift b/caffeine/View/UICaffeineStatisticsChartView.swift new file mode 100644 index 0000000..e1d82f1 --- /dev/null +++ b/caffeine/View/UICaffeineStatisticsChartView.swift @@ -0,0 +1,243 @@ +import Foundation +import UIKit + +/* +* +* This View will display the Caffeine Blod Concentration over a given period of time. +* Above that it could be possible to change the time eg. 12h, 24h, 48h. +* Maybe more statistical relevant content like how much milk, coffee, sugar was consumed +* during the last 30 days. +* +*/ + +class UICaffeineStatisticsChartView: UIView { + private var items: [Double] = [] + private var catmullRom: Bool = true + private var objectCounter: Int { + return items.count + } + private var intersectDistance: Int = 4 + + override init (frame: CGRect) { + super.init(frame: frame) + } + + convenience init () { + self.init(frame: CGRect.zero) + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + override func draw(_ rect: CGRect) { + if !items.isEmpty { + // margin-left && margin-right + let margin: CGFloat = 20.0 + let topBorder: CGFloat = 10 + let bottomBorder: CGFloat = 40 + let graphBorder: CGFloat = 30 + let graphHeight = rect.height - topBorder - bottomBorder - graphBorder + + let spacer = (rect.width - margin * 2) / CGFloat((items.count - 1)) + let maxValue: Int = Int(items.max() ?? 0.0) + + let columnXPoint = { (column: Int) -> CGFloat in + var x: CGFloat = CGFloat(column) * spacer + x += margin + return x + } + let columnYPoint = { (graphPoint: Int) -> CGFloat in + var y: CGFloat = CGFloat(graphPoint) / CGFloat(maxValue) * graphHeight + y = graphHeight + topBorder + graphBorder - y // Flip the graph + return y + } + + for index: Int in 0.. CGPoint { + return CGPoint(x: self.x + x, y: self.y + y) + } + + func translateX(_ x: CGFloat) -> CGPoint { + return CGPoint(x: self.x + x, y: self.y) + } + + func translateY(_ y: CGFloat) -> CGPoint { + return CGPoint(x: self.x, y: self.y + y) + } + + func invertY() -> CGPoint { + return CGPoint(x: self.x, y: -self.y) + } + + func xAxis() -> CGPoint { + return CGPoint(x: 0, y: self.y) + } + + func yAxis() -> CGPoint { + return CGPoint(x: self.x, y: 0) + } + + func addTo(_ a: CGPoint) -> CGPoint { + return CGPoint(x: self.x + a.x, y: self.y + a.y) + } + + func deltaTo(_ a: CGPoint) -> CGPoint { + return CGPoint(x: self.x - a.x, y: self.y - a.y) + } + + func multiplyBy(_ value: CGFloat) -> CGPoint { + return CGPoint(x: self.x * value, y: self.y * value) + } + + func length() -> CGFloat { + return CGFloat( + sqrt( + CDouble( + self.x * self.x + self.y * self.y + ) + ) + ) + } + + func normalize() -> CGPoint { + let l = self.length() + return CGPoint(x: self.x / l, y: self.y / l) + } + + static func fromString(_ string: String) -> CGPoint { + var s = string.replacingOccurrences(of: "{", with: "") + s = s.replacingOccurrences(of: "}", with: "") + s = s.replacingOccurrences(of: " ", with: "") + + let x = NSString(string: s.components(separatedBy: ",").first! as String).doubleValue + let y = NSString(string: s.components(separatedBy: ",").last! as String).doubleValue + + return CGPoint(x: CGFloat(x), y: CGFloat(y)) + } +} diff --git a/caffeine/View/UICaffeineStatisticsTimeSelectorView.swift b/caffeine/View/UICaffeineStatisticsTimeSelectorView.swift new file mode 100644 index 0000000..27729a2 --- /dev/null +++ b/caffeine/View/UICaffeineStatisticsTimeSelectorView.swift @@ -0,0 +1,59 @@ +import Foundation +import UIKit + +enum SelectorState: Int { + case day + case week + case month + case year +} + +class UICaffeineStatisticsTimeSelectorView: UIView { + @IBOutlet private weak var dayButton: UIButton? + @IBOutlet private weak var weekButton: UIButton? + @IBOutlet private weak var monthButton: UIButton? + @IBOutlet private weak var yearButton: UIButton? + @IBOutlet private weak var dayIndicatorView: UIView? + @IBOutlet private weak var weekIndicatorView: UIView? + @IBOutlet private weak var monthIndicatorView: UIView? + @IBOutlet private weak var yearIndicatorView: UIView? + private var selectorState: SelectorState = .day + + override init (frame: CGRect) { + super.init(frame: frame) + } + + convenience init () { + self.init(frame: CGRect.zero) + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + fileprivate func updateIndicatorState() { + for indicator in [ + dayIndicatorView, + weekIndicatorView, + monthIndicatorView, + yearIndicatorView + ] { + indicator?.isHidden = true + } + switch selectorState { + case .day: + dayIndicatorView?.isHidden = false + case .week: + weekIndicatorView?.isHidden = false + case .month: + monthIndicatorView?.isHidden = false + case .year: + yearIndicatorView?.isHidden = false + } + } + + func buttonTapped(with selectorState: SelectorState) { + self.selectorState = selectorState + updateIndicatorState() + } +} diff --git a/caffeine/View/UISliderPopupView.swift b/caffeine/View/UISliderPopupView.swift new file mode 100644 index 0000000..fbb8daf --- /dev/null +++ b/caffeine/View/UISliderPopupView.swift @@ -0,0 +1,10 @@ +import Foundation +import UIKit + +class UISliderPopupView: UIView { + @IBOutlet private weak var popUpLabel: UILabel? + + func setLabel(text: String?) { + popUpLabel?.text = text + } +} diff --git a/caffeine/ViewController/ConsumptionViewController.swift b/caffeine/ViewController/ConsumptionViewController.swift new file mode 100644 index 0000000..46fb6a1 --- /dev/null +++ b/caffeine/ViewController/ConsumptionViewController.swift @@ -0,0 +1,83 @@ +import Foundation +import UIKit + +class ConsumptionViewController: UIViewController { + + @IBOutlet private weak var todayCaffeineLabel: UILabel? + @IBOutlet private weak var maximumCaffeineLabel: UILabel? + + @IBOutlet private weak var firstButton: UICaffeineConsumptionRecentButton? + @IBOutlet private weak var secondButton: UICaffeineConsumptionRecentButton? + @IBOutlet private weak var thirdButton: UICaffeineConsumptionRecentButton? + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + reloadData() + + HealthStoreService.shared.requestPermission { [weak self] in + self?.reloadData() + } + } + + private func reloadData() { + DispatchQueue.main.async( + execute: { [weak self] () -> Void in + let healthyAmount = Int(round(CaffeineDataService.shared.healthyAmount() ?? 0.0)) + self?.maximumCaffeineLabel?.text = "\(healthyAmount)" + + let recentConsumables = DrinksService.shared.getRecentDrinks() + self?.firstButton?.setConsumable(recentConsumables[0]) + self?.secondButton?.setConsumable(recentConsumables[1]) + self?.thirdButton?.setConsumable(recentConsumables[2]) + } + ) + + CaffeineDataService.shared.getCaffeineConsumption { [weak self] (success, caffeineReturn) -> Void in + guard success, let caffeineReturn = caffeineReturn else { return } + DispatchQueue.main.async( + execute: { () -> Void in + self?.todayCaffeineLabel?.text = "\(Int(round(caffeineReturn)))" + } + ) + } + } + + @IBAction private func firstButtonTouchUpInside(_ sender: Any?) { + recentButtonTouch(0) + } + + @IBAction private func secondButtonTouchUpInside(_ sender: Any?) { + recentButtonTouch(1) + } + + @IBAction private func thirdButtonTouchUpInside(_ sender: Any?) { + recentButtonTouch(2) + } + + private func recentButtonTouch(_ recent: Int) { + let recentConsumables = DrinksService.shared.getRecentDrinks() + let recentConsumable = recentConsumables[recent] + DrinksService.shared.drink(recentConsumable) + + let alertController = UIAlertController( + title: "\(NSLocalizedString("ShortcutHeading", comment: ""))", + message: NSLocalizedString("ShortcutText", comment: ""), + preferredStyle: .alert + ) + + let okAction = UIAlertAction( + title: NSLocalizedString("ShortcutOK", comment: ""), + style: .default, + handler: nil + ) + alertController.addAction(okAction) + + present( + alertController, + animated: true, + completion: reloadData + ) + } + +} diff --git a/caffeine/ViewController/InputViewController.swift b/caffeine/ViewController/InputViewController.swift new file mode 100644 index 0000000..64475c7 --- /dev/null +++ b/caffeine/ViewController/InputViewController.swift @@ -0,0 +1,200 @@ +import Foundation +import UIKit +import Spring + +/* +* +* Use this ViewController to call the "calculateHKValuesForSetup(coffee:Coffee, milk:Milk, size:Size, sugar:Sugar)" +* function on a ConsumptionObject. The user can select the designated drinks and a button is activated. +* +* TODO: Safe consumed Drinks of last 30 Days for statistical Purpose. +* +*/ + +class InputViewController: UIViewController { + private var shotState: Coffee = .noShot + private var milkState: Milk = .black + private var cupState: Size = .noSize + private var sugarState: Sugar = .noSugar + + private var shotSender: UICaffeineInputButton? + private var milkSender: UICaffeineInputButton? + private var cupSender: UICaffeineInputButton? + private var sugarSender: UICaffeineInputButton? + + @IBOutlet var inputOKButton: UICaffeineInputOKButton? + + // MARK: Input Shot, Milk, Cupsize and Sugar IBActions + @IBAction private func oneShotIconClicked(_ sender: UICaffeineInputButton) { + changeCoffeeState(sender, state: .singleShot) + } + + @IBAction private func twoShotIconClicked(_ sender: UICaffeineInputButton) { + changeCoffeeState(sender, state: .doubleShot) + } + + @IBAction private func threeShotIconClicked(_ sender: UICaffeineInputButton) { + changeCoffeeState(sender, state: .tripleShot) + } + + @IBAction private func lactosefreeMilkIconClicked(_ sender: UICaffeineInputButton) { + changeMilkState(sender, state: .lactoseFree) + } + + @IBAction private func wholeMilkIconClicked(_ sender: UICaffeineInputButton) { + changeMilkState(sender, state: .fullFat) + } + + @IBAction private func soyMIlkIconClicked(_ sender: UICaffeineInputButton) { + changeMilkState(sender, state: .soyMilk) + } + + @IBAction private func smallIconClicked(_ sender: UICaffeineInputButton) { + changeCupState(sender, state: .small) + } + + @IBAction private func middleIconClicked(_ sender: UICaffeineInputButton) { + changeCupState(sender, state: .medium) + } + + @IBAction private func largeIconClicked(_ sender: UICaffeineInputButton) { + changeCupState(sender, state: .large) + } + + @IBAction private func oneSugarIconClicked(_ sender: UICaffeineInputButton) { + changeSugarState(sender, state: .singlePiece) + } + + @IBAction private func twoSugarIconClicked(_ sender: UICaffeineInputButton) { + changeSugarState(sender, state: .twoPieces) + } + + @IBAction private func threeSugarIconClicked(_ sender: UICaffeineInputButton) { + changeSugarState(sender, state: .threePieces) + } + + @IBAction private func inputOKButtonClicked(_ sender: UICaffeineInputOKButton) { + // TODO: TapticEngine Vibration after Swift/iOS Update + + DrinksService.shared.drink( + .init( + coffee: shotState, + milk: milkState, + size: cupState, + sugar: sugarState, + date: .init() + ) + ) + + if shotState != .noShot { + buttonTap(shotSender, animated: true) + shotState = .noShot + } + if milkState != .black { + buttonTap(milkSender, animated: true) + milkState = .black + } + if cupState != .noSize { + buttonTap(cupSender, animated: true) + cupState = .noSize + } + if sugarState != .noSugar { + buttonTap(sugarSender, animated: true) + sugarState = .noSugar + } + checkInputOKButtonState() + } + + // MARK: State Machine + private func changeCoffeeState(_ sender: UICaffeineInputButton, state: Coffee) { + if shotState == .noShot { + shotSender = sender + shotState = state + } else { + if shotState == state { + shotState = .noShot + } else { + buttonTap(shotSender) + shotSender = sender + shotState = state + } + } + buttonTap(sender, animated: true) + checkInputOKButtonState() + } + + private func changeMilkState(_ sender: UICaffeineInputButton, state: Milk) { + if milkState == .black { + milkSender = sender + milkState = state + } else { + if milkState == state { + milkState = .black + } else { + buttonTap(milkSender) + milkSender = sender + milkState = state + } + } + buttonTap(sender, animated: true) + checkInputOKButtonState() + } + + private func changeCupState(_ sender: UICaffeineInputButton, state: Size) { + if cupState == .noSize { + cupSender = sender + cupState = state + } else { + if cupState == state { + cupState = .noSize + } else { + buttonTap(cupSender) + cupSender = sender + cupState = state + } + } + buttonTap(sender, animated: true) + checkInputOKButtonState() + } + + private func changeSugarState(_ sender: UICaffeineInputButton, state: Sugar) { + if sugarState == .noSugar { + sugarSender = sender + sugarState = state + } else { + if sugarState == state { + sugarState = .noSugar + } else { + buttonTap(sugarSender) + sugarSender = sender + sugarState = state + } + } + buttonTap(sender, animated: true) + checkInputOKButtonState() + } + + // Button Logic + private func checkInputOKButtonState() { + if shotState != .noShot { + inputOKButton?.makeButtonSelectable() + } else { + if milkState != .black && cupState != .noSize { + inputOKButton?.makeButtonSelectable() + } else { + inputOKButton?.makeButtonNotSelectable() + } + } + } + + // MARK: Button Tap handler + + private func buttonTap(_ sender: UICaffeineInputButton?) { + buttonTap(sender, animated: false) + } + + private func buttonTap(_ sender: UICaffeineInputButton?, animated: Bool) { + guard let sender = sender else { return } + sender.tapAnimation(animated) + } +} diff --git a/caffeine/ViewController/SettingsViewController.swift b/caffeine/ViewController/SettingsViewController.swift new file mode 100644 index 0000000..0c7c40f --- /dev/null +++ b/caffeine/ViewController/SettingsViewController.swift @@ -0,0 +1,162 @@ +import Foundation +import UIKit + +internal enum SliderType { + case weight + case height + case sensibility + case sex + + var sliderMin: Float { + switch self { + case .weight: + return 0 + case .height: + return 0 + case .sensibility: + return 0 + case .sex: + return 0 + } + } + + var sliderMax: Float { + switch self { + case .weight: + return 150 + case .height: + return 225 + case .sensibility: + return 100 + case .sex: + return 100 + } + } +} + +/* +* +* Use this ViewController to modify the Personal data Values: +* - Weight +* - Body height +* - Gender +* - Caffeine Sensibility +* - oral contraceptives consumption +* +* Also use this Controller for pushing, when the Application is launched for +* the first time. All Data is Modified via a DataManager Object. +* +* +*/ +class SettingsViewController: UIViewController { + @IBOutlet private weak var weigthSlider: UICaffeineSlider? + @IBOutlet private weak var heightSlider: UICaffeineSlider? + @IBOutlet private weak var sensibilitySlider: UICaffeineSlider? + @IBOutlet private weak var sexSlider: UICaffeineSlider? + @IBOutlet private weak var oralContraceptivesSwitch: UISwitch? + + private var weightConst: NSLayoutConstraint = .init() + private var heightConst: NSLayoutConstraint = .init() + private var sensibilityConst: NSLayoutConstraint = .init() + + override func viewDidLoad() { + super.viewDidLoad() + setupSlider() + } + + private func setupSlider() { + weigthSlider?.sliderType = .weight + heightSlider?.sliderType = .height + sensibilitySlider?.sliderType = .sensibility + sexSlider?.sliderType = .sex + + weigthSlider?.value = Float(UserSettings.userWeight) + heightSlider?.value = Float(UserSettings.userHeight) + sensibilitySlider?.value = Float(UserSettings.userSensibility) + sexSlider?.value = Float(UserSettings.userSex) + oralContraceptivesSwitch?.setOn(UserSettings.userSteroids, animated: false) + + weightConst = NSLayoutConstraint( + item: (weigthSlider?.sliderPopupView)!, + attribute: NSLayoutConstraint.Attribute.centerX, + relatedBy: NSLayoutConstraint.Relation.equal, + toItem: view, + attribute: NSLayoutConstraint.Attribute.centerX, + multiplier: 1, + constant: 0 + ) + heightConst = NSLayoutConstraint( + item: (heightSlider?.sliderPopupView)!, + attribute: NSLayoutConstraint.Attribute.centerX, + relatedBy: NSLayoutConstraint.Relation.equal, + toItem: view, + attribute: NSLayoutConstraint.Attribute.centerX, + multiplier: 1, + constant: 0 + ) + sensibilityConst = NSLayoutConstraint( + item: (sensibilitySlider?.sliderPopupView)!, + attribute: NSLayoutConstraint.Attribute.centerX, + relatedBy: NSLayoutConstraint.Relation.equal, + toItem: view, + attribute: NSLayoutConstraint.Attribute.centerX, + multiplier: 1, + constant: 0 + ) + + view.addConstraint(weightConst) + view.addConstraint(heightConst) + view.addConstraint(sensibilityConst) + updatePopup() + } + + private func updatePopup() { + weigthSlider?.updateSliderPopup() + heightSlider?.updateSliderPopup() + sensibilitySlider?.updateSliderPopup() + + weightConst.constant = getNewConstantPositionForSender(weigthSlider) + heightConst.constant = getNewConstantPositionForSender(heightSlider) + sensibilityConst.constant = getNewConstantPositionForSender(sensibilitySlider) + } + + // TODO: Change Data via UserManager Class + // MARK: Slider and Button Actions + + @IBAction private func weightSliderValueChanged(_ sender: UICaffeineSlider) { + sender.updateSliderPopup() + weightConst.constant = self.getNewConstantPositionForSender(sender) + UserSettings.userWeight = Int(round(sender.value)) + } + + @IBAction private func heightSliderValueChanged(_ sender: UICaffeineSlider) { + sender.updateSliderPopup() + heightConst.constant = self.getNewConstantPositionForSender(sender) + UserSettings.userHeight = Int(round(sender.value)) + } + + @IBAction private func sensibilitySliderValueChaged(_ sender: UICaffeineSlider) { + sender.updateSliderPopup() + sensibilityConst.constant = self.getNewConstantPositionForSender(sender) + UserSettings.userSensibility = Int(round(sender.value)) + } + + @IBAction private func sexSliderValueChaged(_ sender: UICaffeineSlider) { + sender.updateSliderPopup() + UserSettings.userSex = Int(round(sender.value)) + } + + @IBAction private func oralContraceptivesSwitchValueChanged(_ sender: UISwitch) { + UserSettings.userSteroids = sender.isOn + } + + private func getNewConstantPositionForSender(_ sender: UICaffeineSlider?) -> CGFloat { + guard let sender = sender else { return 0.0 } + let valueMargin = sender.maximumValue - sender.minimumValue + let differenceFromRefferencepoint = Double(sender.value - ((valueMargin / 2) + sender.minimumValue)) + let percentage: Double = differenceFromRefferencepoint / Double(valueMargin / 2) + let sliderThumButtonWidth = 32.0 + let segment: Double = (Double(sender.bounds.width) - sliderThumButtonWidth) / 2 + return CGFloat(segment * percentage) + } +} diff --git a/caffeine/ViewController/StatisticsViewController.swift b/caffeine/ViewController/StatisticsViewController.swift new file mode 100644 index 0000000..472bd43 --- /dev/null +++ b/caffeine/ViewController/StatisticsViewController.swift @@ -0,0 +1,165 @@ +import Foundation +import UIKit +import Spring + +/* +* +* This ViewController will implement the UICaffeineStatisticsChartView (SwiftCharts Library) +* It will display the Caffeine Blod Concentration over a given period of time. +* Above that it could be possible to change the time eg. 12h, 24h, 48h. +* Maybe more statistical relevant content like how much milk, coffee, sugar was consumed +* during the last 30 days. +* +*/ +class StatisticsViewController: UIViewController { + + @IBOutlet private weak var timeSelectorView: UICaffeineStatisticsTimeSelectorView? + @IBOutlet private weak var statisticsChartView: UICaffeineStatisticsChartView? + @IBOutlet private weak var caffeineRateLabel: UILabel? + @IBOutlet private weak var totalCupsLabel: UILabel? + @IBOutlet private weak var totalMilkLabel: UILabel? + @IBOutlet private weak var totalSugarLabel: UILabel? + + @IBOutlet private weak var totalShotsImageView: DesignableImageView? + @IBOutlet private weak var totalMilkImageView: DesignableImageView? + @IBOutlet private weak var totalSugarImageView: DesignableImageView? + + @IBOutlet private weak var leftStatisticsDescriptionLabel: UILabel? + @IBOutlet private weak var centerStatisticsDescriptionLabel: UILabel? + @IBOutlet private weak var rightStatisticsDescriptionLabel: UILabel? + + private var selectorState: SelectorState = .day + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + updateStatisticsChartView() + + CaffeineDataService.shared.getCaffeineConcentration { (success, caffeineReturn) -> Void in + guard success, let caffeineReturn = caffeineReturn else { return } + DispatchQueue.main.async( + execute: { [weak self] () -> Void in + self?.caffeineRateLabel?.text = "\(Int(round(caffeineReturn)))" + } + ) + } + updateTotalLabels() + animate() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + for totalImageView in [totalShotsImageView, totalMilkImageView, totalSugarImageView] { + totalImageView?.isHidden = true + } + } + + @IBAction private func dayButtonTapped(_ sender: AnyObject) { + timeSelectorView?.buttonTapped(with: .day) + selectorState = .day + updateStatisticsChartView() + updateTotalLabels() + } + + @IBAction private func weekButtonTapped(_ sender: AnyObject) { + timeSelectorView?.buttonTapped(with: .week) + selectorState = .week + updateStatisticsChartView() + updateTotalLabels() + } + + @IBAction private func monthButtonTapped(_ sender: AnyObject) { + timeSelectorView?.buttonTapped(with: .month) + selectorState = .month + updateStatisticsChartView() + updateTotalLabels() + } + + @IBAction private func yearButtonTapped(_ sender: AnyObject) { + timeSelectorView?.buttonTapped(with: .year) + selectorState = .year + updateStatisticsChartView() + updateTotalLabels() + } + + private func updateStatisticsChartView() { + let consumption = DrinksService.shared.getConsumption(for: .init(with: selectorState)) + let distance = intersectDistance[selectorState.rawValue] + let catmull = catmullRomSelection[selectorState.rawValue] + + statisticsChartView?.setUpGraphView( + consumption.periodicData.map { + $0.reduce( + 0.0, { $0 + ConsumableProperties($1).caffeine } + ) + }, + intersectDistance: distance, + catmullRom: catmull + ) + + switch (selectorState) { + case .day: + leftStatisticsDescriptionLabel?.text = "0" + centerStatisticsDescriptionLabel?.text = "12" + rightStatisticsDescriptionLabel?.text = "24" + case .week: + guard Calendar.current.shortWeekdaySymbols.count >= 6 else { return } + leftStatisticsDescriptionLabel?.text = Calendar.current.shortWeekdaySymbols[0] + centerStatisticsDescriptionLabel?.text = Calendar.current.shortWeekdaySymbols[3] + rightStatisticsDescriptionLabel?.text = Calendar.current.shortWeekdaySymbols[6] + case .month: + leftStatisticsDescriptionLabel?.text = "1st" + centerStatisticsDescriptionLabel?.text = "15th" + rightStatisticsDescriptionLabel?.text = "30th" + case .year: + guard Calendar.current.shortMonthSymbols.count >= 12 else { return } + leftStatisticsDescriptionLabel?.text = Calendar.current.shortMonthSymbols[0] + centerStatisticsDescriptionLabel?.text = "" + rightStatisticsDescriptionLabel?.text = Calendar.current.shortMonthSymbols[11] + } + } + + private func updateTotalLabels() { + let consumption = DrinksService.shared.getConsumption(for: .init(with: selectorState)) + + var milktext = "" + if consumption.milk < 1000 { + milktext = "\(Int(round(consumption.milk))) ml" + } else if consumption.milk < 100000 { + milktext = "\(String(format: "%.1f", consumption.milk / 1000)) l" + } else { + milktext = "\(Int(round(consumption.milk / 1000))) l" + } + + var sugartext = "" + if consumption.sugar < 1000 { + sugartext = "\(String(format: "%.1f", Double(consumption.sugar))) g" + } else if consumption.sugar < 100000 { + sugartext = "\(String(format: "%.1f", Double(consumption.sugar) / Double(1000))) kg" + } else { + sugartext = "\(Int(round(Double(consumption.sugar) / Double(1000)))) kg" + } + + totalCupsLabel?.text = "\(consumption.shots)" + totalMilkLabel?.text = milktext + totalSugarLabel?.text = sugartext + } + + private func animate() { + var counter: Int = 0 + for totalImageView in [totalShotsImageView, totalMilkImageView, totalSugarImageView] { + totalImageView?.isHidden = false + totalImageView?.animation = "fadeIn" + totalImageView?.delay = 0.1 + 0.3 * CGFloat(counter) + totalImageView?.force = 1.0 + totalImageView?.duration = 0.5 + totalImageView?.damping = 0.9 + totalImageView?.velocity = 0.5 + totalImageView?.scaleX = 0.5 + totalImageView?.scaleY = 0.5 + totalImageView?.curve = "spring" + counter += 1 + totalImageView?.animate() + } + } +} diff --git a/caffeine/caffeine.entitlements b/caffeine/caffeine.entitlements new file mode 100644 index 0000000..2ab14a2 --- /dev/null +++ b/caffeine/caffeine.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.healthkit + + com.apple.developer.healthkit.access + + + diff --git a/caffeine/de.lproj/LaunchScreen.strings b/caffeine/de.lproj/LaunchScreen.strings new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/caffeine/de.lproj/LaunchScreen.strings @@ -0,0 +1 @@ + diff --git a/caffeine/de.lproj/Main.strings b/caffeine/de.lproj/Main.strings new file mode 100644 index 0000000..bb4919a --- /dev/null +++ b/caffeine/de.lproj/Main.strings @@ -0,0 +1,198 @@ + +/* Class = "UILabel"; text = "Female"; ObjectID = "02z-dn-G2q"; */ +"02z-dn-G2q.text" = "Weiblich"; + +/* Class = "UITabBarItem"; title = "Statistics"; ObjectID = "0Hr-Kn-Iax"; */ +"0Hr-Kn-Iax.title" = "Statistik"; + +/* Class = "UILabel"; text = "Lactose Free"; ObjectID = "1Qr-hz-Sc2"; */ +"1Qr-hz-Sc2.text" = "Laktosefrei"; + +/* Class = "UILabel"; text = "0"; ObjectID = "1V0-Rb-Rgv"; */ +"1V0-Rb-Rgv.text" = "0"; + +/* Class = "UILabel"; text = "TOTAL MILK"; ObjectID = "4Uz-Th-NJ3"; */ +"4Uz-Th-NJ3.text" = "MILCH GESAMT"; + +/* Class = "UILabel"; text = "Large"; ObjectID = "4qq-Xu-anz"; */ +"4qq-Xu-anz.text" = "Groß"; + +/* Class = "UILabel"; text = "0 %"; ObjectID = "5KO-yR-QRU"; */ +"5KO-yR-QRU.text" = "0 %"; + +/* Class = "UILabel"; text = "0"; ObjectID = "5g3-Rc-ZtG"; */ +"5g3-Rc-ZtG.text" = "0"; + +/* Class = "UILabel"; text = "Maximum Daily Intake:"; ObjectID = "6j2-AH-mcV"; */ +"6j2-AH-mcV.text" = "Maximaler tägl. Konsum:"; + +/* Class = "UINavigationItem"; title = "Statistics"; ObjectID = "Bm1-18-M96"; */ +"Bm1-18-M96.title" = "Statistik"; + +/* Class = "UILabel"; text = "24"; ObjectID = "BpZ-sz-LPF"; */ +"BpZ-sz-LPF.text" = "24"; + +/* Class = "UILabel"; text = "12"; ObjectID = "CTF-ux-Uua"; */ +"CTF-ux-Uua.text" = "12"; + +/* Class = "UITabBarItem"; title = "Consumption"; ObjectID = "D9d-aW-n7R"; */ +"D9d-aW-n7R.title" = "Konsum"; + +/* Class = "UILabel"; text = "Oral contraceptives:"; ObjectID = "Dx8-1t-RST"; */ +"Dx8-1t-RST.text" = "Antibabypille:"; + +/* Class = "UILabel"; text = "Recent Drinks:"; ObjectID = "FAz-NA-YSi"; */ +"FAz-NA-YSi.text" = "Kürzlich verwendet:"; + +/* Class = "UIButton"; normalTitle = "MONTH"; ObjectID = "FG1-Lg-jBi"; */ +"FG1-Lg-jBi.normalTitle" = "MONAT"; + +/* Class = "UILabel"; text = "50 %"; ObjectID = "Fd0-Sd-XBj"; */ +"Fd0-Sd-XBj.text" = "50 %"; + +/* Class = "UILabel"; text = "175 cm"; ObjectID = "GJc-es-2gE"; */ +"GJc-es-2gE.text" = "175 cm"; + +/* Class = "UILabel"; text = "2 Sugar"; ObjectID = "GcC-ch-tCo"; */ +"GcC-ch-tCo.text" = "2 Zucker"; + +/* Class = "UILabel"; text = "1 Sugar"; ObjectID = "GmS-Zt-HH9"; */ +"GmS-Zt-HH9.text" = "1 Zucker"; + +/* Class = "UITabBarItem"; title = "Input"; ObjectID = "Ius-ki-M3j"; */ +"Ius-ki-M3j.title" = "Eingabe"; + +/* Class = "UILabel"; text = "2 Shots"; ObjectID = "Ivs-47-qai"; */ +"Ivs-47-qai.text" = "2 Shots"; + +/* Class = "UILabel"; text = "3 Shots"; ObjectID = "JD5-Aw-Qvg"; */ +"JD5-Aw-Qvg.text" = "3 Shots"; + +/* Class = "UINavigationItem"; title = "Consumption"; ObjectID = "Ke8-uG-4YK"; */ +"Ke8-uG-4YK.title" = "Konsum"; + +/* Class = "UILabel"; text = "2 Shots"; ObjectID = "LbY-ke-QK2"; */ +"LbY-ke-QK2.text" = "2 Shots"; + +/* Class = "UILabel"; text = "TOTAL SHOTS"; ObjectID = "MCa-sT-gcY"; */ +"MCa-sT-gcY.text" = "SHOTS GESAMT"; + +/* Class = "UILabel"; text = "3 Shots"; ObjectID = "NyK-35-O8h"; */ +"NyK-35-O8h.text" = "3 Shots"; + +/* Class = "UILabel"; text = "50 kg"; ObjectID = "QU3-r5-AKx"; */ +"QU3-r5-AKx.text" = "50 kg"; + +/* Class = "UILabel"; text = "0 g"; ObjectID = "RIb-ij-VTN"; */ +"RIb-ij-VTN.text" = "0 g"; + +/* Class = "UILabel"; text = "3 Sugar"; ObjectID = "Reb-it-qmT"; */ +"Reb-it-qmT.text" = "3 Zucker"; + +/* Class = "UILabel"; text = "225 cm"; ObjectID = "TP7-Tc-2IS"; */ +"TP7-Tc-2IS.text" = "225 cm"; + +/* Class = "UILabel"; text = "Whole Milk"; ObjectID = "Txe-ek-Gor"; */ +"Txe-ek-Gor.text" = "Vollmilch"; + +/* Class = "UILabel"; text = "1 Shots"; ObjectID = "VGD-nL-Z1t"; */ +"VGD-nL-Z1t.text" = "1 Shots"; + +/* Class = "UILabel"; text = "TOTAL SUGAR"; ObjectID = "WLF-qd-JGA"; */ +"WLF-qd-JGA.text" = "ZUCKER GESAMT"; + +/* Class = "UILabel"; text = "Lactose Free, 1 Sugar"; ObjectID = "XUD-PP-L0S"; */ +"XUD-PP-L0S.text" = "Laktosefrei, 1 Zucker"; + +/* Class = "UILabel"; text = "180"; ObjectID = "YpP-Ki-Eaf"; */ +"YpP-Ki-Eaf.text" = "180"; + +/* Class = "UILabel"; text = "Sex:"; ObjectID = "ZHc-Pf-x4v"; */ +"ZHc-Pf-x4v.text" = "Geschlecht:"; + +/* Class = "UILabel"; text = "Height in cm:"; ObjectID = "aLE-Se-yr3"; */ +"aLE-Se-yr3.text" = "Größe in cm:"; + +/* Class = "UIButton"; normalTitle = "SAVE!"; ObjectID = "afE-hj-sQr"; */ +"afE-hj-sQr.normalTitle" = "SPEICHERN!"; + +/* Class = "UINavigationItem"; title = "Settings"; ObjectID = "boU-aK-bph"; */ +"boU-aK-bph.title" = "Einstellungen"; + +/* Class = "UILabel"; text = "Soy Milk, No Sugar"; ObjectID = "c3J-4C-bX0"; */ +"c3J-4C-bX0.text" = "Sojamilch, Kein Zucker"; + +/* Class = "UILabel"; text = "125 cm"; ObjectID = "dCl-4i-5Td"; */ +"dCl-4i-5Td.text" = "125 cm"; + +/* Class = "UILabel"; text = "Small"; ObjectID = "ehv-eN-oMe"; */ +"ehv-eN-oMe.text" = "Klein"; + +/* Class = "UILabel"; text = "Medium"; ObjectID = "fU3-cs-iwf"; */ +"fU3-cs-iwf.text" = "Medium"; + +/* Class = "UIButton"; normalTitle = "WEEK"; ObjectID = "h1N-7C-oBq"; */ +"h1N-7C-oBq.normalTitle" = "WOCHE"; + +/* Class = "UILabel"; text = "1 Shot"; ObjectID = "hN5-1X-Wet"; */ +"hN5-1X-Wet.text" = "1 Shot"; + +/* Class = "UILabel"; text = "CAFFEINE DOSE"; ObjectID = "iMj-M0-avC"; */ +"iMj-M0-avC.text" = "KOFFEIN DOSIS"; + +/* Class = "UILabel"; text = "Current Intake:"; ObjectID = "jCz-nl-aWo"; */ +"jCz-nl-aWo.text" = "Derzeitige Aufnahme:"; + +/* Class = "UIButton"; normalTitle = "DAY"; ObjectID = "jW2-Oe-2dp"; */ +"jW2-Oe-2dp.normalTitle" = "TAG"; + +/* Class = "UILabel"; text = "Soy Milk"; ObjectID = "lte-jz-J9Z"; */ +"lte-jz-J9Z.text" = "Sojamilch"; + +/* Class = "UILabel"; text = "mg"; ObjectID = "n0r-B3-41e"; */ +"n0r-B3-41e.text" = "mg"; + +/* Class = "UILabel"; text = "Sensiblity in %:"; ObjectID = "nUI-28-uki"; */ +"nUI-28-uki.text" = "Empfindlichkeit in %:"; + +/* Class = "UILabel"; text = "150 kg"; ObjectID = "oQS-aY-u2a"; */ +"oQS-aY-u2a.text" = "150 kg"; + +/* Class = "UIButton"; normalTitle = "YEAR"; ObjectID = "otl-fT-7FH"; */ +"otl-fT-7FH.normalTitle" = "JAHR"; + +/* Class = "UILabel"; text = "Male"; ObjectID = "pPR-WE-KCF"; */ +"pPR-WE-KCF.text" = "Männlich"; + +/* Class = "UILabel"; text = "mg"; ObjectID = "pqg-LB-a9a"; */ +"pqg-LB-a9a.text" = "mg"; + +/* Class = "UINavigationItem"; title = "Input"; ObjectID = "qGN-JZ-WQ5"; */ +"qGN-JZ-WQ5.title" = "Eingabe"; + +/* Class = "UILabel"; text = "100 %"; ObjectID = "qqf-UZ-HGN"; */ +"qqf-UZ-HGN.text" = "100 %"; + +/* Class = "UILabel"; text = "mg"; ObjectID = "rUn-St-jDK"; */ +"rUn-St-jDK.text" = "mg"; + +/* Class = "UILabel"; text = "350"; ObjectID = "s5a-zk-qu1"; */ +"s5a-zk-qu1.text" = "350"; + +/* Class = "UILabel"; text = "100 kg"; ObjectID = "tct-4Z-LQM"; */ +"tct-4Z-LQM.text" = "100 kg"; + +/* Class = "UILabel"; text = "Weight in kg:"; ObjectID = "tdT-Zs-JmM"; */ +"tdT-Zs-JmM.text" = "Gewicht in kg:"; + +/* Class = "UILabel"; text = "Whole Milk, 2 Sugar"; ObjectID = "voV-HE-PCG"; */ +"voV-HE-PCG.text" = "Vollmilch, 2 Zucker"; + +/* Class = "UILabel"; text = "0 ml"; ObjectID = "w6f-EU-tV6"; */ +"w6f-EU-tV6.text" = "0 ml"; + +/* Class = "UITabBarItem"; title = "Settings"; ObjectID = "xN8-1N-Dxw"; */ +"xN8-1N-Dxw.title" = "Einstellungen"; + +/* Class = "UILabel"; text = "0"; ObjectID = "ybR-m9-Q76"; */ +"ybR-m9-Q76.text" = "0"; diff --git a/caffeineUITests/Info.plist b/caffeineUITests/Info.plist new file mode 100644 index 0000000..64d65ca --- /dev/null +++ b/caffeineUITests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/caffeineUITests/caffeineUITests.swift b/caffeineUITests/caffeineUITests.swift new file mode 100644 index 0000000..740fbc5 --- /dev/null +++ b/caffeineUITests/caffeineUITests.swift @@ -0,0 +1,90 @@ +// +// caffeineUITests.swift +// caffeineUITests +// +// Created by cr0ss on 18.09.15. +// Copyright © 2015 kayos. All rights reserved. +// + +import XCTest + +class caffeineUITests: XCTestCase { + + private var app: XCUIApplication? + + override func setUp() { + super.setUp() + + continueAfterFailure = true + let app = XCUIApplication() + setupSnapshot(app) + self.app = app + self.app?.launch() + } + + func testScreenshots() { + + if app?.staticTexts.containing(.init(format: "label CONTAINS[c] %@", "Turn All Categories On")).count ?? 0 > 0 { + app?.tables.staticTexts["Turn All Categories On"].tap() + app?.navigationBars["Health Access"].buttons["Allow"].tap() + } else if app?.staticTexts.containing(.init(format: "label CONTAINS[c] %@", "All Categories On")).count ?? 0 > 0 { + app?.tables.staticTexts["All Categories On"].tap() + app?.navigationBars["Health Access"].buttons["Allow"].tap() + } + + snapshot("0-Consumption") + let tabBarsQuery = app?.tabBars + tabBarsQuery?.buttons.allElementsBoundByIndex[1].tap() + + let saveElementsQuery: XCUIElementQuery? + + saveElementsQuery = app?.scrollViews.otherElements.containing( + .button, + identifier: "input_save_button" + ) + + saveElementsQuery?.children(matching: .other) + .element(boundBy: 0) + .children(matching: .other) + .element(boundBy: 1) + .children(matching: .button) + .element + .tap() + saveElementsQuery? + .children(matching: .other) + .element(boundBy: 1) + .children(matching: .other) + .element(boundBy: 1) + .children(matching: .button) + .element + .tap() + saveElementsQuery? + .children(matching: .other) + .element(boundBy: 2) + .children(matching: .other) + .element(boundBy: 0) + .children(matching: .button) + .element + .tap() + saveElementsQuery? + .children(matching: .other) + .element(boundBy: 3) + .children(matching: .other) + .element(boundBy: 0) + .children(matching: .button) + .element + .tap() + + snapshot("1-Input") + app?.scrollViews.otherElements.buttons["input_save_button"].tap() + + tabBarsQuery?.buttons.allElementsBoundByIndex[2].tap() + snapshot("2-Daily") + + app?.scrollViews.otherElements.buttons.allElementsBoundByIndex[1].tap() + snapshot("3-Monthly") + + tabBarsQuery?.buttons.allElementsBoundByIndex[3].tap() + snapshot("4-Settings") + } +} diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 0000000..0615b88 --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,18 @@ +# Customise this file, documentation can be found here: +# https://github.com/fastlane/fastlane/tree/master/fastlane/docs +# All available actions: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Actions.md +# can also be listed using the `fastlane actions` command +# All lines starting with a # are ignored when running `fastlane` +default_platform :ios + +platform :ios do + desc "Creates new screenshots and uploads them to iTunes Connect" + lane :screens do + snapshot + frame + end + + lane :frame do + frameit(path: "./fastlane/screenshots") + end +end diff --git a/fastlane/README.md b/fastlane/README.md new file mode 100644 index 0000000..645286e --- /dev/null +++ b/fastlane/README.md @@ -0,0 +1,34 @@ +fastlane documentation +================ +# Installation + +Make sure you have the latest version of the Xcode command line tools installed: + +``` +xcode-select --install +``` + +Install _fastlane_ using +``` +[sudo] gem install fastlane -NV +``` +or alternatively using `brew cask install fastlane` + +# Available Actions +## iOS +### ios screens +``` +fastlane ios screens +``` +Creates new screenshots and uploads them to iTunes Connect +### ios frame +``` +fastlane ios frame +``` + + +---- + +This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. +More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). +The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). diff --git a/fastlane/Snapfile b/fastlane/Snapfile new file mode 100644 index 0000000..12cc7cc --- /dev/null +++ b/fastlane/Snapfile @@ -0,0 +1,29 @@ +# Uncomment the lines below you want to change by removing the # in the beginning +# A list of devices you want to take the screenshots from +devices([ + "iPhone 8 Plus", + "iPhone 11 Pro Max", +]) + +languages([ + "en-US", + "de-DE", +]) + +# Arguments to pass to the app on launch. See https://github.com/fastlane/snapshot#launch-arguments +# launch_arguments(["-favColor red"]) + +# The name of the scheme which contains the UI Tests +scheme "caffeineUITests" + +# Where should the resulting screenshots be stored? +output_directory "./fastlane/screenshots" + +clear_previous_screenshots true # remove the '#' to clear all previously generated screenshots before creating new ones + +# Choose which project/workspace to use +# project "./Project.xcodeproj" +workspace "./caffeine.xcworkspace" + +# For more information about all available options run +# snapshot --help diff --git a/fastlane/SnapshotHelper.swift b/fastlane/SnapshotHelper.swift new file mode 100644 index 0000000..2f08cf2 --- /dev/null +++ b/fastlane/SnapshotHelper.swift @@ -0,0 +1,279 @@ +// +// SnapshotHelper.swift +// Example +// +// Created by Felix Krause on 10/8/15. +// +// ----------------------------------------------------- +// IMPORTANT: When modifying this file, make sure to +// increment the version number at the very +// bottom of the file to notify users about +// the new SnapshotHelper.swift +// ----------------------------------------------------- +import Foundation +import XCTest + +var deviceLanguage = "" +var locale = "" + +func setupSnapshot(_ app: XCUIApplication, waitForAnimations: Bool = true) { + Snapshot.setupSnapshot(app, waitForAnimations: waitForAnimations) +} + +func snapshot(_ name: String, waitForLoadingIndicator: Bool) { + if waitForLoadingIndicator { + Snapshot.snapshot(name) + } else { + Snapshot.snapshot(name, timeWaitingForIdle: 0) + } +} + +/// - Parameters: +/// - name: The name of the snapshot +/// - timeout: Amount of seconds to wait until the network loading indicator disappears. Pass `0` if you don't want to wait. +func snapshot(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) { + Snapshot.snapshot(name, timeWaitingForIdle: timeout) +} + +enum SnapshotError: Error, CustomDebugStringConvertible { + case cannotFindSimulatorHomeDirectory + case cannotRunOnPhysicalDevice + + var debugDescription: String { + switch self { + case .cannotFindSimulatorHomeDirectory: + return "Couldn't find simulator home location. Please, check SIMULATOR_HOST_HOME env variable." + case .cannotRunOnPhysicalDevice: + return "Can't use Snapshot on a physical device." + } + } +} + +@objcMembers +open class Snapshot: NSObject { + static var app: XCUIApplication? + static var waitForAnimations = true + static var cacheDirectory: URL? + static var screenshotsDirectory: URL? { + return cacheDirectory?.appendingPathComponent("screenshots", isDirectory: true) + } + + open class func setupSnapshot(_ app: XCUIApplication, waitForAnimations: Bool = true) { + + Snapshot.app = app + Snapshot.waitForAnimations = waitForAnimations + + do { + let cacheDir = try getCacheDirectory() + Snapshot.cacheDirectory = cacheDir + setLanguage(app) + setLocale(app) + setLaunchArguments(app) + } catch let error { + NSLog(error.localizedDescription) + } + } + + class func setLanguage(_ app: XCUIApplication) { + guard let cacheDirectory = self.cacheDirectory else { + NSLog("CacheDirectory is not set - probably running on a physical device?") + return + } + + let path = cacheDirectory.appendingPathComponent("language.txt") + + do { + let trimCharacterSet = CharacterSet.whitespacesAndNewlines + deviceLanguage = try String(contentsOf: path, encoding: .utf8).trimmingCharacters(in: trimCharacterSet) + app.launchArguments += ["-AppleLanguages", "(\(deviceLanguage))"] + } catch { + NSLog("Couldn't detect/set language...") + } + } + + class func setLocale(_ app: XCUIApplication) { + guard let cacheDirectory = self.cacheDirectory else { + NSLog("CacheDirectory is not set - probably running on a physical device?") + return + } + + let path = cacheDirectory.appendingPathComponent("locale.txt") + + do { + let trimCharacterSet = CharacterSet.whitespacesAndNewlines + locale = try String(contentsOf: path, encoding: .utf8).trimmingCharacters(in: trimCharacterSet) + } catch { + NSLog("Couldn't detect/set locale...") + } + + if locale.isEmpty && !deviceLanguage.isEmpty { + locale = Locale(identifier: deviceLanguage).identifier + } + + if !locale.isEmpty { + app.launchArguments += ["-AppleLocale", "\"\(locale)\""] + } + } + + class func setLaunchArguments(_ app: XCUIApplication) { + guard let cacheDirectory = self.cacheDirectory else { + NSLog("CacheDirectory is not set - probably running on a physical device?") + return + } + + let path = cacheDirectory.appendingPathComponent("snapshot-launch_arguments.txt") + app.launchArguments += ["-FASTLANE_SNAPSHOT", "YES", "-ui_testing"] + + do { + let launchArguments = try String(contentsOf: path, encoding: String.Encoding.utf8) + let regex = try NSRegularExpression(pattern: "(\\\".+?\\\"|\\S+)", options: []) + let matches = regex.matches(in: launchArguments, options: [], range: NSRange(location: 0, length: launchArguments.count)) + let results = matches.map { result -> String in + (launchArguments as NSString).substring(with: result.range) + } + app.launchArguments += results + } catch { + NSLog("Couldn't detect/set launch_arguments...") + } + } + + open class func snapshot(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) { + if timeout > 0 { + waitForLoadingIndicatorToDisappear(within: timeout) + } + + NSLog("snapshot: \(name)") // more information about this, check out https://docs.fastlane.tools/actions/snapshot/#how-does-it-work + if Snapshot.waitForAnimations { + sleep(1) // Waiting for the animation to be finished (kind of) + } + + #if os(OSX) + guard let app = self.app else { + NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().") + return + } + + app.typeKey(XCUIKeyboardKeySecondaryFn, modifierFlags: []) + #else + + guard self.app != nil else { + NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().") + return + } + + let screenshot = XCUIScreen.main.screenshot() + guard var simulator = ProcessInfo().environment["SIMULATOR_DEVICE_NAME"], let screenshotsDir = screenshotsDirectory else { return } + + do { + // The simulator name contains "Clone X of " inside the screenshot file when running parallelized UI Tests on concurrent devices + let regex = try NSRegularExpression(pattern: "Clone [0-9]+ of ") + let range = NSRange(location: 0, length: simulator.count) + simulator = regex.stringByReplacingMatches(in: simulator, range: range, withTemplate: "") + + let path = screenshotsDir.appendingPathComponent("\(simulator)-\(name).png") + try screenshot.pngRepresentation.write(to: path) + } catch let error { + NSLog("Problem writing screenshot: \(name) to \(screenshotsDir)/\(simulator)-\(name).png") + NSLog(error.localizedDescription) + } + #endif + } + + class func waitForLoadingIndicatorToDisappear(within timeout: TimeInterval) { + #if os(tvOS) + return + #endif + + guard let app = self.app else { + NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().") + return + } + + let networkLoadingIndicator = app.otherElements.deviceStatusBars.networkLoadingIndicators.element + let networkLoadingIndicatorDisappeared = XCTNSPredicateExpectation(predicate: NSPredicate(format: "exists == false"), object: networkLoadingIndicator) + _ = XCTWaiter.wait(for: [networkLoadingIndicatorDisappeared], timeout: timeout) + } + + class func getCacheDirectory() throws -> URL { + let cachePath = "Library/Caches/tools.fastlane" + // on OSX config is stored in /Users//Library + // and on iOS/tvOS/WatchOS it's in simulator's home dir + #if os(OSX) + let homeDir = URL(fileURLWithPath: NSHomeDirectory()) + return homeDir.appendingPathComponent(cachePath) + #elseif arch(i386) || arch(x86_64) + guard let simulatorHostHome = ProcessInfo().environment["SIMULATOR_HOST_HOME"] else { + throw SnapshotError.cannotFindSimulatorHomeDirectory + } + let homeDir = URL(fileURLWithPath: simulatorHostHome) + return homeDir.appendingPathComponent(cachePath) + #else + throw SnapshotError.cannotRunOnPhysicalDevice + #endif + } +} + +private extension XCUIElementAttributes { + var isNetworkLoadingIndicator: Bool { + if hasAllowListedIdentifier { return false } + + let hasOldLoadingIndicatorSize = frame.size == CGSize(width: 10, height: 20) + let hasNewLoadingIndicatorSize = frame.size.width.isBetween(46, and: 47) && frame.size.height.isBetween(2, and: 3) + + return hasOldLoadingIndicatorSize || hasNewLoadingIndicatorSize + } + + var hasAllowListedIdentifier: Bool { + let allowListedIdentifiers = ["GeofenceLocationTrackingOn", "StandardLocationTrackingOn"] + + return allowListedIdentifiers.contains(identifier) + } + + func isStatusBar(_ deviceWidth: CGFloat) -> Bool { + if elementType == .statusBar { return true } + guard frame.origin == .zero else { return false } + + let oldStatusBarSize = CGSize(width: deviceWidth, height: 20) + let newStatusBarSize = CGSize(width: deviceWidth, height: 44) + + return [oldStatusBarSize, newStatusBarSize].contains(frame.size) + } +} + +private extension XCUIElementQuery { + var networkLoadingIndicators: XCUIElementQuery { + let isNetworkLoadingIndicator = NSPredicate { (evaluatedObject, _) in + guard let element = evaluatedObject as? XCUIElementAttributes else { return false } + + return element.isNetworkLoadingIndicator + } + + return self.containing(isNetworkLoadingIndicator) + } + + var deviceStatusBars: XCUIElementQuery { + guard let app = Snapshot.app else { + fatalError("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().") + } + + let deviceWidth = app.windows.firstMatch.frame.width + + let isStatusBar = NSPredicate { (evaluatedObject, _) in + guard let element = evaluatedObject as? XCUIElementAttributes else { return false } + + return element.isStatusBar(deviceWidth) + } + + return self.containing(isStatusBar) + } +} + +private extension CGFloat { + func isBetween(_ numberA: CGFloat, and numberB: CGFloat) -> Bool { + return numberA...numberB ~= self + } +} + +// Please don't remove the lines below +// They are used to detect outdated configuration files +// SnapshotHelperVersion [1.23] diff --git a/fastlane/screenshots/Framefile.json b/fastlane/screenshots/Framefile.json new file mode 100644 index 0000000..1fcd4e3 --- /dev/null +++ b/fastlane/screenshots/Framefile.json @@ -0,0 +1,44 @@ +{ + "device_frame_version": "latest", + "default": { + "background": "./background.jpg", + "padding": 50, + "show_complete_frame": false, + "stack_title" : false, + "title_below_image": false, + "frame": "WHITE", + "use_platform": "IOS" + }, + "data": [ + { + "filter": "Consumption", + "title": { + "color": "#ffffff" + } + }, + { + "filter": "Input", + "title": { + "color": "#ffffff" + } + }, + { + "filter": "Daily", + "title": { + "color": "#ffffff" + } + }, + { + "filter": "Monthly", + "title": { + "color": "#ffffff" + } + }, + { + "filter": "Settings", + "title": { + "color": "#ffffff" + } + } + ] +} diff --git a/fastlane/screenshots/background.jpg b/fastlane/screenshots/background.jpg new file mode 100644 index 0000000..6868864 Binary files /dev/null and b/fastlane/screenshots/background.jpg differ diff --git a/fastlane/screenshots/de-DE/title.strings b/fastlane/screenshots/de-DE/title.strings new file mode 100644 index 0000000..8e00476 --- /dev/null +++ b/fastlane/screenshots/de-DE/title.strings @@ -0,0 +1,9 @@ +"Consumption" = "CONSUMPTION"; + +"Input" = "INPUT"; + +"Daily" = "DAILY-STATS"; + +"Monthly" = "MONTHLY-STATS"; + +"Settings" = "SETTINGS"; diff --git a/fastlane/screenshots/en-US/title.strings b/fastlane/screenshots/en-US/title.strings new file mode 100644 index 0000000..8e00476 --- /dev/null +++ b/fastlane/screenshots/en-US/title.strings @@ -0,0 +1,9 @@ +"Consumption" = "CONSUMPTION"; + +"Input" = "INPUT"; + +"Daily" = "DAILY-STATS"; + +"Monthly" = "MONTHLY-STATS"; + +"Settings" = "SETTINGS"; diff --git a/fastlane/screenshots/screenshots.html b/fastlane/screenshots/screenshots.html new file mode 100644 index 0000000..bf82457 --- /dev/null +++ b/fastlane/screenshots/screenshots.html @@ -0,0 +1,598 @@ + + + + fastlane/snapshot + + + + +
+ + +
+

By Language:

+

de-DE

+
+ + + + + + + + + + + + + + + + + + + + + +
+ iPhone 11 Pro Max +
+ + de-DE iPhone 11 Pro Max + + + + de-DE iPhone 11 Pro Max + + + + de-DE iPhone 11 Pro Max + + + + de-DE iPhone 11 Pro Max + + + + de-DE iPhone 11 Pro Max + +
+ iPhone 8 Plus +
+ + de-DE iPhone 8 Plus + + + + de-DE iPhone 8 Plus + + + + de-DE iPhone 8 Plus + + + + de-DE iPhone 8 Plus + + + + de-DE iPhone 8 Plus + +
+

en-US

+
+ + + + + + + + + + + + + + + + + + + + + +
+ iPhone 11 Pro Max +
+ + en-US iPhone 11 Pro Max + + + + en-US iPhone 11 Pro Max + + + + en-US iPhone 11 Pro Max + + + + en-US iPhone 11 Pro Max + + + + en-US iPhone 11 Pro Max + +
+ iPhone 8 Plus +
+ + en-US iPhone 8 Plus + + + + en-US iPhone 8 Plus + + + + en-US iPhone 8 Plus + + + + en-US iPhone 8 Plus + + + + en-US iPhone 8 Plus + +
+
+

By Screen:

+

0-Consumption

+
+ + + + + + + + + + + + + + + +
+ iPhone 11 Pro Max +
+ + de-DE iPhone 11 Pro Max + +
de-DE
+
+ + en-US iPhone 11 Pro Max + +
en-US
+
+ iPhone 8 Plus +
+ + de-DE iPhone 8 Plus + +
de-DE
+
+ + en-US iPhone 8 Plus + +
en-US
+
+

1-Input

+
+ + + + + + + + + + + + + + + +
+ iPhone 11 Pro Max +
+ + de-DE iPhone 11 Pro Max + +
de-DE
+
+ + en-US iPhone 11 Pro Max + +
en-US
+
+ iPhone 8 Plus +
+ + de-DE iPhone 8 Plus + +
de-DE
+
+ + en-US iPhone 8 Plus + +
en-US
+
+

2-Daily

+
+ + + + + + + + + + + + + + + +
+ iPhone 11 Pro Max +
+ + de-DE iPhone 11 Pro Max + +
de-DE
+
+ + en-US iPhone 11 Pro Max + +
en-US
+
+ iPhone 8 Plus +
+ + de-DE iPhone 8 Plus + +
de-DE
+
+ + en-US iPhone 8 Plus + +
en-US
+
+

3-Monthly

+
+ + + + + + + + + + + + + + + +
+ iPhone 11 Pro Max +
+ + de-DE iPhone 11 Pro Max + +
de-DE
+
+ + en-US iPhone 11 Pro Max + +
en-US
+
+ iPhone 8 Plus +
+ + de-DE iPhone 8 Plus + +
de-DE
+
+ + en-US iPhone 8 Plus + +
en-US
+
+

4-Settings

+
+ + + + + + + + + + + + + + + +
+ iPhone 11 Pro Max +
+ + de-DE iPhone 11 Pro Max + +
de-DE
+
+ + en-US iPhone 11 Pro Max + +
en-US
+
+ iPhone 8 Plus +
+ + de-DE iPhone 8 Plus + +
de-DE
+
+ + en-US iPhone 8 Plus + +
en-US
+
+
+
+ +
+
+ + + diff --git a/swiftlint.yml b/swiftlint.yml new file mode 100644 index 0000000..a7e0c8f --- /dev/null +++ b/swiftlint.yml @@ -0,0 +1,68 @@ +# entries after this are temporarily added. Remove one by one and fix! +# testing jenkins +disabled_rules: + +opt_in_rules: + - nesting + - redundant_optional_initialization + - closure_end_indentation + - contains_over_first_not_nil + - empty_parameters + - force_unwrapping + +# autocorrect: + - explicit_init + - closure_spacing + - operator_usage_whitespace + +# manually: + - empty_count + - private_outlet + - private_action + - private_over_fileprivate + - implicitly_unwrapped_optional + - let_var_whitespace + - yoda_condition + - first_where + - for_where + - multiline_arguments + - multiline_arguments_brackets + - multiline_function_chains + - multiline_literal_brackets + - multiple_closures_with_trailing_closure + - no_space_in_method_call + +excluded: + - Pods + - Templates + - GMRChat + - Frameworks + - GMRUnitTests + +# make it difficult to forget +force_unwrapping: warning +force_cast: error +implicitly_unwrapped_optional: error +multiline_arguments: error +multiline_arguments_brackets: error +multiline_function_chains: error +multiline_literal_brackets: error +multiple_closures_with_trailing_closure: error +no_space_in_method_call: error +private_outlet: error +private_action: error +private_over_fileprivate: error + +cyclomatic_complexity: + - 20 # warning + - 30 # error +type_body_length: + - 450 # warning + - 600 # error +function_body_length: + - 100 # warning + - 200 # error +file_length: + - 600 # warning + - 1000 # error +