From 6c46f90d5f3ebdefe6f308881f6bcb05b34a5001 Mon Sep 17 00:00:00 2001 From: Serhiy Mytrovtsiy Date: Mon, 11 Dec 2023 21:57:53 +0100 Subject: [PATCH] feat: initialized Notification settings view for the modules --- Kit/constants.swift | 2 +- Kit/module/module.swift | 7 ++- Kit/module/notifications.swift | 83 ++++++++++++++++++++++++++++++++++ Kit/module/settings.swift | 27 ++++++++++- 4 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 Kit/module/notifications.swift diff --git a/Kit/constants.swift b/Kit/constants.swift index 1582dee4..a134120a 100644 --- a/Kit/constants.swift +++ b/Kit/constants.swift @@ -67,7 +67,7 @@ public enum ModuleType: Int { case bluetooth case clock - var rawValue: String { + public var rawValue: String { switch self { case .CPU: return "CPU" case .RAM: return "RAM" diff --git a/Kit/module/module.swift b/Kit/module/module.swift index a9e69205..f72b903b 100644 --- a/Kit/module/module.swift +++ b/Kit/module/module.swift @@ -95,6 +95,7 @@ open class Module: Module_p { private var settingsView: Settings_v? = nil private var popup: PopupWindow? = nil private var popupView: Popup_p? = nil + private var notificationsView: NotificationsWrapper? = nil private let log: NextLog private var readers: [Reader_p] = [] @@ -108,13 +109,14 @@ open class Module: Module_p { } } - public init(popup: Popup_p? = nil, settings: Settings_v? = nil, portal: Portal_p? = nil) { + public init(popup: Popup_p? = nil, settings: Settings_v? = nil, portal: Portal_p? = nil, notifications: NotificationsWrapper? = nil) { self.portal = portal self.config = module_c(in: Bundle(for: type(of: self)).path(forResource: "config", ofType: "plist")!) self.log = NextLog.shared.copy(category: self.config.name) self.settingsView = settings self.popupView = popup + self.notificationsView = notifications self.menuBar = MenuBar(moduleName: self.config.name) self.available = self.isAvailable() self.enabled = Store.shared.bool(key: "\(self.config.name)_state", defaultValue: self.config.defaultState) @@ -150,7 +152,8 @@ open class Module: Module_p { widgets: &self.menuBar.widgets, enabled: self.enabled, moduleSettings: self.settingsView, - popupSettings: self.popupView + popupSettings: self.popupView, + notificationsSettings: self.notificationsView ) self.popup = PopupWindow(title: self.config.name, view: self.popupView, visibilityCallback: self.visibilityCallback) diff --git a/Kit/module/notifications.swift b/Kit/module/notifications.swift new file mode 100644 index 00000000..534a4bcf --- /dev/null +++ b/Kit/module/notifications.swift @@ -0,0 +1,83 @@ +// +// notifications.swift +// Kit +// +// Created by Serhiy Mytrovtsiy on 04/12/2023 +// Using Swift 5.0 +// Running on macOS 14.1 +// +// Copyright © 2023 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import UserNotifications + +open class NotificationsWrapper: NSStackView { + public let module: String + + private var ids: [String: Bool?] = [:] + + public init(_ module: ModuleType, _ ids: [String] = []) { + self.module = module.rawValue + super.init(frame: NSRect.zero) + self.initIDs(ids) + + self.orientation = .vertical + self.distribution = .gravityAreas + self.translatesAutoresizingMaskIntoConstraints = false + self.edgeInsets = NSEdgeInsets( + top: Constants.Settings.margin, + left: Constants.Settings.margin, + bottom: Constants.Settings.margin, + right: Constants.Settings.margin + ) + self.spacing = Constants.Settings.margin + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func initIDs(_ ids: [String]) { + for id in ids { + let notificationID = "Stats_\(self.module)_\(id)" + self.ids[notificationID] = nil + removeNotification(notificationID) + } + } + + public func checkDouble(id rid: String, value: Double, threshold: Double, title: String, subtitle: String, less: Bool = false) { + let id = "Stats_\(self.module)_\(rid)" + let first = less ? value > threshold : value < threshold + let second = less ? value <= threshold : value >= threshold + + if self.ids[id] != nil, first { + removeNotification(id) + self.ids[id] = nil + } + + if self.ids[id] == nil && second { + self.showNotification(id: id, title: title, subtitle: subtitle) + self.ids[id] = true + } + } + + private func showNotification(id: String, title: String, subtitle: String? = nil) { + let content = UNMutableNotificationContent() + content.title = title + if let value = subtitle { + content.subtitle = value + } + content.sound = UNNotificationSound.default + + let request = UNNotificationRequest(identifier: id, content: content, trigger: nil) + let center = UNUserNotificationCenter.current() + + center.requestAuthorization(options: [.alert, .sound]) { _, _ in } + center.add(request) { (error: Error?) in + if let err = error { + print(err) + } + } + } +} diff --git a/Kit/module/settings.swift b/Kit/module/settings.swift index 49a72345..c82af568 100644 --- a/Kit/module/settings.swift +++ b/Kit/module/settings.swift @@ -25,16 +25,19 @@ open class Settings: NSStackView, Settings_p { private var widgets: [Widget] private var moduleSettings: Settings_v? private var popupSettings: Popup_p? + private var notificationsSettings: NotificationsWrapper? private var moduleSettingsContainer: NSStackView? private var widgetSettingsContainer: NSStackView? private var popupSettingsContainer: NSStackView? + private var notificationsSettingsContainer: NSStackView? private var enableControl: NSControl? private var oneViewRow: NSView? private let noWidgetsView: EmptyView = EmptyView(msg: localizedString("No available widgets to configure")) private let noPopupSettingsView: EmptyView = EmptyView(msg: localizedString("No options to configure for the popup in this module")) + private let noNotificationsView: EmptyView = EmptyView(msg: localizedString("No notifications available in this module")) private var globalOneView: Bool { Store.shared.bool(key: "OneView", defaultValue: false) @@ -48,11 +51,12 @@ open class Settings: NSStackView, Settings_p { } } - init(config: UnsafePointer, widgets: UnsafeMutablePointer<[Widget]>, enabled: Bool, moduleSettings: Settings_v?, popupSettings: Popup_p?) { + init(config: UnsafePointer, widgets: UnsafeMutablePointer<[Widget]>, enabled: Bool, moduleSettings: Settings_v?, popupSettings: Popup_p?, notificationsSettings: NotificationsWrapper?) { self.config = config self.widgets = widgets.pointee self.moduleSettings = moduleSettings self.popupSettings = popupSettings + self.notificationsSettings = notificationsSettings super.init(frame: NSRect.zero) @@ -107,9 +111,20 @@ open class Settings: NSStackView, Settings_p { return view }() + let notificationsTab: NSTabViewItem = NSTabViewItem() + notificationsTab.label = localizedString("Notifications") + notificationsTab.view = { + let view = ScrollableStackView(frame: tabView.frame) + view.stackView.spacing = 0 + self.notificationsSettingsContainer = view.stackView + self.loadNotificationsSettings() + return view + }() + tabView.addTabViewItem(moduleTab) tabView.addTabViewItem(widgetTab) tabView.addTabViewItem(popupTab) + tabView.addTabViewItem(notificationsTab) self.addArrangedSubview(widgetSelector) self.addArrangedSubview(tabView) @@ -200,6 +215,16 @@ open class Settings: NSStackView, Settings_p { } } + private func loadNotificationsSettings() { + self.notificationsSettingsContainer?.subviews.forEach{ $0.removeFromSuperview() } + + if let notificationsView = self.notificationsSettings { + self.notificationsSettingsContainer?.addArrangedSubview(notificationsView) + } else { + self.notificationsSettingsContainer?.addArrangedSubview(self.noNotificationsView) + } + } + @objc private func toggleOneView(_ sender: NSControl) { guard !self.globalOneView else { return } self.oneViewState = controlState(sender)