mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-28 07:34:32 +00:00
Applied latest changes from boring.notch
This commit is contained in:
parent
bad481588b
commit
628d76b41c
18 changed files with 940 additions and 339 deletions
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
125
DynamicIsland/components/Settings/SettingsWindowController.swift
Normal file
125
DynamicIsland/components/Settings/SettingsWindowController.swift
Normal 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) {
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue