├── .gitignore ├── Air.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── gabriel.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── gabriel.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── Air ├── Air.entitlements ├── AirQuality.swift ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── mac-128x128.png │ │ ├── mac-128x128@2x.png │ │ ├── mac-16x16.png │ │ ├── mac-16x16@2x.png │ │ ├── mac-256x256.png │ │ ├── mac-256x256@2x.png │ │ ├── mac-32x32.png │ │ ├── mac-32x32@2x.png │ │ ├── mac-512x512.png │ │ └── mac-512x512@2x.png │ └── Contents.json ├── Info.plist ├── Menu.xib ├── MenuView.xib ├── MenuViewController.swift ├── PopOver.swift └── UserDefaults.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | Secrets.swift 2 | -------------------------------------------------------------------------------- /Air.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C02EC7FA21A0C4EB00B44FE5 /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = C02EC7F921A0C4EB00B44FE5 /* UserDefaults.swift */; }; 11 | C02EC80221A0E46A00B44FE5 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = C02EC80121A0E46A00B44FE5 /* Secrets.swift */; }; 12 | C0B42B6D21A0B1CA00EA0F54 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0B42B6C21A0B1CA00EA0F54 /* AppDelegate.swift */; }; 13 | C0B42B7121A0B1CC00EA0F54 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C0B42B7021A0B1CC00EA0F54 /* Assets.xcassets */; }; 14 | C0B42B7D21A0B1FB00EA0F54 /* AirQuality.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0B42B7C21A0B1FB00EA0F54 /* AirQuality.swift */; }; 15 | C0B42B7F21A0B23500EA0F54 /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0B42B7E21A0B23500EA0F54 /* MenuViewController.swift */; }; 16 | C0B42B8121A0B27B00EA0F54 /* MenuView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C0B42B8021A0B27B00EA0F54 /* MenuView.xib */; }; 17 | C0B42B8321A0B2B100EA0F54 /* PopOver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0B42B8221A0B2B100EA0F54 /* PopOver.swift */; }; 18 | C0B42BA121A0B98000EA0F54 /* Menu.xib in Resources */ = {isa = PBXBuildFile; fileRef = C0B42BA021A0B98000EA0F54 /* Menu.xib */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | C02EC7F921A0C4EB00B44FE5 /* UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults.swift; sourceTree = ""; }; 23 | C02EC80121A0E46A00B44FE5 /* Secrets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Secrets.swift; sourceTree = ""; }; 24 | C0B42B6921A0B1CA00EA0F54 /* Air.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Air.app; sourceTree = BUILT_PRODUCTS_DIR; }; 25 | C0B42B6C21A0B1CA00EA0F54 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 26 | C0B42B7021A0B1CC00EA0F54 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 27 | C0B42B7521A0B1CC00EA0F54 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 28 | C0B42B7621A0B1CC00EA0F54 /* Air.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Air.entitlements; sourceTree = ""; }; 29 | C0B42B7C21A0B1FB00EA0F54 /* AirQuality.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirQuality.swift; sourceTree = ""; }; 30 | C0B42B7E21A0B23500EA0F54 /* MenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuViewController.swift; sourceTree = ""; }; 31 | C0B42B8021A0B27B00EA0F54 /* MenuView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MenuView.xib; sourceTree = ""; }; 32 | C0B42B8221A0B2B100EA0F54 /* PopOver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopOver.swift; sourceTree = ""; }; 33 | C0B42BA021A0B98000EA0F54 /* Menu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = Menu.xib; sourceTree = ""; }; 34 | /* End PBXFileReference section */ 35 | 36 | /* Begin PBXFrameworksBuildPhase section */ 37 | C0B42B6621A0B1CA00EA0F54 /* Frameworks */ = { 38 | isa = PBXFrameworksBuildPhase; 39 | buildActionMask = 2147483647; 40 | files = ( 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXFrameworksBuildPhase section */ 45 | 46 | /* Begin PBXGroup section */ 47 | C0B42B6021A0B1CA00EA0F54 = { 48 | isa = PBXGroup; 49 | children = ( 50 | C0B42B6B21A0B1CA00EA0F54 /* Air */, 51 | C0B42B6A21A0B1CA00EA0F54 /* Products */, 52 | ); 53 | sourceTree = ""; 54 | }; 55 | C0B42B6A21A0B1CA00EA0F54 /* Products */ = { 56 | isa = PBXGroup; 57 | children = ( 58 | C0B42B6921A0B1CA00EA0F54 /* Air.app */, 59 | ); 60 | name = Products; 61 | sourceTree = ""; 62 | }; 63 | C0B42B6B21A0B1CA00EA0F54 /* Air */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | C0B42B6C21A0B1CA00EA0F54 /* AppDelegate.swift */, 67 | C0B42B7E21A0B23500EA0F54 /* MenuViewController.swift */, 68 | C02EC80121A0E46A00B44FE5 /* Secrets.swift */, 69 | C02EC7F921A0C4EB00B44FE5 /* UserDefaults.swift */, 70 | C0B42BA021A0B98000EA0F54 /* Menu.xib */, 71 | C0B42B8221A0B2B100EA0F54 /* PopOver.swift */, 72 | C0B42B8021A0B27B00EA0F54 /* MenuView.xib */, 73 | C0B42B7021A0B1CC00EA0F54 /* Assets.xcassets */, 74 | C0B42B7521A0B1CC00EA0F54 /* Info.plist */, 75 | C0B42B7621A0B1CC00EA0F54 /* Air.entitlements */, 76 | C0B42B7C21A0B1FB00EA0F54 /* AirQuality.swift */, 77 | ); 78 | path = Air; 79 | sourceTree = ""; 80 | }; 81 | /* End PBXGroup section */ 82 | 83 | /* Begin PBXNativeTarget section */ 84 | C0B42B6821A0B1CA00EA0F54 /* Air */ = { 85 | isa = PBXNativeTarget; 86 | buildConfigurationList = C0B42B7921A0B1CC00EA0F54 /* Build configuration list for PBXNativeTarget "Air" */; 87 | buildPhases = ( 88 | C0B42B6521A0B1CA00EA0F54 /* Sources */, 89 | C0B42B6621A0B1CA00EA0F54 /* Frameworks */, 90 | C0B42B6721A0B1CA00EA0F54 /* Resources */, 91 | ); 92 | buildRules = ( 93 | ); 94 | dependencies = ( 95 | ); 96 | name = Air; 97 | productName = Air; 98 | productReference = C0B42B6921A0B1CA00EA0F54 /* Air.app */; 99 | productType = "com.apple.product-type.application"; 100 | }; 101 | /* End PBXNativeTarget section */ 102 | 103 | /* Begin PBXProject section */ 104 | C0B42B6121A0B1CA00EA0F54 /* Project object */ = { 105 | isa = PBXProject; 106 | attributes = { 107 | LastSwiftUpdateCheck = 1010; 108 | LastUpgradeCheck = 1010; 109 | ORGANIZATIONNAME = gabriel.lewis; 110 | TargetAttributes = { 111 | C0B42B6821A0B1CA00EA0F54 = { 112 | CreatedOnToolsVersion = 10.1; 113 | SystemCapabilities = { 114 | com.apple.Sandbox = { 115 | enabled = 0; 116 | }; 117 | }; 118 | }; 119 | }; 120 | }; 121 | buildConfigurationList = C0B42B6421A0B1CA00EA0F54 /* Build configuration list for PBXProject "Air" */; 122 | compatibilityVersion = "Xcode 9.3"; 123 | developmentRegion = en; 124 | hasScannedForEncodings = 0; 125 | knownRegions = ( 126 | en, 127 | Base, 128 | ); 129 | mainGroup = C0B42B6021A0B1CA00EA0F54; 130 | productRefGroup = C0B42B6A21A0B1CA00EA0F54 /* Products */; 131 | projectDirPath = ""; 132 | projectRoot = ""; 133 | targets = ( 134 | C0B42B6821A0B1CA00EA0F54 /* Air */, 135 | ); 136 | }; 137 | /* End PBXProject section */ 138 | 139 | /* Begin PBXResourcesBuildPhase section */ 140 | C0B42B6721A0B1CA00EA0F54 /* Resources */ = { 141 | isa = PBXResourcesBuildPhase; 142 | buildActionMask = 2147483647; 143 | files = ( 144 | C0B42B7121A0B1CC00EA0F54 /* Assets.xcassets in Resources */, 145 | C0B42BA121A0B98000EA0F54 /* Menu.xib in Resources */, 146 | C0B42B8121A0B27B00EA0F54 /* MenuView.xib in Resources */, 147 | ); 148 | runOnlyForDeploymentPostprocessing = 0; 149 | }; 150 | /* End PBXResourcesBuildPhase section */ 151 | 152 | /* Begin PBXSourcesBuildPhase section */ 153 | C0B42B6521A0B1CA00EA0F54 /* Sources */ = { 154 | isa = PBXSourcesBuildPhase; 155 | buildActionMask = 2147483647; 156 | files = ( 157 | C0B42B8321A0B2B100EA0F54 /* PopOver.swift in Sources */, 158 | C02EC80221A0E46A00B44FE5 /* Secrets.swift in Sources */, 159 | C02EC7FA21A0C4EB00B44FE5 /* UserDefaults.swift in Sources */, 160 | C0B42B7D21A0B1FB00EA0F54 /* AirQuality.swift in Sources */, 161 | C0B42B6D21A0B1CA00EA0F54 /* AppDelegate.swift in Sources */, 162 | C0B42B7F21A0B23500EA0F54 /* MenuViewController.swift in Sources */, 163 | ); 164 | runOnlyForDeploymentPostprocessing = 0; 165 | }; 166 | /* End PBXSourcesBuildPhase section */ 167 | 168 | /* Begin XCBuildConfiguration section */ 169 | C0B42B7721A0B1CC00EA0F54 /* Debug */ = { 170 | isa = XCBuildConfiguration; 171 | buildSettings = { 172 | ALWAYS_SEARCH_USER_PATHS = NO; 173 | CLANG_ANALYZER_NONNULL = YES; 174 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 175 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 176 | CLANG_CXX_LIBRARY = "libc++"; 177 | CLANG_ENABLE_MODULES = YES; 178 | CLANG_ENABLE_OBJC_ARC = YES; 179 | CLANG_ENABLE_OBJC_WEAK = YES; 180 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 181 | CLANG_WARN_BOOL_CONVERSION = YES; 182 | CLANG_WARN_COMMA = YES; 183 | CLANG_WARN_CONSTANT_CONVERSION = YES; 184 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 185 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 186 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 187 | CLANG_WARN_EMPTY_BODY = YES; 188 | CLANG_WARN_ENUM_CONVERSION = YES; 189 | CLANG_WARN_INFINITE_RECURSION = YES; 190 | CLANG_WARN_INT_CONVERSION = YES; 191 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 192 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 193 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 194 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 195 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 196 | CLANG_WARN_STRICT_PROTOTYPES = YES; 197 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 198 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 199 | CLANG_WARN_UNREACHABLE_CODE = YES; 200 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 201 | CODE_SIGN_IDENTITY = "Mac Developer"; 202 | COPY_PHASE_STRIP = NO; 203 | DEBUG_INFORMATION_FORMAT = dwarf; 204 | ENABLE_STRICT_OBJC_MSGSEND = YES; 205 | ENABLE_TESTABILITY = YES; 206 | GCC_C_LANGUAGE_STANDARD = gnu11; 207 | GCC_DYNAMIC_NO_PIC = NO; 208 | GCC_NO_COMMON_BLOCKS = YES; 209 | GCC_OPTIMIZATION_LEVEL = 0; 210 | GCC_PREPROCESSOR_DEFINITIONS = ( 211 | "DEBUG=1", 212 | "$(inherited)", 213 | ); 214 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 215 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 216 | GCC_WARN_UNDECLARED_SELECTOR = YES; 217 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 218 | GCC_WARN_UNUSED_FUNCTION = YES; 219 | GCC_WARN_UNUSED_VARIABLE = YES; 220 | MACOSX_DEPLOYMENT_TARGET = 10.14; 221 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 222 | MTL_FAST_MATH = YES; 223 | ONLY_ACTIVE_ARCH = YES; 224 | SDKROOT = macosx; 225 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 226 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 227 | }; 228 | name = Debug; 229 | }; 230 | C0B42B7821A0B1CC00EA0F54 /* Release */ = { 231 | isa = XCBuildConfiguration; 232 | buildSettings = { 233 | ALWAYS_SEARCH_USER_PATHS = NO; 234 | CLANG_ANALYZER_NONNULL = YES; 235 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 236 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 237 | CLANG_CXX_LIBRARY = "libc++"; 238 | CLANG_ENABLE_MODULES = YES; 239 | CLANG_ENABLE_OBJC_ARC = YES; 240 | CLANG_ENABLE_OBJC_WEAK = YES; 241 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 242 | CLANG_WARN_BOOL_CONVERSION = YES; 243 | CLANG_WARN_COMMA = YES; 244 | CLANG_WARN_CONSTANT_CONVERSION = YES; 245 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 246 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 247 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 248 | CLANG_WARN_EMPTY_BODY = YES; 249 | CLANG_WARN_ENUM_CONVERSION = YES; 250 | CLANG_WARN_INFINITE_RECURSION = YES; 251 | CLANG_WARN_INT_CONVERSION = YES; 252 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 253 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 254 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 255 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 256 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 257 | CLANG_WARN_STRICT_PROTOTYPES = YES; 258 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 259 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 260 | CLANG_WARN_UNREACHABLE_CODE = YES; 261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 262 | CODE_SIGN_IDENTITY = "Mac Developer"; 263 | COPY_PHASE_STRIP = NO; 264 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 265 | ENABLE_NS_ASSERTIONS = NO; 266 | ENABLE_STRICT_OBJC_MSGSEND = YES; 267 | GCC_C_LANGUAGE_STANDARD = gnu11; 268 | GCC_NO_COMMON_BLOCKS = YES; 269 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 270 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 271 | GCC_WARN_UNDECLARED_SELECTOR = YES; 272 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 273 | GCC_WARN_UNUSED_FUNCTION = YES; 274 | GCC_WARN_UNUSED_VARIABLE = YES; 275 | MACOSX_DEPLOYMENT_TARGET = 10.14; 276 | MTL_ENABLE_DEBUG_INFO = NO; 277 | MTL_FAST_MATH = YES; 278 | SDKROOT = macosx; 279 | SWIFT_COMPILATION_MODE = wholemodule; 280 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 281 | }; 282 | name = Release; 283 | }; 284 | C0B42B7A21A0B1CC00EA0F54 /* Debug */ = { 285 | isa = XCBuildConfiguration; 286 | buildSettings = { 287 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 288 | CODE_SIGN_STYLE = Automatic; 289 | COMBINE_HIDPI_IMAGES = YES; 290 | DEVELOPMENT_TEAM = N4SPYPM5DG; 291 | INFOPLIST_FILE = Air/Info.plist; 292 | LD_RUNPATH_SEARCH_PATHS = ( 293 | "$(inherited)", 294 | "@executable_path/../Frameworks", 295 | ); 296 | MACOSX_DEPLOYMENT_TARGET = 10.13; 297 | PRODUCT_BUNDLE_IDENTIFIER = "com.gabriel-lewis.Air"; 298 | PRODUCT_NAME = "$(TARGET_NAME)"; 299 | SWIFT_VERSION = 4.0; 300 | }; 301 | name = Debug; 302 | }; 303 | C0B42B7B21A0B1CC00EA0F54 /* Release */ = { 304 | isa = XCBuildConfiguration; 305 | buildSettings = { 306 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 307 | CODE_SIGN_STYLE = Automatic; 308 | COMBINE_HIDPI_IMAGES = YES; 309 | DEVELOPMENT_TEAM = N4SPYPM5DG; 310 | INFOPLIST_FILE = Air/Info.plist; 311 | LD_RUNPATH_SEARCH_PATHS = ( 312 | "$(inherited)", 313 | "@executable_path/../Frameworks", 314 | ); 315 | MACOSX_DEPLOYMENT_TARGET = 10.13; 316 | PRODUCT_BUNDLE_IDENTIFIER = "com.gabriel-lewis.Air"; 317 | PRODUCT_NAME = "$(TARGET_NAME)"; 318 | SWIFT_VERSION = 4.0; 319 | }; 320 | name = Release; 321 | }; 322 | /* End XCBuildConfiguration section */ 323 | 324 | /* Begin XCConfigurationList section */ 325 | C0B42B6421A0B1CA00EA0F54 /* Build configuration list for PBXProject "Air" */ = { 326 | isa = XCConfigurationList; 327 | buildConfigurations = ( 328 | C0B42B7721A0B1CC00EA0F54 /* Debug */, 329 | C0B42B7821A0B1CC00EA0F54 /* Release */, 330 | ); 331 | defaultConfigurationIsVisible = 0; 332 | defaultConfigurationName = Release; 333 | }; 334 | C0B42B7921A0B1CC00EA0F54 /* Build configuration list for PBXNativeTarget "Air" */ = { 335 | isa = XCConfigurationList; 336 | buildConfigurations = ( 337 | C0B42B7A21A0B1CC00EA0F54 /* Debug */, 338 | C0B42B7B21A0B1CC00EA0F54 /* Release */, 339 | ); 340 | defaultConfigurationIsVisible = 0; 341 | defaultConfigurationName = Release; 342 | }; 343 | /* End XCConfigurationList section */ 344 | }; 345 | rootObject = C0B42B6121A0B1CA00EA0F54 /* Project object */; 346 | } 347 | -------------------------------------------------------------------------------- /Air.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Air.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Air.xcodeproj/project.xcworkspace/xcuserdata/gabriel.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gabriel-Lewis/Air/d437bf8ee34d48a3876984ddbf195f12e46535bf/Air.xcodeproj/project.xcworkspace/xcuserdata/gabriel.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Air.xcodeproj/xcuserdata/gabriel.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /Air.xcodeproj/xcuserdata/gabriel.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Air.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Air/Air.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Air/AirQuality.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AirQuality.swift 3 | // Air 4 | // 5 | // Created by Gabriel Lewis on 11/17/18. 6 | // Copyright © 2018 gabriel.lewis. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | typealias AirQualityData = [AirQualityDatum] 12 | 13 | struct AirQualityDatum: Codable { 14 | let dateObserved: String 15 | let hourObserved: Int 16 | let localTimeZone, reportingArea, stateCode: String 17 | let latitude, longitude: Double 18 | let parameterName: String 19 | let aqi: Int 20 | let category: Category 21 | 22 | enum CodingKeys: String, CodingKey { 23 | case dateObserved = "DateObserved" 24 | case hourObserved = "HourObserved" 25 | case localTimeZone = "LocalTimeZone" 26 | case reportingArea = "ReportingArea" 27 | case stateCode = "StateCode" 28 | case latitude = "Latitude" 29 | case longitude = "Longitude" 30 | case parameterName = "ParameterName" 31 | case aqi = "AQI" 32 | case category = "Category" 33 | } 34 | } 35 | 36 | struct Category: Codable { 37 | let number: Int 38 | let name: String 39 | 40 | enum CodingKeys: String, CodingKey { 41 | case number = "Number" 42 | case name = "Name" 43 | } 44 | } 45 | 46 | 47 | 48 | class AirQuality { 49 | private var url: URL { 50 | let urlString = "http://www.airnowapi.org/aq/observation/zipCode/current/?format=application/json&API_KEY=\(Secrets.apiKey)&zipCode=" 51 | guard let zipcode = zipcode else { 52 | return URL(string: urlString + "94115")! 53 | } 54 | return URL(string: urlString + zipcode)! 55 | } 56 | 57 | var zipcode: String? { 58 | return UserDefaults.standard.zipcode 59 | } 60 | 61 | static let shared = AirQuality() 62 | private init() { 63 | 64 | } 65 | 66 | func getAirQuality(completion: @escaping ((String, Category) -> ())) { 67 | self.unauthenticatedRequest { (data, err) in 68 | if let err = err as? NSError { 69 | print("err: \(err.code)") 70 | } 71 | let decoder = JSONDecoder() 72 | guard let data = data else { fatalError() } 73 | guard let json = try? decoder.decode(AirQualityData.self, from: data) else { fatalError() } 74 | 75 | for quality in json { 76 | print("quality: \(quality)") 77 | } 78 | guard json.isEmpty == false else { 79 | completion("N/A", Category.init(number: -1, name: "n/a")) 80 | return 81 | } 82 | 83 | completion("\(json[1].aqi)", json[1].category) 84 | } 85 | } 86 | 87 | func unauthenticatedRequest(data: Data? = nil, completion: @escaping ((Data?, Error?) -> Void)) { 88 | var request = URLRequest(url: url) 89 | request.httpMethod = "GET" 90 | request.timeoutInterval = 60 91 | let session = URLSession.shared 92 | request.addValue("application/json", forHTTPHeaderField: "Content-Type") 93 | request.addValue("application/json", forHTTPHeaderField: "Accept") 94 | 95 | let task = session.dataTask(with: request) { data, _, error -> Void in 96 | guard error == nil else { 97 | completion(nil, error) 98 | return 99 | } 100 | guard let data = data else { 101 | completion(nil, nil) 102 | return 103 | } 104 | completion(data, nil) 105 | } 106 | task.resume() 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Air/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Air 4 | // 5 | // Created by Gabriel Lewis on 11/17/18. 6 | // Copyright © 2018 gabriel.lewis. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import NotificationCenter 11 | 12 | @NSApplicationMain 13 | class AppDelegate: NSObject, NSApplicationDelegate { 14 | 15 | @IBOutlet weak var window: NSWindow! 16 | static var shared: AppDelegate? { 17 | return NSApplication.shared.delegate as? AppDelegate 18 | } 19 | var timer: Timer? 20 | let popover = NSPopover() 21 | let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) 22 | 23 | func showPopover(sender: AnyObject?) { 24 | NSApplication.shared.activate(ignoringOtherApps: true) 25 | if let button = statusItem.button { 26 | popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY) 27 | } 28 | } 29 | 30 | var menuVew: MenuViewController? 31 | 32 | func closePopover(sender: AnyObject?) { 33 | popover.performClose(sender) 34 | } 35 | 36 | @objc func togglePopover(sender: AnyObject?) { 37 | if popover.isShown { 38 | closePopover(sender: sender) 39 | } else { 40 | showPopover(sender: sender) 41 | } 42 | } 43 | 44 | func applicationDidFinishLaunching(_ notification: Notification) { 45 | 46 | 47 | if let button = statusItem.button { 48 | button.title = "..." 49 | button.target = self 50 | button.action = #selector(self.togglePopover(sender:)) 51 | button.highlight(true) 52 | } 53 | let menu = MenuViewController(nibName: NSNib.Name(rawValue: "MenuView"), bundle: nil) 54 | popover.contentViewController = menu 55 | self.menuVew = menu 56 | popover.animates = true 57 | popover.behavior = .transient 58 | 59 | updateAirQuality() 60 | startTimer() 61 | } 62 | 63 | func startTimer() { 64 | self.timer?.invalidate() 65 | self.timer = Timer.scheduledTimer(timeInterval: 600, target: self, selector: #selector(updateAirQuality), userInfo: nil, repeats: true) 66 | } 67 | 68 | @objc func updateAirQuality() { 69 | AirQuality.shared.getAirQuality { (aqi, category) in 70 | DispatchQueue.main.async { 71 | if let button = self.statusItem.button { 72 | self.statusItem.button?.highlight(true) 73 | let pstyle = NSMutableParagraphStyle() 74 | pstyle.alignment = .center 75 | 76 | switch category.number { 77 | case 0, 1: 78 | button.layer?.backgroundColor = NSColor.green.cgColor 79 | button.attributedTitle = NSAttributedString(string: aqi, attributes: [ NSAttributedStringKey.foregroundColor : NSColor.black, NSAttributedStringKey.paragraphStyle : pstyle ]) 80 | case 2: 81 | button.layer?.backgroundColor = NSColor.yellow.cgColor 82 | button.attributedTitle = NSAttributedString(string: aqi, attributes: [ NSAttributedStringKey.foregroundColor : NSColor.black, NSAttributedStringKey.paragraphStyle : pstyle ]) 83 | case 3: 84 | button.layer?.backgroundColor = NSColor.orange.cgColor 85 | button.attributedTitle = NSAttributedString(string: aqi, attributes: [ NSAttributedStringKey.foregroundColor : NSColor.black, NSAttributedStringKey.paragraphStyle : pstyle ]) 86 | case 4: 87 | button.layer?.backgroundColor = NSColor.red.cgColor 88 | button.attributedTitle = NSAttributedString(string: aqi, attributes: [ NSAttributedStringKey.foregroundColor : NSColor.white, NSAttributedStringKey.paragraphStyle : pstyle ]) 89 | case 5: 90 | button.layer?.backgroundColor = NSColor.purple.cgColor 91 | button.attributedTitle = NSAttributedString(string: aqi, attributes: [ NSAttributedStringKey.foregroundColor : NSColor.white, NSAttributedStringKey.paragraphStyle : pstyle ]) 92 | case 6: 93 | button.layer?.backgroundColor = NSColor.black.cgColor 94 | button.attributedTitle = NSAttributedString(string: aqi, attributes: [ NSAttributedStringKey.foregroundColor : NSColor.white, NSAttributedStringKey.paragraphStyle : pstyle ]) 95 | default: 96 | button.layer?.backgroundColor = NSColor.gray.cgColor 97 | button.attributedTitle = NSAttributedString(string: aqi, attributes: [ NSAttributedStringKey.foregroundColor : NSColor.white, NSAttributedStringKey.paragraphStyle : pstyle ]) 98 | } 99 | } 100 | self.menuVew?.updateDateLabel() 101 | } 102 | } 103 | } 104 | 105 | 106 | } 107 | -------------------------------------------------------------------------------- /Air/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "mac-16x16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "mac-16x16@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "mac-32x32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "mac-32x32@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "mac-128x128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "mac-128x128@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "mac-256x256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "mac-256x256@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "mac-512x512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "mac-512x512@2x.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Air/Assets.xcassets/AppIcon.appiconset/mac-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gabriel-Lewis/Air/d437bf8ee34d48a3876984ddbf195f12e46535bf/Air/Assets.xcassets/AppIcon.appiconset/mac-128x128.png -------------------------------------------------------------------------------- /Air/Assets.xcassets/AppIcon.appiconset/mac-128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gabriel-Lewis/Air/d437bf8ee34d48a3876984ddbf195f12e46535bf/Air/Assets.xcassets/AppIcon.appiconset/mac-128x128@2x.png -------------------------------------------------------------------------------- /Air/Assets.xcassets/AppIcon.appiconset/mac-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gabriel-Lewis/Air/d437bf8ee34d48a3876984ddbf195f12e46535bf/Air/Assets.xcassets/AppIcon.appiconset/mac-16x16.png -------------------------------------------------------------------------------- /Air/Assets.xcassets/AppIcon.appiconset/mac-16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gabriel-Lewis/Air/d437bf8ee34d48a3876984ddbf195f12e46535bf/Air/Assets.xcassets/AppIcon.appiconset/mac-16x16@2x.png -------------------------------------------------------------------------------- /Air/Assets.xcassets/AppIcon.appiconset/mac-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gabriel-Lewis/Air/d437bf8ee34d48a3876984ddbf195f12e46535bf/Air/Assets.xcassets/AppIcon.appiconset/mac-256x256.png -------------------------------------------------------------------------------- /Air/Assets.xcassets/AppIcon.appiconset/mac-256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gabriel-Lewis/Air/d437bf8ee34d48a3876984ddbf195f12e46535bf/Air/Assets.xcassets/AppIcon.appiconset/mac-256x256@2x.png -------------------------------------------------------------------------------- /Air/Assets.xcassets/AppIcon.appiconset/mac-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gabriel-Lewis/Air/d437bf8ee34d48a3876984ddbf195f12e46535bf/Air/Assets.xcassets/AppIcon.appiconset/mac-32x32.png -------------------------------------------------------------------------------- /Air/Assets.xcassets/AppIcon.appiconset/mac-32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gabriel-Lewis/Air/d437bf8ee34d48a3876984ddbf195f12e46535bf/Air/Assets.xcassets/AppIcon.appiconset/mac-32x32@2x.png -------------------------------------------------------------------------------- /Air/Assets.xcassets/AppIcon.appiconset/mac-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gabriel-Lewis/Air/d437bf8ee34d48a3876984ddbf195f12e46535bf/Air/Assets.xcassets/AppIcon.appiconset/mac-512x512.png -------------------------------------------------------------------------------- /Air/Assets.xcassets/AppIcon.appiconset/mac-512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gabriel-Lewis/Air/d437bf8ee34d48a3876984ddbf195f12e46535bf/Air/Assets.xcassets/AppIcon.appiconset/mac-512x512@2x.png -------------------------------------------------------------------------------- /Air/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Air/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0.1 21 | CFBundleVersion 22 | 1 23 | LSApplicationCategoryType 24 | public.app-category.weather 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | LSUIElement 28 | 29 | NSAppTransportSecurity 30 | 31 | NSAllowsArbitraryLoads 32 | 33 | 34 | NSHumanReadableCopyright 35 | Copyright © 2018 Gabriel Lewis. All rights reserved. 36 | NSMainNibFile 37 | Menu 38 | NSPrincipalClass 39 | NSApplication 40 | 41 | 42 | -------------------------------------------------------------------------------- /Air/Menu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Air/MenuView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 33 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Air/MenuViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuViewController.swift 3 | // Air 4 | // 5 | // Created by Gabriel Lewis on 11/17/18. 6 | // Copyright © 2018 gabriel.lewis. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class MenuViewController: NSViewController { 12 | 13 | var formatter: DateFormatter = { 14 | let formatter = DateFormatter() 15 | formatter.dateStyle = .short 16 | formatter.timeStyle = .short 17 | return formatter 18 | }() 19 | 20 | @IBOutlet weak var dateLabel: NSTextField? 21 | @IBOutlet weak var zipcodeLabel: NSTextField! 22 | 23 | var date: Date = Date() 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | let date = formatter.string(from: self.date) 27 | self.dateLabel?.cell?.title = date 28 | zipcodeLabel.delegate = self 29 | if let zipcode = UserDefaults.standard.zipcode { 30 | self.zipcodeLabel.cell?.title = zipcode 31 | } else { 32 | self.zipcodeLabel.cell?.title = "94115" 33 | } 34 | 35 | } 36 | 37 | @IBAction func quit(_ sender: NSButton) { 38 | NSApplication.shared.terminate(self) 39 | } 40 | 41 | @IBAction func zipcodeDidChange(_ sender: NSTextField) { 42 | guard let text = sender.cell?.title else { return } 43 | guard validZipCode(postalCode: text) else { return } 44 | UserDefaults.standard.zipcode = text 45 | UserDefaults.standard.synchronize() 46 | } 47 | 48 | func textFieldDidChange(_ sender: NSTextField) { 49 | guard let text = sender.cell?.title else { return } 50 | guard validZipCode(postalCode: text) else { return } 51 | UserDefaults.standard.zipcode = text 52 | UserDefaults.standard.synchronize() 53 | refresh() 54 | } 55 | 56 | 57 | @IBAction func refreshCode(_ sender: Any) { 58 | refresh() 59 | } 60 | 61 | @objc func refresh() { 62 | AppDelegate.shared?.updateAirQuality() 63 | } 64 | 65 | func validZipCode(postalCode:String)->Bool{ 66 | let postalcodeRegex = "^[0-9]{5}(-[0-9]{4})?$" 67 | let pinPredicate = NSPredicate(format: "SELF MATCHES %@", postalcodeRegex) 68 | let bool = pinPredicate.evaluate(with: postalCode) as Bool 69 | return bool 70 | } 71 | 72 | func updateDateLabel() { 73 | self.date = Date() 74 | let dateString = formatter.string(from: date) 75 | self.dateLabel?.cell?.title = "\(dateString)" 76 | } 77 | } 78 | 79 | extension MenuViewController: NSTextFieldDelegate { 80 | public override func controlTextDidChange(_ obj: Notification) { 81 | if let textField = obj.object as? NSTextField, self.zipcodeLabel.identifier == textField.identifier { 82 | guard validZipCode(postalCode: textField.stringValue) else { return } 83 | UserDefaults.standard.zipcode = textField.stringValue 84 | UserDefaults.standard.synchronize() 85 | refresh() 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Air/PopOver.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PopOver.swift 3 | // Air 4 | // 5 | // Created by Gabriel Lewis on 11/17/18. 6 | // Copyright © 2018 gabriel.lewis. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class PopOver: NSView { 12 | 13 | override func draw(_ dirtyRect: NSRect) { 14 | super.draw(dirtyRect) 15 | } 16 | 17 | override func viewDidMoveToWindow() { 18 | 19 | guard let frameView = window?.contentView?.superview else { 20 | return 21 | } 22 | 23 | // let backgroundView = NSView(frame: frameView.bounds) 24 | // backgroundView.wantsLayer = true 25 | // backgroundView.layer?.backgroundColor = NSColor(red: 0.21, green: 0.24, blue: 0.28, alpha: 1).cgColor 26 | // backgroundView.autoresizingMask = [NSView.AutoresizingMask.width, NSView.AutoresizingMask.height] 27 | // 28 | // frameView.addSubview(backgroundView, positioned: .below, relativeTo: frameView) 29 | 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Air/UserDefaults.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefaults.swift 3 | // Air 4 | // 5 | // Created by Gabriel Lewis on 11/17/18. 6 | // Copyright © 2018 gabriel.lewis. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension UserDefaults { 12 | var zipcode: String? { 13 | get { 14 | return UserDefaults.standard.string(forKey: #function) 15 | } 16 | set { 17 | guard let string = newValue else { return } 18 | UserDefaults.standard.set(string, forKey: #function) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Air 2 | 3 | Mac menu bar app to track Air Quality 4 | 5 | 6 | --------------------------------------------------------------------------------