├── .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 |
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 |
--------------------------------------------------------------------------------