// // DynamicIslandHeader.swift // DynamicIsland // // Created by Harsh Vardhan Goswami on 04/08/24. // import Defaults import SwiftUI struct DynamicIslandHeader: View { @EnvironmentObject var vm: DynamicIslandViewModel @EnvironmentObject var webcamManager: WebcamManager @ObservedObject var batteryModel = BatteryStatusViewModel.shared @ObservedObject var coordinator = DynamicIslandViewCoordinator.shared @ObservedObject var clipboardManager = ClipboardManager.shared @ObservedObject var tvm = TrayDrop.shared @ObservedObject var timerManager = TimerManager.shared @ObservedObject var doNotDisturbManager = DoNotDisturbManager.shared @State private var showClipboardPopover = false @State private var showColorPickerPopover = false @State private var showTimerPopover = false @Default(.enableTimerFeature) var enableTimerFeature var body: some View { HStack(spacing: 0) { if !Defaults[.enableMinimalisticUI] { HStack { if (!tvm.isEmpty || coordinator.alwaysShowTabs) && Defaults[.dynamicShelf] { TabSelectionView() } else if vm.notchState == .open { EmptyView() } } .frame(maxWidth: .infinity, alignment: .leading) .opacity(vm.notchState == .closed ? 0 : 1) .blur(radius: vm.notchState == .closed ? 20 : 0) .animation(.smooth.delay(0.1), value: vm.notchState) .zIndex(2) } if vm.notchState == .open && !Defaults[.enableMinimalisticUI] { Rectangle() .fill(NSScreen.screens .first(where: { $0.localizedName == coordinator.selectedScreen })?.safeAreaInsets.top ?? 0 > 0 ? .black : .clear) .frame(width: vm.closedNotchSize.width) .mask { NotchShape() } } HStack(spacing: 4) { if vm.notchState == .open && !Defaults[.enableMinimalisticUI] { if Defaults[.showMirror] { Button(action: { vm.toggleCameraPreview() }) { Capsule() .fill(.black) .frame(width: 30, height: 30) .overlay { Image(systemName: "web.camera") .foregroundColor(.white) .padding() .imageScale(.medium) } } .buttonStyle(PlainButtonStyle()) } if Defaults[.enableClipboardManager] && Defaults[.showClipboardIcon] { Button(action: { // Switch behavior based on display mode switch Defaults[.clipboardDisplayMode] { case .panel: ClipboardPanelManager.shared.toggleClipboardPanel() case .popover: showClipboardPopover.toggle() } }) { Capsule() .fill(.black) .frame(width: 30, height: 30) .overlay { Image(systemName: "doc.on.clipboard") .foregroundColor(.white) .padding() .imageScale(.medium) } } .buttonStyle(PlainButtonStyle()) .popover(isPresented: $showClipboardPopover, arrowEdge: .bottom) { ClipboardPopover() } .onChange(of: showClipboardPopover) { isActive in vm.isClipboardPopoverActive = isActive // If popover was closed, trigger a hover recheck if !isActive { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { vm.shouldRecheckHover.toggle() } } } .onAppear { if Defaults[.enableClipboardManager] && !clipboardManager.isMonitoring { clipboardManager.startMonitoring() } } } // ColorPicker button if Defaults[.enableColorPickerFeature] { Button(action: { switch Defaults[.colorPickerDisplayMode] { case .panel: ColorPickerPanelManager.shared.toggleColorPickerPanel() case .popover: showColorPickerPopover.toggle() } }) { Capsule() .fill(.black) .frame(width: 30, height: 30) .overlay { Image(systemName: "eyedropper") .foregroundColor(.white) .padding() .imageScale(.medium) } } .buttonStyle(PlainButtonStyle()) .popover(isPresented: $showColorPickerPopover, arrowEdge: .bottom) { ColorPickerPopover() } .onChange(of: showColorPickerPopover) { isActive in vm.isColorPickerPopoverActive = isActive // If popover was closed, trigger a hover recheck if !isActive { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { vm.shouldRecheckHover.toggle() } } } } if Defaults[.enableTimerFeature] { Button(action: { withAnimation(.smooth) { showTimerPopover.toggle() } }) { Capsule() .fill(.black) .frame(width: 30, height: 30) .overlay { Image(systemName: "timer") .foregroundColor(.white) .padding() .imageScale(.medium) } } .buttonStyle(PlainButtonStyle()) .popover(isPresented: $showTimerPopover, arrowEdge: .bottom) { TimerPopover() } .onChange(of: showTimerPopover) { isActive in vm.isTimerPopoverActive = isActive if !isActive { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { vm.shouldRecheckHover.toggle() } } } } if Defaults[.settingsIconInNotch] { Button(action: { SettingsWindowController.shared.showWindow() }) { Capsule() .fill(.black) .frame(width: 30, height: 30) .overlay { Image(systemName: "gear") .foregroundColor(.white) .padding() .imageScale(.medium) } } .buttonStyle(PlainButtonStyle()) } // Screen Recording Indicator if Defaults[.enableScreenRecordingDetection] && Defaults[.showRecordingIndicator] && !shouldSuppressStatusIndicators { RecordingIndicator() .frame(width: 30, height: 30) // Same size as other header elements } if Defaults[.enableDoNotDisturbDetection] && Defaults[.showDoNotDisturbIndicator] && doNotDisturbManager.isDoNotDisturbActive && !shouldSuppressStatusIndicators { FocusIndicator() .frame(width: 30, height: 30) .transition(.opacity) } if Defaults[.showBatteryIndicator] { DynamicIslandBatteryView( batteryWidth: 30, isCharging: batteryModel.isCharging, isInLowPowerMode: batteryModel.isInLowPowerMode, isPluggedIn: batteryModel.isPluggedIn, levelBattery: batteryModel.levelBattery, maxCapacity: batteryModel.maxCapacity, timeToFullCharge: batteryModel.timeToFullCharge, isForNotification: false ) } } } .font(.system(.headline, design: .rounded)) .frame(maxWidth: .infinity, alignment: .trailing) .opacity(vm.notchState == .closed ? 0 : 1) .blur(radius: vm.notchState == .closed ? 20 : 0) .animation(.smooth.delay(0.1), value: vm.notchState) .zIndex(2) } .foregroundColor(.gray) .environmentObject(vm) .onChange(of: coordinator.shouldToggleClipboardPopover) { _ in // Only toggle if clipboard is enabled if Defaults[.enableClipboardManager] { switch Defaults[.clipboardDisplayMode] { case .panel: ClipboardPanelManager.shared.toggleClipboardPanel() case .popover: showClipboardPopover.toggle() } } } .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("ToggleClipboardPopover"))) { _ in // Handle keyboard shortcut for popover mode if Defaults[.enableClipboardManager] && Defaults[.clipboardDisplayMode] == .popover { showClipboardPopover.toggle() } } .onChange(of: enableTimerFeature) { _, newValue in if !newValue { showTimerPopover = false vm.isTimerPopoverActive = false } } } } private extension DynamicIslandHeader { var shouldSuppressStatusIndicators: Bool { Defaults[.settingsIconInNotch] && Defaults[.enableClipboardManager] && Defaults[.showClipboardIcon] && Defaults[.enableColorPickerFeature] && Defaults[.enableTimerFeature] } } #Preview { DynamicIslandHeader() .environmentObject(DynamicIslandViewModel()) .environmentObject(WebcamManager.shared) }