mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
253 lines
11 KiB
Swift
253 lines
11 KiB
Swift
//
|
|
// 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
|
|
@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] {
|
|
RecordingIndicator()
|
|
.frame(width: 30, height: 30) // Same size as other header elements
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
DynamicIslandHeader()
|
|
.environmentObject(DynamicIslandViewModel())
|
|
.environmentObject(WebcamManager.shared)
|
|
}
|