diff --git a/MTMR.xcodeproj/project.pbxproj b/MTMR.xcodeproj/project.pbxproj index a319333..caa0476 100644 --- a/MTMR.xcodeproj/project.pbxproj +++ b/MTMR.xcodeproj/project.pbxproj @@ -38,6 +38,8 @@ B059D624205E04F3006E6B86 /* CustomButtonTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */; }; B059D62D205F11E8006E6B86 /* DFRFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B059D62C205F11E8006E6B86 /* DFRFoundation.framework */; }; B0679BC1215AE73F000FC6B4 /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = B0679BC0215AE73F000FC6B4 /* dsa_pub.pem */; }; + B08126EF217BD0B900A98970 /* PomodoroBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B08126EE217BD0B900A98970 /* PomodoroBarItem.swift */; }; + B08126F1217BE19000A98970 /* WidgetProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B08126F0217BE19000A98970 /* WidgetProtocol.swift */; }; B08173272135F02B005D4908 /* NightShiftBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B08173262135F02B005D4908 /* NightShiftBarItem.swift */; }; B081732A2135F354005D4908 /* CoreBrightness.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B08173292135F354005D4908 /* CoreBrightness.framework */; }; B081732C213739FE005D4908 /* DnDBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B081732B213739FE005D4908 /* DnDBarItem.swift */; }; @@ -110,6 +112,8 @@ B059D62A205F0E7D006E6B86 /* TouchBarPrivateApi-Bridging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TouchBarPrivateApi-Bridging.h"; sourceTree = ""; }; B059D62C205F11E8006E6B86 /* DFRFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DFRFoundation.framework; path = ../../../../../System/Library/PrivateFrameworks/DFRFoundation.framework; sourceTree = ""; }; B0679BC0215AE73F000FC6B4 /* dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = dsa_pub.pem; sourceTree = ""; }; + B08126EE217BD0B900A98970 /* PomodoroBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PomodoroBarItem.swift; sourceTree = ""; }; + B08126F0217BE19000A98970 /* WidgetProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetProtocol.swift; sourceTree = ""; }; B08173262135F02B005D4908 /* NightShiftBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightShiftBarItem.swift; sourceTree = ""; }; B08173282135F128005D4908 /* CBBlueLightClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBBlueLightClient.h; sourceTree = ""; }; B08173292135F354005D4908 /* CoreBrightness.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreBrightness.framework; path = ../../../../../System/Library/PrivateFrameworks/CoreBrightness.framework; sourceTree = ""; }; @@ -284,6 +288,8 @@ 60669B4220AD8FA80074E817 /* GroupBarItem.swift */, B08173262135F02B005D4908 /* NightShiftBarItem.swift */, B081732B213739FE005D4908 /* DnDBarItem.swift */, + B08126EE217BD0B900A98970 /* PomodoroBarItem.swift */, + B08126F0217BE19000A98970 /* WidgetProtocol.swift */, ); path = Widgets; sourceTree = ""; @@ -432,9 +438,11 @@ B05600D32083E9BB00EB218D /* CustomSlider.swift in Sources */, 36C2ECD9207B74B4003CDA33 /* AppleScriptTouchBarItem.swift in Sources */, B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */, + B08126F1217BE19000A98970 /* WidgetProtocol.swift in Sources */, B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */, 6042B6A72083E03A00C525C8 /* AppScrubberTouchBarItem.swift in Sources */, B082B253205C7D8000BC04DC /* AppDelegate.swift in Sources */, + B08126EF217BD0B900A98970 /* PomodoroBarItem.swift in Sources */, B081732C213739FE005D4908 /* DnDBarItem.swift in Sources */, 60C44AFD20A373A100C0EC91 /* MusicBarItem.swift in Sources */, B059D624205E04F3006E6B86 /* CustomButtonTouchBarItem.swift in Sources */, diff --git a/MTMR/Info.plist b/MTMR/Info.plist index 038ca21..53888ff 100644 --- a/MTMR/Info.plist +++ b/MTMR/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 0.18.5 CFBundleVersion - 82 + 109 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/MTMR/ItemsParsing.swift b/MTMR/ItemsParsing.swift index 26fc5b3..7843aff 100644 --- a/MTMR/ItemsParsing.swift +++ b/MTMR/ItemsParsing.swift @@ -40,8 +40,14 @@ struct BarItemDefinition: Decodable { } } +typealias ParametersDecoder = (Decoder) throws -> ( + item: ItemType, + action: ActionType, + longAction: LongActionType, + parameters: [GeneralParameters.CodingKeys: GeneralParameter] +) + class SupportedTypesHolder { - typealias ParametersDecoder = (Decoder) throws -> (item: ItemType, action: ActionType, longAction: LongActionType, parameters: [GeneralParameters.CodingKeys: GeneralParameter]) private var supportedTypes: [String: ParametersDecoder] = [ "escape": { _ in ( item: .staticButton(title: "esc"), @@ -304,6 +310,8 @@ class SupportedTypesHolder { parameters: [:] ) }, + + PomodoroBarItem.name: PomodoroBarItem.decoder, ] static let sharedInstance = SupportedTypesHolder() @@ -345,6 +353,7 @@ enum ItemType: Decodable { case groupBar(items: [BarItemDefinition]) case nightShift() case dnd() + case pomodoro(workTime: Double, restTime: Double) private enum CodingKeys: String, CodingKey { case type @@ -361,6 +370,8 @@ enum ItemType: Decodable { case url case longUrl case items + case workTime + case restTime } enum ItemTypeRaw: String, Decodable { @@ -378,6 +389,7 @@ enum ItemType: Decodable { case groupBar case nightShift case dnd + case pomodoro } init(from decoder: Decoder) throws { @@ -439,6 +451,11 @@ enum ItemType: Decodable { case .dnd: self = .dnd() + + case .pomodoro: + let workTime = try container.decodeIfPresent(Double.self, forKey: .workTime) ?? 1500.0 + let restTime = try container.decodeIfPresent(Double.self, forKey: .restTime) ?? 600.0 + self = .pomodoro(workTime: workTime, restTime: restTime) } } } diff --git a/MTMR/TouchBarController.swift b/MTMR/TouchBarController.swift index 4b1fe39..ed54402 100644 --- a/MTMR/TouchBarController.swift +++ b/MTMR/TouchBarController.swift @@ -48,6 +48,8 @@ extension ItemType { return "com.toxblh.mtmr.nightShift." case .dnd(items: _): return "com.toxblh.mtmr.dnd." + case .pomodoro(interval: _): + return PomodoroBarItem.identifier } } @@ -281,6 +283,8 @@ class TouchBarController: NSObject, NSTouchBarDelegate { barItem = NightShiftBarItem(identifier: identifier) case .dnd(): barItem = DnDBarItem(identifier: identifier) + case .pomodoro(workTime: let workTime, restTime: let restTime): + barItem = PomodoroBarItem(identifier: identifier, workTime: workTime, restTime: restTime) } if let action = self.action(forItem: item), let item = barItem as? CustomButtonTouchBarItem { diff --git a/MTMR/Widgets/PomodoroBarItem.swift b/MTMR/Widgets/PomodoroBarItem.swift new file mode 100644 index 0000000..c106938 --- /dev/null +++ b/MTMR/Widgets/PomodoroBarItem.swift @@ -0,0 +1,123 @@ +// +// PomodoroBarItem.swift +// MTMR +// +// Created by Daniel Apatin on 10.05.2018. +// Copyright © 2018 Anton Palgunov. All rights reserved. +// + +import Cocoa + +class PomodoroBarItem: CustomButtonTouchBarItem, Widget { + static let identifier = "com.toxblh.mtmr.pomodoro." + static let name = "pomodoro" + static let decoder: ParametersDecoder = { decoder in + enum CodingKeys: String, CodingKey { + case workTime + case restTime + } + + let container = try decoder.container(keyedBy: CodingKeys.self) + let workTime = try container.decodeIfPresent(Double.self, forKey: .workTime) + let restTime = try container.decodeIfPresent(Double.self, forKey: .restTime) + + return ( + item: .pomodoro(workTime: workTime ?? 1500.00, restTime: restTime ?? 300), + action: .none, + longAction: .none, + parameters: [:] + ) + } + + private enum TimeTypes { + case work + case rest + case none + } + private let defaultTitle = "🍅" + private let workTime: TimeInterval + private let restTime: TimeInterval + private var typeTime: TimeTypes = .none + private var timer: DispatchSourceTimer? + + private var timeLeft: Int = 0 + private var timeLeftString: String { + return String(format: "%.2i:%.2i", timeLeft / 60, timeLeft % 60) + } + + init(identifier: NSTouchBarItem.Identifier, workTime: TimeInterval, restTime: TimeInterval) { + self.workTime = workTime + self.restTime = restTime + super.init(identifier: identifier, title: defaultTitle) + tapClosure = { [weak self] in self?.startStopWork() } + longTapClosure = { [weak self] in self?.startStopRest() } + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + timer?.cancel() + timer = nil + } + + @objc func startStopWork() { + typeTime = .work + startStopTimer() + } + + @objc func startStopRest() { + typeTime = .rest + startStopTimer() + } + + func startStopTimer() { + timer == nil ? start() : reset() + } + + private func start() { + timeLeft = Int(typeTime == .work ? workTime : restTime) + let queue: DispatchQueue = DispatchQueue(label: "Timer") + timer = DispatchSource.makeTimerSource(flags: .strict, queue: queue) + timer?.schedule(deadline: .now(), repeating: .seconds(1), leeway: .never) + timer?.setEventHandler(handler: tick) + timer?.resume() + + NSSound.beep() + } + + private func finish() { + if typeTime != .none { + sendNotification() + } + + reset() + } + + private func reset() { + typeTime = .none + timer?.cancel() + timer = nil + title = defaultTitle + } + + private func tick() { + timeLeft -= 1 + DispatchQueue.main.async { + if self.timeLeft >= 0 { + self.title = self.defaultTitle + " " + self.timeLeftString + } else { + self.finish() + } + } + } + + private func sendNotification() { + let notification: NSUserNotification = NSUserNotification() + notification.title = "Pomodoro" + notification.informativeText = typeTime == .work ? "it's time to rest your mind!" : "It's time to work!" + notification.soundName = "Submarine" + NSUserNotificationCenter.default.deliver(notification) + } +} diff --git a/MTMR/Widgets/WidgetProtocol.swift b/MTMR/Widgets/WidgetProtocol.swift new file mode 100644 index 0000000..2775c47 --- /dev/null +++ b/MTMR/Widgets/WidgetProtocol.swift @@ -0,0 +1,13 @@ +// +// WidgetProtocol.swift +// MTMR +// +// Created by Anton Palgunov on 20/10/2018. +// Copyright © 2018 Anton Palgunov. All rights reserved. +// + +protocol Widget { + static var name: String { get } + static var identifier: String { get } + static var decoder: ParametersDecoder { get } +} diff --git a/README.md b/README.md index 0227fb5..19a31dc 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,4 @@ - -

- -

+

# My TouchBar. My rules @@ -26,48 +23,12 @@ My the idea is to create the program like a platform for plugins for customization TouchBar. I very like BTT and a full custom TouchBar (my [BTT preset](https://github.com/Toxblh/btt-touchbar-preset)). And I want to create it. And it's my the first Swift project for MacOS :) -### Roadmap -- [x] Create the first prototype with TouchBar in Storyboard -- [x] Put in stripe menu on startup the application -- [x] Find how to simulate real buttons like brightness, volume, night shift and etc. -- [x] Time in touchbar! -- [x] First the weather plugin -- [x] Find how to open full-screen TouchBar without the cross and stripe menu -- [x] Find how to add haptic feedback -- [x] Add icon and menu in StatusBar -- [x] Hide from Dock -- [x] Status menu: "preferences", "quit" -- [x] JSON or another approch for save preset, maybe in `~/Library/Application Support/MTMR/` -- [x] Custom buttons size, actions by click -- [x] Layout: [always left, NSSliderView for center, always right] -- [x] System for autoupdate (https://sparkle-project.org/) -- [ ] Overwrite default values from item types (e.g. title for brightness) -- [ ] Custom settings for paddings and margins for buttons -- [ ] XPC Service for scripts -- [ ] UI for settings -- [ ] Import config from BTT - -Settings: -- [ ] Interface for plugins and export like presets -- [x] Startup at login -- [ ] Show on/off in Dock -- [ ] Show on/off in StatusBar -- [ ] On/off Haptic Feedback - -Maybe: -- [ ] Refactoring the application on packages (AppleScript, JavaScript? and Swift?) - - ## Installation -- Download last [release](https://github.com/Toxblh/MTMR/releases) +- Download last [release](https://github.com/Toxblh/MTMR/releases) from github - Or via Homebrew `brew cask install mtmr` -## Preset - -File for customize your preset for MTMR: `open ~/Library/Application\ Support/MTMR/items.json` - ## Built-in button types: - +> Buttons - escape - exitTouchbar - brightnessUp @@ -77,9 +38,6 @@ File for customize your preset for MTMR: `open ~/Library/Application\ Support/MT - volumeDown - volumeUp - mute -- dock (half-long click to open app, full-long click to kill app) -- nightShift -- dnd (Dont disturb) > Native Plugins - battery @@ -87,6 +45,10 @@ File for customize your preset for MTMR: `open ~/Library/Application\ Support/MT - weather - inputsource - music (tap for pause, longTap for next) +- dock (half-long click to open app, full-long click to kill app) +- nightShift +- dnd (Don't disturb) +- pomodoro > Media Keys - previous @@ -139,39 +101,50 @@ File for customize your preset for MTMR: `open ~/Library/Application\ Support/MT "align": "center", "bordered": true, "title": "stats", -"items": [{ button }, {button}, ...] +"items": [ + { "type": "play" }, { "type": "mute" }, ...] } ``` ## Native plugins -- `weather` +#### `weather` > Provider: https://openweathermap.org Need allowance location service ```js "type": "weather", - "refreshInterval": 600, + "refreshInterval": 600, // in seconds "units": "metric", // or imperial "icon_type": "text" // or images "api_key": "" // you can get the key on openweather ``` -- `currency` +#### `currency` > Provider: https://coinbase.com ```js "type": "currency", - "refreshInterval": 600, + "refreshInterval": 600, // in seconds "align": "right", "from": "BTC", "to": "USD", ``` -- `music` +#### `music` ```js { "type": "music", "align": "center", "width": 80, "bordered": false, - "refreshInterval": 2, + "refreshInterval": 2, // in seconds +}, +``` + +#### `pomodoro` +> Pomodoro plugin. One click for start work timer, longclick for start rest timer. Click in progress for reset. +```js +{ + "type": "pomodoro", + "workTime": 1200, // set time work in seconds. Default 1500 (25 min) + "restTime": 600, // set time rest in seconds. Default 300 (5 min) }, ``` @@ -235,7 +208,12 @@ This then you want to use longPress for some operations is will the same values - `align` can stick the item to the side. default is center ```js - "align": "left" //or "right" or "center" + "align": "left" // "left", "right" or "center" +``` + +- `bordered` you can do button without border +```js + "bordered": "false" // "true" or "false" ``` ## Example configuration: @@ -296,6 +274,37 @@ This then you want to use longPress for some operations is will the same values ``` +### Roadmap +- [x] Create the first prototype with TouchBar in Storyboard +- [x] Put in stripe menu on startup the application +- [x] Find how to simulate real buttons like brightness, volume, night shift and etc. +- [x] Time in touchbar! +- [x] First the weather plugin +- [x] Find how to open full-screen TouchBar without the cross and stripe menu +- [x] Find how to add haptic feedback +- [x] Add icon and menu in StatusBar +- [x] Hide from Dock +- [x] Status menu: "preferences", "quit" +- [x] JSON or another approch for save preset, maybe in `~/Library/Application Support/MTMR/` +- [x] Custom buttons size, actions by click +- [x] Layout: [always left, NSSliderView for center, always right] +- [x] System for autoupdate (https://sparkle-project.org/) +- [ ] Overwrite default values from item types (e.g. title for brightness) +- [ ] Custom settings for paddings and margins for buttons +- [ ] XPC Service for scripts +- [ ] UI for settings +- [ ] Import config from BTT + +Settings: +- [ ] Interface for plugins and export like presets +- [x] Startup at login +- [ ] Show on/off in Dock +- [ ] Show on/off in StatusBar +- [ ] On/off Haptic Feedback + +Maybe: +- [ ] Refactoring the application on packages (AppleScript, JavaScript? and Swift?) + ### Author's presets [@Toxblh preset](Resources/toxblh.json)