Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Luminare: New Settings Window + Other Improvements #361

Merged
merged 106 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
b30c589
✨ Add Icon Luminare view
MrKai77 Apr 20, 2024
951ceef
✨ Add Luminare Accent Color & Radial Menu panes
MrKai77 Apr 20, 2024
1736962
🎨 Update `Defaults` in code
MrKai77 Apr 20, 2024
71afc30
✨ Ability to dismiss padding modal
MrKai77 Apr 20, 2024
e229cc7
✨ Add Luminare Keybindings tab
MrKai77 Apr 21, 2024
0776a39
✨ Ability to select window action in keybinds list
MrKai77 Apr 24, 2024
7c05274
✨ Finish implementing Keycorder
MrKai77 Apr 26, 2024
3078ad6
✨ Luminare Permissions tab
MrKai77 Apr 26, 2024
ea35bdc
✨ Ability to set `focusWindowOnResize`
MrKai77 Apr 26, 2024
057729a
💄 Refinements, implement more views with Luminare
MrKai77 Apr 28, 2024
72b0b17
✨ Improve keybind list view
MrKai77 Apr 30, 2024
efb1f53
🔀 Merge branch `develop` into `Luminare`
MrKai77 May 2, 2024
3c00b54
✨ Organize files
MrKai77 May 4, 2024
9f55dc0
🐛 Fix typo
MrKai77 May 4, 2024
2bd7621
🐛 Don’t allow user to set keybinds while >1 item is selected
MrKai77 May 4, 2024
8f90d68
✨ Make “Change” button work
MrKai77 May 11, 2024
19b2361
✨ Add previews
MrKai77 May 11, 2024
021fbf9
✨ Show radial menu preview when window is opened
MrKai77 May 11, 2024
43da404
✨ Ability to configure cycling keybinds
MrKai77 May 13, 2024
aed98f0
✨ Ability to configure custom colors
MrKai77 May 14, 2024
193b387
🔀 Merge branch `develop` into `Luminare`
MrKai77 May 21, 2024
2a83a11
🎨 Remove unnecassary `.continuous` styling
MrKai77 May 21, 2024
cf6fc99
🔀 Merge branch `develop` into`Luminare`
MrKai77 May 24, 2024
811be9b
✨ #88 Make Loop able to resize itself
MrKai77 May 24, 2024
8b7ad43
✨ Remove old parts that have already been reimplemented
MrKai77 May 24, 2024
b0b388d
✨ Disable cursor interaction, size increment options
MrKai77 May 24, 2024
a4c3db5
✨ Add empty states to lists
MrKai77 May 24, 2024
8d16888
🐛 Make radial menu not trigger when setting trigger key
MrKai77 May 24, 2024
0f0636e
✨ #88 Support modal windows
MrKai77 May 24, 2024
05fca08
✨ Ability to set coordinates for custom window actions
MrKai77 May 25, 2024
8c07109
✨ Implement new app exclusion system
MrKai77 May 25, 2024
eb409b7
🔥 Remove `moreInformation` for `macOSCenter`
MrKai77 May 25, 2024
cdd1e63
⚡ Use `id` instead of `self`
MrKai77 May 25, 2024
ce45025
⚡ Improve performance
MrKai77 May 25, 2024
6be605c
✨ Move Accessibility permissions into Advanced
MrKai77 May 27, 2024
74378c8
🌐 Add localization support
MrKai77 May 27, 2024
193d6cf
✨ Improve hover states on custom keybinds
MrKai77 May 27, 2024
4fcc2f3
🐛 Properly show gradient section on appearance
MrKai77 May 27, 2024
5eb2a2f
✨ Radial Menu improvements
MrKai77 May 28, 2024
e0eac22
✨ Fix Loop not resizing itself on external monitors
MrKai77 May 28, 2024
b6a1c7f
✨ Basic `preview window` preview support
MrKai77 May 28, 2024
d8e1459
✨ Working preview section
MrKai77 May 28, 2024
e61d6b5
✨ Make preview SwiftUI
MrKai77 May 28, 2024
482321a
🚚 Move Luminare stuff into `LuminareManager`
MrKai77 May 28, 2024
dfcf958
🐛 Fix event monitor when setting trigger key
MrKai77 May 28, 2024
5a2fe5f
✨ Add credits
MrKai77 May 28, 2024
5f3fb18
💄 Fix alignment
MrKai77 May 28, 2024
4eb98a0
✨ Move GitHub contributors to its own CreditItem
MrKai77 May 28, 2024
bd940ad
✨ Add inactive focus state to window
MrKai77 May 29, 2024
aa357c7
💄 Make checkmark user’s accent color
MrKai77 May 29, 2024
da063f6
✨ Small updates to keep up to date with the Luminare package
MrKai77 May 30, 2024
de4637e
💄 Cosmetic button style
MrKai77 May 30, 2024
bbbae16
✨ Ability to show/hide dock icon
MrKai77 May 30, 2024
d53a542
#309 Implement accessibility access checker
MrKai77 May 30, 2024
4cf3833
🐛 Fix #366
MrKai77 May 30, 2024
65a3d31
🐛 Fix #363
MrKai77 May 30, 2024
21bb911
✨ #350 Increase menubar activation area
MrKai77 May 30, 2024
861e513
✨ Make top window snapping trigger less sensitive
MrKai77 May 30, 2024
808161a
✨ Use Luminare GitHub repo instead of local package
MrKai77 Jun 1, 2024
4132738
🙈 Update `.gitignore`
MrKai77 Jun 1, 2024
775ed51
💄 Fix alignment of keybinds
MrKai77 Jun 2, 2024
23ab1ac
💫 Add animation when toggling Stage Manager support
MrKai77 Jun 2, 2024
f3b9474
✨ Make `Suggest new icon` button work
MrKai77 Jun 2, 2024
51d3375
Merge branch 'develop' into Luminare
MrKai77 Jun 2, 2024
6a37f38
✨ Add infos
MrKai77 Jun 2, 2024
52e99c5
✨ Use `ObservableObject` classes
MrKai77 Jun 4, 2024
8b2fe9e
⚡ Quick optimization
MrKai77 Jun 4, 2024
f913863
🐛 Fix infinite loop when setting corner radius of radial menu
MrKai77 Jun 4, 2024
9bbdc95
💄 Fix small UI regression in padding configuration
MrKai77 Jun 4, 2024
1603dd5
🚚 Move `UNNotifications` support to its own file
MrKai77 Jun 4, 2024
fc3ddff
🔀 Merge branch `develop` into Luminare
MrKai77 Jun 4, 2024
41e5d71
🎨 Format
MrKai77 Jun 4, 2024
22697b6
⚰️ Remove dead code
MrKai77 Jun 4, 2024
5d034c9
🍱 Add Summer & Developer icons
MrKai77 Jun 8, 2024
56b5755
✨ Update credits
MrKai77 Jun 8, 2024
333c45d
🎨 Format
MrKai77 Jun 8, 2024
a5b031c
🍱 Center Developer icon
MrKai77 Jun 8, 2024
66c6cb4
🔀 Merge pull request #378 from `MrKai77/jsdev-icons`
MrKai77 Jun 8, 2024
1f52877
✨ Ability to use screen with cursor
MrKai77 Jun 8, 2024
9ce5818
🐛 Remove force unwrap
MrKai77 Jun 8, 2024
4390252
🐛 Fix window snapping on multi-display setups
MrKai77 Jun 8, 2024
516b88b
🔀 Merge pull request #379 from `MrKai77/mrkai77/loop-419-ignore-mouse…
MrKai77 Jun 8, 2024
cc9525a
🔀 Merge branch `develop` into `Luminare`
MrKai77 Jun 8, 2024
7c6b0fa
✨ Reduce useless cycle/custom action creation click
MrKai77 Jun 8, 2024
a8d7348
🔀 Merge pull request #380 from `MrKai77/mrkai77/loop-356-reduce-usele…
MrKai77 Jun 8, 2024
f331dda
✨ Fix LOOP-513
MrKai77 Jun 8, 2024
34764dd
🔀 Merge pull request #381 from `MrKai77/mrkai77/loop-513-switches-not…
MrKai77 Jun 8, 2024
07025e7
🐛 Clip preview window
MrKai77 Jun 8, 2024
fa5f7e8
🔀 Merge pull request #383 from `MrKai77/mrkai77/loop-1-preview-clippi…
MrKai77 Jun 8, 2024
c2fa502
✨ Ability to move cursor with window
MrKai77 Jun 8, 2024
724e3b5
🔀 Merge pull request #384 from `MrKai77/mrkai77/loop-497-option-to-mo…
MrKai77 Jun 8, 2024
905e06f
💄 Set trigger delay slider step to `0.1`
MrKai77 Jun 9, 2024
083bd6d
💄 Make keybind fixed size in list item
MrKai77 Jun 9, 2024
58acc30
🔀 Merge pull request #385 from `MrKai77/mrkai77/loop-507-ui-bug-keybi…
MrKai77 Jun 9, 2024
4c1fb79
💄 Add Nucleo icons
MrKai77 Jun 9, 2024
da17480
💄 Make Nucleo icons template images
MrKai77 Jun 9, 2024
a39bdbb
🍱 Add clipboard icon
MrKai77 Jun 9, 2024
86db030
💄 Custom-calculated window size icons
MrKai77 Jun 9, 2024
9f62313
✨ Support pixel-based custom keybind icons
MrKai77 Jun 9, 2024
66270ea
✨ More improvements to icon calculation
MrKai77 Jun 10, 2024
ed30400
✨ Use custom icons when configuring custom actions
MrKai77 Jun 10, 2024
068f520
🐛 Improve many bugs in custom window action modal
MrKai77 Jun 10, 2024
5edbf0c
🎨 Format
MrKai77 Jun 10, 2024
d513925
🎨 Cleanup
MrKai77 Jun 10, 2024
baffbff
✨ Improve name detection for custom actions
MrKai77 Jun 10, 2024
821f8ff
✨ Better support for cycling keybind icons
MrKai77 Jun 10, 2024
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
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
Loading