From 8b5c2f10b7f92fdab849965a34be5b3dbce5682c Mon Sep 17 00:00:00 2001 From: connorgmeehan Date: Fri, 24 Jul 2020 23:22:25 +1000 Subject: [PATCH] WIP Implementation of up next widget --- MTMR.xcodeproj/project.pbxproj | 4 + MTMR/ItemsParsing.swift | 9 ++ MTMR/TouchBarController.swift | 4 + MTMR/Widgets/UpNextBarItem.swift | 155 +++++++++++++++++++++++++++++++ README.md | 16 ++++ 5 files changed, 188 insertions(+) create mode 100644 MTMR/Widgets/UpNextBarItem.swift diff --git a/MTMR.xcodeproj/project.pbxproj b/MTMR.xcodeproj/project.pbxproj index 855eff5..882beef 100644 --- a/MTMR.xcodeproj/project.pbxproj +++ b/MTMR.xcodeproj/project.pbxproj @@ -71,6 +71,7 @@ B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */; }; BAF5AB5724317B4300B04904 /* BasicView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAF5AB5624317B4300B04904 /* BasicView.swift */; }; BAF5AB5924317CAF00B04904 /* SwipeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAF5AB5824317CAF00B04904 /* SwipeItem.swift */; }; + F29F6A2524BC7148004FF8E4 /* UpNextBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F29F6A2424BC7148004FF8E4 /* UpNextBarItem.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -163,6 +164,7 @@ B0F8771B207AC92700D6E430 /* TouchBarSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TouchBarSupport.h; sourceTree = ""; }; BAF5AB5624317B4300B04904 /* BasicView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicView.swift; sourceTree = ""; }; BAF5AB5824317CAF00B04904 /* SwipeItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeItem.swift; sourceTree = ""; }; + F29F6A2424BC7148004FF8E4 /* UpNextBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpNextBarItem.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -322,6 +324,7 @@ 4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */, B08126F0217BE19000A98970 /* WidgetProtocol.swift */, B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */, + F29F6A2424BC7148004FF8E4 /* UpNextBarItem.swift */, ); path = Widgets; sourceTree = ""; @@ -488,6 +491,7 @@ 6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */, 4CDC6E5022FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift in Sources */, 607EEA4B2087835F009DA5F0 /* WeatherBarItem.swift in Sources */, + F29F6A2524BC7148004FF8E4 /* UpNextBarItem.swift in Sources */, B0F3112520C9E35F0076BB88 /* SupportNSTouchBar.swift in Sources */, 4CFF5E5C22E623DD00BFB1EE /* YandexWeatherBarItem.swift in Sources */, 6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */, diff --git a/MTMR/ItemsParsing.swift b/MTMR/ItemsParsing.swift index 851cf15..7dd4003 100644 --- a/MTMR/ItemsParsing.swift +++ b/MTMR/ItemsParsing.swift @@ -227,6 +227,7 @@ enum ItemType: Decodable { case network(flip: Bool) case darkMode case swipe(direction: String, fingers: Int, minOffset: Float, sourceApple: SourceProtocol?, sourceBash: SourceProtocol?) + case upnext(from: Double, to: Double, nthEvent: Int) private enum CodingKeys: String, CodingKey { case type @@ -258,6 +259,7 @@ enum ItemType: Decodable { case direction case fingers case minOffset + case nthEvent } enum ItemTypeRaw: String, Decodable { @@ -281,6 +283,7 @@ enum ItemType: Decodable { case network case darkMode case swipe + case upnext } init(from decoder: Decoder) throws { @@ -378,6 +381,12 @@ enum ItemType: Decodable { let fingers = try container.decode(Int.self, forKey: .fingers) let minOffset = try container.decodeIfPresent(Float.self, forKey: .minOffset) ?? 0.0 self = .swipe(direction: direction, fingers: fingers, minOffset: minOffset, sourceApple: sourceApple, sourceBash: sourceBash) + + case .upnext: + let from = try container.decodeIfPresent(Double.self, forKey: .from) ?? 0 // Lower bounds of period of time in hours to search for events + let to = try container.decodeIfPresent(Double.self, forKey: .to) ?? 1 // Upper bounds of period of time in hours to search for events + let nthEvent = try container.decodeIfPresent(Int.self, forKey: .nthEvent) ?? 1 // 1 indexed array. Get the 1st, 2nd, 3rd event to display multiple notifications + self = .upnext(from: from, to: to, nthEvent: nthEvent) } } } diff --git a/MTMR/TouchBarController.swift b/MTMR/TouchBarController.swift index e0af17e..9341255 100644 --- a/MTMR/TouchBarController.swift +++ b/MTMR/TouchBarController.swift @@ -59,6 +59,8 @@ extension ItemType { return DarkModeBarItem.identifier case .swipe(direction: _, fingers: _, minOffset: _, sourceApple: _, sourceBash: _): return "com.toxblh.mtmr.swipe." + case .upnext: + return "com.connorgmeehan.mtmrupnext." } } } @@ -301,6 +303,8 @@ class TouchBarController: NSObject, NSTouchBarDelegate { barItem = DarkModeBarItem(identifier: identifier) case let .swipe(direction: direction, fingers: fingers, minOffset: minOffset, sourceApple: sourceApple, sourceBash: sourceBash): barItem = SwipeItem(identifier: identifier, direction: direction, fingers: fingers, minOffset: minOffset, sourceApple: sourceApple, sourceBash: sourceBash) + case let .upnext(from: from, to: to, nthEvent: nthEvent): + barItem = UpNextBarItem(identifier: identifier, interval: 10, from: from, to: to, nthEvent: nthEvent) } if let action = self.action(forItem: item), let item = barItem as? CustomButtonTouchBarItem { diff --git a/MTMR/Widgets/UpNextBarItem.swift b/MTMR/Widgets/UpNextBarItem.swift new file mode 100644 index 0000000..943dcee --- /dev/null +++ b/MTMR/Widgets/UpNextBarItem.swift @@ -0,0 +1,155 @@ +// +// UpNextBarItems.swift +// MTMR +// +// Created by Connor Meehan on 13/7/20. +// Copyright © 2020 Anton Palgunov. All rights reserved. +// +// + +import Foundation +import EventKit + +class UpNextBarItem: CustomButtonTouchBarItem { + private let activity: NSBackgroundActivityScheduler // Update scheduler + private let eventStore = EKEventStore() // + private let df = DateFormatter() + private let buttonTemplate = "🗓 %@ - %@ " + + // Settings + private var futureSearchCutoff: Double + private var pastSearchCutoff: Double + private var nthEvent: Int + + // State + private var hasPermission: Bool = false + + + /// <#Description#> + /// - Parameters: + /// - identifier: Unique identifier of widget + /// - interval: Update view interval in seconds + /// - from: Relative to current time, how far back we search for events in hours + /// - to: Relative to current time, how far forward we search for events in hours + /// - nthEvent: Which event to show (1 is first, 2 is second, and so on) + init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, from: Double, to: Double, nthEvent: Int) { + activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updateCheck") + activity.interval = interval + self.pastSearchCutoff = from + self.futureSearchCutoff = to + self.nthEvent = nthEvent + self.df.dateFormat = "HH:mm" + + if (nthEvent <= 0) { + fatalError("Error on UpNext bar item. nthEvent property must be greater than 0.") + } + + super.init(identifier: identifier, title: " ") + let authorizationStatus = EKEventStore.authorizationStatus(for: EKEntityType.event) + switch authorizationStatus { + case .notDetermined: + print("notDetermined") + case .restricted: + print("restricted") + case .denied: + print("denied") + case .authorized: + print("authorizded") + default: + print("Unkown EKEventStore authorization status") + } + eventStore.requestAccess(to: .event){ granted, error in + self.hasPermission = granted; + if(!granted) { + NSLog("Error: MTMR UpNextBarWidget not given calendar access.") + return + } + self.updateView() + } + + + tapClosure = { [weak self] in self?.gotoAppleCalendar() } + + + // Start activity to update view + activity.repeats = true + activity.qualityOfService = .utility + activity.schedule { (completion: NSBackgroundActivityScheduler.CompletionHandler) in + self.updateView() + completion(NSBackgroundActivityScheduler.Result.finished) + } + updateView() + + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func updateView() { + if (!self.hasPermission) { + self.title = "🗓 No permissions" + return + } + var upcomingEvents = self.getUpcomingEvents() + upcomingEvents.sort(by: {$0.startDate.compare($1.startDate) == .orderedAscending}) + for event in upcomingEvents { + print("\(event.title) - \(event.startDate)") + } + if (upcomingEvents.count >= self.nthEvent) { + let event = upcomingEvents[self.nthEvent-1] + let title = event.title + let startDateString = self.df.string(for: event.startDate) + print("TITLE: " + title + " STARTDATE: " + (startDateString ?? "nil")) + + DispatchQueue.main.async { + print("SHOW") + self.image = nil + self.title = String(format: self.buttonTemplate, title, startDateString ?? "No time") + self.view.isHidden = false + } + } else { + // Do not display any event + DispatchQueue.main.async { + print("HIDE " + String(upcomingEvents.count) + " " + String(self.nthEvent) + " " + String(upcomingEvents.count > self.nthEvent)) + self.image = nil + self.title = "" + self.view.isHidden = true + } + } + } + + func getUpcomingEvents() -> [UpNextEventModel] { + var upcomingEvents: [UpNextEventModel] = [] + + NSLog("Getting calendar events...") + // Calculate the range we're going to search for events in + let dateLowerBounds = Date(timeIntervalSinceNow: self.pastSearchCutoff * 360) + let dateUpperBounds = Date(timeIntervalSinceNow: self.futureSearchCutoff * 360) + + let calendars = self.eventStore.calendars(for: .event) + + + for calendar in calendars { + + let predicate = self.eventStore.predicateForEvents(withStart: dateLowerBounds, end: dateUpperBounds, calendars: [calendar]) + + let events = self.eventStore.events(matching: predicate) + for event in events { + upcomingEvents.append(UpNextEventModel(title: event.title, startDate: event.startDate)) + } + } + + print("Found " + String(upcomingEvents.count) + " events.") + return upcomingEvents + } + + func gotoAppleCalendar() { + print("CLICK") + NSWorkspace.shared.open(URL(fileURLWithPath: "/Applications/Photos.app")) + }} + +struct UpNextEventModel { + var title: String + var startDate: Date +} diff --git a/README.md b/README.md index e2b2332..c8b10e0 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ The pre-installed configuration contains less or more than you'll probably want, - darkMode - pomodoro - network +- upnext (Calendar events) > Media Keys @@ -343,6 +344,21 @@ To close a group, use the button: }, ``` +#### `upnext` + +> Calender next event plugin + +```js +{ + "type": "upnext", + "from": 0, // Lower bound of search range for next event in hours. Default 0 (current time) + "to": 1, // Upper bounds of search range for next event in hours. Default 1 (one hour in the future) + "nthEvent": 1 // Sets this touchbar button to show the nthEvent. Default 1 (the first upcoming event) +}, +``` + + + ## Actions: - `hidKey`