Atoll/DynamicIsland/DynamicIslandViewCoordinator.swift
2025-10-07 19:45:55 +05:30

248 lines
7.7 KiB
Swift

//
// DynamicIslandViewCoordinator.swift
// DynamicIsland
//
// Created by Alexander on 2024-11-20.
//
import Combine
import Defaults
import SwiftUI
import TheBoringWorkerNotifier
enum SneakContentType {
case brightness
case volume
case backlight
case music
case mic
case battery
case download
case timer
case recording
case bluetoothAudio
}
struct sneakPeek {
var show: Bool = false
var type: SneakContentType = .music
var value: CGFloat = 0
var icon: String = ""
}
struct SharedSneakPeek: Codable {
var show: Bool
var type: String
var value: String
var icon: String
}
enum BrowserType {
case chromium
case safari
}
struct ExpandedItem {
var show: Bool = false
var type: SneakContentType = .battery
var value: CGFloat = 0
var browser: BrowserType = .chromium
}
class DynamicIslandViewCoordinator: ObservableObject {
static let shared = DynamicIslandViewCoordinator()
var notifier: TheBoringWorkerNotifier
@Published var currentView: NotchViews = .home
@AppStorage("firstLaunch") var firstLaunch: Bool = true
@AppStorage("showWhatsNew") var showWhatsNew: Bool = true
@AppStorage("musicLiveActivityEnabled") var musicLiveActivityEnabled: Bool = true
@AppStorage("timerLiveActivityEnabled") var timerLiveActivityEnabled: Bool = true
@AppStorage("currentMicStatus") var currentMicStatus: Bool = true
@AppStorage("alwaysShowTabs") var alwaysShowTabs: Bool = true {
didSet {
if !alwaysShowTabs {
openLastTabByDefault = false
if TrayDrop.shared.isEmpty || !Defaults[.openShelfByDefault] {
currentView = .home
}
}
}
}
@AppStorage("openLastTabByDefault") var openLastTabByDefault: Bool = false {
didSet {
if openLastTabByDefault {
alwaysShowTabs = true
}
}
}
@AppStorage("hudReplacement") var hudReplacement: Bool = true {
didSet {
notifier.postNotification(name: notifier.toggleHudReplacementNotification.name, userInfo: nil)
}
}
@AppStorage("preferred_screen_name") var preferredScreen = NSScreen.main?.localizedName ?? "Unknown" {
didSet {
selectedScreen = preferredScreen
NotificationCenter.default.post(name: Notification.Name.selectedScreenChanged, object: nil)
}
}
@Published var selectedScreen: String = NSScreen.main?.localizedName ?? "Unknown"
@Published var optionKeyPressed: Bool = true
private init() {
notifier = TheBoringWorkerNotifier()
selectedScreen = preferredScreen
}
func setupWorkersNotificationObservers() {
notifier.setupObserver(notification: notifier.micStatusNotification, handler: initialMicStatus)
notifier.setupObserver(notification: notifier.sneakPeakNotification, handler: sneakPeekEvent)
}
@objc func sneakPeekEvent(_ notification: Notification) {
let decoder = JSONDecoder()
if let decodedData = try? decoder.decode(
SharedSneakPeek.self, from: notification.userInfo?.first?.value as! Data)
{
let contentType =
decodedData.type == "brightness"
? SneakContentType.brightness
: decodedData.type == "volume"
? SneakContentType.volume
: decodedData.type == "backlight"
? SneakContentType.backlight
: decodedData.type == "mic"
? SneakContentType.mic
: decodedData.type == "timer"
? SneakContentType.timer : SneakContentType.brightness
let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.numberStyle = .decimal
let value = CGFloat((formatter.number(from: decodedData.value) ?? 0.0).floatValue)
let icon = decodedData.icon
print("Decoded: \(decodedData), Parsed value: \(value)")
toggleSneakPeek(status: decodedData.show, type: contentType, value: value, icon: icon)
} else {
print("Failed to decode JSON data")
}
}
func toggleSneakPeek(status: Bool, type: SneakContentType, duration: TimeInterval = 1.5, value: CGFloat = 0, icon: String = "") {
sneakPeekDuration = duration
if type != .music && type != .timer {
// close()
if !hudReplacement {
return
}
}
DispatchQueue.main.async {
withAnimation(.smooth) {
self.sneakPeek.show = status
self.sneakPeek.type = type
self.sneakPeek.value = value
self.sneakPeek.icon = icon
}
}
if type == .mic {
currentMicStatus = value == 1
}
}
private var sneakPeekDuration: TimeInterval = 1.5
private var sneakPeekTask: Task<Void, Never>?
// Helper function to manage sneakPeek timer using Swift Concurrency
private func scheduleSneakPeekHide(after duration: TimeInterval) {
sneakPeekTask?.cancel()
sneakPeekTask = Task { [weak self] in
try? await Task.sleep(for: .seconds(duration))
guard let self = self, !Task.isCancelled else { return }
await MainActor.run {
withAnimation {
// Hide the sneak peek with the correct type that was showing
self.toggleSneakPeek(status: false, type: self.sneakPeek.type)
self.sneakPeekDuration = 1.5
}
}
}
}
@Published var sneakPeek: sneakPeek = .init() {
didSet {
if sneakPeek.show {
scheduleSneakPeekHide(after: sneakPeekDuration)
} else {
sneakPeekTask?.cancel()
}
}
}
func toggleExpandingView(
status: Bool,
type: SneakContentType,
value: CGFloat = 0,
browser: BrowserType = .chromium
) {
Task { @MainActor in
withAnimation(.smooth) {
self.expandingView.show = status
self.expandingView.type = type
self.expandingView.value = value
self.expandingView.browser = browser
}
}
}
private var expandingViewTask: Task<Void, Never>?
@Published var expandingView: ExpandedItem = .init() {
didSet {
if expandingView.show {
expandingViewTask?.cancel()
let duration: TimeInterval = (expandingView.type == .download ? 2 : 3)
expandingViewTask = Task { [weak self] in
try? await Task.sleep(for: .seconds(duration))
guard let self = self, !Task.isCancelled else { return }
self.toggleExpandingView(status: false, type: .battery)
}
} else {
expandingViewTask?.cancel()
}
}
}
@objc func initialMicStatus(_ notification: Notification) {
currentMicStatus = notification.userInfo?.first?.value as! Bool
}
func toggleMic() {
notifier.postNotification(name: notifier.toggleMicNotification.name, userInfo: nil)
}
func showEmpty() {
currentView = .home
}
// MARK: - Clipboard Management
@Published var shouldToggleClipboardPopover: Bool = false
func toggleClipboardPopover() {
// Toggle the published property to trigger UI updates
shouldToggleClipboardPopover.toggle()
}
}