Skip to content

Commit

Permalink
🔀 Merge pull request #361 from MrKai77/Luminare
Browse files Browse the repository at this point in the history
✨ Luminare: New Settings Window + Other Improvements

Merging since this is now stable enough for myself to consider this a "development version" (and the branch is called "develop")! Of course, there are still some minor bugs to be fixed, but those can be done in other PRs :D
  • Loading branch information
MrKai77 committed Jun 10, 2024
2 parents a20bfa3 + 821f8ff commit b7b9d28
Show file tree
Hide file tree
Showing 184 changed files with 4,811 additions and 4,284 deletions.
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ Loop.app
*.mode2v3
*.perspectivev3
*.xcuserstate
project.xcworkspace
xcuserdata
Loop.zip
Build/
Build/
332 changes: 153 additions & 179 deletions Loop.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"originHash" : "c2a93a9d7a879ed92e3a49ac894535c44c007c6516c13aaedf4acff1c43a7b4a",
"pins" : [
{
"identity" : "defaults",
"kind" : "remoteSourceControl",
"location" : "https://github.com/sindresorhus/Defaults",
"state" : {
"branch" : "main",
"revision" : "38925e3cfacf3fb89a81a35b1cd44fd5a5b7e0fa"
}
},
{
"identity" : "menubarextraaccess",
"kind" : "remoteSourceControl",
"location" : "https://github.com/orchetect/MenuBarExtraAccess",
"state" : {
"revision" : "f5896b47e15e114975897354c7e1082c51a2bffd",
"version" : "1.0.5"
}
},
{
"identity" : "sparkle",
"kind" : "remoteSourceControl",
"location" : "https://github.com/sparkle-project/Sparkle",
"state" : {
"branch" : "2.x",
"revision" : "e9989a8ae7d09c946d07d325d71414024bd1e2dd"
}
}
],
"version" : 3
}
4 changes: 2 additions & 2 deletions Loop/About Window/AboutView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ struct AboutView: View {
.controlSize(.large)

Button {
self.isShowingAcknowledgements = true
isShowingAcknowledgements = true
} label: {
Text("Acknowledgements")
.foregroundColor(.primary)
Expand All @@ -115,7 +115,7 @@ struct AboutView: View {
.controlSize(.large)
.popover(isPresented: $isShowingAcknowledgements) {
VStack {
ForEach(0..<packages.count, id: \.self) { idx in
ForEach(0 ..< packages.count, id: \.self) { idx in
HStack {
Text(packages[idx].name)
Spacer()
Expand Down
2 changes: 1 addition & 1 deletion Loop/About Window/AboutViewController.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// AboutViewManager.swift
// AboutViewController.swift
// Loop
//
// Created by Kai Azim on 2023-04-08.
Expand Down
100 changes: 100 additions & 0 deletions Loop/AppDelegate+UNNotifications.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//
// AppDelegate+UNNotifications.swift
// Loop
//
// Created by Kai Azim on 2024-06-03.
//

import SwiftUI
import UserNotifications

extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(
_: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> ()
) {
if response.actionIdentifier == "setIconAction",
let icon = response.notification.request.content.userInfo["icon"] as? String {
IconManager.setAppIcon(to: icon)
}

completionHandler()
}

// Implementation is necessary to show notifications even when the app has focus!
func userNotificationCenter(
_: UNUserNotificationCenter,
willPresent _: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> ()
) {
completionHandler([.banner])
}

static func requestNotificationAuthorization() {
UNUserNotificationCenter.current().requestAuthorization(
options: [.alert]
) { accepted, error in
if !accepted {
print("User Notification access denied.")
}

if let error {
print(error)
}
}
}

private static func registerNotificationCategories() {
let setIconAction = UNNotificationAction(
identifier: "setIconAction",
title: .init(localized: .init("Notification/Set Icon: Action", defaultValue: "Set Current Icon")),
options: .destructive
)
let notificationCategory = UNNotificationCategory(
identifier: "icon_unlocked",
actions: [setIconAction],
intentIdentifiers: []
)
UNUserNotificationCenter.current().setNotificationCategories([notificationCategory])
}

static func areNotificationsEnabled() -> Bool {
let group = DispatchGroup()
group.enter()

var notificationsEnabled = false

UNUserNotificationCenter.current().getNotificationSettings { notificationSettings in
notificationsEnabled = notificationSettings.authorizationStatus != UNAuthorizationStatus.denied
group.leave()
}

group.wait()
return notificationsEnabled
}

static func sendNotification(_ content: UNMutableNotificationContent) {
let uuidString = UUID().uuidString
let request = UNNotificationRequest(
identifier: uuidString,
content: content,
trigger: nil
)

requestNotificationAuthorization()
registerNotificationCategories()

UNUserNotificationCenter.current().add(request)
}

static func sendNotification(_ title: String, _ body: String) {
let content = UNMutableNotificationContent()

content.title = title
content.body = body
content.categoryIdentifier = UUID().uuidString

AppDelegate.sendNotification(content)
}
}
159 changes: 26 additions & 133 deletions Loop/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
// Created by Kai Azim on 2023-10-05.
//

import SwiftUI
import Defaults
import SwiftUI
import UserNotifications

class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDelegate {
private let loopManager = LoopManager()
private let windowDragManager = WindowDragManager()
class AppDelegate: NSObject, NSApplicationDelegate {
static let loopManager = LoopManager()
static let windowDragManager = WindowDragManager()
static var isActive: Bool = false

private var launchedAsLoginItem: Bool {
guard let event = NSAppleEventManager.shared().currentAppleEvent else { return false }
Expand All @@ -20,152 +21,44 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
event.paramDescriptor(forKeyword: keyAEPropData)?.enumCodeValue == keyAELaunchedAsLogInItem
}

func applicationDidFinishLaunching(_ notification: Notification) {
NSApp.setActivationPolicy(.accessory)

func applicationDidFinishLaunching(_: Notification) {
// Check & ask for accessibility access
AccessibilityManager.requestAccess()
UNUserNotificationCenter.current().delegate = self

AppDelegate.requestNotificationAuthorization()

IconManager.refreshCurrentAppIcon()
loopManager.startObservingKeys()
windowDragManager.addObservers()

if !self.launchedAsLoginItem {
self.openSettings()
AppDelegate.loopManager.start()
AppDelegate.windowDragManager.addObservers()

if !launchedAsLoginItem {
LuminareManager.open()
} else {
// Dock icon is usually handled by LuminareManager, but in this case, it is manually set
if !Defaults[.showDockIcon] {
NSApp.setActivationPolicy(.accessory)
}
}
}

func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
NSApp.setActivationPolicy(.accessory)
for window in NSApp.windows where window.delegate != nil {
window.delegate = nil
}
func applicationShouldTerminateAfterLastWindowClosed(_: NSApplication) -> Bool {
LuminareManager.fullyClose()
return false
}

func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
self.openSettings()
func applicationShouldHandleReopen(_: NSApplication, hasVisibleWindows _: Bool) -> Bool {
LuminareManager.open()
return true
}

// Mostly taken from https://github.com/Wouter01/SwiftUI-WindowManagement
func openSettings() {
// Settings window is already open
guard !NSApp.windows.contains(where: { $0.toolbar?.items != nil }) else {
NSApp.windows.first { $0.toolbar?.items != nil }?.orderFrontRegardless()

return
}

let eventSource = CGEventSource(stateID: .hidSystemState)
let keyCommand = CGEvent(keyboardEventSource: eventSource, virtualKey: CGKeyCode.kVK_ANSI_Comma, keyDown: true)
guard let keyCommand else { return }

keyCommand.flags = .maskCommand
let event = NSEvent(cgEvent: keyCommand)
guard let event else { return }

NSApp.sendEvent(event)

for window in NSApp.windows where window.toolbar?.items != nil {
window.orderFrontRegardless()
window.center()
}
}

// ----------
// MARK: - Notifications
// ----------

func userNotificationCenter(
_: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
if response.actionIdentifier == "setIconAction",
let icon = response.notification.request.content.userInfo["icon"] as? String {
IconManager.setAppIcon(to: icon)
}

completionHandler()
}

// Implementation is necessary to show notifications even when the app has focus!
func userNotificationCenter(
_: UNUserNotificationCenter,
willPresent _: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
) {
completionHandler([.banner])
func applicationWillBecomeActive(_: Notification) {
Notification.Name.activeStateChanged.post(object: true)
AppDelegate.isActive = true
}

static func requestNotificationAuthorization() {
UNUserNotificationCenter.current().requestAuthorization(
options: [.alert]
) { accepted, error in
if !accepted {
print("User Notification access denied.")
}

if let error = error {
print(error)
}
}
}

private static func registerNotificationCategories() {
let setIconAction = UNNotificationAction(
identifier: "setIconAction",
title: .init(localized: .init("Notification/Set Icon: Action", defaultValue: "Set Current Icon")),
options: .destructive
)
let notificationCategory = UNNotificationCategory(
identifier: "icon_unlocked",
actions: [setIconAction],
intentIdentifiers: []
)
UNUserNotificationCenter.current().setNotificationCategories([notificationCategory])
}

static func areNotificationsEnabled() -> Bool {
let group = DispatchGroup()
group.enter()

var notificationsEnabled = false

UNUserNotificationCenter.current().getNotificationSettings { notificationSettings in
notificationsEnabled = notificationSettings.authorizationStatus != UNAuthorizationStatus.denied
group.leave()
}

group.wait()
return notificationsEnabled
}

static func sendNotification(_ content: UNMutableNotificationContent) {
let uuidString = UUID().uuidString
let request = UNNotificationRequest(
identifier: uuidString,
content: content,
trigger: nil
)

requestNotificationAuthorization()
registerNotificationCategories()

UNUserNotificationCenter.current().add(request)
}

static func sendNotification(_ title: String, _ body: String) {
let content = UNMutableNotificationContent()

content.title = title
content.body = body
content.categoryIdentifier = UUID().uuidString

AppDelegate.sendNotification(content)
func applicationWillResignActive(_: Notification) {
Notification.Name.activeStateChanged.post(object: false)
AppDelegate.isActive = false
}
}
Loading

0 comments on commit b7b9d28

Please sign in to comment.