M Tomatsupo.xcodeproj/project.pbxproj => Tomatsupo.xcodeproj/project.pbxproj +2 -2
@@ 301,7 301,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
- MACOSX_DEPLOYMENT_TARGET = 10.15;
+ MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 0.0.1;
PRODUCT_BUNDLE_IDENTIFIER = fm.stardust.app.Tomatsupo;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ 327,7 327,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
- MACOSX_DEPLOYMENT_TARGET = 10.15;
+ MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 0.0.1;
PRODUCT_BUNDLE_IDENTIFIER = fm.stardust.app.Tomatsupo;
PRODUCT_NAME = "$(TARGET_NAME)";
M Tomatsupo.xcodeproj/xcuserdata/izzym.xcuserdatad/xcschemes/xcschememanagement.plist => Tomatsupo.xcodeproj/xcuserdata/izzym.xcuserdatad/xcschemes/xcschememanagement.plist +8 -0
@@ 10,5 10,13 @@
<integer>0</integer>
</dict>
</dict>
+ <key>SuppressBuildableAutocreation</key>
+ <dict>
+ <key>83EBE73825A422B3002C9CBF</key>
+ <dict>
+ <key>primary</key>
+ <true/>
+ </dict>
+ </dict>
</dict>
</plist>
M Tomatsupo/DoNotDisturb.swift => Tomatsupo/DoNotDisturb.swift +8 -105
@@ 15,21 15,7 @@ public enum DoNotDisturbState {
case disabled
}
-public protocol DoNotDisturb {
- var doNotDisturb: DoNotDisturbState {get set}
-}
-
-public class DoNotDisturbGenerator {
- class func make() -> DoNotDisturb {
- if #available(macOS 11, *) {
- return DoNotDisturbBigSur()
- } else {
- return DoNotDisturbCatalina()
- }
- }
-}
-
-public struct DoNotDisturbBigSur: DoNotDisturb {
+public struct DoNotDisturb {
private var preservePrevious = UserDefaults(suiteName: "com.apple.ncprefs.plist")!.data(forKey: "dnd_prefs")!.hexEncodedString(options: Data.HexEncodingOptions.upperCase)
@@ 59,98 45,15 @@ extension Data {
func hexEncodedString(options: HexEncodingOptions = []) -> String {
let hexDigits = options.contains(.upperCase) ? "0123456789ABCDEF" : "0123456789abcdef"
- if #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) {
- let utf8Digits = Array(hexDigits.utf8)
- return String(unsafeUninitializedCapacity: 2 * count) { (ptr) -> Int in
- var p = ptr.baseAddress!
- for byte in self {
- p[0] = utf8Digits[Int(byte / 16)]
- p[1] = utf8Digits[Int(byte % 16)]
- p += 2
- }
- return 2 * count
- }
- } else {
- let utf16Digits = Array(hexDigits.utf16)
- var chars: [unichar] = []
- chars.reserveCapacity(2 * count)
+ let utf8Digits = Array(hexDigits.utf8)
+ return String(unsafeUninitializedCapacity: 2 * count) { (ptr) -> Int in
+ var p = ptr.baseAddress!
for byte in self {
- chars.append(utf16Digits[Int(byte / 16)])
- chars.append(utf16Digits[Int(byte % 16)])
- }
- return String(utf16CodeUnits: chars, count: chars.count)
- }
- }
-}
-
-// The following code is Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
-// and used under the MIT license - https://github.com/sindresorhus/do-not-disturb
-public struct DoNotDisturbCatalina: DoNotDisturb{
- private static let appId = "com.apple.notificationcenterui" as CFString
-
- private func set(_ key: String, value: CFPropertyList?) {
- CFPreferencesSetValue(key as CFString, value, DoNotDisturbCatalina.appId, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)
- }
-
- private func commitChanges() {
- CFPreferencesSynchronize(DoNotDisturbCatalina.appId, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)
- DistributedNotificationCenter.default().postNotificationName(NSNotification.Name("com.apple.notificationcenterui.dndprefs_changed"), object: nil, deliverImmediately: true)
- }
-
- private func restartNotificationCenter() {
- NSRunningApplication.runningApplications(withBundleIdentifier: "com.apple.notificationcenterui").first?.forceTerminate()
- }
-
- private func enable() {
- guard doNotDisturb == .disabled else {
- return
- }
-
- set("doNotDisturb", value: true as CFPropertyList)
- set("doNotDisturbDate", value: Date() as CFPropertyList)
- commitChanges()
- restartNotificationCenter()
- }
-
- private func disable() {
- guard doNotDisturb == .enabled else {
- return
- }
-
- set("doNotDisturb", value: false as CFPropertyList)
- set("doNotDisturbDate", value: nil)
- commitChanges()
- restartNotificationCenter()
- restoreMenubarIcon()
- }
-
- private func restoreMenubarIcon() {
- set("dndStart", value: 0 as CFPropertyList)
- set("dndEnd", value: 1440 as CFPropertyList)
-
- // We need to sleep for a little bit, otherwise it doesn't take effect.
- // It works with 0.3, but not with 0.2, so we're using 0.4 just to be sure.
- usleep(useconds_t(0.4 * Double(USEC_PER_SEC)))
-
- set("dndStart", value: nil)
- set("dndEnd", value: nil)
- commitChanges()
- }
-
- public var doNotDisturb: DoNotDisturbState {
- get {
- let state = CFPreferencesGetAppBooleanValue("doNotDisturb" as CFString, DoNotDisturbCatalina.appId, nil)
- return state ? .enabled : .disabled
- }
- set {
- switch newValue {
- case .enabled:
- enable()
- case .disabled:
- disable()
- case .original:
- disable() //TODO
+ p[0] = utf8Digits[Int(byte / 16)]
+ p[1] = utf8Digits[Int(byte % 16)]
+ p += 2
}
+ return 2 * count
}
}
}
M Tomatsupo/Logger.swift => Tomatsupo/Logger.swift +2 -1
@@ 8,9 8,10 @@
import Foundation
import os.log
-@available(OSX 11.0, *)
public extension Logger {
private static var subsystem = Bundle.main.bundleIdentifier!
static let notifications = Logger(subsystem: subsystem, category: "notifications")
+ static let model = Logger(subsystem: subsystem, category: "model")
static let async = Logger(subsystem: subsystem, category: "async")
+ static let ui = Logger(subsystem: subsystem, category: "ui")
}
M Tomatsupo/Model.swift => Tomatsupo/Model.swift +0 -33
@@ 26,39 26,6 @@ struct Model {
var deadline: DispatchTime?
- func remainingSeconds() -> Int? {
- guard let deadline = deadline else {
- return nil
- }
-
- switch DispatchTime.now().distance(to: deadline) {
- case .seconds(let dist):
- return dist
- default:
- if #available(OSX 11.0, *) {
- Logger.async.info("Unhandled case in remainingSeconds")
- } else {
- // TODO: Logging on Catalina
- }
- return nil
- }
- }
-
- func fuzzyFormat(seconds: Int) -> String {
- switch seconds {
- case 0..<300:
- return "Less than five minutes to go!"
- case 300..<500:
- return "Less than ten minutes..."
- case 500..<780:
- return "About ten minutes left"
- case 780..<(17*60):
- return "About fifteen minutes, eh?"
- default:
- return "Time to get some work done!"
- }
- }
-
init(debug: Bool = false) {
let userDefaults = UserDefaults.standard
userDefaults.register(
M Tomatsupo/TomatsupoApp.swift => Tomatsupo/TomatsupoApp.swift +65 -16
@@ 29,11 29,21 @@ class AppDelegate: NSObject, NSApplicationDelegate {
var model = Model()
var workItem, dndWhileWorkingItem: NSMenuItem?
- var dnd: DoNotDisturb = DoNotDisturbGenerator.make()
+ var infoItem = NSMenuItem()
+
+ var notificationUUID: String?
+
+ var dnd: DoNotDisturb = DoNotDisturb()
var workTask: DispatchWorkItem?
+
+ #if DEBUG
+ var debugItem = NSMenuItem(title: "Use short time", action: #selector(handleDebugToggle(_:)), keyEquivalent: "")
+ #endif
func applicationDidFinishLaunching(_ notification: Notification) {
-
+ #if DEBUG
+ print("Running in Debug configuration")
+ #endif
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
if let button = statusItem?.button {
@@ 48,6 58,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
}
+ func applicationWillTerminate(_ notification: Notification) {
+ reset()
+ }
+
func createMenu() {
menu.autoenablesItems = false
workItem = NSMenuItem(title: "Start working",
@@ 55,16 69,25 @@ class AppDelegate: NSObject, NSApplicationDelegate {
keyEquivalent: ".")
menu.addItem(workItem!)
+ infoItem.title = ""
+ infoItem.isHidden = true
+ infoItem.isEnabled = false
+ menu.addItem(infoItem)
+
dndWhileWorkingItem =
NSMenuItem(title: "Enable Do Not Disturb while working",
action: #selector(handleToggleDnDWhileWorking(_:)),
keyEquivalent: "")
dndWhileWorkingItem?.state = model.enableDnDWhileWorking ? .on : .off
-
menu.addItem(dndWhileWorkingItem!)
menu.addItem(NSMenuItem.separator())
+ #if DEBUG
+ debugItem.state = .on
+ menu.addItem(debugItem)
+ model.workDuration = 1
+ #endif
menu.addItem(NSMenuItem(title: "Quit",
action: #selector(NSApplication.terminate(_:)),
keyEquivalent: "q"))
@@ 75,12 98,19 @@ class AppDelegate: NSObject, NSApplicationDelegate {
case nil:
workTask = DispatchWorkItem {
self.reset()
- self.notifyUser()
+
}
if model.enableDnDWhileWorking {
dnd.doNotDisturb = .enabled
}
+ self.scheduleNotification()
+ let startTime = Date()
+ let formatter = DateFormatter()
+ formatter.dateStyle = .none
+ formatter.timeStyle = .short
+ infoItem.title = "Began working at \(formatter.string(from: startTime))"
+ infoItem.isHidden = false
model.deadline = .now() + .seconds(model.workDuration*60)
DispatchQueue.main.asyncAfter(deadline: model.deadline!, execute: workTask!)
@@ 104,37 134,50 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
}
+ #if DEBUG
+ @objc func handleDebugToggle(_ sender: NSMenuItem) {
+ if model.workDuration != 1 {
+ logger.model.info("Setting short time")
+ debugItem.state = .on
+ model.workDuration = 1
+ } else {
+ print("Setting regular time")
+ debugItem.state = .off
+ model.workDuration = 25
+ }
+ }
+ #endif
+
func requestNotifications() {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
if let error = error {
- if #available(OSX 11.0, *) {
- Logger.notifications.warning("Well that sucks - you didn't give us permission to notify you (\(error.localizedDescription)")
- } else {
- // Todo - earlier logging
- }
+ Logger.notifications.warning("Well that sucks - you didn't give us permission to notify you (\(error.localizedDescription)")
return
}
}
}
- func notifyUser() {
+ func scheduleNotification() {
+ Logger.notifications.debug("Scheduling notification (in \(self.model.workDuration)m)")
+
let content = UNMutableNotificationContent()
+
+ let uuid = UUID().uuidString
+ notificationUUID = uuid
+
content.title = "Work timer completed!"
content.body = "As Blake would say, go grab a quarentini 🍸"
content.categoryIdentifier = "alert"
content.sound = UNNotificationSound.default
- let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
+ let trigger = UNTimeIntervalNotificationTrigger(timeInterval: TimeInterval(model.workDuration * 60), repeats: false)
+ let request = UNNotificationRequest(identifier: uuid, content: content, trigger: trigger)
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.add(request, withCompletionHandler: { error in
if let error = error {
- if #available(OSX 11.0, *) {
- Logger.notifications.error("Error notifying: \(error.localizedDescription)")
- } else {
- // TODO: Logging on older OS versions
- }
+ Logger.notifications.error("Error notifying: \(error.localizedDescription)")
}
})
}
@@ 143,8 186,14 @@ class AppDelegate: NSObject, NSApplicationDelegate {
if dndWhileWorkingItem?.state == .on {
dnd.doNotDisturb = .original
}
+ if let uuid = notificationUUID {
+ let nc = UNUserNotificationCenter.current()
+ nc.removePendingNotificationRequests(withIdentifiers: [uuid])
+ }
+
dndWhileWorkingItem?.isEnabled = true
workItem?.title = "Start working"
workTask = nil
+ infoItem.isHidden = true
}
}