diff --git a/MTMR.xcodeproj/project.pbxproj b/MTMR.xcodeproj/project.pbxproj index 2958cd6..d2b0a60 100644 --- a/MTMR.xcodeproj/project.pbxproj +++ b/MTMR.xcodeproj/project.pbxproj @@ -18,6 +18,8 @@ 6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */; }; 6042B6A72083E03A00C525C8 /* AppScrubberTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */; }; 6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */ = {isa = PBXBuildFile; fileRef = 6042B6A92083E27000C525C8 /* DeprecatedCarbonAPI.c */; }; + 607EEA4B2087835F009DA5F0 /* WeatherBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607EEA4A2087835F009DA5F0 /* WeatherBarItem.swift */; }; + 607EEA4D2087A8DA009DA5F0 /* CurrencyBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */; }; B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0008E542080286C003AD4DD /* SupportHelpers.swift */; }; B05600D32083E9BB00EB218D /* CustomSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B05600D22083E9BB00EB218D /* CustomSlider.swift */; }; B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B059D621205E03F5006E6B86 /* TouchBarController.swift */; }; @@ -66,6 +68,8 @@ 6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScrubberTouchBarItem.swift; sourceTree = ""; }; 6042B6A82083E1F500C525C8 /* DeprecatedCarbonAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeprecatedCarbonAPI.h; sourceTree = ""; }; 6042B6A92083E27000C525C8 /* DeprecatedCarbonAPI.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = DeprecatedCarbonAPI.c; sourceTree = ""; }; + 607EEA4A2087835F009DA5F0 /* WeatherBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherBarItem.swift; sourceTree = ""; }; + 607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyBarItem.swift; sourceTree = ""; }; B0008E542080286C003AD4DD /* SupportHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportHelpers.swift; sourceTree = ""; }; B05600D22083E9BB00EB218D /* CustomSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSlider.swift; sourceTree = ""; }; B059D621205E03F5006E6B86 /* TouchBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchBarController.swift; sourceTree = ""; }; @@ -172,6 +176,8 @@ 368EDDE620812A1D00E10953 /* ScrollViewItem.swift */, 6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */, B05600D22083E9BB00EB218D /* CustomSlider.swift */, + 607EEA4A2087835F009DA5F0 /* WeatherBarItem.swift */, + 607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */, ); path = MTMR; sourceTree = ""; @@ -340,11 +346,13 @@ B082B253205C7D8000BC04DC /* AppDelegate.swift in Sources */, B059D624205E04F3006E6B86 /* CustomButtonTouchBarItem.swift in Sources */, 6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */, + 607EEA4B2087835F009DA5F0 /* WeatherBarItem.swift in Sources */, 6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */, B09EB1E4207C082000D5C1E0 /* HapticFeedback.swift in Sources */, 36C2ECDB207C3FE7003CDA33 /* ItemsParsing.swift in Sources */, B0A7E9AA205D6AA400EEF070 /* KeyPress.swift in Sources */, 36C2ECD7207B6DAE003CDA33 /* TimeTouchBarItem.swift in Sources */, + 607EEA4D2087A8DA009DA5F0 /* CurrencyBarItem.swift in Sources */, 6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */, 368EDDE720812A1D00E10953 /* ScrollViewItem.swift in Sources */, ); diff --git a/MTMR.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/MTMR.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..3ddf867 --- /dev/null +++ b/MTMR.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + BuildSystemType + Latest + + diff --git a/MTMR/CurrencyBarItem.swift b/MTMR/CurrencyBarItem.swift new file mode 100644 index 0000000..584f9b9 --- /dev/null +++ b/MTMR/CurrencyBarItem.swift @@ -0,0 +1,97 @@ +// +// CurrencyBarItem.swift +// MTMR +// +// Created by Daniel Apatin on 18.04.2018. +// Copyright © 2018 Anton Palgunov. All rights reserved. +// + +import Cocoa +import CoreLocation + +class CurrencyBarItem: NSCustomTouchBarItem { + private var timer: Timer! + private var interval: TimeInterval! + private var prefix: String + private var from: String + private var to: String + private let button = NSButton(title: "", target: nil, action: nil) + + private let currencies = [ + "USD": "$", + "EUR": "€", + "RUB": "₽", + "JPY": "¥", + "GBP": "₤", + "CAD": "$", + "KRW": "₩", + "CNY": "¥", + "AUD": "$", + "BRL": "R$", + "IDR": "Rp", + "MXN": "$", + "SGD": "$", + "CHF": "Fr." + ] + + init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, from: String, to: String) { + self.interval = interval + self.from = from + self.to = to + + if let prefix = currencies[from] { + self.prefix = prefix + } else { + self.prefix = from + } + + super.init(identifier: identifier) + + button.bezelColor = .clear + button.title = "⏳" + self.view = button + + timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(updateCurrency), userInfo: nil, repeats: true) + + updateCurrency() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc func updateCurrency() { + let urlRequest = URLRequest(url: URL(string: "https://api.coinbase.com/v2/exchange-rates?currency=\(from)")!) + + let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in + if error == nil { + do { + let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String : AnyObject] + + var value: String! + + if let data_array = json["data"] as? [String : AnyObject] { + if let rates = data_array["rates"] as? [String : AnyObject] { + if let item = rates["\(self.to)"] as? String { + value = item + } + } + } + if value != nil { + DispatchQueue.main.async { + self.setCurrency(text: "\(value!)") + } + } + } catch let jsonError { + print(jsonError.localizedDescription) + } + } + } + + task.resume() + } + + func setCurrency(text: String) { + button.title = "\(self.prefix)\(text)" + } +} diff --git a/MTMR/Info.plist b/MTMR/Info.plist index 321e78e..f881c6b 100644 --- a/MTMR/Info.plist +++ b/MTMR/Info.plist @@ -26,6 +26,8 @@ NSHumanReadableCopyright Copyright © 2018 Anton Palgunov. All rights reserved. + NSLocationUsageDescription + ... NSMainStoryboardFile Main NSPrincipalClass diff --git a/MTMR/ItemsParsing.swift b/MTMR/ItemsParsing.swift index 5851f67..39db97c 100644 --- a/MTMR/ItemsParsing.swift +++ b/MTMR/ItemsParsing.swift @@ -73,12 +73,20 @@ class SupportedTypesHolder { return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_NEXT), parameters: [.image: imageParameter]) }, "weather": { decoder in - enum CodingKeys: String, CodingKey { case refreshInterval } + enum CodingKeys: String, CodingKey { case refreshInterval; case units; case api_key } let container = try decoder.container(keyedBy: CodingKeys.self) let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) - let scriptPath = Bundle.main.path(forResource: "Weather", ofType: "scpt")! - let item = ItemType.appleScriptTitledButton(source: Source(filePath: scriptPath), refreshInterval: interval ?? 1800.0) - return (item: item, action: .none, parameters: [:]) + let units = try container.decodeIfPresent(String.self, forKey: .units) + let api_key = try container.decodeIfPresent(String.self, forKey: .api_key) + return (item: .weather(interval: interval ?? 1800.00, units: units ?? "metric", api_key: api_key ?? "32c4256d09a4c52b38aecddba7a078f6"), action: .none, parameters: [:]) + }, + "currency": { decoder in + enum CodingKeys: String, CodingKey { case refreshInterval; case from; case to } + let container = try decoder.container(keyedBy: CodingKeys.self) + let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) + let from = try container.decodeIfPresent(String.self, forKey: .from) + let to = try container.decodeIfPresent(String.self, forKey: .to) + return (item: .currency(interval: interval ?? 600.00, from: from ?? "RUB", to: to ?? "USD"), action: .none, parameters: [:]) }, "dock": { decoder in return (item: .dock(), action: .none, parameters: [:]) @@ -140,12 +148,18 @@ enum ItemType: Decodable { case dock() case volume() case brightness(refreshInterval: Double) + case weather(interval: Double, units: String, api_key: String) + case currency(interval: Double, from: String, to: String) private enum CodingKeys: String, CodingKey { case type case title case source case refreshInterval + case from + case to + case units + case api_key case formatTemplate case image } @@ -157,6 +171,8 @@ enum ItemType: Decodable { case dock case volume case brightness + case weather + case currency } init(from decoder: Decoder) throws { @@ -180,6 +196,16 @@ enum ItemType: Decodable { case .brightness: let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 0.5 self = .brightness(refreshInterval: interval) + case .weather: + let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0 + let units = try container.decodeIfPresent(String.self, forKey: .units) ?? "metric" + let api_key = try container.decodeIfPresent(String.self, forKey: .api_key) ?? "32c4256d09a4c52b38aecddba7a078f6" + self = .weather(interval: interval, units: units, api_key: api_key) + case .currency: + let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0 + let from = try container.decodeIfPresent(String.self, forKey: .from) ?? "RUB" + let to = try container.decodeIfPresent(String.self, forKey: .to) ?? "USD" + self = .currency(interval: interval, from: from, to: to) } } } diff --git a/MTMR/TouchBarController.swift b/MTMR/TouchBarController.swift index c652951..2ff16a4 100644 --- a/MTMR/TouchBarController.swift +++ b/MTMR/TouchBarController.swift @@ -29,6 +29,10 @@ extension ItemType { return "com.toxblh.mtmr.volume" case .brightness(refreshInterval: _): return "com.toxblh.mtmr.brightness" + case .weather(interval: _, units: _, api_key: _): + return "com.toxblh.mtmr.weather" + case .currency(interval: _, from: _, to: _): + return "com.toxblh.mtmr.currency" } } @@ -174,7 +178,12 @@ class TouchBarController: NSObject, NSTouchBarDelegate { } else { barItem = BrightnessViewController(identifier: identifier, refreshInterval: interval) } + case .weather(interval: let interval, units: let units, api_key: let api_key): + barItem = WeatherBarItem(identifier: identifier, interval: interval, units: units, api_key: api_key) + case .currency(interval: let interval, from: let from, to: let to): + barItem = CurrencyBarItem(identifier: identifier, interval: interval, from: from, to: to) } + if case .width(let value)? = item.additionalParameters[.width], let widthBarItem = barItem as? CanSetWidth { widthBarItem.setWidth(value: value) } diff --git a/MTMR/WeatherBarItem.swift b/MTMR/WeatherBarItem.swift new file mode 100644 index 0000000..b902375 --- /dev/null +++ b/MTMR/WeatherBarItem.swift @@ -0,0 +1,152 @@ +// +// WeatherBarItem.swift +// MTMR +// +// Created by Daniel Apatin on 18.04.2018. +// Copyright © 2018 Anton Palgunov. All rights reserved. +// + +import Cocoa +import CoreLocation + +class WeatherBarItem: NSCustomTouchBarItem, CLLocationManagerDelegate { + private let dateFormatter = DateFormatter() + private var timer: Timer! + private var interval: TimeInterval! + private var units: String + private var api_key: String + private var units_str = "°F" + private let button = NSButton(title: "", target: nil, action: nil) + private var prev_location: CLLocation! + private var location: CLLocation! + + private var manager:CLLocationManager! + + init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, units: String, api_key: String) { + self.interval = interval + self.units = units + self.api_key = api_key + + if self.units == "metric" { + units_str = "°C" + } + + super.init(identifier: identifier) + + button.bezelColor = .clear + button.title = "⏳" + self.view = button + + let status = CLLocationManager.authorizationStatus() + if status == .restricted || status == .denied { + print("User permission not given") + return + } + + if !CLLocationManager.locationServicesEnabled() { + print("not enabled"); + return + } + + timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(updateWeather), userInfo: nil, repeats: true) + + manager = CLLocationManager() + manager.delegate = self + manager.desiredAccuracy = kCLLocationAccuracyHundredMeters + manager.startUpdatingLocation() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc func updateWeather() { + if self.location != nil { + let urlRequest = URLRequest(url: URL(string: "https://api.openweathermap.org/data/2.5/weather?lat=\(location.coordinate.latitude)&lon=\(location.coordinate.longitude)&units=\(self.units)&appid=\(self.api_key)")!) + + let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in + + if error == nil { + do { + let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String : AnyObject] + + var temperature: Int! + var condition_icon = "" + + if let main = json["main"] as? [String : AnyObject] { + if let temp = main["temp"] as? Int { + temperature = temp + } + } + + if let weather = json["weather"] as? NSArray, let item = weather[0] as? NSDictionary { + let icon = item["icon"] as! String + switch (icon) { + case "01d", "01n": + condition_icon = "☀️" + break + case "02d", "02n": + condition_icon = "⛅️" + break + case "03d", "03n", "04d", "04n": + condition_icon = "☁️" + break + case "09d", "09n": + condition_icon = "⛅️" + break + case "10d", "10n": + condition_icon = "🌦" + break + case "11d", "11n": + condition_icon = "🌩" + break + case "13d", "13n": + condition_icon = "❄️" + break + case "50d", "50n": + condition_icon = "🌫" + break + default: + condition_icon = "" + } + } + + if temperature != nil { + DispatchQueue.main.async { + self.setWeather(text: "\(condition_icon) \(temperature!)\(self.units_str)") + } + } + } catch let jsonError { + print(jsonError.localizedDescription) + } + } + } + + task.resume() + } + } + + func setWeather(text: String) { + button.title = text + } + + func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + let lastLocation = locations.last! + self.location = lastLocation + if prev_location == nil { + updateWeather() + } + prev_location = lastLocation + } + + func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { + print(error); + } + + func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { + print("inside didChangeAuthorization "); + updateWeather() + } + +} +