├── .gitignore ├── MainMenu.xib ├── Offline Time.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── Offline Time ├── AppConstants.plist ├── AppConstantsManager.swift ├── AppDelegate.swift ├── ConfirmationText.plist ├── ConfirmationTextManager.swift ├── CustomSlider.swift ├── CustomSliderCell.swift ├── Images.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon_128x128.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_16x16.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256.png │ │ ├── icon_256x256@2x.png │ │ ├── icon_32x32.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512.png │ │ └── icon_512x512@2x.png │ ├── SliderKnob.imageset │ │ ├── Contents.json │ │ └── knob.pdf │ ├── SliderKnobDark.imageset │ │ ├── Contents.json │ │ └── dark-knob.png │ ├── StatusBarButtonImage.imageset │ │ ├── Contents.json │ │ └── menubar_icon.pdf │ └── StatusBarButtonImage2.imageset │ │ ├── Contents.json │ │ ├── Status.png │ │ └── Status@2x.png ├── Info.plist ├── Offline Time-Bridging-Header.h ├── PopupMenu.swift ├── PopupMenu.xib ├── Reachability.swift ├── SALView.swift ├── SALViewController.xib ├── SliderView.swift ├── SliderViewController.xib ├── TweetView.swift ├── TweetViewController.xib └── Vendor │ ├── PALoginItemUtility.h │ └── PALoginItemUtility.m ├── Offline TimeTests ├── Info.plist └── Offline_TimeTests.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode (from gitignore.io) 2 | build/ 3 | *.pbxuser 4 | !default.pbxuser 5 | *.mode1v3 6 | !default.mode1v3 7 | *.mode2v3 8 | !default.mode2v3 9 | *.perspectivev3 10 | !default.perspectivev3 11 | xcuserdata 12 | *.xccheckout 13 | *.moved-aside 14 | DerivedData 15 | *.hmap 16 | *.ipa 17 | *.xcuserstate 18 | 19 | # CocoaPod 20 | Pods/* 21 | Podfile.lock 22 | 23 | # others 24 | *.swp 25 | !.gitkeep 26 | .DS_Store 27 | -------------------------------------------------------------------------------- /MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Offline Time.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 40076F961BB8CF17002DB024 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40076F951BB8CF17002DB024 /* AppDelegate.swift */; }; 11 | 40076F9A1BB8CF17002DB024 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 40076F991BB8CF17002DB024 /* Images.xcassets */; }; 12 | 40076FA91BB8CF17002DB024 /* Offline_TimeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40076FA81BB8CF17002DB024 /* Offline_TimeTests.swift */; }; 13 | 40076FB31BB8CF2A002DB024 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 40076FB21BB8CF2A002DB024 /* MainMenu.xib */; }; 14 | 40076FB71BB8D008002DB024 /* CoreWLAN.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 40076FB61BB8D008002DB024 /* CoreWLAN.framework */; }; 15 | 40076FBF1BB8D275002DB024 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 40076FBE1BB8D275002DB024 /* Cocoa.framework */; }; 16 | 40076FC51BB8DC36002DB024 /* PopupMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 40076FC41BB8DC36002DB024 /* PopupMenu.xib */; }; 17 | 40076FC71BB8DC9C002DB024 /* PopupMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40076FC61BB8DC9C002DB024 /* PopupMenu.swift */; }; 18 | 405558351BB9497A0044D232 /* PALoginItemUtility.m in Sources */ = {isa = PBXBuildFile; fileRef = 405558341BB9497A0044D232 /* PALoginItemUtility.m */; }; 19 | 405BEA6F1BBA644100F216CF /* AppConstantsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 405BEA6E1BBA644100F216CF /* AppConstantsManager.swift */; }; 20 | 405BEA711BBA645600F216CF /* AppConstants.plist in Resources */ = {isa = PBXBuildFile; fileRef = 405BEA701BBA645600F216CF /* AppConstants.plist */; }; 21 | 405BEA731BBA6E2A00F216CF /* CustomSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 405BEA721BBA6E2A00F216CF /* CustomSlider.swift */; }; 22 | 405BEA751BBA6E4A00F216CF /* CustomSliderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 405BEA741BBA6E4A00F216CF /* CustomSliderCell.swift */; }; 23 | 408B2E311BB8FB43001E3515 /* SliderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 408B2E301BB8FB43001E3515 /* SliderView.swift */; }; 24 | 408B2E3C1BB90FB3001E3515 /* SALViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 408B2E3B1BB90FB3001E3515 /* SALViewController.xib */; }; 25 | 408B2E401BB9111F001E3515 /* SALView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 408B2E3F1BB9111F001E3515 /* SALView.swift */; }; 26 | 408B2E451BB92381001E3515 /* ConfirmationText.plist in Resources */ = {isa = PBXBuildFile; fileRef = 408B2E441BB92381001E3515 /* ConfirmationText.plist */; }; 27 | 408B2E471BB923D5001E3515 /* ConfirmationTextManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 408B2E461BB923D5001E3515 /* ConfirmationTextManager.swift */; }; 28 | 408B2EB51BB93DCA001E3515 /* ServiceManagement.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 408B2EB41BB93DCA001E3515 /* ServiceManagement.framework */; }; 29 | 40A15C671BB8F44D004A7FE2 /* SliderViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 40A15C661BB8F44D004A7FE2 /* SliderViewController.xib */; }; 30 | 40CEF6471BBA7E810042CB6F /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40CEF6461BBA7E810042CB6F /* Reachability.swift */; }; 31 | 40FE29DB1BD7756E00E3D415 /* TweetViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 40FE29DA1BD7756E00E3D415 /* TweetViewController.xib */; settings = {ASSET_TAGS = (); }; }; 32 | 40FE29DD1BD775A600E3D415 /* TweetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40FE29DC1BD775A600E3D415 /* TweetView.swift */; settings = {ASSET_TAGS = (); }; }; 33 | /* End PBXBuildFile section */ 34 | 35 | /* Begin PBXContainerItemProxy section */ 36 | 40076FA31BB8CF17002DB024 /* PBXContainerItemProxy */ = { 37 | isa = PBXContainerItemProxy; 38 | containerPortal = 40076F881BB8CF17002DB024 /* Project object */; 39 | proxyType = 1; 40 | remoteGlobalIDString = 40076F8F1BB8CF17002DB024; 41 | remoteInfo = "Offline Time"; 42 | }; 43 | /* End PBXContainerItemProxy section */ 44 | 45 | /* Begin PBXFileReference section */ 46 | 40076F901BB8CF17002DB024 /* Offline Time.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Offline Time.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | 40076F941BB8CF17002DB024 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 48 | 40076F951BB8CF17002DB024 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 49 | 40076F991BB8CF17002DB024 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 50 | 40076FA21BB8CF17002DB024 /* Offline TimeTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Offline TimeTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | 40076FA71BB8CF17002DB024 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 52 | 40076FA81BB8CF17002DB024 /* Offline_TimeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Offline_TimeTests.swift; sourceTree = ""; }; 53 | 40076FB21BB8CF2A002DB024 /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = MainMenu.xib; path = ../MainMenu.xib; sourceTree = ""; }; 54 | 40076FB61BB8D008002DB024 /* CoreWLAN.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreWLAN.framework; path = System/Library/Frameworks/CoreWLAN.framework; sourceTree = SDKROOT; }; 55 | 40076FBA1BB8D1F3002DB024 /* Offline Time-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Offline Time-Bridging-Header.h"; sourceTree = ""; }; 56 | 40076FBE1BB8D275002DB024 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 57 | 40076FC41BB8DC36002DB024 /* PopupMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PopupMenu.xib; sourceTree = ""; }; 58 | 40076FC61BB8DC9C002DB024 /* PopupMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PopupMenu.swift; sourceTree = ""; }; 59 | 405558331BB9497A0044D232 /* PALoginItemUtility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PALoginItemUtility.h; sourceTree = ""; }; 60 | 405558341BB9497A0044D232 /* PALoginItemUtility.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PALoginItemUtility.m; sourceTree = ""; }; 61 | 405BEA6E1BBA644100F216CF /* AppConstantsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppConstantsManager.swift; sourceTree = ""; }; 62 | 405BEA701BBA645600F216CF /* AppConstants.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = AppConstants.plist; sourceTree = ""; }; 63 | 405BEA721BBA6E2A00F216CF /* CustomSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomSlider.swift; sourceTree = ""; }; 64 | 405BEA741BBA6E4A00F216CF /* CustomSliderCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomSliderCell.swift; sourceTree = ""; }; 65 | 408B2E301BB8FB43001E3515 /* SliderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderView.swift; sourceTree = ""; }; 66 | 408B2E3B1BB90FB3001E3515 /* SALViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SALViewController.xib; sourceTree = ""; }; 67 | 408B2E3F1BB9111F001E3515 /* SALView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SALView.swift; sourceTree = ""; }; 68 | 408B2E441BB92381001E3515 /* ConfirmationText.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = ConfirmationText.plist; sourceTree = ""; }; 69 | 408B2E461BB923D5001E3515 /* ConfirmationTextManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfirmationTextManager.swift; sourceTree = ""; }; 70 | 408B2EB41BB93DCA001E3515 /* ServiceManagement.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ServiceManagement.framework; path = System/Library/Frameworks/ServiceManagement.framework; sourceTree = SDKROOT; }; 71 | 40A15C661BB8F44D004A7FE2 /* SliderViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SliderViewController.xib; sourceTree = ""; }; 72 | 40CEF6461BBA7E810042CB6F /* Reachability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reachability.swift; sourceTree = ""; }; 73 | 40FA797A1BBA29F800A5A8E8 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; 74 | 40FE29DA1BD7756E00E3D415 /* TweetViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TweetViewController.xib; sourceTree = ""; }; 75 | 40FE29DC1BD775A600E3D415 /* TweetView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweetView.swift; sourceTree = ""; }; 76 | /* End PBXFileReference section */ 77 | 78 | /* Begin PBXFrameworksBuildPhase section */ 79 | 40076F8D1BB8CF17002DB024 /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | 408B2EB51BB93DCA001E3515 /* ServiceManagement.framework in Frameworks */, 84 | 40076FBF1BB8D275002DB024 /* Cocoa.framework in Frameworks */, 85 | 40076FB71BB8D008002DB024 /* CoreWLAN.framework in Frameworks */, 86 | ); 87 | runOnlyForDeploymentPostprocessing = 0; 88 | }; 89 | 40076F9F1BB8CF17002DB024 /* Frameworks */ = { 90 | isa = PBXFrameworksBuildPhase; 91 | buildActionMask = 2147483647; 92 | files = ( 93 | ); 94 | runOnlyForDeploymentPostprocessing = 0; 95 | }; 96 | /* End PBXFrameworksBuildPhase section */ 97 | 98 | /* Begin PBXGroup section */ 99 | 40076F871BB8CF17002DB024 = { 100 | isa = PBXGroup; 101 | children = ( 102 | 40FA797A1BBA29F800A5A8E8 /* SystemConfiguration.framework */, 103 | 408B2EB41BB93DCA001E3515 /* ServiceManagement.framework */, 104 | 40076FBE1BB8D275002DB024 /* Cocoa.framework */, 105 | 40076FB61BB8D008002DB024 /* CoreWLAN.framework */, 106 | 40076F921BB8CF17002DB024 /* Offline Time */, 107 | 40076FA51BB8CF17002DB024 /* Offline TimeTests */, 108 | 40076F911BB8CF17002DB024 /* Products */, 109 | ); 110 | sourceTree = ""; 111 | }; 112 | 40076F911BB8CF17002DB024 /* Products */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 40076F901BB8CF17002DB024 /* Offline Time.app */, 116 | 40076FA21BB8CF17002DB024 /* Offline TimeTests.xctest */, 117 | ); 118 | name = Products; 119 | sourceTree = ""; 120 | }; 121 | 40076F921BB8CF17002DB024 /* Offline Time */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 40076FBA1BB8D1F3002DB024 /* Offline Time-Bridging-Header.h */, 125 | 40076F951BB8CF17002DB024 /* AppDelegate.swift */, 126 | 405BEA6E1BBA644100F216CF /* AppConstantsManager.swift */, 127 | 40076FC61BB8DC9C002DB024 /* PopupMenu.swift */, 128 | 408B2E461BB923D5001E3515 /* ConfirmationTextManager.swift */, 129 | 408B2E3F1BB9111F001E3515 /* SALView.swift */, 130 | 408B2E301BB8FB43001E3515 /* SliderView.swift */, 131 | 40FE29DC1BD775A600E3D415 /* TweetView.swift */, 132 | 40076FB21BB8CF2A002DB024 /* MainMenu.xib */, 133 | 40076FC41BB8DC36002DB024 /* PopupMenu.xib */, 134 | 408B2E3B1BB90FB3001E3515 /* SALViewController.xib */, 135 | 40A15C661BB8F44D004A7FE2 /* SliderViewController.xib */, 136 | 40FE29DA1BD7756E00E3D415 /* TweetViewController.xib */, 137 | 405BEA721BBA6E2A00F216CF /* CustomSlider.swift */, 138 | 405BEA741BBA6E4A00F216CF /* CustomSliderCell.swift */, 139 | 40CEF6461BBA7E810042CB6F /* Reachability.swift */, 140 | 40076F991BB8CF17002DB024 /* Images.xcassets */, 141 | 405558321BB9497A0044D232 /* Vendor */, 142 | 40076F931BB8CF17002DB024 /* Supporting Files */, 143 | ); 144 | path = "Offline Time"; 145 | sourceTree = ""; 146 | }; 147 | 40076F931BB8CF17002DB024 /* Supporting Files */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 405BEA701BBA645600F216CF /* AppConstants.plist */, 151 | 40076F941BB8CF17002DB024 /* Info.plist */, 152 | 408B2E441BB92381001E3515 /* ConfirmationText.plist */, 153 | ); 154 | name = "Supporting Files"; 155 | sourceTree = ""; 156 | }; 157 | 40076FA51BB8CF17002DB024 /* Offline TimeTests */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | 40076FA81BB8CF17002DB024 /* Offline_TimeTests.swift */, 161 | 40076FA61BB8CF17002DB024 /* Supporting Files */, 162 | ); 163 | path = "Offline TimeTests"; 164 | sourceTree = ""; 165 | }; 166 | 40076FA61BB8CF17002DB024 /* Supporting Files */ = { 167 | isa = PBXGroup; 168 | children = ( 169 | 40076FA71BB8CF17002DB024 /* Info.plist */, 170 | ); 171 | name = "Supporting Files"; 172 | sourceTree = ""; 173 | }; 174 | 405558321BB9497A0044D232 /* Vendor */ = { 175 | isa = PBXGroup; 176 | children = ( 177 | 405558331BB9497A0044D232 /* PALoginItemUtility.h */, 178 | 405558341BB9497A0044D232 /* PALoginItemUtility.m */, 179 | ); 180 | path = Vendor; 181 | sourceTree = ""; 182 | }; 183 | /* End PBXGroup section */ 184 | 185 | /* Begin PBXNativeTarget section */ 186 | 40076F8F1BB8CF17002DB024 /* Offline Time */ = { 187 | isa = PBXNativeTarget; 188 | buildConfigurationList = 40076FAC1BB8CF17002DB024 /* Build configuration list for PBXNativeTarget "Offline Time" */; 189 | buildPhases = ( 190 | 40076F8C1BB8CF17002DB024 /* Sources */, 191 | 40076F8D1BB8CF17002DB024 /* Frameworks */, 192 | 40076F8E1BB8CF17002DB024 /* Resources */, 193 | ); 194 | buildRules = ( 195 | ); 196 | dependencies = ( 197 | ); 198 | name = "Offline Time"; 199 | productName = "Offline Time"; 200 | productReference = 40076F901BB8CF17002DB024 /* Offline Time.app */; 201 | productType = "com.apple.product-type.application"; 202 | }; 203 | 40076FA11BB8CF17002DB024 /* Offline TimeTests */ = { 204 | isa = PBXNativeTarget; 205 | buildConfigurationList = 40076FAF1BB8CF17002DB024 /* Build configuration list for PBXNativeTarget "Offline TimeTests" */; 206 | buildPhases = ( 207 | 40076F9E1BB8CF17002DB024 /* Sources */, 208 | 40076F9F1BB8CF17002DB024 /* Frameworks */, 209 | 40076FA01BB8CF17002DB024 /* Resources */, 210 | ); 211 | buildRules = ( 212 | ); 213 | dependencies = ( 214 | 40076FA41BB8CF17002DB024 /* PBXTargetDependency */, 215 | ); 216 | name = "Offline TimeTests"; 217 | productName = "Offline TimeTests"; 218 | productReference = 40076FA21BB8CF17002DB024 /* Offline TimeTests.xctest */; 219 | productType = "com.apple.product-type.bundle.unit-test"; 220 | }; 221 | /* End PBXNativeTarget section */ 222 | 223 | /* Begin PBXProject section */ 224 | 40076F881BB8CF17002DB024 /* Project object */ = { 225 | isa = PBXProject; 226 | attributes = { 227 | LastSwiftMigration = 0700; 228 | LastSwiftUpdateCheck = 0700; 229 | LastUpgradeCheck = 0700; 230 | ORGANIZATIONNAME = 96Problems; 231 | TargetAttributes = { 232 | 40076F8F1BB8CF17002DB024 = { 233 | CreatedOnToolsVersion = 6.4; 234 | }; 235 | 40076FA11BB8CF17002DB024 = { 236 | CreatedOnToolsVersion = 6.4; 237 | TestTargetID = 40076F8F1BB8CF17002DB024; 238 | }; 239 | }; 240 | }; 241 | buildConfigurationList = 40076F8B1BB8CF17002DB024 /* Build configuration list for PBXProject "Offline Time" */; 242 | compatibilityVersion = "Xcode 3.2"; 243 | developmentRegion = English; 244 | hasScannedForEncodings = 0; 245 | knownRegions = ( 246 | en, 247 | Base, 248 | ); 249 | mainGroup = 40076F871BB8CF17002DB024; 250 | productRefGroup = 40076F911BB8CF17002DB024 /* Products */; 251 | projectDirPath = ""; 252 | projectRoot = ""; 253 | targets = ( 254 | 40076F8F1BB8CF17002DB024 /* Offline Time */, 255 | 40076FA11BB8CF17002DB024 /* Offline TimeTests */, 256 | ); 257 | }; 258 | /* End PBXProject section */ 259 | 260 | /* Begin PBXResourcesBuildPhase section */ 261 | 40076F8E1BB8CF17002DB024 /* Resources */ = { 262 | isa = PBXResourcesBuildPhase; 263 | buildActionMask = 2147483647; 264 | files = ( 265 | 40FE29DB1BD7756E00E3D415 /* TweetViewController.xib in Resources */, 266 | 40076F9A1BB8CF17002DB024 /* Images.xcassets in Resources */, 267 | 40A15C671BB8F44D004A7FE2 /* SliderViewController.xib in Resources */, 268 | 40076FB31BB8CF2A002DB024 /* MainMenu.xib in Resources */, 269 | 40076FC51BB8DC36002DB024 /* PopupMenu.xib in Resources */, 270 | 405BEA711BBA645600F216CF /* AppConstants.plist in Resources */, 271 | 408B2E451BB92381001E3515 /* ConfirmationText.plist in Resources */, 272 | 408B2E3C1BB90FB3001E3515 /* SALViewController.xib in Resources */, 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | }; 276 | 40076FA01BB8CF17002DB024 /* Resources */ = { 277 | isa = PBXResourcesBuildPhase; 278 | buildActionMask = 2147483647; 279 | files = ( 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | }; 283 | /* End PBXResourcesBuildPhase section */ 284 | 285 | /* Begin PBXSourcesBuildPhase section */ 286 | 40076F8C1BB8CF17002DB024 /* Sources */ = { 287 | isa = PBXSourcesBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | 40076FC71BB8DC9C002DB024 /* PopupMenu.swift in Sources */, 291 | 408B2E471BB923D5001E3515 /* ConfirmationTextManager.swift in Sources */, 292 | 408B2E401BB9111F001E3515 /* SALView.swift in Sources */, 293 | 405BEA731BBA6E2A00F216CF /* CustomSlider.swift in Sources */, 294 | 40076F961BB8CF17002DB024 /* AppDelegate.swift in Sources */, 295 | 405BEA6F1BBA644100F216CF /* AppConstantsManager.swift in Sources */, 296 | 40FE29DD1BD775A600E3D415 /* TweetView.swift in Sources */, 297 | 405558351BB9497A0044D232 /* PALoginItemUtility.m in Sources */, 298 | 405BEA751BBA6E4A00F216CF /* CustomSliderCell.swift in Sources */, 299 | 40CEF6471BBA7E810042CB6F /* Reachability.swift in Sources */, 300 | 408B2E311BB8FB43001E3515 /* SliderView.swift in Sources */, 301 | ); 302 | runOnlyForDeploymentPostprocessing = 0; 303 | }; 304 | 40076F9E1BB8CF17002DB024 /* Sources */ = { 305 | isa = PBXSourcesBuildPhase; 306 | buildActionMask = 2147483647; 307 | files = ( 308 | 40076FA91BB8CF17002DB024 /* Offline_TimeTests.swift in Sources */, 309 | ); 310 | runOnlyForDeploymentPostprocessing = 0; 311 | }; 312 | /* End PBXSourcesBuildPhase section */ 313 | 314 | /* Begin PBXTargetDependency section */ 315 | 40076FA41BB8CF17002DB024 /* PBXTargetDependency */ = { 316 | isa = PBXTargetDependency; 317 | target = 40076F8F1BB8CF17002DB024 /* Offline Time */; 318 | targetProxy = 40076FA31BB8CF17002DB024 /* PBXContainerItemProxy */; 319 | }; 320 | /* End PBXTargetDependency section */ 321 | 322 | /* Begin XCBuildConfiguration section */ 323 | 40076FAA1BB8CF17002DB024 /* Debug */ = { 324 | isa = XCBuildConfiguration; 325 | buildSettings = { 326 | ALWAYS_SEARCH_USER_PATHS = NO; 327 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 328 | CLANG_CXX_LIBRARY = "libc++"; 329 | CLANG_ENABLE_MODULES = YES; 330 | CLANG_ENABLE_OBJC_ARC = YES; 331 | CLANG_WARN_BOOL_CONVERSION = YES; 332 | CLANG_WARN_CONSTANT_CONVERSION = YES; 333 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 334 | CLANG_WARN_EMPTY_BODY = YES; 335 | CLANG_WARN_ENUM_CONVERSION = YES; 336 | CLANG_WARN_INT_CONVERSION = YES; 337 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 338 | CLANG_WARN_UNREACHABLE_CODE = YES; 339 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 340 | CODE_SIGN_IDENTITY = "-"; 341 | COPY_PHASE_STRIP = NO; 342 | DEBUG_INFORMATION_FORMAT = dwarf; 343 | ENABLE_STRICT_OBJC_MSGSEND = YES; 344 | ENABLE_TESTABILITY = YES; 345 | GCC_C_LANGUAGE_STANDARD = gnu99; 346 | GCC_DYNAMIC_NO_PIC = NO; 347 | GCC_NO_COMMON_BLOCKS = YES; 348 | GCC_OPTIMIZATION_LEVEL = 0; 349 | GCC_PREPROCESSOR_DEFINITIONS = ( 350 | "DEBUG=1", 351 | "$(inherited)", 352 | ); 353 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 354 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 355 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 356 | GCC_WARN_UNDECLARED_SELECTOR = YES; 357 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 358 | GCC_WARN_UNUSED_FUNCTION = YES; 359 | GCC_WARN_UNUSED_VARIABLE = YES; 360 | MACOSX_DEPLOYMENT_TARGET = 10.10; 361 | MTL_ENABLE_DEBUG_INFO = YES; 362 | ONLY_ACTIVE_ARCH = YES; 363 | SDKROOT = macosx; 364 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 365 | }; 366 | name = Debug; 367 | }; 368 | 40076FAB1BB8CF17002DB024 /* Release */ = { 369 | isa = XCBuildConfiguration; 370 | buildSettings = { 371 | ALWAYS_SEARCH_USER_PATHS = NO; 372 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 373 | CLANG_CXX_LIBRARY = "libc++"; 374 | CLANG_ENABLE_MODULES = YES; 375 | CLANG_ENABLE_OBJC_ARC = YES; 376 | CLANG_WARN_BOOL_CONVERSION = YES; 377 | CLANG_WARN_CONSTANT_CONVERSION = YES; 378 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 379 | CLANG_WARN_EMPTY_BODY = YES; 380 | CLANG_WARN_ENUM_CONVERSION = YES; 381 | CLANG_WARN_INT_CONVERSION = YES; 382 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 383 | CLANG_WARN_UNREACHABLE_CODE = YES; 384 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 385 | CODE_SIGN_IDENTITY = "-"; 386 | COPY_PHASE_STRIP = NO; 387 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 388 | ENABLE_NS_ASSERTIONS = NO; 389 | ENABLE_STRICT_OBJC_MSGSEND = YES; 390 | GCC_C_LANGUAGE_STANDARD = gnu99; 391 | GCC_NO_COMMON_BLOCKS = YES; 392 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 393 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 394 | GCC_WARN_UNDECLARED_SELECTOR = YES; 395 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 396 | GCC_WARN_UNUSED_FUNCTION = YES; 397 | GCC_WARN_UNUSED_VARIABLE = YES; 398 | MACOSX_DEPLOYMENT_TARGET = 10.10; 399 | MTL_ENABLE_DEBUG_INFO = NO; 400 | SDKROOT = macosx; 401 | }; 402 | name = Release; 403 | }; 404 | 40076FAD1BB8CF17002DB024 /* Debug */ = { 405 | isa = XCBuildConfiguration; 406 | buildSettings = { 407 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 408 | CLANG_ENABLE_MODULES = YES; 409 | COMBINE_HIDPI_IMAGES = YES; 410 | INFOPLIST_FILE = "Offline Time/Info.plist"; 411 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 412 | OTHER_SWIFT_FLAGS = "-D DEBUG"; 413 | PRODUCT_BUNDLE_IDENTIFIER = "com.96Problems.$(PRODUCT_NAME:rfc1034identifier)"; 414 | PRODUCT_NAME = "$(TARGET_NAME)"; 415 | SWIFT_OBJC_BRIDGING_HEADER = "Offline Time/Offline Time-Bridging-Header.h"; 416 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 417 | }; 418 | name = Debug; 419 | }; 420 | 40076FAE1BB8CF17002DB024 /* Release */ = { 421 | isa = XCBuildConfiguration; 422 | buildSettings = { 423 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 424 | CLANG_ENABLE_MODULES = YES; 425 | COMBINE_HIDPI_IMAGES = YES; 426 | INFOPLIST_FILE = "Offline Time/Info.plist"; 427 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 428 | OTHER_SWIFT_FLAGS = "-D RELEASE"; 429 | PRODUCT_BUNDLE_IDENTIFIER = "com.96Problems.$(PRODUCT_NAME:rfc1034identifier)"; 430 | PRODUCT_NAME = "$(TARGET_NAME)"; 431 | SWIFT_OBJC_BRIDGING_HEADER = "Offline Time/Offline Time-Bridging-Header.h"; 432 | }; 433 | name = Release; 434 | }; 435 | 40076FB01BB8CF17002DB024 /* Debug */ = { 436 | isa = XCBuildConfiguration; 437 | buildSettings = { 438 | BUNDLE_LOADER = "$(TEST_HOST)"; 439 | COMBINE_HIDPI_IMAGES = YES; 440 | FRAMEWORK_SEARCH_PATHS = ( 441 | "$(DEVELOPER_FRAMEWORKS_DIR)", 442 | "$(inherited)", 443 | ); 444 | GCC_PREPROCESSOR_DEFINITIONS = ( 445 | "DEBUG=1", 446 | "$(inherited)", 447 | ); 448 | INFOPLIST_FILE = "Offline TimeTests/Info.plist"; 449 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 450 | PRODUCT_BUNDLE_IDENTIFIER = "com.96Problems.$(PRODUCT_NAME:rfc1034identifier)"; 451 | PRODUCT_NAME = "$(TARGET_NAME)"; 452 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Offline Time.app/Contents/MacOS/Offline Time"; 453 | }; 454 | name = Debug; 455 | }; 456 | 40076FB11BB8CF17002DB024 /* Release */ = { 457 | isa = XCBuildConfiguration; 458 | buildSettings = { 459 | BUNDLE_LOADER = "$(TEST_HOST)"; 460 | COMBINE_HIDPI_IMAGES = YES; 461 | FRAMEWORK_SEARCH_PATHS = ( 462 | "$(DEVELOPER_FRAMEWORKS_DIR)", 463 | "$(inherited)", 464 | ); 465 | INFOPLIST_FILE = "Offline TimeTests/Info.plist"; 466 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 467 | PRODUCT_BUNDLE_IDENTIFIER = "com.96Problems.$(PRODUCT_NAME:rfc1034identifier)"; 468 | PRODUCT_NAME = "$(TARGET_NAME)"; 469 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Offline Time.app/Contents/MacOS/Offline Time"; 470 | }; 471 | name = Release; 472 | }; 473 | /* End XCBuildConfiguration section */ 474 | 475 | /* Begin XCConfigurationList section */ 476 | 40076F8B1BB8CF17002DB024 /* Build configuration list for PBXProject "Offline Time" */ = { 477 | isa = XCConfigurationList; 478 | buildConfigurations = ( 479 | 40076FAA1BB8CF17002DB024 /* Debug */, 480 | 40076FAB1BB8CF17002DB024 /* Release */, 481 | ); 482 | defaultConfigurationIsVisible = 0; 483 | defaultConfigurationName = Release; 484 | }; 485 | 40076FAC1BB8CF17002DB024 /* Build configuration list for PBXNativeTarget "Offline Time" */ = { 486 | isa = XCConfigurationList; 487 | buildConfigurations = ( 488 | 40076FAD1BB8CF17002DB024 /* Debug */, 489 | 40076FAE1BB8CF17002DB024 /* Release */, 490 | ); 491 | defaultConfigurationIsVisible = 0; 492 | defaultConfigurationName = Release; 493 | }; 494 | 40076FAF1BB8CF17002DB024 /* Build configuration list for PBXNativeTarget "Offline TimeTests" */ = { 495 | isa = XCConfigurationList; 496 | buildConfigurations = ( 497 | 40076FB01BB8CF17002DB024 /* Debug */, 498 | 40076FB11BB8CF17002DB024 /* Release */, 499 | ); 500 | defaultConfigurationIsVisible = 0; 501 | defaultConfigurationName = Release; 502 | }; 503 | /* End XCConfigurationList section */ 504 | }; 505 | rootObject = 40076F881BB8CF17002DB024 /* Project object */; 506 | } 507 | -------------------------------------------------------------------------------- /Offline Time.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Offline Time/AppConstants.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FirstDraftURL 6 | http://www.96problems.com/first-draft 7 | 96ProblemsURL 8 | http://www.96problems.com/ 9 | TweetURL 10 | https://www.twitter.com/intent/tweet?text=Completed🕑{{ minutes }} of Offline Time doing […insert your activity…] ! Give it a go and see how much you'll get done! http://96problems.com/offline-time&source=webclient 11 | SuggestTweetMsg 12 | Congrats! Would you like to tweet about your accomplishment? 13 | CONGRATULATION_MSG 14 | Congrats! You completed O.T. of {{ minutes }}🕒 15 | TOO_BAD_MSG 16 | Hmm… Too bad you missed your goal of {{ minutes }}😔 17 | DENY_MSG 18 | Uh uh uh! {{ minutes }}😔 19 | 20 | 21 | -------------------------------------------------------------------------------- /Offline Time/AppConstantsManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppConstantsManager.swift 3 | // First Draft 4 | // 5 | // Created by Naoto Ida on 9/18/15. 6 | // Copyright (c) 2015 96 Problems. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class AppConstantsManager { 12 | static let sharedInstance = AppConstantsManager() 13 | var constants = NSMutableDictionary() 14 | let plist = NSBundle.mainBundle().pathForResource("AppConstants", ofType: "plist") 15 | 16 | init() { 17 | self.constants = NSMutableDictionary(contentsOfFile: self.plist!)! 18 | } 19 | 20 | func value(key: String) -> AnyObject { 21 | return self.constants.valueForKey(key)! 22 | } 23 | } -------------------------------------------------------------------------------- /Offline Time/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Offline Time 4 | // 5 | // Created by Naoto Ida on 9/28/15. 6 | // Copyright (c) 2015 96Problems. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Foundation 11 | import CoreWLAN 12 | import ServiceManagement 13 | 14 | @NSApplicationMain 15 | class AppDelegate: NSObject, NSApplicationDelegate { 16 | let defaults = NSUserDefaults.standardUserDefaults() 17 | let mainBundle = NSBundle.mainBundle() 18 | var helperBundle: NSBundle! 19 | let constants = AppConstantsManager.sharedInstance 20 | let appName = NSBundle.mainBundle().objectForInfoDictionaryKey(kCFBundleNameKey as String) as! String 21 | 22 | var statusItem: NSStatusItem? 23 | var sliderView: SliderView? 24 | var popupMenu: PopupMenu? 25 | 26 | var menuViewController: NSViewController? 27 | var timer: NSTimer? 28 | var secondsTimer: NSTimer? 29 | var runningInfinitely = false 30 | var confTextManager: ConfirmationTextManager? 31 | var wifiIconIsHidden = false 32 | 33 | func applicationDidFinishLaunching(aNotification: NSNotification) { 34 | self.setupStatusItem() 35 | } 36 | 37 | func applicationShouldTerminate(sender: NSApplication) -> NSApplicationTerminateReply { 38 | return NSApplicationTerminateReply.TerminateNow 39 | } 40 | 41 | func applicationWillTerminate(aNotification: NSNotification) { 42 | } 43 | 44 | func setupStatusItem() { 45 | if self.statusItem == nil { 46 | self.statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(-2) 47 | } 48 | if let button = self.statusItem!.button { 49 | button.image = NSImage(named: "StatusBarButtonImage") 50 | button.image?.template = true 51 | } 52 | 53 | // Get menu 54 | var views: NSArray? 55 | NSBundle.mainBundle().loadNibNamed("PopupMenu", owner: self, topLevelObjects: &views) 56 | var menu: PopupMenu! 57 | for view in views! { 58 | if view.isMemberOfClass(PopupMenu) { 59 | menu = view as! PopupMenu 60 | } 61 | } 62 | self.popupMenu = menu 63 | 64 | // Get sliderView 65 | var views2: NSArray? 66 | NSBundle.mainBundle().loadNibNamed("SliderViewController", owner: self, topLevelObjects: &views2) 67 | for view in views2! { 68 | if view.isMemberOfClass(SliderView) { 69 | self.sliderView = view as? SliderView 70 | } 71 | } 72 | self.popupMenu!.sliderMenuItem.view = sliderView 73 | 74 | 75 | // Start at Login 76 | var salView: SALView! 77 | var views3: NSArray? 78 | NSBundle.mainBundle().loadNibNamed("SALViewController", owner: self, topLevelObjects: &views3) 79 | for view in views3! { 80 | if view.isMemberOfClass(SALView) { 81 | salView = view as! SALView 82 | } 83 | } 84 | 85 | self.popupMenu!.startAtLoginMenuItem.view = salView 86 | (self.popupMenu!.startAtLoginMenuItem.view as! SALView).customDelegate = self.popupMenu 87 | let shouldStartOnStartup = PALoginItemUtility.isCurrentApplicatonInLoginItems() 88 | if shouldStartOnStartup { 89 | salView.button.integerValue = 1 90 | } 91 | 92 | 93 | // Tweet 94 | var tweetView: TweetView! 95 | var views4: NSArray? 96 | NSBundle.mainBundle().loadNibNamed("TweetViewController", owner: self, topLevelObjects: &views4) 97 | for view in views4! { 98 | if view.isMemberOfClass(TweetView) { 99 | tweetView = view as! TweetView 100 | } 101 | } 102 | self.popupMenu!.toggleTweetMenuItem.view = tweetView 103 | if self.defaults.boolForKey("SHOULD_PROMPT_TWEET") { 104 | (self.popupMenu!.toggleTweetMenuItem.view as! TweetView).button.integerValue = 1 105 | } else { 106 | if !self.defaults.boolForKey("LAUNCHED_PREVIOUSLY") { 107 | self.defaults.setBool(true, forKey: "LAUNCHED_PREVIOUSLY") 108 | (self.popupMenu!.toggleTweetMenuItem.view as! TweetView).button.integerValue = 1 109 | } else { 110 | (self.popupMenu!.toggleTweetMenuItem.view as! TweetView).button.integerValue = 0 111 | } 112 | } 113 | 114 | 115 | // Start button 116 | let startButtonMenuItem = NSMenuItem(title: "Start Offline Time", action: "onSelectTime:", keyEquivalent: "") 117 | self.popupMenu?.startMenuItem = startButtonMenuItem 118 | self.popupMenu?.insertItem(self.popupMenu!.startMenuItem, atIndex: 0) 119 | 120 | self.statusItem?.menu = self.popupMenu 121 | 122 | self.popupMenu?.customDelegate = self 123 | } 124 | 125 | // Wifi 126 | func hideWifiIcon() { 127 | // 128 | // println("Hiding wifi icon") 129 | // let systemUIServer = self.defaults.persistentDomainForName("com.apple.systemuiserver") as! [String: AnyObject] 130 | // var menuItems = systemUIServer["menuExtras"] as! [String] 131 | // var wasActuallyAtZero = false 132 | // var i = 0 133 | // var wifiPos = 0 134 | // for menuItem in menuItems { 135 | // if menuItem == "/System/Library/CoreServices/Menu Extras/AirPort.menu" && i != 0 { 136 | // println("Wi-fi icon is shown at index \(i)") 137 | // wifiPos = i 138 | // } else if menuItem == "/System/Library/CoreServices/Menu Extras/AirPort.menu" && i == 0 { 139 | // println("Wi-fi icon is hown at index 0") 140 | // wifiPos = 0 141 | // wasActuallyAtZero = true 142 | // } 143 | // i++ 144 | // } 145 | // if wifiPos != 0 || (wifiPos == 0 && wasActuallyAtZero) { 146 | // menuItems.removeAtIndex(wifiPos) 147 | // self.wifiIconIsHidden = true 148 | // var newSystemUIServer: NSMutableDictionary = NSMutableDictionary(dictionary: systemUIServer) 149 | // newSystemUIServer.setValue(menuItems, forKey: "menuExtras") 150 | // self.defaults.setPersistentDomain(newSystemUIServer as [NSObject : AnyObject], forName: "com.apple.systemuiserver") 151 | // 152 | // // Run shell command to restart SystemUIServer 153 | // let task = NSTask() 154 | // task.launchPath = "/bin/bash" 155 | // task.arguments = ["-c", "killall SystemUIServer -HUP"] 156 | // task.launch() 157 | // } else { 158 | // println("No need to hide it because it already is") 159 | // } 160 | } 161 | 162 | func showWifiIcon() { 163 | // println("Showing wifi icon") 164 | // let systemUIServer = self.defaults.persistentDomainForName("com.apple.systemuiserver") as! [String: AnyObject] 165 | // var menuItems = systemUIServer["menuExtras"] as! [String] 166 | // println(menuItems) 167 | // var wifiIconIsAlreadyShown = false 168 | // for menuItem in menuItems { 169 | // if menuItem == "/System/Library/CoreServices/Menu Extras/AirPort.menu" { 170 | // wifiIconIsAlreadyShown = true 171 | // } 172 | // } 173 | // 174 | // if !wifiIconIsAlreadyShown { 175 | // // Run shell command to show wifi icon 176 | // let task = NSTask() 177 | // task.launchPath = "/bin/bash" 178 | // task.arguments = ["-c", "defaults write com.apple.systemuiserver menuExtras -array-add '/System/Library/CoreServices/Menu Extras/Airport.menu'"] 179 | // task.launch() 180 | // 181 | // let task2 = NSTask() 182 | // task2.launchPath = "/bin/bash" 183 | // task2.arguments = ["-c", "killall SystemUIServer -HUP"] 184 | // task2.launch() 185 | // } 186 | // self.wifiIconIsHidden = false 187 | } 188 | 189 | func stopWifi() { 190 | // if let button = self.statusItem!.button { 191 | // button.image = NSImage(named: "StatusBarButtonImage2") 192 | // button.image?.setTemplate(true) 193 | // } 194 | let iN = CWWiFiClient.sharedWiFiClient().interface()!.interfaceName 195 | let wifi = CWWiFiClient.sharedWiFiClient().interfaceWithName(iN) 196 | do { 197 | try wifi!.setPower(false) 198 | } catch let error as NSError { 199 | print(error) 200 | } 201 | } 202 | 203 | func startWifi() { 204 | // if let button = self.statusItem!.button { 205 | // button.image = NSImage(named: "StatusBarButtonImage") 206 | // button.image?.setTemplate(true) 207 | // } 208 | let iN = CWWiFiClient.sharedWiFiClient().interface()!.interfaceName 209 | let wifi = CWWiFiClient.sharedWiFiClient().interfaceWithName(iN) 210 | do { 211 | try wifi!.setPower(true) 212 | } catch let error as NSError { 213 | print(error) 214 | } 215 | } 216 | 217 | // MARK: - Actions 218 | @IBAction func onSelectTime(sender: AnyObject) { 219 | if self.timer == nil && !self.runningInfinitely { 220 | self.sliderView?.timeSlider.enabled = false 221 | self.hideWifiIcon() 222 | print("Starting timer with: \(self.sliderView?.requestedMinutes) Minutes.") 223 | // #if RELEASE 224 | self.stopWifi() 225 | // #endif 226 | // self.popupMenu?.itemAtIndex(3)?.enabled = false 227 | if self.sliderView?.requestedMinutes != -1 { 228 | self.sliderView?.confirmSelectedTime() 229 | self.startTimer() 230 | } else { 231 | self.runInfinitely() 232 | } 233 | } else { 234 | if self.confTextManager != nil { 235 | self.confTextManager?.counter++ 236 | if self.confTextManager!.texts.count + 1 > self.confTextManager!.counter { 237 | self.popupMenu?.startMenuItem.title = self.confTextManager!.getTextForCurrentCounter() 238 | } else { 239 | self.cancelTimer(shouldCongradulate: false) 240 | self.runningInfinitely = false 241 | } 242 | } 243 | } 244 | } 245 | 246 | func startTimer() { 247 | if self.confTextManager == nil { 248 | self.confTextManager = ConfirmationTextManager() 249 | } 250 | self.popupMenu?.quitMenuItem.hidden = true 251 | self.popupMenu?.startMenuItem.title = self.confTextManager!.getTextForCurrentCounter() 252 | self.sliderView?.updateSlider() 253 | // Check every 60 seconds 254 | self.timer = NSTimer.scheduledTimerWithTimeInterval(60.0, target: self, selector: "checkTimer", userInfo: nil, repeats: true) 255 | NSRunLoop.currentRunLoop().addTimer(self.timer!, forMode: NSDefaultRunLoopMode) 256 | NSRunLoop.currentRunLoop().addTimer(self.timer!, forMode: NSEventTrackingRunLoopMode) 257 | 258 | // Check every 1 second 259 | self.secondsTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "checkTimerEverySecond", userInfo: nil, repeats: true) 260 | NSRunLoop.currentRunLoop().addTimer(self.secondsTimer!, forMode: NSDefaultRunLoopMode) 261 | NSRunLoop.currentRunLoop().addTimer(self.secondsTimer!, forMode: NSEventTrackingRunLoopMode) 262 | } 263 | 264 | func checkTimer() { 265 | self.sliderView?.minutesRemaining-- 266 | print("Time remaining: \(self.sliderView?.minutesRemaining) Minutes.") 267 | 268 | self.sliderView?.updateSlider() 269 | 270 | if self.sliderView?.minutesRemaining <= 0 { 271 | self.cancelTimer(shouldCongradulate: true) 272 | print("Done!") 273 | } 274 | } 275 | 276 | func checkTimerEverySecond() { 277 | self.sliderView?.secondsRemaining-- 278 | self.sliderView?.updateTimerText() 279 | 280 | guard self.sliderView!.secondsRemaining + 5 < self.sliderView?.requestedSeconds else { 281 | return 282 | } 283 | if Reachability.isConnectedToNetwork() { 284 | // No .... 285 | //self.cancelTimer(shouldCongradulate: false) 286 | self.forceContinuationOfTimer() 287 | } else { 288 | // Keep it up! 289 | } 290 | } 291 | 292 | func forceContinuationOfTimer() { 293 | self.stopWifi() 294 | self.showNotificationOnQuitAttempt() 295 | } 296 | 297 | func runInfinitely() { 298 | print("Running infinitely") 299 | if self.confTextManager == nil { 300 | self.confTextManager = ConfirmationTextManager() 301 | } 302 | self.runningInfinitely = true 303 | self.popupMenu?.quitMenuItem.hidden = true 304 | self.popupMenu?.startMenuItem.title = self.confTextManager!.getTextForCurrentCounter() 305 | self.sliderView?.remainingLabel.stringValue = "Running Infinitely" 306 | } 307 | 308 | func cancelTimer(shouldCongradulate shouldCongradulate: Bool) { 309 | print("Canceling timer") 310 | self.popupMenu?.quitMenuItem.hidden = false 311 | self.timer?.invalidate() 312 | self.timer = nil 313 | self.secondsTimer?.invalidate() 314 | self.secondsTimer = nil 315 | self.sliderView?.timeSlider.enabled = true 316 | self.popupMenu?.startMenuItem.enabled = true 317 | // #if RELEASE 318 | self.startWifi() 319 | // #endif 320 | self.popupMenu?.startMenuItem.title = "Start \(self.appName)" 321 | 322 | if shouldCongradulate { 323 | self.showNotificationOnTimerCompletion() 324 | } else if !shouldCongradulate && (self.confTextManager?.counter == self.confTextManager!.texts.count + 1) { 325 | } else { 326 | self.showNotificationOnWifiOn() 327 | } 328 | self.sliderView?.remainingLabel.stringValue = "\(self.appName): 10 Minutes" 329 | self.sliderView?.timeSlider.integerValue = 1 330 | self.confTextManager?.counter = 1 331 | self.sliderView?.resetTimes() 332 | self.confTextManager = nil 333 | } 334 | 335 | func showNotificationOnTimerCompletion() { 336 | self.notify(self.constants.value("CONGRATULATION_MSG") as! String) 337 | self.promptForTweet() 338 | } 339 | 340 | func showNotificationOnWifiOn() { 341 | self.notify(self.constants.value("TOO_BAD_MSG") as! String) 342 | } 343 | 344 | func showNotificationOnQuitAttempt() { 345 | self.notify(self.constants.value("DENY_MSG") as! String) 346 | } 347 | 348 | func notify(text: String) { 349 | let notification = NSUserNotification() 350 | notification.title = self.appName 351 | notification.informativeText = text.replaceCustomTagWithRequestedMinutes(self.sliderView!.convertMinutesIntoRegularFormat(self.sliderView!.requestedMinutes)) 352 | NSUserNotificationCenter.defaultUserNotificationCenter().deliverNotification(notification) 353 | } 354 | 355 | func promptForTweet() { 356 | if self.defaults.boolForKey("SHOULD_PROMPT_TWEET") { 357 | 358 | // Just in case someone completes infinite... 359 | guard self.sliderView!.requestedMinutes > 0 else { return } 360 | 361 | let alert = NSAlert() 362 | let askMsg = self.constants.value("SuggestTweetMsg") as? NSString 363 | alert.messageText = askMsg!.stringByReplacingOccurrencesOfString("\\n", withString: "\n") 364 | alert.addButtonWithTitle("Tweet") 365 | alert.addButtonWithTitle("Cancel") 366 | switch alert.runModal() { 367 | case NSAlertFirstButtonReturn: 368 | let rawTweetURL = self.constants.value("TweetURL") as! String, 369 | tweetURL = rawTweetURL.replaceCustomTagWithRequestedMinutes("\(self.sliderView!.requestedMinutes)min"), 370 | // tweetURLstr = tweetURL.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)! 371 | tweetURLstr = tweetURL.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()) 372 | 373 | NSWorkspace.sharedWorkspace().openURL(NSURL(string: tweetURLstr!)!) 374 | break 375 | default: 376 | break 377 | } 378 | } 379 | } 380 | } 381 | 382 | extension AppDelegate: PopupMenuDelegate { 383 | func onToggleStartup(state: Int) { 384 | if state == 1 { 385 | PALoginItemUtility.addCurrentApplicatonToLoginItems() 386 | } else { 387 | PALoginItemUtility.removeCurrentApplicatonToLoginItems() 388 | } 389 | } 390 | 391 | func onRequestQuit() { 392 | NSApplication.sharedApplication().terminate(self) 393 | } 394 | } 395 | 396 | extension AppDelegate: NSUserNotificationCenterDelegate { 397 | func userNotificationCenter(center: NSUserNotificationCenter, didActivateNotification notification: NSUserNotification) { 398 | } 399 | func userNotificationCenter(center: NSUserNotificationCenter, shouldPresentNotification notification: NSUserNotification) -> Bool { 400 | return true 401 | } 402 | } 403 | 404 | extension String { 405 | func replaceCustomTagWithRequestedMinutes(minutes: String) -> String { 406 | return stringByReplacingOccurrencesOfString("{{ minutes }}", withString: "\(minutes)") 407 | } 408 | } -------------------------------------------------------------------------------- /Offline Time/ConfirmationText.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Text1 6 | Cancel Offline Time? (5) 7 | Text2 8 | You want to give up? (4) 9 | Text3 10 | You can't do it? (3) 11 | Text4 12 | Are You Sure? (2) 13 | Text5 14 | Last chance..? (1) 15 | 16 | 17 | -------------------------------------------------------------------------------- /Offline Time/ConfirmationTextManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConfirmationTextManager.swift 3 | // Offline Time 4 | // 5 | // Created by Naoto Ida on 9/28/15. 6 | // Copyright (c) 2015 96Problems. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class ConfirmationTextManager { 12 | let resources = NSBundle.mainBundle() 13 | var counter = 1 14 | var texts = NSMutableDictionary() 15 | 16 | init() { 17 | self.getTexts() 18 | } 19 | 20 | func getTexts() { 21 | let plist = self.resources.pathForResource("ConfirmationText", ofType: "plist") 22 | self.texts = NSMutableDictionary(contentsOfFile: plist!)! 23 | } 24 | 25 | func getTextForCurrentCounter() -> String { 26 | return self.texts["Text\(self.counter)"] as! String 27 | } 28 | } -------------------------------------------------------------------------------- /Offline Time/CustomSlider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomSlider.swift 3 | // Offline Time 4 | // 5 | // Created by Naoto Ida on 9/29/15. 6 | // Copyright (c) 2015 96Problems. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class CustomSlider: NSSlider { 12 | 13 | override func drawRect(dirtyRect: NSRect) { 14 | super.drawRect(dirtyRect) 15 | } 16 | 17 | override func setNeedsDisplayInRect(invalidRect: NSRect) { 18 | super.setNeedsDisplayInRect(self.bounds) 19 | } 20 | 21 | @IBAction func darkModeChanged(sender: AnyObject) { 22 | self.needsDisplay = true 23 | } 24 | 25 | func setupListener() { 26 | NSDistributedNotificationCenter.defaultCenter().addObserver(self, selector: "darkModeChanged:", name: "AppleInterfaceThemeChangedNotification", object: nil) 27 | } 28 | 29 | deinit { 30 | NSDistributedNotificationCenter.defaultCenter().removeObserver(self) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Offline Time/CustomSliderCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomSliderCell.swift 3 | // Offline Time 4 | // 5 | // Created by Naoto Ida on 9/29/15. 6 | // Copyright (c) 2015 96Problems. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class CustomSliderCell: NSSliderCell { 12 | let dict: [String: AnyObject] = NSUserDefaults.standardUserDefaults().persistentDomainForName(NSGlobalDomain)! 13 | 14 | var activeColor = NSColor(calibratedRed: 236/255, green: 92/255, blue: 111/255, alpha: 1.0) 15 | var inactiveColor = NSColor(calibratedRed: 240/255, green: 240/255, blue: 240/255, alpha: 1.0) 16 | 17 | required init?(coder aDecoder: NSCoder) { 18 | super.init(coder: aDecoder) 19 | } 20 | 21 | override func drawKnob(knobRect: NSRect) { 22 | let style = self.dict["AppleInterfaceStyle"] 23 | var image = NSImage(named: "SliderKnob") 24 | if let darkModeOn = style as? String { 25 | print(darkModeOn) 26 | if darkModeOn == "Dark" || darkModeOn == "dark" { 27 | image = NSImage(named: "SliderKnobDark") 28 | } 29 | } 30 | // For some patterns 31 | // NSColor(patternImage: image!).set() 32 | // NSRectFill(NSMakeRect(knobRect.origin.x, knobRect.origin.y, 20, 20)) 33 | let x = knobRect.origin.x + (knobRect.size.width - image!.size.width) / 2 34 | let y = NSMaxY(knobRect) - (knobRect.size.height - image!.size.height) / 2 - 22 35 | image?.drawAtPoint(NSMakePoint(x, y), fromRect: NSZeroRect, operation: NSCompositingOperation.CompositeSourceOver, fraction: 1.0) 36 | } 37 | 38 | 39 | override func drawBarInside(aRect: NSRect, flipped: Bool) { 40 | let style = self.dict["AppleInterfaceStyle"] 41 | if let darkModeOn = style as? String { 42 | print(darkModeOn) 43 | if darkModeOn == "Dark" || darkModeOn == "dark" { 44 | self.activeColor = NSColor(calibratedRed: 255/255, green: 0/255, blue: 0/255, alpha: 1.0) 45 | } 46 | } 47 | 48 | 49 | var rect = aRect 50 | rect.size.height = CGFloat(5) 51 | let barRadius = CGFloat(2.5) 52 | let value = CGFloat((self.doubleValue - self.minValue) / (self.maxValue - self.minValue)) 53 | 54 | let finalWidth = CGFloat(value * (self.controlView!.frame.size.width - 8)) 55 | var leftRect = rect 56 | leftRect.size.width = finalWidth 57 | let bg = NSBezierPath(roundedRect: rect, xRadius: barRadius, yRadius: barRadius) 58 | self.inactiveColor.setFill() 59 | bg.fill() 60 | let active = NSBezierPath(roundedRect: leftRect, xRadius: barRadius, yRadius: barRadius) 61 | self.activeColor.setFill() 62 | active.fill() 63 | } 64 | 65 | // There are no ticks on me! 66 | override func rectOfTickMarkAtIndex(index: Int) -> NSRect { 67 | return NSZeroRect 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Offline Time/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "icon_16x16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "icon_16x16@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "icon_32x32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "icon_32x32@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "icon_128x128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "icon_128x128@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "icon_256x256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "icon_256x256@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "icon_512x512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "icon_512x512@2x.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Offline Time/Images.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/96-problems/Offline-Time/b5e062f59ab4f72147d3f7d3bbee1251ab0b5358/Offline Time/Images.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /Offline Time/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/96-problems/Offline-Time/b5e062f59ab4f72147d3f7d3bbee1251ab0b5358/Offline Time/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /Offline Time/Images.xcassets/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/96-problems/Offline-Time/b5e062f59ab4f72147d3f7d3bbee1251ab0b5358/Offline Time/Images.xcassets/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /Offline Time/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/96-problems/Offline-Time/b5e062f59ab4f72147d3f7d3bbee1251ab0b5358/Offline Time/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /Offline Time/Images.xcassets/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/96-problems/Offline-Time/b5e062f59ab4f72147d3f7d3bbee1251ab0b5358/Offline Time/Images.xcassets/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /Offline Time/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/96-problems/Offline-Time/b5e062f59ab4f72147d3f7d3bbee1251ab0b5358/Offline Time/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /Offline Time/Images.xcassets/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/96-problems/Offline-Time/b5e062f59ab4f72147d3f7d3bbee1251ab0b5358/Offline Time/Images.xcassets/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /Offline Time/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/96-problems/Offline-Time/b5e062f59ab4f72147d3f7d3bbee1251ab0b5358/Offline Time/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /Offline Time/Images.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/96-problems/Offline-Time/b5e062f59ab4f72147d3f7d3bbee1251ab0b5358/Offline Time/Images.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /Offline Time/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/96-problems/Offline-Time/b5e062f59ab4f72147d3f7d3bbee1251ab0b5358/Offline Time/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /Offline Time/Images.xcassets/SliderKnob.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "knob.pdf" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Offline Time/Images.xcassets/SliderKnob.imageset/knob.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/96-problems/Offline-Time/b5e062f59ab4f72147d3f7d3bbee1251ab0b5358/Offline Time/Images.xcassets/SliderKnob.imageset/knob.pdf -------------------------------------------------------------------------------- /Offline Time/Images.xcassets/SliderKnobDark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "dark-knob.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Offline Time/Images.xcassets/SliderKnobDark.imageset/dark-knob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/96-problems/Offline-Time/b5e062f59ab4f72147d3f7d3bbee1251ab0b5358/Offline Time/Images.xcassets/SliderKnobDark.imageset/dark-knob.png -------------------------------------------------------------------------------- /Offline Time/Images.xcassets/StatusBarButtonImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "menubar_icon.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Offline Time/Images.xcassets/StatusBarButtonImage.imageset/menubar_icon.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/96-problems/Offline-Time/b5e062f59ab4f72147d3f7d3bbee1251ab0b5358/Offline Time/Images.xcassets/StatusBarButtonImage.imageset/menubar_icon.pdf -------------------------------------------------------------------------------- /Offline Time/Images.xcassets/StatusBarButtonImage2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Status.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "Status@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Offline Time/Images.xcassets/StatusBarButtonImage2.imageset/Status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/96-problems/Offline-Time/b5e062f59ab4f72147d3f7d3bbee1251ab0b5358/Offline Time/Images.xcassets/StatusBarButtonImage2.imageset/Status.png -------------------------------------------------------------------------------- /Offline Time/Images.xcassets/StatusBarButtonImage2.imageset/Status@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/96-problems/Offline-Time/b5e062f59ab4f72147d3f7d3bbee1251ab0b5358/Offline Time/Images.xcassets/StatusBarButtonImage2.imageset/Status@2x.png -------------------------------------------------------------------------------- /Offline Time/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | LSUIElement 28 | 29 | NSHumanReadableCopyright 30 | Copyright © 2015 96Problems. All rights reserved. 31 | NSMainNibFile 32 | MainMenu 33 | NSPrincipalClass 34 | NSApplication 35 | 36 | 37 | -------------------------------------------------------------------------------- /Offline Time/Offline Time-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "PALoginItemUtility.h" -------------------------------------------------------------------------------- /Offline Time/PopupMenu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PopupMenu.swift 3 | // Offline Time 4 | // 5 | // Created by Naoto Ida on 9/28/15. 6 | // Copyright (c) 2015 96Problems. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | protocol PopupMenuDelegate { 12 | func onToggleStartup(state: Int) 13 | func onRequestQuit() 14 | } 15 | 16 | class PopupMenu: NSMenu { 17 | var customDelegate: PopupMenuDelegate? 18 | let constants = AppConstantsManager.sharedInstance 19 | 20 | @IBOutlet var firstDraftMenuItem: NSMenuItem! 21 | @IBOutlet var quitMenuItem: NSMenuItem! 22 | @IBOutlet var sliderMenuItem: NSMenuItem! 23 | @IBOutlet var startAtLoginMenuItem: NSMenuItem! 24 | @IBOutlet weak var toggleTweetMenuItem: NSMenuItem! 25 | 26 | var startMenuItem: NSMenuItem! 27 | 28 | required init?(coder aDecoder: NSCoder) { 29 | super.init(coder: aDecoder) 30 | } 31 | 32 | override func validateMenuItem(menuItem: NSMenuItem) -> Bool { 33 | return true 34 | } 35 | 36 | override func validateToolbarItem(theItem: NSToolbarItem) -> Bool { 37 | return true 38 | } 39 | 40 | @IBAction func toggleStartup(sender:AnyObject) { 41 | self.customDelegate?.onToggleStartup(sender.integerValue) 42 | } 43 | 44 | @IBAction func showFirstDraft(sender: AnyObject) { 45 | let url = NSURL(string: self.constants.value("FirstDraftURL") as! String) 46 | NSWorkspace.sharedWorkspace().openURL(url!) 47 | } 48 | 49 | @IBAction func show96Problems(sender: AnyObject) { 50 | let url = NSURL(string: self.constants.value("96ProblemsURL") as! String) 51 | NSWorkspace.sharedWorkspace().openURL(url!) 52 | } 53 | 54 | @IBAction func quitButtonPressed(sender: AnyObject) { 55 | self.customDelegate?.onRequestQuit() 56 | } 57 | } 58 | 59 | extension PopupMenu: SALViewDelegate { 60 | func toggledSetting(state: Int) { 61 | self.toggleStartup(state) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Offline Time/PopupMenu.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 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /Offline Time/Reachability.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Reachability.swift 3 | // First Draft 4 | // 5 | // Created by Naoto Ida on 8/18/15. 6 | // Copyright (c) 2015 96 Problems. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SystemConfiguration 11 | 12 | public class Reachability { 13 | 14 | class func isConnectedToNetwork() -> Bool { 15 | var zeroAddress = sockaddr_in() 16 | zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress)) 17 | zeroAddress.sin_family = sa_family_t(AF_INET) 18 | let defaultRouteReachability = withUnsafePointer(&zeroAddress) { 19 | SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0)) 20 | } 21 | var flags = SCNetworkReachabilityFlags() 22 | if !SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) { 23 | return false 24 | } 25 | let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0 26 | let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0 27 | return (isReachable && !needsConnection) 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /Offline Time/SALView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SALView.swift 3 | // Offline Time 4 | // 5 | // Created by Naoto Ida on 9/28/15. 6 | // Copyright (c) 2015 96Problems. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | protocol SALViewDelegate { 12 | func toggledSetting(state: Int) 13 | } 14 | 15 | class SALView: NSView { 16 | @IBOutlet var button: NSButton! 17 | 18 | var customDelegate: SALViewDelegate? 19 | 20 | override func drawRect(dirtyRect: NSRect) { 21 | super.drawRect(dirtyRect) 22 | } 23 | 24 | @IBAction func toggleSetting(sender: NSButton) { 25 | Swift.print("Start At Login setting: \(sender.integerValue)") 26 | self.customDelegate?.toggledSetting(sender.integerValue) 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Offline Time/SALViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Offline Time/SliderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SliderView.swift 3 | // Offline Time 4 | // 5 | // Created by Naoto Ida on 9/28/15. 6 | // Copyright (c) 2015 96Problems. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class SliderView: NSView { 12 | 13 | @IBOutlet var timeSlider: NSSlider! 14 | @IBOutlet var remainingLabel: NSTextField! 15 | 16 | var requestedMinutes = 10 17 | var minutesRemaining = 10 18 | var requestedSeconds = 600 19 | var secondsRemaining = 600 20 | 21 | override func drawRect(dirtyRect: NSRect) { 22 | super.drawRect(dirtyRect) 23 | } 24 | 25 | func resetTimes() { 26 | self.requestedMinutes = 10 27 | self.minutesRemaining = 10 28 | self.requestedSeconds = self.requestedMinutes * 60 29 | self.secondsRemaining = self.minutesRemaining * 60 30 | } 31 | 32 | @IBAction func timeChanged(sender: NSSlider) { 33 | self.requestedMinutes = self.calculatedMinutes(sender.integerValue) 34 | self.requestedSeconds = self.requestedMinutes * 60 35 | Swift.print("Requested minutes: \(self.requestedMinutes)") 36 | Swift.print("Requested seconds: \(self.requestedSeconds)") 37 | self.minutesRemaining = self.requestedMinutes 38 | self.secondsRemaining = self.requestedSeconds 39 | 40 | self.convertSecondsToHHMMSS(self.requestedSeconds) 41 | self.remainingLabel.stringValue = "Timer: \(self.convertMinutesIntoRegularFormat(self.requestedMinutes))" 42 | } 43 | 44 | func confirmSelectedTime() { 45 | self.requestedMinutes = self.calculatedMinutes(self.timeSlider.integerValue) 46 | self.minutesRemaining = self.requestedMinutes 47 | } 48 | 49 | func calculatedSeconds(sliderValue: Int) -> Int { 50 | // return 1 51 | if sliderValue < 7 { 52 | return sliderValue * 10 53 | } else if sliderValue == 7 { 54 | return 90 * 60 55 | } else if sliderValue == 8 { 56 | return 120 * 60 57 | } else if sliderValue == 9 { 58 | return 180 * 60 59 | } else if sliderValue == 10 { 60 | return 240 * 60 61 | } else if sliderValue == 11 { 62 | return 300 * 60 63 | } else if sliderValue == 12 { 64 | return 360 * 60 65 | } else if sliderValue == 13 { 66 | return 420 * 60 67 | } else if sliderValue == 14 { 68 | return 480 * 60 69 | } else if sliderValue == 15 { 70 | return 540 * 60 71 | } else if sliderValue == 16 { 72 | return 600 * 60 73 | } else if sliderValue == 17 { 74 | return 660 * 60 75 | } else if sliderValue == 18 { 76 | return 720 * 60 77 | } else if sliderValue == 19 { 78 | return 780 * 60 79 | } else if sliderValue == 20 { 80 | return 840 * 60 81 | } else if sliderValue == 21 { 82 | return 900 * 60 83 | } else if sliderValue == 22 { 84 | return 960 * 60 85 | } else if sliderValue == 23 { 86 | return 1020 * 60 87 | } else if sliderValue == 24 { 88 | return 1080 * 60 89 | } else if sliderValue == 25 { 90 | return 1140 * 60 91 | } else if sliderValue == 26 { 92 | return 1200 * 60 93 | } else if sliderValue == 27 { 94 | return 1260 * 60 95 | } else if sliderValue == 28 { 96 | return 1320 * 60 97 | } else if sliderValue == 29 { 98 | return 1380 * 60 99 | } else if sliderValue == 30 { 100 | return 1440 * 60 101 | } else if sliderValue == 31 { 102 | return -1 103 | } else { 104 | return 0 105 | } 106 | } 107 | 108 | func calculatedMinutes(sliderValue: Int) -> Int { 109 | // return 1 110 | if sliderValue < 7 { 111 | return sliderValue * 10 112 | } else if sliderValue == 7 { 113 | return 90 114 | } else if sliderValue == 8 { 115 | return 120 116 | } else if sliderValue == 9 { 117 | return 180 118 | } else if sliderValue == 10 { 119 | return 240 120 | } else if sliderValue == 11 { 121 | return 300 122 | } else if sliderValue == 12 { 123 | return 360 124 | } else if sliderValue == 13 { 125 | return 420 126 | } else if sliderValue == 14 { 127 | return 480 128 | } else if sliderValue == 15 { 129 | return 540 130 | } else if sliderValue == 16 { 131 | return 600 132 | } else if sliderValue == 17 { 133 | return 660 134 | } else if sliderValue == 18 { 135 | return 720 136 | } else if sliderValue == 19 { 137 | return 780 138 | } else if sliderValue == 20 { 139 | return 840 140 | } else if sliderValue == 21 { 141 | return 900 142 | } else if sliderValue == 22 { 143 | return 960 144 | } else if sliderValue == 23 { 145 | return 1020 146 | } else if sliderValue == 24 { 147 | return 1080 148 | } else if sliderValue == 25 { 149 | return 1140 150 | } else if sliderValue == 26 { 151 | return 1200 152 | } else if sliderValue == 27 { 153 | return 1260 154 | } else if sliderValue == 28 { 155 | return 1320 156 | } else if sliderValue == 29 { 157 | return 1380 158 | } else if sliderValue == 30 { 159 | return 1440 160 | } else if sliderValue == 31 { 161 | return -1 162 | } else { 163 | return 0 164 | } 165 | } 166 | 167 | func convertMinutesIntoRegularFormat(minutes: Int) -> String { 168 | if minutes < 60 && minutes != -1 { 169 | return "\(minutes) Minutes" 170 | } else if minutes == 60 { 171 | return "1 Hour" 172 | } else if minutes == 90 { 173 | return "1 Hour And 30 Minutes" 174 | } else if minutes == -1 { 175 | return "∞" 176 | } else { 177 | return "\(minutes/60) Hours" 178 | } 179 | } 180 | 181 | func updateTimerText() { 182 | // self.remainingLabel.stringValue = "Currently Remaining: \(self.convertMinutesIntoRegularFormat(self.minutesRemaining))" 183 | // if self.requestedMinutes == -1 { 184 | // self.remainingLabel.stringValue = "Running Infinitely." 185 | // } 186 | self.remainingLabel.stringValue = self.convertSecondsToHHMMSS(self.secondsRemaining) 187 | self.remainingLabel.needsDisplay = true 188 | } 189 | 190 | func convertSecondsToHHMMSS(seconds: Int) -> String { 191 | var hhmmss = "" 192 | var hours = String(seconds / 3600), 193 | remainder = seconds % 3600, 194 | minutes = String(remainder / 60), 195 | seconds = String(remainder % 60) 196 | 197 | if hours == "0" { 198 | hours = "00" 199 | } else { 200 | if (hours as NSString).length == 1 { 201 | hours = "0" + hours 202 | } 203 | } 204 | if minutes == "0" { 205 | minutes = "00" 206 | } else { 207 | if (minutes as NSString).length == 1 { 208 | minutes = "0" + minutes 209 | } 210 | } 211 | if seconds == "0" { 212 | seconds = "00" 213 | } else { 214 | if (seconds as NSString).length == 1 { 215 | seconds = "0" + seconds 216 | } 217 | } 218 | 219 | if hours == "00" && minutes == "00" { 220 | hhmmss = "00:00:\(String(seconds)) and Counting..." 221 | } else if hours == "00" && minutes != "00" { 222 | hhmmss = "00:\(String(minutes)):\(String(seconds)) and Counting..." 223 | } else { 224 | hhmmss = "\(String(hours)):\(String(minutes)):\(String(seconds)) and Counting..." 225 | } 226 | return hhmmss 227 | } 228 | 229 | func updateSlider() { 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /Offline Time/SliderViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Offline Time/TweetView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TweetView.swift 3 | // Offline Time 4 | // 5 | // Created by Naoto Ida on 10/21/15. 6 | // Copyright © 2015 96Problems. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class TweetView: NSView { 12 | 13 | let localStore = NSUserDefaults.standardUserDefaults() 14 | 15 | @IBOutlet var button: NSButton! 16 | 17 | override func drawRect(dirtyRect: NSRect) { 18 | super.drawRect(dirtyRect) 19 | } 20 | 21 | @IBAction func onToggleCheck(sender: AnyObject) { 22 | Swift.print(self.button.state) 23 | self.localStore.setBool(self.button.state.toBool()!, forKey: "SHOULD_PROMPT_TWEET") 24 | } 25 | } 26 | 27 | extension Int { 28 | func toBool () -> Bool? { 29 | switch self { 30 | case 0: 31 | return false 32 | case 1: 33 | return true 34 | default: 35 | return nil 36 | } 37 | 38 | } 39 | } -------------------------------------------------------------------------------- /Offline Time/TweetViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Offline Time/Vendor/PALoginItemUtility.h: -------------------------------------------------------------------------------- 1 | // 2 | // PALoginItemUtility.h 3 | // devMod 4 | // 5 | // Created by Paolo Tagliani on 10/25/14. 6 | // Copyright (c) 2014 Paolo Tagliani. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PALoginItemUtility : NSObject 12 | 13 | + (BOOL)isCurrentApplicatonInLoginItems; 14 | + (void)addCurrentApplicatonToLoginItems; 15 | + (void)removeCurrentApplicatonToLoginItems; 16 | 17 | @end -------------------------------------------------------------------------------- /Offline Time/Vendor/PALoginItemUtility.m: -------------------------------------------------------------------------------- 1 | // 2 | // PALoginItemUtility.m 3 | // devMod 4 | // 5 | // Created by Paolo Tagliani on 10/25/14. 6 | // Copyright (c) 2014 Paolo Tagliani. All rights reserved. 7 | // 8 | 9 | #import "PALoginItemUtility.h" 10 | 11 | @implementation PALoginItemUtility 12 | 13 | + (BOOL)isCurrentApplicatonInLoginItems{ 14 | LSSharedFileListRef sharedFileList = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL); 15 | NSString *applicationPath = [NSBundle mainBundle].bundlePath; 16 | BOOL result = NO; 17 | 18 | if (sharedFileList) { 19 | NSArray *sharedFileListArray = nil; 20 | UInt32 seedValue; 21 | 22 | sharedFileListArray = CFBridgingRelease(LSSharedFileListCopySnapshot(sharedFileList, &seedValue)); 23 | 24 | for (id sharedFile in sharedFileListArray) { 25 | LSSharedFileListItemRef sharedFileListItem = (__bridge LSSharedFileListItemRef)sharedFile; 26 | CFURLRef applicationPathURL = NULL; 27 | 28 | applicationPathURL = LSSharedFileListItemCopyResolvedURL(sharedFileListItem, 0, NULL); 29 | 30 | if (applicationPathURL != NULL) { 31 | NSString *resolvedApplicationPath = [(__bridge NSURL *)applicationPathURL path]; 32 | 33 | CFRelease(applicationPathURL); 34 | 35 | if ([resolvedApplicationPath compare: applicationPath] == NSOrderedSame) { 36 | result = YES; 37 | 38 | break; 39 | } 40 | } 41 | } 42 | 43 | CFRelease(sharedFileList); 44 | } else { 45 | NSLog(@"Unable to create the shared file list."); 46 | } 47 | 48 | return result; 49 | 50 | } 51 | 52 | + (void)addCurrentApplicatonToLoginItems{ 53 | LSSharedFileListRef sharedFileList = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL); 54 | NSString *applicationPath = [NSBundle mainBundle].bundlePath; 55 | NSURL *applicationPathURL = [NSURL fileURLWithPath: applicationPath]; 56 | 57 | if (sharedFileList) { 58 | LSSharedFileListItemRef sharedFileListItem = LSSharedFileListInsertItemURL(sharedFileList, kLSSharedFileListItemLast, NULL, NULL, (__bridge CFURLRef)applicationPathURL, NULL, NULL); 59 | 60 | if (sharedFileListItem) { 61 | CFRelease(sharedFileListItem); 62 | } 63 | 64 | CFRelease(sharedFileList); 65 | } else { 66 | NSLog(@"Unable to create the shared file list."); 67 | } 68 | } 69 | 70 | + (void)removeCurrentApplicatonToLoginItems{ 71 | LSSharedFileListRef sharedFileList = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL); 72 | NSString *applicationPath = [NSBundle mainBundle].bundlePath; 73 | 74 | if (sharedFileList) { 75 | NSArray *sharedFileListArray = nil; 76 | UInt32 seedValue; 77 | 78 | sharedFileListArray = CFBridgingRelease(LSSharedFileListCopySnapshot(sharedFileList, &seedValue)); 79 | 80 | for (id sharedFile in sharedFileListArray) { 81 | LSSharedFileListItemRef sharedFileListItem = (__bridge LSSharedFileListItemRef)sharedFile; 82 | CFURLRef applicationPathURL = NULL; 83 | applicationPathURL = LSSharedFileListItemCopyResolvedURL(sharedFileListItem, 0, NULL); 84 | 85 | if (applicationPathURL != NULL) { 86 | NSString *resolvedApplicationPath = [(__bridge NSURL *)applicationPathURL path]; 87 | 88 | if ([resolvedApplicationPath compare: applicationPath] == NSOrderedSame) { 89 | LSSharedFileListItemRemove(sharedFileList, sharedFileListItem); 90 | } 91 | 92 | CFRelease(applicationPathURL); 93 | } 94 | } 95 | 96 | CFRelease(sharedFileList); 97 | } else { 98 | NSLog(@"Unable to create the shared file list."); 99 | } 100 | 101 | } 102 | 103 | @end -------------------------------------------------------------------------------- /Offline TimeTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Offline TimeTests/Offline_TimeTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Offline_TimeTests.swift 3 | // Offline TimeTests 4 | // 5 | // Created by Naoto Ida on 9/28/15. 6 | // Copyright (c) 2015 96Problems. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import XCTest 11 | 12 | class Offline_TimeTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | XCTAssert(true, "Pass") 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measureBlock() { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Offline-Time 2 | We decided to make Offline Time after reading Jonathan Franzen's quote, “What you have to do… is you plug in an Ethernet cable with superglue, and then you saw off the little head of it.” In referencing how he stays productive and in the zone when writing. 3 | 4 | Offline Time is a small app we made as a companion app to our upcoming writing app to help us stay focused like Franzen. How it works is: 5 | 6 | 1. You set a timer for how long you want to be offline and press start. 7 | 2. It switches off your WiFi 8 | 3. If you try to stop the timer it takes 5 times reopening the window and putting up with passive-aggressive messages 9 | 4. When you finish your offline time it congratulates you and offers you a chance to brag 10 | 11 | It was just a small #1DayHack by our team, so it's not the most complete or perfect app, but it is all on Github for you to help perfect if you feel so inclined! 12 | 13 | p.s. You can read more about why we made this in our Medium series in the lead up to NaNoWriMo https://medium.com/30daysofwriting-prep/15-go-offline-like-franzen-58ed459690ce#.ku5exv58g 14 | --------------------------------------------------------------------------------