@@ -7,24 +7,25 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
77 private var currentMode : ProgressMode = . year
88 private var updateTimer : Timer ?
99 private let launchAtLoginKey = " LaunchAtLogin "
10- private let customStartDateKey = " CustomStartDate "
1110 private let customEndDateKey = " CustomEndDate "
1211 private var yearMenuItem : NSMenuItem ?
1312 private var monthMenuItem : NSMenuItem ?
1413 private var dayMenuItem : NSMenuItem ?
14+ private var weekMenuItem : NSMenuItem ?
15+ private var workWeekMenuItem : NSMenuItem ?
1516 private var customMenuItem : NSMenuItem ?
1617
17-
18- private var customStartDate : Date ?
1918 private var customEndDate : Date ?
2019
2120 private enum ProgressMode : CaseIterable {
22- case year, month, day, custom
21+ case year, month, week , workWeek , day, custom
2322
2423 var title : String {
2524 switch self {
2625 case . year: return " of yyyy "
27- case . month: return " of MMM "
26+ case . month: return " of MMMM "
27+ case . week: return " of Week "
28+ case . workWeek: return " of Work Week "
2829 case . day: return " of Today "
2930 case . custom: return " Custom "
3031 }
@@ -46,10 +47,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
4647 func applicationDidFinishLaunching( _ aNotification: Notification ) {
4748 NSApp . setActivationPolicy ( . accessory)
4849
49-
50- if let startDate = UserDefaults . standard. object ( forKey: customStartDateKey) as? Date ,
51- let endDate = UserDefaults . standard. object ( forKey: customEndDateKey) as? Date {
52- customStartDate = startDate
50+ if let endDate = UserDefaults . standard. object ( forKey: customEndDateKey) as? Date {
5351 customEndDate = endDate
5452 }
5553
@@ -78,21 +76,26 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
7876
7977 yearMenuItem = NSMenuItem ( title: " Year Progress " , action: #selector( selectYearMode) , keyEquivalent: " " )
8078 monthMenuItem = NSMenuItem ( title: " Month Progress " , action: #selector( selectMonthMode) , keyEquivalent: " " )
79+ weekMenuItem = NSMenuItem ( title: " Week Progress " , action: #selector( selectWeekMode) , keyEquivalent: " " )
80+ workWeekMenuItem = NSMenuItem ( title: " Work Week Progress " , action: #selector( selectWorkWeekMode) , keyEquivalent: " " )
8181 dayMenuItem = NSMenuItem ( title: " Day Progress " , action: #selector( selectDayMode) , keyEquivalent: " " )
8282 customMenuItem = NSMenuItem ( title: " Custom Date Progress " , action: #selector( selectCustomMode) , keyEquivalent: " " )
8383
8484 if let yearItem = yearMenuItem, let monthItem = monthMenuItem,
85+ let weekItem = weekMenuItem, let workWeekItem = workWeekMenuItem,
8586 let dayItem = dayMenuItem, let customItem = customMenuItem {
8687 menu. addItem ( yearItem)
8788 menu. addItem ( monthItem)
89+ menu. addItem ( weekItem)
90+ menu. addItem ( workWeekItem)
8891 menu. addItem ( dayItem)
8992 menu. addItem ( customItem)
9093 }
9194
9295 menu. addItem ( NSMenuItem . separator ( ) )
9396
9497
95- let configureCustomItem = NSMenuItem ( title: " Configure Custom Dates ... " , action: #selector( configureCustomDates) , keyEquivalent: " " )
98+ let configureCustomItem = NSMenuItem ( title: " Configure Custom End Date ... " , action: #selector( configureCustomDates) , keyEquivalent: " " )
9699 menu. addItem ( configureCustomItem)
97100
98101
@@ -121,96 +124,78 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
121124 updateProgress ( )
122125 }
123126
127+ @objc private func selectWeekMode( ) {
128+ currentMode = . week
129+ updateMenuCheckmarks ( )
130+ updateProgress ( )
131+ }
132+
133+ @objc private func selectWorkWeekMode( ) {
134+ currentMode = . workWeek
135+ updateMenuCheckmarks ( )
136+ updateProgress ( )
137+ }
138+
124139 @objc private func selectDayMode( ) {
125140 currentMode = . day
126141 updateMenuCheckmarks ( )
127142 updateProgress ( )
128143 }
129144
130145 @objc private func selectCustomMode( ) {
131-
132- if let _ = customStartDate, let _ = customEndDate {
146+ if let _ = customEndDate {
133147 currentMode = . custom
134148 updateMenuCheckmarks ( )
135149 updateProgress ( )
136150 } else {
137-
138151 configureCustomDates ( )
139152 }
140153 }
141154
142155 @objc private func configureCustomDates( ) {
143-
144156 let window = NSWindow (
145- contentRect: NSRect ( x: 0 , y: 0 , width: 400 , height: 200 ) ,
157+ contentRect: NSRect ( x: 0 , y: 0 , width: 400 , height: 150 ) ,
146158 styleMask: [ . titled, . closable] ,
147159 backing: . buffered,
148160 defer: false
149161 )
150- window. title = " Configure Custom Dates "
162+ window. title = " Configure Custom End Date "
151163 window. center ( )
152164
153-
154- let contentView = NSView ( frame: NSRect ( x: 0 , y: 0 , width: 400 , height: 200 ) )
155-
156-
157- let startLabel = NSTextField ( labelWithString: " Start Date: " )
158- startLabel. frame = NSRect ( x: 20 , y: 160 , width: 100 , height: 20 )
159- contentView. addSubview ( startLabel)
160-
161-
162- let startDatePicker = NSDatePicker ( )
163- startDatePicker. datePickerStyle = . textField
164- startDatePicker. datePickerMode = . single
165- startDatePicker. frame = NSRect ( x: 130 , y: 160 , width: 200 , height: 20 )
166- if let startDate = customStartDate {
167- startDatePicker. dateValue = startDate
168- } else {
169- startDatePicker. dateValue = Date ( )
170- }
171- contentView. addSubview ( startDatePicker)
172-
165+ let contentView = NSView ( frame: NSRect ( x: 0 , y: 0 , width: 400 , height: 150 ) )
173166
174167 let endLabel = NSTextField ( labelWithString: " End Date: " )
175- endLabel. frame = NSRect ( x: 20 , y: 120 , width: 100 , height: 20 )
168+ endLabel. frame = NSRect ( x: 20 , y: 100 , width: 100 , height: 20 )
176169 contentView. addSubview ( endLabel)
177170
178-
179171 let endDatePicker = NSDatePicker ( )
180172 endDatePicker. datePickerStyle = . textField
181173 endDatePicker. datePickerMode = . single
182- endDatePicker. frame = NSRect ( x: 130 , y: 120 , width: 200 , height: 20 )
174+ endDatePicker. frame = NSRect ( x: 130 , y: 100 , width: 200 , height: 20 )
183175 if let endDate = customEndDate {
184176 endDatePicker. dateValue = endDate
185177 } else {
186-
187178 endDatePicker. dateValue = Calendar . current. date ( byAdding: . year, value: 1 , to: Date ( ) ) ?? Date ( )
188179 }
189180 contentView. addSubview ( endDatePicker)
190181
191-
192- let descLabel = NSTextField ( wrappingLabelWithString: " Set start and end dates to track progress between them. " )
193- descLabel. frame = NSRect ( x: 20 , y: 80 , width: 360 , height: 30 )
182+ let descLabel = NSTextField ( wrappingLabelWithString: " Set an end date to track progress from today to that date. " )
183+ descLabel. frame = NSRect ( x: 20 , y: 60 , width: 360 , height: 30 )
194184 contentView. addSubview ( descLabel)
195185
196-
197186 let saveButton = NSButton ( title: " Save " , target: nil , action: nil )
198187 saveButton. frame = NSRect ( x: 280 , y: 20 , width: 100 , height: 32 )
199188 saveButton. bezelStyle = . rounded
200189 saveButton. keyEquivalent = " \r "
201190
202-
203191 saveButton. target = self
204192 saveButton. action = #selector( saveCustomDates ( _: ) )
205193
206-
207- objc_setAssociatedObject ( saveButton, UnsafeRawPointer ( bitPattern: 1 ) !, startDatePicker, . OBJC_ASSOCIATION_RETAIN)
208194 objc_setAssociatedObject ( saveButton, UnsafeRawPointer ( bitPattern: 2 ) !, endDatePicker, . OBJC_ASSOCIATION_RETAIN)
209195 objc_setAssociatedObject ( saveButton, UnsafeRawPointer ( bitPattern: 3 ) !, window, . OBJC_ASSOCIATION_RETAIN)
210196
211197 contentView. addSubview ( saveButton)
212198
213-
214199 let cancelButton = NSButton ( title: " Cancel " , target: nil , action: nil )
215200 cancelButton. frame = NSRect ( x: 170 , y: 20 , width: 100 , height: 32 )
216201 cancelButton. bezelStyle = . rounded
@@ -225,41 +210,31 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
225210 }
226211
227212 @objc private func saveCustomDates( _ sender: NSButton ) {
228-
229- guard let startDatePicker = objc_getAssociatedObject ( sender, UnsafeRawPointer ( bitPattern: 1 ) !) as? NSDatePicker ,
230- let endDatePicker = objc_getAssociatedObject ( sender, UnsafeRawPointer ( bitPattern: 2 ) !) as? NSDatePicker ,
213+ guard let endDatePicker = objc_getAssociatedObject ( sender, UnsafeRawPointer ( bitPattern: 2 ) !) as? NSDatePicker ,
231214 let window = objc_getAssociatedObject ( sender, UnsafeRawPointer ( bitPattern: 3 ) !) as? NSWindow else {
232215 return
233216 }
234217
235- let startDate = startDatePicker. dateValue
236218 let endDate = endDatePicker. dateValue
237219
238-
239- if startDate >= endDate {
220+ if Date ( ) >= endDate {
240221 let alert = NSAlert ( )
241- alert. messageText = " Invalid Date Range "
242- alert. informativeText = " The start date must be before the end date . "
222+ alert. messageText = " Invalid Date "
223+ alert. informativeText = " The end date must be in the future . "
243224 alert. alertStyle = . warning
244225 alert. addButton ( withTitle: " OK " )
245226 alert. beginSheetModal ( for: window, completionHandler: nil )
246227 return
247228 }
248229
249-
250- customStartDate = startDate
251230 customEndDate = endDate
252231
253-
254- UserDefaults . standard. set ( startDate, forKey: customStartDateKey)
255232 UserDefaults . standard. set ( endDate, forKey: customEndDateKey)
256233
257-
258234 currentMode = . custom
259235 updateMenuCheckmarks ( )
260236 updateProgress ( )
261237
262-
263238 window. close ( )
264239 }
265240
@@ -270,15 +245,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
270245 }
271246
272247 private func startTimer( ) {
273-
274248 updateTimer? . invalidate ( )
275249
276-
277250 updateTimer = Timer . scheduledTimer ( withTimeInterval: 60.0 , repeats: true ) { [ weak self] _ in
278251 self ? . updateProgress ( )
279252 }
280253
281-
282254 RunLoop . current. add ( updateTimer!, forMode: . common)
283255 }
284256
@@ -318,9 +290,45 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
318290 progress = Double ( calendar. dateComponents ( [ . second] , from: startOfMonth, to: now) . second!) /
319291 Double( calendar. dateComponents ( [ . second] , from: startOfMonth, to: endOfMonth) . second!) * 100
320292
321- dateFormatter. dateFormat = " MMM "
293+ dateFormatter. dateFormat = " MMMM "
322294 displayText = " of \( dateFormatter. string ( from: now) ) "
323295
296+ case . week:
297+ let weekday = calendar. component ( . weekday, from: now)
298+ let daysToSubtract = weekday - calendar. firstWeekday
299+ let startOfWeek = calendar. date ( byAdding: . day, value: - daysToSubtract, to: calendar. startOfDay ( for: now) ) !
300+ let endOfWeek = calendar. date ( byAdding: . day, value: 7 , to: startOfWeek) !
301+
302+ progress = Double ( calendar. dateComponents ( [ . second] , from: startOfWeek, to: now) . second!) /
303+ Double( calendar. dateComponents ( [ . second] , from: startOfWeek, to: endOfWeek) . second!) * 100
304+
305+ let weekOfYear = calendar. component ( . weekOfYear, from: now)
306+ displayText = " Week \( weekOfYear) "
307+
308+ case . workWeek:
309+ let weekday = calendar. component ( . weekday, from: now)
310+
311+
312+ if weekday == 1 || weekday == 7 {
313+ progress = 100.0
314+ displayText = " Weekend! "
315+ } else {
316+
317+ var daysToSubtract = weekday - 2
318+ if daysToSubtract < 0 {
319+ daysToSubtract += 7
320+ }
321+
322+ let startOfWorkWeek = calendar. date ( byAdding: . day, value: - daysToSubtract, to: calendar. startOfDay ( for: now) ) !
323+ let endOfWorkWeek = calendar. date ( byAdding: . day, value: 5 , to: startOfWorkWeek) !
324+
325+ progress = Double ( calendar. dateComponents ( [ . second] , from: startOfWorkWeek, to: now) . second!) /
326+ Double( calendar. dateComponents ( [ . second] , from: startOfWorkWeek, to: endOfWorkWeek) . second!) * 100
327+
328+ let weekOfYear = calendar. component ( . weekOfYear, from: now)
329+ displayText = " Week \( weekOfYear) "
330+ }
331+
324332 case . day:
325333 let startOfDay = calendar. date ( from: calendar. dateComponents ( [ . year, . month, . day] , from: now) ) !
326334 let endOfDay = calendar. date ( byAdding: DateComponents ( day: 1 ) , to: startOfDay) !
@@ -330,36 +338,28 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
330338 displayText = " of Today "
331339
332340 case . custom:
333- if let start = customStartDate, let end = customEndDate {
334-
335- let totalDuration = calendar. dateComponents ( [ . second] , from: start, to: end) . second!
336- let elapsedDuration = calendar. dateComponents ( [ . second] , from: start, to: now) . second!
341+ if let end = customEndDate {
342+ let start = Date ( )
337343
338-
339- if now < start {
340- progress = 0
341- } else if now > end {
344+ if start > end {
342345 progress = 100
343346 } else {
344- progress = Double ( elapsedDuration ) / Double ( totalDuration ) * 100
347+ progress = 0
345348 }
346349
347-
348350 dateFormatter. dateFormat = " MMM d, yyyy "
349- displayText = " \( dateFormatter . string ( from : start ) ) - \( dateFormatter. string ( from: end) ) "
351+ displayText = " until \( dateFormatter. string ( from: end) ) "
350352 } else {
351353 progress = 0
352354 displayText = " Custom (not set) "
353355 }
354356 }
355357
356358 if let button = statusItem. button {
357-
358359 let roundedProgress = Int ( round ( progress / 5.0 ) * 5 )
359360 let imageName = String ( format: " gauge%02d " , roundedProgress)
360361
361362 if let originalImage = NSImage ( named: imageName) {
362-
363363 let resizedImage = NSImage ( size: NSSize ( width: 18 , height: 18 ) )
364364 resizedImage. lockFocus ( )
365365 originalImage. size = NSSize ( width: 18 , height: 18 )
@@ -378,6 +378,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
378378 private func updateMenuCheckmarks( ) {
379379 yearMenuItem? . state = currentMode == . year ? . on : . off
380380 monthMenuItem? . state = currentMode == . month ? . on : . off
381+ weekMenuItem? . state = currentMode == . week ? . on : . off
382+ workWeekMenuItem? . state = currentMode == . workWeek ? . on : . off
381383 dayMenuItem? . state = currentMode == . day ? . on : . off
382384 customMenuItem? . state = currentMode == . custom ? . on : . off
383385 }
@@ -399,7 +401,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
399401 }
400402
401403 func applicationWillTerminate( _ notification: Notification ) {
402-
403404 updateTimer? . invalidate ( )
404405 }
405406}
0 commit comments