Skip to content

Commit c2e5259

Browse files
authored
Notify user on low power situations (#19)
1 parent ee4caec commit c2e5259

File tree

6 files changed

+117
-11
lines changed

6 files changed

+117
-11
lines changed

Juice.xcodeproj/project.pbxproj

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
725AA5E01E05DBDB006E5DDE /* PreferencesCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 725AA5DF1E05DBDB006E5DDE /* PreferencesCoordinator.swift */; };
2323
725AA5E51E05DE22006E5DDE /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 725AA5E31E05DE22006E5DDE /* PreferencesWindowController.swift */; };
2424
725AA5E61E05DE22006E5DDE /* PreferencesWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 725AA5E41E05DE22006E5DDE /* PreferencesWindowController.xib */; };
25+
728C64581E1AB52F008DA4A6 /* LowPowerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 728C64571E1AB52F008DA4A6 /* LowPowerCoordinator.swift */; };
2526
728EB92E1E0601D800920B83 /* ChargeScaleDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 728EB92D1E0601D800920B83 /* ChargeScaleDisplay.swift */; };
2627
728EB9311E06033300920B83 /* PowerSource+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 728EB9301E06033300920B83 /* PowerSource+Extensions.swift */; };
2728
728EB9341E0605CB00920B83 /* FileManager+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 728EB9331E0605CB00920B83 /* FileManager+Extensions.swift */; };
@@ -73,6 +74,7 @@
7374
725AA5DF1E05DBDB006E5DDE /* PreferencesCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PreferencesCoordinator.swift; path = Classes/PreferencesCoordinator.swift; sourceTree = "<group>"; };
7475
725AA5E31E05DE22006E5DDE /* PreferencesWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PreferencesWindowController.swift; path = Classes/PreferencesWindowController.swift; sourceTree = "<group>"; };
7576
725AA5E41E05DE22006E5DDE /* PreferencesWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = PreferencesWindowController.xib; path = Classes/PreferencesWindowController.xib; sourceTree = "<group>"; };
77+
728C64571E1AB52F008DA4A6 /* LowPowerCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LowPowerCoordinator.swift; path = Classes/LowPowerCoordinator.swift; sourceTree = "<group>"; };
7678
728EB92D1E0601D800920B83 /* ChargeScaleDisplay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ChargeScaleDisplay.swift; path = Classes/ChargeScaleDisplay.swift; sourceTree = "<group>"; };
7779
728EB9301E06033300920B83 /* PowerSource+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "PowerSource+Extensions.swift"; path = "Classes/PowerSource+Extensions.swift"; sourceTree = "<group>"; };
7880
728EB9321E0604D400920B83 /* Juice.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Juice.entitlements; sourceTree = "<group>"; };
@@ -138,7 +140,6 @@
138140
725AA5B31E05BF3A006E5DDE = {
139141
isa = PBXGroup;
140142
children = (
141-
AAA3A2A51E0F3F0F0015165E /* Localizable.strings */,
142143
72DA73671E09FEA7003F9262 /* Shared */,
143144
725AA5BE1E05BF3A006E5DDE /* Juice */,
144145
72DA735B1E09FE19003F9262 /* JuiceHelper */,
@@ -160,16 +161,14 @@
160161
725AA5BE1E05BF3A006E5DDE /* Juice */ = {
161162
isa = PBXGroup;
162163
children = (
163-
728EB9321E0604D400920B83 /* Juice.entitlements */,
164+
728C64591E1ABA35008DA4A6 /* Resources */,
164165
728EB92F1E06032600920B83 /* Extensions */,
165166
728EB92C1E0601A700920B83 /* Display String Parsing */,
167+
728C64561E1AB51D008DA4A6 /* Low Power */,
166168
725AA5DE1E05DBCD006E5DDE /* Preferences */,
167169
725AA5D11E05C18A006E5DDE /* Main Application Hierarchy */,
168170
725AA5CC1E05C083006E5DDE /* Power Source */,
169171
725AA5BF1E05BF3A006E5DDE /* AppDelegate.swift */,
170-
725AA5C11E05BF3A006E5DDE /* Assets.xcassets */,
171-
725AA5C31E05BF3A006E5DDE /* MainMenu.xib */,
172-
725AA5C61E05BF3A006E5DDE /* Info.plist */,
173172
);
174173
path = Juice;
175174
sourceTree = "<group>";
@@ -215,6 +214,26 @@
215214
name = Preferences;
216215
sourceTree = "<group>";
217216
};
217+
728C64561E1AB51D008DA4A6 /* Low Power */ = {
218+
isa = PBXGroup;
219+
children = (
220+
728C64571E1AB52F008DA4A6 /* LowPowerCoordinator.swift */,
221+
);
222+
name = "Low Power";
223+
sourceTree = "<group>";
224+
};
225+
728C64591E1ABA35008DA4A6 /* Resources */ = {
226+
isa = PBXGroup;
227+
children = (
228+
725AA5C61E05BF3A006E5DDE /* Info.plist */,
229+
725AA5C31E05BF3A006E5DDE /* MainMenu.xib */,
230+
725AA5C11E05BF3A006E5DDE /* Assets.xcassets */,
231+
AAA3A2A51E0F3F0F0015165E /* Localizable.strings */,
232+
728EB9321E0604D400920B83 /* Juice.entitlements */,
233+
);
234+
name = Resources;
235+
sourceTree = "<group>";
236+
};
218237
728EB92C1E0601A700920B83 /* Display String Parsing */ = {
219238
isa = PBXGroup;
220239
children = (
@@ -462,6 +481,7 @@
462481
725AA5C01E05BF3A006E5DDE /* AppDelegate.swift in Sources */,
463482
725AA5D31E05C19A006E5DDE /* ApplicationCoordinator.swift in Sources */,
464483
725AA5E01E05DBDB006E5DDE /* PreferencesCoordinator.swift in Sources */,
484+
728C64581E1AB52F008DA4A6 /* LowPowerCoordinator.swift in Sources */,
465485
725AA5E51E05DE22006E5DDE /* PreferencesWindowController.swift in Sources */,
466486
728EB93A1E061B4700920B83 /* PreferencesStorage.swift in Sources */,
467487
725AA5DD1E05D3CD006E5DDE /* StatusItemMenu.swift in Sources */,
@@ -510,6 +530,7 @@
510530
AAA3A2A71E0F3F150015165E /* fr */,
511531
);
512532
name = Localizable.strings;
533+
path = ..;
513534
sourceTree = "<group>";
514535
};
515536
/* End PBXVariantGroup section */
@@ -617,7 +638,7 @@
617638
CODE_SIGN_ENTITLEMENTS = Juice/Juice.entitlements;
618639
CODE_SIGN_IDENTITY = "Mac Developer";
619640
COMBINE_HIDPI_IMAGES = YES;
620-
CURRENT_PROJECT_VERSION = 529;
641+
CURRENT_PROJECT_VERSION = 2017.01.02105829;
621642
DEVELOPMENT_TEAM = YN24FFRTC8;
622643
INFOPLIST_FILE = Juice/Info.plist;
623644
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
@@ -635,7 +656,7 @@
635656
CODE_SIGN_ENTITLEMENTS = Juice/Juice.entitlements;
636657
CODE_SIGN_IDENTITY = "Mac Developer";
637658
COMBINE_HIDPI_IMAGES = YES;
638-
CURRENT_PROJECT_VERSION = 529;
659+
CURRENT_PROJECT_VERSION = 2017.01.02105829;
639660
DEVELOPMENT_TEAM = YN24FFRTC8;
640661
INFOPLIST_FILE = Juice/Info.plist;
641662
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
@@ -652,7 +673,7 @@
652673
CODE_SIGN_ENTITLEMENTS = JuiceHelper/JuiceHelper.entitlements;
653674
CODE_SIGN_IDENTITY = "Mac Developer";
654675
COMBINE_HIDPI_IMAGES = YES;
655-
CURRENT_PROJECT_VERSION = 529;
676+
CURRENT_PROJECT_VERSION = 2017.01.02105829;
656677
DEVELOPMENT_TEAM = YN24FFRTC8;
657678
INFOPLIST_FILE = JuiceHelper/Info.plist;
658679
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
@@ -670,7 +691,7 @@
670691
CODE_SIGN_ENTITLEMENTS = JuiceHelper/JuiceHelper.entitlements;
671692
CODE_SIGN_IDENTITY = "Mac Developer";
672693
COMBINE_HIDPI_IMAGES = YES;
673-
CURRENT_PROJECT_VERSION = 529;
694+
CURRENT_PROJECT_VERSION = 2017.01.02105829;
674695
DEVELOPMENT_TEAM = YN24FFRTC8;
675696
INFOPLIST_FILE = JuiceHelper/Info.plist;
676697
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//
2+
// LowPowerCoordinator.swift
3+
// Juice
4+
//
5+
// Created by Brian Michel on 1/2/17.
6+
// Copyright © 2017 Brian Michel. All rights reserved.
7+
//
8+
9+
import AppKit
10+
import RxSwift
11+
12+
final class LowPowerCoordinator {
13+
private enum Constants {
14+
//TODO: Could probably make this a user preference
15+
static let lowPowerNotificationPercentage = 10
16+
static let lowPowerNotificationIdentifier = "com.bsm.macos.juice.low-power-notification"
17+
}
18+
private let powerSourceObservable: Observable<[PowerSource]>
19+
private let notificationCenter = NSUserNotificationCenter.default
20+
private let disposeBag = DisposeBag()
21+
private var lastPowerState: PowerSourceState = .unknown
22+
23+
private var notificationHasBeenDelivered: Bool {
24+
return notificationCenter.deliveredNotifications.filter({ $0.identifier == Constants.lowPowerNotificationIdentifier }).count > 0
25+
}
26+
27+
init(observable: Observable<[PowerSource]>) {
28+
powerSourceObservable = observable
29+
}
30+
31+
func start() {
32+
powerSourceObservable.shareReplay(1).subscribe { (event) in
33+
switch event {
34+
case .next(let sources):
35+
guard let source = sources.first else {
36+
return
37+
}
38+
self.checkForNotificationDismissal(powerSource: source)
39+
self.checkForWarning(powerSource: source)
40+
case .error(let error):
41+
print("Error: \(error)")
42+
case .completed:
43+
break
44+
}
45+
}.addDisposableTo(disposeBag)
46+
}
47+
48+
private func checkForNotificationDismissal(powerSource: PowerSource) {
49+
if lastPowerState == .battery
50+
&& powerSource.state == .ac {
51+
clearDeliveredNotification()
52+
}
53+
54+
lastPowerState = powerSource.state
55+
}
56+
57+
private func checkForWarning(powerSource: PowerSource) {
58+
if powerSource.chargedPercentage <= Constants.lowPowerNotificationPercentage
59+
&& powerSource.state == .battery
60+
&& !notificationHasBeenDelivered {
61+
let userNotification = NSUserNotification()
62+
userNotification.title = NSLocalizedString("Low Power", comment: "Title of notification shown to user when their battery is very low.")
63+
userNotification.informativeText = NSLocalizedString("Your Mac will sleep soon unless plugged into a power outlet.", comment: "Body of notification shown to user when their battery is very low.")
64+
userNotification.hasActionButton = false
65+
userNotification.identifier = Constants.lowPowerNotificationIdentifier
66+
67+
notificationCenter.deliver(userNotification)
68+
}
69+
}
70+
71+
private func clearDeliveredNotification() {
72+
let userNotification = NSUserNotification()
73+
userNotification.identifier = Constants.lowPowerNotificationIdentifier
74+
notificationCenter.removeDeliveredNotification(userNotification)
75+
}
76+
}

Juice/Classes/StatusBarItemCoordinator.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import RxSwift
1111

1212
final class StatusBarItemCoordinator: StatusMenuItemDelegate {
1313
private let preferencesCoordinator = PreferencesCoordinator()
14+
private let lowPowerCoordinator: LowPowerCoordinator
1415

1516
private let statusBarItem = NSStatusBar.system().statusItem(withLength: NSVariableStatusItemLength)
1617
private let statusBarItemMenu = StatusMenuItem(title: "Juice")
@@ -35,6 +36,8 @@ final class StatusBarItemCoordinator: StatusMenuItemDelegate {
3536

3637
return disposable
3738
})
39+
40+
lowPowerCoordinator = LowPowerCoordinator(observable: powerSourcesObservable)
3841

3942
scaleChangeObservable = preferencesStorage
4043
.chargeDisplayScale
@@ -56,6 +59,8 @@ final class StatusBarItemCoordinator: StatusMenuItemDelegate {
5659
scaleChangeObservable.subscribe(onNext: { (scale) in
5760
self.updateLabel(scale: scale)
5861
}).addDisposableTo(disposableBag)
62+
63+
lowPowerCoordinator.start()
5964
}
6065

6166
private func updateLabel(source: PowerSource) {

Juice/Info.plist

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<key>CFBundleShortVersionString</key>
2020
<string>1.0</string>
2121
<key>CFBundleVersion</key>
22-
<string>529</string>
22+
<string>2017.01.02105829</string>
2323
<key>LSApplicationCategoryType</key>
2424
<string>public.app-category.lifestyle</string>
2525
<key>LSMinimumSystemVersion</key>
@@ -32,5 +32,7 @@
3232
<string>MainMenu</string>
3333
<key>NSPrincipalClass</key>
3434
<string>NSApplication</string>
35+
<key>NSUserNotificationAlertStyle</key>
36+
<string>alert</string>
3537
</dict>
3638
</plist>

JuiceHelper/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<key>CFBundleShortVersionString</key>
2020
<string>1.0</string>
2121
<key>CFBundleVersion</key>
22-
<string>529</string>
22+
<string>2017.01.02105829</string>
2323
<key>LSBackgroundOnly</key>
2424
<true/>
2525
<key>LSMinimumSystemVersion</key>

en.lproj/Localizable.strings

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,5 @@
3131
"Unknown Build"="Unknown Build";
3232
"Credits for this app"="Credits for this app";
3333
"Indicates the current battery charge percentage"="Indicates the current battery charge percentage";
34+
"Low Power"="Low Power";
35+
"Your Mac will sleep soon unless plugged into a power outlet."="Your Mac will sleep soon unless plugged into a power outlet.";

0 commit comments

Comments
 (0)