From 706e8219ce54b7deeac8dcca47a8bd181b55dd48 Mon Sep 17 00:00:00 2001 From: Serhiy Mytrovtsiy Date: Wed, 15 Jul 2020 22:18:24 +0200 Subject: [PATCH] - add low battery notification with option to select battery level (disabled/3%/5%/10%/15%/20%/30%/40%/50%) --- Modules/Battery/main.swift | 31 ++++++- Modules/Battery/settings.swift | 80 ++++++++++++++++++ Stats.xcodeproj/project.pbxproj | 4 + .../low-battery.imageset/Contents.json | 21 +++++ .../low-battery.imageset/low-battery.png | Bin 0 -> 36212 bytes Stats/helpers.swift | 10 +-- StatsKit/extensions.swift | 16 ++++ 7 files changed, 152 insertions(+), 10 deletions(-) create mode 100644 Modules/Battery/settings.swift create mode 100644 Stats/Supporting Files/Assets.xcassets/low-battery.imageset/Contents.json create mode 100644 Stats/Supporting Files/Assets.xcassets/low-battery.imageset/low-battery.png 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 0000000000000000000000000000000000000000..651ba3e318eefb33ae6fd319f787b1d28285e7c9 GIT binary patch literal 36212 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 QMIemNU(kQ0d+p)>0Et0&e*gdg literal 0 HcmV?d00001 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) +}