Applied latest changes from boring.notch

This commit is contained in:
Hariharan Mudaliar 2025-07-13 23:17:43 +05:30
commit 628d76b41c
18 changed files with 940 additions and 339 deletions

View file

@ -5,9 +5,9 @@
// Created by Harsh Vardhan Goswami on 08/09/24.
//
import EventKit
import SwiftUI
import Defaults
import EventKit
struct Config: Equatable {
// var count: Int = 10 // 3 days past + today + 7 days future
@ -45,7 +45,9 @@ struct WheelPicker: View {
withAnimation{
scrollPosition = indexForDate(date, offset: offset) - config.offset
}
//haptics.toggle() // Causes double haptic when click
if (Defaults[.enableHaptics]) {
haptics.toggle()
}
}
}
}
@ -84,15 +86,13 @@ struct WheelPicker: View {
private func dayText(date: String, isSelected: Bool) -> some View {
Text(date)
.font(.caption2)
.foregroundStyle(
isSelected ? Defaults[.accentColor] : .gray
)
.foregroundStyle(isSelected ? Color.accentColor : .gray)
}
private func dateCircle(date: Date, isSelected: Bool) -> some View {
ZStack {
Circle()
.fill(isSelected ? Defaults[.accentColor] : .clear)
.fill(isSelected ? Color.accentColor : .clear)
.frame(width: 24, height: 24)
Text("\(date.date)")
.font(.title3)
@ -105,15 +105,17 @@ struct WheelPicker: View {
let todayIndex = indexForDate(Date(), offset: offset)
guard let newIndex = newValue else { return }
let targetDateIndex = newIndex + config.offset
switch targetDateIndex{
case todayIndex-config.past..<todayIndex+config.future:
switch targetDateIndex {
case todayIndex - config.past ..< todayIndex + config.future:
selectedDate = dateForIndex(targetDateIndex, offset: offset)
haptics.toggle()
if (Defaults[.enableHaptics]) {
haptics.toggle()
}
default:
return
}
}
private func scrollToToday(config: Config) {
let today = Date()
let todayIndex = indexForDate(today, offset: -config.offset - config.past)
@ -121,7 +123,7 @@ struct WheelPicker: View {
scrollPosition = todayIndex - config.offset
selectedDate = today
}
private func indexForDate(_ date: Date, offset: Int) -> Int {
let calendar = Calendar.current
let startDate = calendar.startOfDay(for: calendar.date(byAdding: .day, value: offset, to: Date()) ?? Date())
@ -129,23 +131,18 @@ struct WheelPicker: View {
let daysDifference = calendar.dateComponents([.day], from: startDate, to: targetDate).day ?? 0
return daysDifference
}
private func dateForIndex(_ index: Int, offset: Int) -> Date {
let startDate = Calendar.current.date(byAdding: .day, value: offset, to: Date()) ?? Date()
return Calendar.current.date(byAdding: .day, value: index, to: startDate) ?? Date()
}
private func dayForIndex(_ index: Int, offset: Int) -> String {
let date = dateForIndex(index, offset: offset)
return dateToString(for: date)
}
private func dateToString(for date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "E"
return formatter.string(from: date)
}
private func isDateSelected(_ index: Int, offset: Int) -> Bool {
Calendar.current.isDate(dateForIndex(index, offset: offset), inSameDayAs: selectedDate)
}
@ -155,7 +152,7 @@ struct CalendarView: View {
@EnvironmentObject var vm: DynamicIslandViewModel
@StateObject private var calendarManager = CalendarManager()
@State private var selectedDate = Date()
var body: some View {
VStack(spacing: 8) {
HStack {
@ -185,13 +182,19 @@ struct CalendarView: View {
}
.listRowBackground(Color.clear)
.onChange(of: selectedDate) { _, newDate in
calendarManager.updateCurrentDate(newDate)
Task {
await calendarManager.updateCurrentDate(newDate)
}
}
.onChange(of: vm.notchState) { _, _ in
calendarManager.updateCurrentDate(Date.now)
Task {
await calendarManager.updateCurrentDate(Date.now)
}
}
.onAppear {
calendarManager.updateCurrentDate(Date.now)
Task {
await calendarManager.updateCurrentDate(Date.now)
}
}
}
}
@ -211,7 +214,7 @@ struct EmptyEventsView: View {
struct EventListView: View {
@Environment(\.openURL) private var openURL
let events: [EKEvent]
let events: [EventModel]
var body: some View {
ScrollView(showsIndicators: false) {
@ -219,13 +222,11 @@ struct EventListView: View {
VStack(alignment: .trailing, spacing: 5) {
ForEach(events.indices, id: \.self) { index in
VStack(alignment: .trailing) {
if isAllDayEvent(
start: events[index].startDate, end: events[index].endDate)
{
if events[index].isAllDay {
Text("All-day")
} else {
Text("\(events[index].startDate, style: .time)")
Text("\(events[index].endDate, style: .time)")
Text("\(events[index].start, style: .time)")
Text("\(events[index].end, style: .time)")
}
}
.multilineTextAlignment(.trailing)
@ -239,10 +240,10 @@ struct EventListView: View {
HStack(alignment: .top) {
VStack(spacing: 5) {
Image(
systemName: isEventEnded(events[index].endDate)
systemName: events[index].eventStatus == .ended
? "checkmark.circle" : "circle"
)
.foregroundColor(isEventEnded(events[index].endDate) ? .green : .gray)
.foregroundColor(events[index].eventStatus == .ended ? .green : .gray)
.font(.footnote)
Rectangle()
.frame(width: 1)
@ -252,7 +253,7 @@ struct EventListView: View {
.padding(.top, 1)
Button {
if let url = generateEventURL(for: events[index]) {
if let url = events[index].calendarAppURL() {
openURL(url)
}
} label: {
@ -264,7 +265,7 @@ struct EventListView: View {
Spacer(minLength: 0)
}
.opacity(isEventEnded(events[index].endDate) ? 0.6 : 1)
.opacity((events[index].eventStatus == .ended) ? 0.6 : 1)
}
}
}
@ -272,44 +273,12 @@ struct EventListView: View {
.scrollIndicators(.never)
.scrollTargetBehavior(.viewAligned)
}
private func isAllDayEvent(start: Date, end: Date) -> Bool {
let calendar = Calendar.current
let startComponents = calendar.dateComponents([.hour, .minute], from: start)
let endComponents = calendar.dateComponents([.hour, .minute], from: end)
return startComponents.hour == 0 && startComponents.minute == 0 && endComponents.hour == 23
&& endComponents.minute == 59
}
private func isEventEnded(_ end: Date) -> Bool {
return Date.now > end
}
private func generateEventURL(for event: EKEvent) -> URL? {
var dateComponent = ""
if event.hasRecurrenceRules {
if let startDate = event.startDate {
let formatter = DateFormatter()
formatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'"
formatter.timeZone = TimeZone.current
if !event.isAllDay {
formatter.timeZone = TimeZone(secondsFromGMT: 0)
}
dateComponent = "/\(formatter.string(from: startDate))"
}
}
return URL(string: "ical://ekevent\(dateComponent)/\(event.calendarItemIdentifier)?method=show&options=more")
}
}
// Keep the CalendarManager, EmptyEventsView, and EventListView as they were in the previous implementation
#Preview {
CalendarView()
.frame(width: 250)
.padding(.horizontal)
.background(.black)
.environmentObject(DynamicIslandViewModel())
.environmentObject(DynamicIslandViewModel.init())
}

View file

@ -2,10 +2,6 @@
// DynamicIslandExtrasMenu.swift
// DynamicIsland
//
// Created by Harsh Vardhan Goswami on 02/08/24.
// DynamicIslandExtrasMenu.swift
// DynamicIsland
//
// Created by Harsh Vardhan Goswami on 04/08/24.
//
@ -65,7 +61,9 @@ struct DynamicIslandExtrasMenu : View {
}
var settings: some View {
SettingsLink(label: {
Button(action: {
SettingsWindowController.shared.showWindow()
}) {
ZStack {
RoundedRectangle(cornerRadius: 12.0).fill(.black).frame(width: 70, height: 70)
VStack(spacing: 8) {
@ -74,7 +72,7 @@ struct DynamicIslandExtrasMenu : View {
Text("Settings").font(.body)
}
}
})
}
.buttonStyle(PlainButtonStyle()).shadow(color: .black.opacity(0.5), radius: 10)
}
@ -107,5 +105,5 @@ struct DynamicIslandExtrasMenu : View {
#Preview {
DynamicIslandExtrasMenu(vm: .init())
DynamicIslandExtrasMenu(vm: DynamicIslandViewModel())
}

View file

@ -10,6 +10,7 @@ import SwiftUI
struct DynamicIslandHeader: View {
@EnvironmentObject var vm: DynamicIslandViewModel
@EnvironmentObject var webcamManager: WebcamManager
@ObservedObject var batteryModel = BatteryStatusViewModel.shared
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
@StateObject var tvm = TrayDrop.shared
@ -40,8 +41,30 @@ struct DynamicIslandHeader: View {
HStack(spacing: 4) {
if vm.notchState == .open {
if Defaults[.showMirror] {
Button(action: {
if webcamManager.isSessionRunning {
webcamManager.stopSession()
} else {
webcamManager.startSession()
}
}) {
Capsule()
.fill(.black)
.frame(width: 30, height: 30)
.overlay {
Image(systemName: "web.camera")
.foregroundColor(.white)
.padding()
.imageScale(.medium)
}
}
.buttonStyle(PlainButtonStyle())
}
if Defaults[.settingsIconInNotch] {
SettingsLink(label: {
Button(action: {
SettingsWindowController.shared.showWindow()
}) {
Capsule()
.fill(.black)
.frame(width: 30, height: 30)
@ -51,7 +74,7 @@ struct DynamicIslandHeader: View {
.padding()
.imageScale(.medium)
}
})
}
.buttonStyle(PlainButtonStyle())
}
if Defaults[.showBatteryIndicator] {
@ -81,5 +104,7 @@ struct DynamicIslandHeader: View {
}
#Preview {
DynamicIslandHeader().environmentObject(DynamicIslandViewModel())
DynamicIslandHeader()
.environmentObject(DynamicIslandViewModel())
.environmentObject(WebcamManager.shared)
}

View file

@ -3,7 +3,7 @@
// DynamicIsland
//
// Created by Hugo Persson on 2024-08-18.
// Modified by Harsh Vardhan Goswami & Richard Kunkli
// Modified by Harsh Vardhan Goswami & Richard Kunkli & Mustafa Ramadan
//
import Combine
@ -179,11 +179,11 @@ struct MusicControlsView: View {
struct NotchHomeView: View {
@EnvironmentObject var vm: DynamicIslandViewModel
@EnvironmentObject var webcamManager: WebcamManager
@ObservedObject var webcamManager = WebcamManager.shared
@ObservedObject var batteryModel = BatteryStatusViewModel.shared
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
let albumArtNamespace: Namespace.ID
var body: some View {
Group {
if !coordinator.firstLaunch {
@ -196,16 +196,18 @@ struct NotchHomeView: View {
private var mainContent: some View {
HStack(alignment: .top, spacing: 20) {
MusicPlayerView(albumArtNamespace: albumArtNamespace)
if Defaults[.showCalendar] {
CalendarView()
.onHover { isHovering in
vm.isHoveringCalendar = isHovering
}
.environmentObject(vm)
.onHover { isHovering in
vm.isHoveringCalendar = isHovering
}
.environmentObject(vm)
}
if Defaults[.showMirror] && webcamManager.cameraAvailable {
if Defaults[.showMirror],
webcamManager.cameraAvailable,
vm.notchState == .open {
CameraPreviewView(webcamManager: webcamManager)
.scaledToFit()
.opacity(vm.notchState == .closed ? 0 : 1)
@ -249,7 +251,7 @@ struct MusicSliderView: View {
range: 0 ... duration,
color: Defaults[.sliderColor] == SliderColorEnum.albumArt ? Color(
nsColor: color
).ensureMinimumBrightness(factor: 0.8) : Defaults[.sliderColor] == SliderColorEnum.accent ? Defaults[.accentColor] : .white,
).ensureMinimumBrightness(factor: 0.8) : Defaults[.sliderColor] == SliderColorEnum.accent ? .accentColor : .white,
dragging: $dragging,
lastDragged: $lastDragged,
onValueChange: onValueChange
@ -336,7 +338,8 @@ struct CustomSlider: View {
}
#Preview {
NotchHomeView(albumArtNamespace: Namespace().wrappedValue)
.environmentObject(DynamicIslandViewModel())
.environmentObject(WebcamManager())
NotchHomeView(
albumArtNamespace: Namespace().wrappedValue
)
.environmentObject(DynamicIslandViewModel())
}

View file

@ -4,7 +4,7 @@
//
// Created by Kai Azim on 2023-08-24.
// Original source: https://github.com/MrKai77/DynamicNotchKit
// Modified by Alexander Greco on 2025-05-18.
// Modified by Alexander on 2025-05-18.
import SwiftUI

View file

@ -16,95 +16,120 @@ import SwiftUI
import SwiftUIIntrospect
struct SettingsView: View {
@Environment(\.scenePhase) private var scenePhase
@StateObject var extensionManager = DynamicIslandExtensionManager()
let updaterController: SPUStandardUpdaterController
@StateObject private var calendarManager = CalendarManager()
@State private var selectedTab = "General"
let updaterController: SPUStandardUpdaterController?
init(updaterController: SPUStandardUpdaterController? = nil) {
self.updaterController = updaterController
}
var body: some View {
NavigationSplitView {
List(selection: $selectedTab) {
NavigationLink(destination: GeneralSettings()) {
NavigationLink(value: "General") {
Label("General", systemImage: "gear")
}
NavigationLink(destination: Appearance()) {
NavigationLink(value: "Appearance") {
Label("Appearance", systemImage: "eye")
}
NavigationLink(destination: Media()) {
NavigationLink(value: "Media") {
Label("Media", systemImage: "play.laptopcomputer")
}
NavigationLink(destination: CalendarSettings()) {
NavigationLink(value: "Calendar") {
Label("Calendar", systemImage: "calendar")
}
if extensionManager.installedExtensions
.contains(
where: { $0.bundleIdentifier == hudExtension
})
{
NavigationLink(destination: HUD()) {
.contains(where: { $0.bundleIdentifier == hudExtension }) {
NavigationLink(value: "HUD") {
Label("HUDs", systemImage: "dial.medium.fill")
}
}
NavigationLink(destination: Charge()) {
NavigationLink(value: "Battery") {
Label("Battery", systemImage: "battery.100.bolt")
}
if extensionManager.installedExtensions
.contains(
where: { $0.bundleIdentifier == downloadManagerExtension
})
{
NavigationLink(destination: Downloads()) {
.contains(where: { $0.bundleIdentifier == downloadManagerExtension }) {
NavigationLink(value: "Downloads") {
Label("Downloads", systemImage: "square.and.arrow.down")
}
}
NavigationLink(destination: Shelf()) {
NavigationLink(value: "Shelf") {
Label("Shelf", systemImage: "books.vertical")
}
NavigationLink(destination: Shortcuts()) {
NavigationLink(value: "Shortcuts") {
Label("Shortcuts", systemImage: "keyboard")
}
NavigationLink(destination: Extensions()) {
NavigationLink(value: "Extensions") {
Label("Extensions", systemImage: "puzzlepiece.extension")
}
NavigationLink(
destination: About(updaterController: updaterController)
) {
NavigationLink(value: "About") {
Label("About", systemImage: "info.circle")
}
}
.tint(Defaults[.accentColor])
.listStyle(SidebarListStyle())
.toolbar(removing: .sidebarToggle)
.navigationSplitViewColumnWidth(200)
} detail: {
GeneralSettings()
Group {
switch selectedTab {
case "General":
GeneralSettings()
case "Appearance":
Appearance()
case "Media":
Media()
case "Calendar":
CalendarSettings()
case "HUD":
HUD()
case "Battery":
Charge()
case "Downloads":
Downloads()
case "Shelf":
Shelf()
case "Shortcuts":
Shortcuts()
case "Extensions":
Extensions()
case "About":
if let controller = updaterController {
About(updaterController: controller)
} else {
// Fallback with a default controller
About(updaterController: SPUStandardUpdaterController(startingUpdater: false, updaterDelegate: nil, userDriverDelegate: nil))
}
default:
GeneralSettings()
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.navigationSplitViewStyle(.balanced)
.toolbar(removing: .sidebarToggle)
.toolbar {
Button("") {} // Empty label, does nothing
.controlSize(.extraLarge)
.opacity(0) // Invisible, but reserves space for a consistent look between tabs
.disabled(true)
}
.environmentObject(extensionManager)
.environmentObject(calendarManager)
.formStyle(.grouped)
.onChange(of: scenePhase) {
if scenePhase == .active {
NSApp.setActivationPolicy(.regular)
NSApp.activate(ignoringOtherApps: true)
NSApp.keyWindow?.makeKeyAndOrderFront(nil)
}
}
.onReceive(NotificationCenter.default.publisher(for: NSApplication.willResignActiveNotification)) { _ in
NSApp.setActivationPolicy(.accessory)
NSApp.deactivate()
}
.introspect(.window, on: .macOS(.v14, .v15)) { window in
window.toolbarStyle = .unified
window.styleMask.update(with: .resizable)
}
.frame(width: 700)
.background(Color(NSColor.windowBackgroundColor))
}
}
struct GeneralSettings: View {
@State private var screens: [String] = NSScreen.screens.compactMap { $0.localizedName }
let accentColors: [Color] = [.blue, .purple, .pink, .red, .orange, .yellow, .green, .gray]
@EnvironmentObject var vm: DynamicIslandViewModel
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
@Default(.accentColor) var accentColor
@Default(.mirrorShape) var mirrorShape
@Default(.showEmojis) var showEmojis
@Default(.gestureSensitivity) var gestureSensitivity
@ -121,38 +146,6 @@ struct GeneralSettings: View {
var body: some View {
Form {
Section {
HStack {
ForEach(accentColors, id: \.self) { color in
Button(action: {
withAnimation {
Defaults[.accentColor] = color
}
}) {
Circle()
.fill(color)
.frame(width: 20, height: 20)
.overlay(
Circle()
.stroke(Color.white, lineWidth: Defaults[.accentColor] == color ? 2 : 0)
.overlay {
Circle()
.fill(.white)
.frame(width: 7, height: 7)
.opacity(Defaults[.accentColor] == color ? 1 : 0)
}
)
}
.buttonStyle(PlainButtonStyle())
}
Spacer()
ColorPicker("Custom color", selection: $accentColor)
.labelsHidden()
}
} header: {
Text("Accent color")
}
Section {
Defaults.Toggle("Menubar icon", key: .menubarIcon)
LaunchAtLogin.Toggle("Launch at login")
@ -244,7 +237,6 @@ struct GeneralSettings: View {
gestureControls()
}
.tint(Defaults[.accentColor])
.toolbar {
Button("Quit app") {
NSApp.terminate(self)
@ -293,7 +285,7 @@ struct GeneralSettings: View {
@ViewBuilder
func NotchBehaviour() -> some View {
Section {
Defaults.Toggle("Extend hover area", key: .extendHoverArea)
Defaults.Toggle("Extend hover area", key: .extendHoverArea)
Defaults.Toggle("Enable haptics", key: .enableHaptics)
Defaults.Toggle("Open notch on hover", key: .openNotchOnHover)
Toggle("Remember last tab", isOn: $coordinator.openLastTabByDefault)
@ -332,7 +324,6 @@ struct Charge: View {
Text("Battery Information")
}
}
.tint(Defaults[.accentColor])
.navigationTitle("Battery")
}
}
@ -407,7 +398,6 @@ struct Downloads: View {
}
}
}
.tint(Defaults[.accentColor])
.navigationTitle("Downloads")
}
}
@ -453,7 +443,6 @@ struct HUD: View {
}
}
}
.tint(Defaults[.accentColor])
.navigationTitle("HUDs")
}
}
@ -537,7 +526,6 @@ struct Media: View {
Defaults[.enableFullscreenMediaDetection] = newValue != .never
}
}
.tint(Defaults[.accentColor])
.navigationTitle("Media")
}
@ -571,11 +559,13 @@ struct CalendarSettings: View {
Toggle("Show calendar", isOn: $showCalendar)
Section(header: Text("Select Calendars")) {
List {
ForEach(calendarManager.allCalendars, id: \.calendarIdentifier) { calendar in
ForEach(calendarManager.allCalendars, id: \.id) { calendar in
Toggle(isOn: Binding(
get: { calendarManager.getCalendarSelected(calendar) },
set: { isSelected in
calendarManager.setCalendarSelected(calendar, isSelected: isSelected)
Task {
await calendarManager.setCalendarSelected(calendar, isSelected: isSelected)
}
}
)) {
Text(calendar.title)
@ -586,8 +576,12 @@ struct CalendarSettings: View {
}
}
.onAppear {
calendarManager.checkCalendarAuthorization()
Task {
await calendarManager.checkCalendarAuthorization()
}
}
// Add navigation title if it's missing or adjust as needed
.navigationTitle("Calendar")
}
}
@ -659,7 +653,7 @@ struct About: View {
}
VStack(spacing: 0) {
Divider()
Text("Made with 🫶🏻 by the DynamicIsland team")
Text("Made with 🫶🏻 by not so boring not.people")
.foregroundStyle(.secondary)
.padding(.top, 5)
.padding(.bottom, 7)
@ -669,13 +663,12 @@ struct About: View {
.frame(maxWidth: .infinity, alignment: .center)
}
.toolbar {
Button("Welcome window") {
openWindow(id: "onboarding")
}
.controlSize(.extraLarge)
// Button("Welcome window") {
// openWindow(id: "onboarding")
// }
// .controlSize(.extraLarge)
CheckForUpdatesView(updater: updaterController.updater)
}
.tint(Defaults[.accentColor])
.navigationTitle("About")
}
}
@ -692,7 +685,6 @@ struct Shelf: View {
}
}
}
.tint(Defaults[.accentColor])
.navigationTitle("Shelf")
}
}
@ -810,7 +802,6 @@ struct Extensions: View {
}
}
}
.tint(Defaults[.accentColor])
.navigationTitle("Extensions")
// TipsView()
// .padding(.horizontal, 19)
@ -913,7 +904,7 @@ struct Appearance: View {
.buttonStyle(PlainButtonStyle())
.padding(.vertical, 2)
.background(
selectedListVisualizer != nil ? selectedListVisualizer == visualizer ? Defaults[.accentColor] : Color.clear : Color.clear,
selectedListVisualizer != nil ? selectedListVisualizer == visualizer ? Color.accentColor : Color.clear : Color.clear,
in: RoundedRectangle(cornerRadius: 5)
)
.contentShape(Rectangle())
@ -1027,7 +1018,7 @@ struct Appearance: View {
}
Section {
Defaults.Toggle("Enable dynamic mirror", key: .showMirror)
Defaults.Toggle("Enable boring mirror", key: .showMirror)
.disabled(!checkVideoInput())
Picker("Mirror shape", selection: $mirrorShape) {
Text("Circle")
@ -1053,7 +1044,7 @@ struct Appearance: View {
.background(
RoundedRectangle(cornerRadius: 20, style: .circular)
.strokeBorder(
icon == selectedIcon ? Defaults[.accentColor] : .clear,
icon == selectedIcon ? Color.accentColor : .clear,
lineWidth: 2.5
)
)
@ -1066,7 +1057,7 @@ struct Appearance: View {
.padding(.vertical, 3)
.background(
Capsule()
.fill(icon == selectedIcon ? Defaults[.accentColor] : .clear)
.fill(icon == selectedIcon ? Color.accentColor : .clear)
)
}
.onTapGesture {
@ -1086,7 +1077,6 @@ struct Appearance: View {
}
}
}
.tint(Defaults[.accentColor])
.navigationTitle("Appearance")
}
@ -1116,7 +1106,6 @@ struct Shortcuts: View {
KeyboardShortcuts.Recorder("Toggle Notch Open:", name: .toggleNotchOpen)
}
}
.tint(Defaults[.accentColor])
.navigationTitle("Shortcuts")
}
}

View file

@ -0,0 +1,125 @@
//
// SettingsWindowController.swift
// DynamicIsland
//
// Created by Alexander on 2025-06-14.
//
import AppKit
import SwiftUI
import Defaults
import Sparkle
class SettingsWindowController: NSWindowController {
static let shared = SettingsWindowController()
private var updaterController: SPUStandardUpdaterController?
private init() {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 700, height: 600),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered,
defer: false
)
super.init(window: window)
setupWindow()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setUpdaterController(_ controller: SPUStandardUpdaterController) {
self.updaterController = controller
// Recreate the content view with the proper updater controller
setupWindow()
}
private func setupWindow() {
guard let window = window else { return }
window.title = "Dynamic Island Settings"
window.titlebarAppearsTransparent = false
window.titleVisibility = .visible
window.toolbarStyle = .unified
window.isMovableByWindowBackground = true
// Make it behave like a regular app window with proper Spaces support
window.collectionBehavior = [.managed, .participatesInCycle, .fullScreenAuxiliary]
// Ensure proper window behavior
window.hidesOnDeactivate = false
window.isExcludedFromWindowsMenu = false
// Configure window to be a standard document-style window
window.isRestorable = true
window.identifier = NSUserInterfaceItemIdentifier("DynamicIslandSettingsWindow")
// Create the SwiftUI content
let settingsView = SettingsView(updaterController: updaterController)
let hostingView = NSHostingView(rootView: settingsView)
window.contentView = hostingView
// Handle window closing
window.delegate = self
}
func showWindow() {
// Set app to regular mode first
NSApp.setActivationPolicy(.regular)
// If window is already visible, bring it to front properly
if window?.isVisible == true {
NSApp.activate(ignoringOtherApps: true)
window?.orderFrontRegardless()
window?.makeKeyAndOrderFront(nil)
return
}
// Show the window with proper ordering
window?.orderFrontRegardless()
window?.makeKeyAndOrderFront(nil)
window?.center()
// Activate the app and ensure window gets focus
NSApp.activate(ignoringOtherApps: true)
// Force window to front after activation
DispatchQueue.main.async { [weak self] in
self?.window?.makeKeyAndOrderFront(nil)
}
}
override func close() {
super.close()
relinquishFocus()
}
private func relinquishFocus() {
window?.orderOut(nil)
// Set app back to accessory mode immediately
NSApp.setActivationPolicy(.accessory)
}
}
extension SettingsWindowController: NSWindowDelegate {
func windowWillClose(_ notification: Notification) {
relinquishFocus()
}
func windowShouldClose(_ sender: NSWindow) -> Bool {
return true
}
func windowDidBecomeKey(_ notification: Notification) {
// Ensure app is in regular mode when window becomes key
NSApp.setActivationPolicy(.regular)
}
func windowDidResignKey(_ notification: Notification) {
}
}

View file

@ -118,5 +118,5 @@ struct CameraPreviewLayerView: NSViewRepresentable {
}
#Preview {
CameraPreviewView(webcamManager: WebcamManager())
CameraPreviewView(webcamManager: WebcamManager.shared)
}