244 lines
8.3 KiB
Swift
244 lines
8.3 KiB
Swift
import Foundation
|
|
import MacToolsPluginKit
|
|
|
|
struct SystemStatusMetricPreference: Codable, Equatable, Identifiable, Sendable {
|
|
let kind: SystemStatusMetricKind
|
|
var isVisible: Bool
|
|
|
|
var id: String { kind.rawValue }
|
|
}
|
|
|
|
struct SystemStatusConfiguration: Equatable, Sendable {
|
|
var panelItems: [SystemStatusMetricPreference]
|
|
var menuBarItems: [SystemStatusMetricPreference]
|
|
|
|
static let `default` = SystemStatusConfiguration(
|
|
panelItems: SystemStatusComponentLayout.defaultPanelMetricKinds.map {
|
|
SystemStatusMetricPreference(kind: $0, isVisible: true)
|
|
},
|
|
menuBarItems: SystemStatusComponentLayout.defaultMenuBarMetricKinds.map {
|
|
SystemStatusMetricPreference(kind: $0, isVisible: false)
|
|
}
|
|
)
|
|
|
|
var visiblePanelMetricKinds: [SystemStatusMetricKind] {
|
|
panelItems.filter(\.isVisible).map(\.kind)
|
|
}
|
|
|
|
var visibleMenuBarMetricKinds: [SystemStatusMetricKind] {
|
|
menuBarItems.filter(\.isVisible).map(\.kind)
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
protocol SystemStatusConfigurationStoring: AnyObject {
|
|
func load() -> SystemStatusConfiguration
|
|
func save(_ configuration: SystemStatusConfiguration)
|
|
}
|
|
|
|
@MainActor
|
|
final class SystemStatusPluginStorageConfigurationStore: SystemStatusConfigurationStoring {
|
|
private enum Key {
|
|
static let panelOrder = "settings.panel.order"
|
|
static let panelHidden = "settings.panel.hidden"
|
|
static let menuBarOrder = "settings.menuBar.order"
|
|
static let menuBarVisible = "settings.menuBar.visible"
|
|
}
|
|
|
|
private let storage: PluginStorage
|
|
|
|
init(storage: PluginStorage) {
|
|
self.storage = storage
|
|
}
|
|
|
|
func load() -> SystemStatusConfiguration {
|
|
let panelOrder = storage.stringArray(forKey: Key.panelOrder)
|
|
let panelHidden = storage.stringArray(forKey: Key.panelHidden)
|
|
let menuBarOrder = storage.stringArray(forKey: Key.menuBarOrder)
|
|
let menuBarVisible = storage.stringArray(forKey: Key.menuBarVisible)
|
|
|
|
return SystemStatusConfiguration(
|
|
panelItems: Self.normalizedItems(
|
|
storedOrder: panelOrder,
|
|
visibilityIDs: Set(panelHidden ?? []),
|
|
defaults: SystemStatusComponentLayout.defaultPanelMetricKinds,
|
|
defaultVisibility: true,
|
|
visibilityMode: .hiddenSet,
|
|
usesDefaultVisibility: panelHidden == nil
|
|
),
|
|
menuBarItems: Self.normalizedItems(
|
|
storedOrder: menuBarOrder,
|
|
visibilityIDs: Set(menuBarVisible ?? []),
|
|
defaults: SystemStatusComponentLayout.defaultMenuBarMetricKinds,
|
|
defaultVisibility: false,
|
|
visibilityMode: .visibleSet,
|
|
usesDefaultVisibility: menuBarVisible == nil
|
|
)
|
|
)
|
|
}
|
|
|
|
func save(_ configuration: SystemStatusConfiguration) {
|
|
let panelItems = Self.normalizedItems(
|
|
configuration.panelItems,
|
|
defaults: SystemStatusComponentLayout.defaultPanelMetricKinds,
|
|
defaultVisibility: true
|
|
)
|
|
let menuBarItems = Self.normalizedItems(
|
|
configuration.menuBarItems,
|
|
defaults: SystemStatusComponentLayout.defaultMenuBarMetricKinds,
|
|
defaultVisibility: false
|
|
)
|
|
|
|
storage.set(panelItems.map { $0.kind.rawValue }, forKey: Key.panelOrder)
|
|
storage.set(panelItems.filter { !$0.isVisible }.map { $0.kind.rawValue }, forKey: Key.panelHidden)
|
|
storage.set(menuBarItems.map { $0.kind.rawValue }, forKey: Key.menuBarOrder)
|
|
storage.set(menuBarItems.filter(\.isVisible).map { $0.kind.rawValue }, forKey: Key.menuBarVisible)
|
|
}
|
|
|
|
private enum VisibilityMode {
|
|
case hiddenSet
|
|
case visibleSet
|
|
}
|
|
|
|
private static func normalizedItems(
|
|
storedOrder: [String]?,
|
|
visibilityIDs: Set<String>,
|
|
defaults: [SystemStatusMetricKind],
|
|
defaultVisibility: Bool,
|
|
visibilityMode: VisibilityMode,
|
|
usesDefaultVisibility: Bool
|
|
) -> [SystemStatusMetricPreference] {
|
|
let storedKinds = storedOrder?
|
|
.compactMap(SystemStatusMetricKind.init(rawValue:))
|
|
.filter { defaults.contains($0) } ?? []
|
|
let orderedKinds = normalizedKinds(storedKinds, defaults: defaults)
|
|
|
|
return orderedKinds.map { kind in
|
|
let isVisible: Bool
|
|
switch visibilityMode {
|
|
case .hiddenSet:
|
|
isVisible = !visibilityIDs.contains(kind.rawValue)
|
|
case .visibleSet:
|
|
isVisible = visibilityIDs.contains(kind.rawValue)
|
|
}
|
|
|
|
return SystemStatusMetricPreference(
|
|
kind: kind,
|
|
isVisible: usesDefaultVisibility ? defaultVisibility : isVisible
|
|
)
|
|
}
|
|
}
|
|
|
|
private static func normalizedItems(
|
|
_ items: [SystemStatusMetricPreference],
|
|
defaults: [SystemStatusMetricKind],
|
|
defaultVisibility: Bool
|
|
) -> [SystemStatusMetricPreference] {
|
|
let visibilityByKind = Dictionary(uniqueKeysWithValues: items.map { ($0.kind, $0.isVisible) })
|
|
return normalizedKinds(items.map(\.kind), defaults: defaults).map { kind in
|
|
SystemStatusMetricPreference(
|
|
kind: kind,
|
|
isVisible: visibilityByKind[kind] ?? defaultVisibility
|
|
)
|
|
}
|
|
}
|
|
|
|
private static func normalizedKinds(
|
|
_ kinds: [SystemStatusMetricKind],
|
|
defaults: [SystemStatusMetricKind]
|
|
) -> [SystemStatusMetricKind] {
|
|
var seen: Set<SystemStatusMetricKind> = []
|
|
var result: [SystemStatusMetricKind] = []
|
|
|
|
for kind in kinds where defaults.contains(kind) && !seen.contains(kind) {
|
|
result.append(kind)
|
|
seen.insert(kind)
|
|
}
|
|
|
|
for kind in defaults where !seen.contains(kind) {
|
|
result.append(kind)
|
|
seen.insert(kind)
|
|
}
|
|
|
|
return result
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
final class SystemStatusSettingsController: ObservableObject {
|
|
@Published private(set) var configuration: SystemStatusConfiguration
|
|
|
|
var onConfigurationChange: (() -> Void)?
|
|
|
|
private let store: SystemStatusConfigurationStoring
|
|
|
|
init(store: SystemStatusConfigurationStoring) {
|
|
self.store = store
|
|
self.configuration = store.load()
|
|
}
|
|
|
|
func setPanelMetric(_ kind: SystemStatusMetricKind, visible isVisible: Bool) {
|
|
update { configuration in
|
|
guard let index = configuration.panelItems.firstIndex(where: { $0.kind == kind }) else {
|
|
return
|
|
}
|
|
|
|
configuration.panelItems[index].isVisible = isVisible
|
|
}
|
|
}
|
|
|
|
func setMenuBarMetric(_ kind: SystemStatusMetricKind, visible isVisible: Bool) {
|
|
update { configuration in
|
|
guard let index = configuration.menuBarItems.firstIndex(where: { $0.kind == kind }) else {
|
|
return
|
|
}
|
|
|
|
configuration.menuBarItems[index].isVisible = isVisible
|
|
}
|
|
}
|
|
|
|
func movePanelMetric(_ kind: SystemStatusMetricKind, toOffset targetOffset: Int) {
|
|
update { configuration in
|
|
move(kind, in: &configuration.panelItems, toOffset: targetOffset)
|
|
}
|
|
}
|
|
|
|
func moveMenuBarMetric(_ kind: SystemStatusMetricKind, toOffset targetOffset: Int) {
|
|
update { configuration in
|
|
move(kind, in: &configuration.menuBarItems, toOffset: targetOffset)
|
|
}
|
|
}
|
|
|
|
private func update(_ body: (inout SystemStatusConfiguration) -> Void) {
|
|
var updatedConfiguration = configuration
|
|
body(&updatedConfiguration)
|
|
|
|
guard updatedConfiguration != configuration else {
|
|
return
|
|
}
|
|
|
|
configuration = updatedConfiguration
|
|
store.save(updatedConfiguration)
|
|
onConfigurationChange?()
|
|
}
|
|
|
|
private func move(
|
|
_ kind: SystemStatusMetricKind,
|
|
in items: inout [SystemStatusMetricPreference],
|
|
toOffset targetOffset: Int
|
|
) {
|
|
guard let currentIndex = items.firstIndex(where: { $0.kind == kind }) else {
|
|
return
|
|
}
|
|
|
|
let clampedOffset = min(max(targetOffset, 0), items.count)
|
|
guard currentIndex != clampedOffset, currentIndex + 1 != clampedOffset else {
|
|
return
|
|
}
|
|
|
|
let item = items.remove(at: currentIndex)
|
|
let insertionIndex = currentIndex < clampedOffset ? clampedOffset - 1 : clampedOffset
|
|
items.insert(item, at: insertionIndex)
|
|
}
|
|
}
|