diff --git a/Modules/Battery/main.swift b/Modules/Battery/main.swift index 2994c840..fec08c91 100644 --- a/Modules/Battery/main.swift +++ b/Modules/Battery/main.swift @@ -27,7 +27,7 @@ struct Battery_Usage: value_t { var temperature: Double = 0 var ACwatts: Int = 0 - var ACstatus: Bool = true + var ACstatus: Bool = false var timeToEmpty: Int = 0 var timeToCharge: Int = 0 @@ -42,12 +42,18 @@ struct Battery_Usage: value_t { public class Battery: Module { private var usageReader: UsageReader? = nil private let popupView: Popup = Popup() + private var settingsView: Settings + + private let store: UnsafePointer public init(_ store: UnsafePointer) { + self.store = store + self.settingsView = Settings("Battery", store: store) + super.init( store: store, popup: self.popupView, - settings: nil + settings: self.settingsView ) guard self.available else { return } @@ -76,6 +82,7 @@ public class Battery: Module { return } + self.checkNotification(value: value!) self.popupView.usageCallback(value!) if let widget = self.widget as? Mini { widget.setValue(abs(value!.level), sufix: "%") @@ -88,4 +95,24 @@ public class Battery: Module { ) } } + + private func checkNotification(value: Battery_Usage) { + let level = self.store.pointee.string(key: "\(self.config.name)_lowLevelNotification", defaultValue: "0.15") + if level == "Disabled" { + return + } + + var subtitle = "\((Int(value.level*100)))% remaining" + if value.timeToEmpty != 0 { + subtitle += " (\(Double(value.timeToEmpty*60).printSecondsToHoursMinutesSeconds()))" + } + if let notificationLevel = Double(level), value.level <= notificationLevel { + showNotification( + title: "Low battery", + subtitle: subtitle, + id: "battery-level", + icon: NSImage(named: NSImage.Name("low-battery"))! + ) + } + } } diff --git a/Modules/Battery/settings.swift b/Modules/Battery/settings.swift new file mode 100644 index 00000000..7f266785 --- /dev/null +++ b/Modules/Battery/settings.swift @@ -0,0 +1,80 @@ +// +// settings.swift +// Battery +// +// Created by Serhiy Mytrovtsiy on 15/07/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import StatsKit +import ModuleKit +import SystemConfiguration + +internal class Settings: NSView, Settings_v { + public var callback: (() -> Void) = {} + + private let title: String + private let store: UnsafePointer + private var button: NSPopUpButton? + + private let levelsList: [String] = ["Disabled", "0.03", "0.05", "0.1", "0.15", "0.2", "0.25", "0.3", "0.4", "0.5"] + private var lowLevelNotification: String { + get { + return self.store.pointee.string(key: "\(self.title)_lowLevelNotification", defaultValue: "0.15") + } + } + + public init(_ title: String, store: UnsafePointer) { + self.title = title + self.store = store + + super.init(frame: CGRect(x: Constants.Settings.margin, y: Constants.Settings.margin, width: Constants.Settings.width - (Constants.Settings.margin*2), height: 0)) + + self.wantsLayer = true + self.canDrawConcurrently = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func load(widget: widget_t) { + self.subviews.forEach{ $0.removeFromSuperview() } + + let rowHeight: CGFloat = 30 + + let levels: [String] = self.levelsList.map { (v: String) -> String in + if let level = Double(v) { + return "\(Int(level*100))%" + } + return v + } + + self.addSubview(SelectTitleRow( + frame: NSRect( + x:Constants.Settings.margin, + y: Constants.Settings.margin + (rowHeight + Constants.Settings.margin) * 0, + width: self.frame.width - (Constants.Settings.margin*2), + height: rowHeight + ), + title: "Low level notification", + action: #selector(changeUpdateInterval), + items: levels, + selected: self.lowLevelNotification == "Disabled" ? self.lowLevelNotification : "\(Int((Double(self.lowLevelNotification) ?? 0)*100))%" + )) + + self.setFrameSize(NSSize(width: self.frame.width, height: 30 + (Constants.Settings.margin*2))) + } + + @objc private func changeUpdateInterval(_ sender: NSMenuItem) { + if sender.title == "Disabled" { + store.pointee.set(key: "\(self.title)_lowLevelNotification", value: sender.title) + } else if let value = Double(sender.title.replacingOccurrences(of: "%", with: "")) { + store.pointee.set(key: "\(self.title)_lowLevelNotification", value: "\(value/100)") + } + } +} diff --git a/Stats.xcodeproj/project.pbxproj b/Stats.xcodeproj/project.pbxproj index 115e821c..7b011abc 100644 --- a/Stats.xcodeproj/project.pbxproj +++ b/Stats.xcodeproj/project.pbxproj @@ -97,6 +97,7 @@ 9ABFF912248BF39500C9041A /* Battery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ABFF911248BF39500C9041A /* Battery.swift */; }; 9ABFF914248C30A800C9041A /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ABFF913248C30A800C9041A /* popup.swift */; }; 9AD33AC624BCD3EE007E8820 /* helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AD33AC524BCD3EE007E8820 /* helpers.swift */; }; + 9AD64FA224BF86C100419D59 /* settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AD64FA124BF86C100419D59 /* settings.swift */; }; 9AE29ADC249A50350071B02D /* Sensors.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AE29AD5249A50350071B02D /* Sensors.framework */; }; 9AE29ADD249A50350071B02D /* Sensors.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9AE29AD5249A50350071B02D /* Sensors.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 9AE29AE1249A50640071B02D /* ModuleKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; }; @@ -469,6 +470,7 @@ 9ABFF911248BF39500C9041A /* Battery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Battery.swift; sourceTree = ""; }; 9ABFF913248C30A800C9041A /* popup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = popup.swift; sourceTree = ""; }; 9AD33AC524BCD3EE007E8820 /* helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = helpers.swift; sourceTree = ""; }; + 9AD64FA124BF86C100419D59 /* settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = settings.swift; sourceTree = ""; }; 9AE29AD5249A50350071B02D /* Sensors.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Sensors.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9AE29AEC249A50960071B02D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; name = Info.plist; path = Modules/Sensors/Info.plist; sourceTree = SOURCE_ROOT; }; 9AE29AF1249A50CD0071B02D /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = main.swift; path = Modules/Sensors/main.swift; sourceTree = SOURCE_ROOT; }; @@ -778,6 +780,7 @@ 9ABFF902248BEBD700C9041A /* main.swift */, 9ABFF90F248BEE7200C9041A /* readers.swift */, 9ABFF913248C30A800C9041A /* popup.swift */, + 9AD64FA124BF86C100419D59 /* settings.swift */, 9ABFF8F9248BEBCB00C9041A /* Info.plist */, 9ABFF904248BEC0B00C9041A /* config.plist */, ); @@ -1350,6 +1353,7 @@ files = ( 9ABFF910248BEE7200C9041A /* readers.swift in Sources */, 9ABFF914248C30A800C9041A /* popup.swift in Sources */, + 9AD64FA224BF86C100419D59 /* settings.swift in Sources */, 9ABFF903248BEBD700C9041A /* main.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Stats/Supporting Files/Assets.xcassets/low-battery.imageset/Contents.json b/Stats/Supporting Files/Assets.xcassets/low-battery.imageset/Contents.json new file mode 100644 index 00000000..3914fc90 --- /dev/null +++ b/Stats/Supporting Files/Assets.xcassets/low-battery.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "low-battery.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Stats/Supporting Files/Assets.xcassets/low-battery.imageset/low-battery.png b/Stats/Supporting Files/Assets.xcassets/low-battery.imageset/low-battery.png new file mode 100644 index 00000000..651ba3e3 Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/low-battery.imageset/low-battery.png differ diff --git a/Stats/helpers.swift b/Stats/helpers.swift index 18208b33..2bf63d4f 100644 --- a/Stats/helpers.swift +++ b/Stats/helpers.swift @@ -67,14 +67,8 @@ extension AppDelegate { } if IsNewestVersion(currentVersion: prevVersion, latestVersion: currentVersion) { - let notification = NSUserNotification() - notification.identifier = "updated-from-\(prevVersion)-to-\(currentVersion)" - notification.title = "Successfully updated" - notification.subtitle = "Stats was updated to the v\(currentVersion)" - notification.soundName = NSUserNotificationDefaultSoundName - notification.hasActionButton = false - - NSUserNotificationCenter.default.deliver(notification) + showNotification(title: "Successfully updated", subtitle: "Stats was updated to the v\(currentVersion)", id: "updated-from-\(prevVersion)-to-\(currentVersion)" + ) } os_log(.debug, log: log, "Detected previous version %s. Current version (%s) set", prevVersion, currentVersion) diff --git a/StatsKit/extensions.swift b/StatsKit/extensions.swift index 92eb43be..1f7358d9 100644 --- a/StatsKit/extensions.swift +++ b/StatsKit/extensions.swift @@ -767,3 +767,19 @@ public enum updateIntervals: updateInterval { case never = "Never" } extension updateIntervals: CaseIterable {} + +public func showNotification(title: String, subtitle: String, id: String = UUID().uuidString, icon: NSImage? = nil) { + let notification = NSUserNotification() + + notification.identifier = id + notification.title = title + notification.subtitle = subtitle + notification.soundName = NSUserNotificationDefaultSoundName + notification.hasActionButton = false + + if icon != nil { + notification.setValue(icon, forKey: "_identityImage") + } + + NSUserNotificationCenter.default.deliver(notification) +}