Atoll/DynamicIsland/models/SharingStateManager.swift
2026-01-29 12:26:04 +05:30

172 lines
4.3 KiB
Swift

/*
* Atoll (DynamicIsland)
* Copyright (C) 2024-2026 Atoll Contributors
*
* Originally from boring.notch project
* Modified and adapted for Atoll (DynamicIsland)
* See NOTICE for details.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import AppKit
import Combine
import Foundation
extension Notification.Name {
static let sharingDidFinish = Notification.Name("com.ebullioscopic.sharingDidFinish")
}
@MainActor
final class SharingStateManager: ObservableObject {
static let shared = SharingStateManager()
private var activeSessions: Int = 0 {
didSet {
let newValue = activeSessions > 0
if newValue != preventNotchClose {
preventNotchClose = newValue
if !newValue {
NotificationCenter.default.post(name: .sharingDidFinish, object: nil)
}
}
}
}
@Published var preventNotchClose: Bool = false
private var activeDelegates: [UUID: SharingLifecycleDelegate] = [:]
private init() {}
func requestCloseIfReady() {
if !preventNotchClose {
NotificationCenter.default.post(name: .sharingDidFinish, object: nil)
}
}
func beginInteraction() {
activeSessions += 1
}
func endInteraction() {
if activeSessions > 0 { activeSessions -= 1 }
}
func makeDelegate(onEnd: (() -> Void)? = nil) -> SharingLifecycleDelegate {
let id = UUID()
let delegate = SharingLifecycleDelegate(id: id, onEnd: { [weak self] in
onEnd?()
self?.unregisterDelegate(id: id)
}, onBegin: { [weak self] in
self?.beginInteraction()
}, onFinish: { [weak self] in
self?.endInteraction()
})
activeDelegates[id] = delegate
return delegate
}
private func unregisterDelegate(id: UUID) {
activeDelegates.removeValue(forKey: id)
}
}
final class SharingLifecycleDelegate: NSObject, NSSharingServiceDelegate, NSSharingServicePickerDelegate {
let id: UUID
private let onEnd: () -> Void
private let onBegin: () -> Void
private let onFinish: () -> Void
private var pickerActive = false
private var serviceInProgress = false
private var finished = false
private var timeoutTask: Task<Void, Never>?
init(id: UUID, onEnd: @escaping () -> Void, onBegin: @escaping () -> Void, onFinish: @escaping () -> Void) {
self.id = id
self.onEnd = onEnd
self.onBegin = onBegin
self.onFinish = onFinish
}
deinit {
timeoutTask?.cancel()
}
func markPickerBegan() {
guard !pickerActive else { return }
pickerActive = true
onBegin()
}
func markServiceBegan() {
guard !serviceInProgress else { return }
serviceInProgress = true
onBegin()
startTimeoutFallback()
}
private func startTimeoutFallback() {
timeoutTask?.cancel()
timeoutTask = Task { @MainActor [weak self] in
try? await Task.sleep(for: .seconds(2))
guard let self = self, !Task.isCancelled else { return }
if !self.finished {
self.finishIfNeeded()
}
}
}
private func finishIfNeeded() {
guard !finished else { return }
finished = true
timeoutTask?.cancel()
onFinish()
onEnd()
}
// MARK: - NSSharingServicePickerDelegate
func sharingServicePicker(_ sharingServicePicker: NSSharingServicePicker, didChoose service: NSSharingService?) {
if service == nil {
if pickerActive && !serviceInProgress {
finishIfNeeded()
}
return
}
service?.delegate = self
serviceInProgress = true
startTimeoutFallback()
}
// MARK: - NSSharingServiceDelegate
func sharingService(_ sharingService: NSSharingService, willShareItems items: [Any]) {
if !pickerActive && !serviceInProgress {
onBegin()
}
serviceInProgress = true
}
func sharingService(_ sharingService: NSSharingService, didShareItems items: [Any]) {
finishIfNeeded()
}
func sharingService(_ sharingService: NSSharingService, didFailToShareItems items: [Any], error: Error) {
finishIfNeeded()
}
}