Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Core-Monitor.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
objects = {

/* Begin PBXBuildFile section */
A19600032026091900000002 /* BatteryDetailFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A19600032026091900000001 /* BatteryDetailFormatterTests.swift */; };
A19600022026091900000002 /* HelperXPCClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A19600022026091900000001 /* HelperXPCClientTests.swift */; };
A19600012026091900000002 /* RuntimeSafetyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A19600012026091900000001 /* RuntimeSafetyTests.swift */; };
A19400012026091900000002 /* FanReadingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A19400012026091900000001 /* FanReadingTests.swift */; };
A19400022026091900000002 /* DiskStatsRefreshPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A19400022026091900000001 /* DiskStatsRefreshPolicyTests.swift */; };
A19400032026091900000002 /* SettingsWindowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A19400032026091900000001 /* SettingsWindowTests.swift */; };
Expand Down Expand Up @@ -51,6 +54,9 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
A19600032026091900000001 /* BatteryDetailFormatterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryDetailFormatterTests.swift; sourceTree = "<group>"; };
A19600022026091900000001 /* HelperXPCClientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperXPCClientTests.swift; sourceTree = "<group>"; };
A19600012026091900000001 /* RuntimeSafetyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeSafetyTests.swift; sourceTree = "<group>"; };
A19400012026091900000001 /* FanReadingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FanReadingTests.swift; sourceTree = "<group>"; };
A19400022026091900000001 /* DiskStatsRefreshPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiskStatsRefreshPolicyTests.swift; sourceTree = "<group>"; };
A19400032026091900000001 /* SettingsWindowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsWindowTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -115,6 +121,9 @@
1A2F97F19E4BA4C0B1070411 /* Core-MonitorTests */ = {
isa = PBXGroup;
children = (
A19600032026091900000001 /* BatteryDetailFormatterTests.swift */,
A19600022026091900000001 /* HelperXPCClientTests.swift */,
A19600012026091900000001 /* RuntimeSafetyTests.swift */,
A19400012026091900000001 /* FanReadingTests.swift */,
A19400022026091900000001 /* DiskStatsRefreshPolicyTests.swift */,
A19400032026091900000001 /* SettingsWindowTests.swift */,
Expand Down Expand Up @@ -331,6 +340,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A19600032026091900000002 /* BatteryDetailFormatterTests.swift in Sources */,
A19600022026091900000002 /* HelperXPCClientTests.swift in Sources */,
A19600012026091900000002 /* RuntimeSafetyTests.swift in Sources */,
A19400012026091900000002 /* FanReadingTests.swift in Sources */,
A19400022026091900000002 /* DiskStatsRefreshPolicyTests.swift in Sources */,
A19400032026091900000002 /* SettingsWindowTests.swift in Sources */,
Expand Down
6 changes: 5 additions & 1 deletion Core-Monitor/AlertManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,10 @@ final class AlertManager: NSObject, ObservableObject {
}
}

static func notificationIdentifier(for kind: AlertRuleKind) -> String {
"coremonitor.alert.\(kind.rawValue)"
}

private func deliverDesktopNotification(for event: AlertEvent) {
guard authorizationStatus == .authorized || authorizationStatus == .provisional else { return }

Expand All @@ -317,7 +321,7 @@ final class AlertManager: NSObject, ObservableObject {
]

let request = UNNotificationRequest(
identifier: "coremonitor.alert.\(event.id.uuidString)",
identifier: Self.notificationIdentifier(for: event.kind),
content: content,
trigger: nil
)
Expand Down
67 changes: 36 additions & 31 deletions Core-Monitor/BatteryDetailFormatter.swift
Original file line number Diff line number Diff line change
@@ -1,71 +1,76 @@
import Foundation

enum BatteryDetailFormatter {
static func powerStateDescription(for info: BatteryInfo) -> String {
static func powerStateDescription(for info: BatteryInfo, locale: Locale = AppLocaleStore.currentLocale) -> String {
if info.isCharging {
return "Charging"
return localized("Charging", locale: locale)
}
if info.isPluggedIn {
return "AC Power"
return localized("AC Power", locale: locale)
}
return "Battery Power"
return localized("Battery Power", locale: locale)
Comment thread
offyotto marked this conversation as resolved.
}

static func sourceDescription(for info: BatteryInfo) -> String? {
static func sourceDescription(for info: BatteryInfo, locale: Locale = AppLocaleStore.currentLocale) -> String? {
if let source = info.source?.trimmingCharacters(in: .whitespacesAndNewlines), !source.isEmpty {
switch source {
case "AC Power":
return "Power Adapter"
return localized("Power Adapter", locale: locale)
case "Battery Power":
return "Internal Battery"
return localized("Internal Battery", locale: locale)
default:
return source
}
}

guard info.hasBattery else { return nil }
return info.isPluggedIn ? "Power Adapter" : "Internal Battery"
return localized(info.isPluggedIn ? "Power Adapter" : "Internal Battery", locale: locale)
}

static func runtimeDescription(for info: BatteryInfo) -> String? {
static func runtimeDescription(for info: BatteryInfo, locale: Locale = AppLocaleStore.currentLocale) -> String? {
guard let minutes = info.timeRemainingMinutes, minutes >= 0 else { return nil }
if minutes == 0 {
return info.isCharging ? "Finishing soon" : "Less than 1m remaining"
return localized(info.isCharging ? "Finishing soon" : "Less than 1m remaining", locale: locale)
}

let formattedDuration = durationDescription(minutes: minutes)
let formattedDuration = durationDescription(minutes: minutes, locale: locale)
if info.isCharging {
return "\(formattedDuration) until full"
return String(format: localized("%@ until full", locale: locale), locale: locale, formattedDuration)
}
return "\(formattedDuration) remaining"
return String(format: localized("%@ remaining", locale: locale), locale: locale, formattedDuration)
}

static func durationDescription(minutes: Int) -> String {
let clampedMinutes = max(minutes, 0)
if clampedMinutes < 60 {
return "\(clampedMinutes)m"
}

let hours = clampedMinutes / 60
let remainingMinutes = clampedMinutes % 60
if remainingMinutes == 0 {
return "\(hours)h"
}
return "\(hours)h \(remainingMinutes)m"
static func durationDescription(minutes: Int, locale: Locale = AppLocaleStore.currentLocale) -> String {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.hour, .minute]
formatter.unitsStyle = .abbreviated
formatter.zeroFormattingBehavior = .dropLeading
var calendar = Calendar(identifier: .gregorian)
calendar.locale = locale
formatter.calendar = calendar
return formatter.string(from: TimeInterval(max(0, minutes)) * 60) ?? "0"
}

static func temperatureDescription(_ temperature: Double?) -> String? {
static func temperatureDescription(_ temperature: Double?, locale: Locale = AppLocaleStore.currentLocale) -> String? {
guard let temperature else { return nil }
return String(format: "%.1f °C", temperature)
return String(format: "%.1f °C", locale: locale, temperature)
}

static func voltageDescription(_ voltage: Double?) -> String? {
static func voltageDescription(_ voltage: Double?, locale: Locale = AppLocaleStore.currentLocale) -> String? {
guard let voltage else { return nil }
return String(format: "%.2f V", voltage)
return String(format: "%.2f V", locale: locale, voltage)
}

static func amperageDescription(_ amperage: Double?) -> String? {
static func amperageDescription(_ amperage: Double?, locale: Locale = AppLocaleStore.currentLocale) -> String? {
guard let amperage else { return nil }
return String(format: "%.2f A", amperage)
return String(format: "%.2f A", locale: locale, amperage)
}

private static func localized(_ key: String, locale: Locale) -> String {
let language = Bundle.preferredLocalizations(from: Bundle.main.localizations, forPreferences: [locale.identifier]).first ?? "en"
guard let path = Bundle.main.path(forResource: language, ofType: "lproj"), let bundle = Bundle(path: path) else { return key }
let existing = bundle.localizedString(forKey: key, value: key, table: nil)
if existing != key { return existing }
return bundle.localizedString(forKey: key, value: key, table: "BatteryDetails")
}
}
Loading