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
|
|
@ -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())
|
||||
}
|
||||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -118,5 +118,5 @@ struct CameraPreviewLayerView: NSViewRepresentable {
|
|||
}
|
||||
|
||||
#Preview {
|
||||
CameraPreviewView(webcamManager: WebcamManager())
|
||||
CameraPreviewView(webcamManager: WebcamManager.shared)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue