mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Make some changes: fixes and stuff
This commit is contained in:
parent
05d41f6bb7
commit
d0448ec2cb
10 changed files with 209 additions and 150 deletions
|
|
@ -34,7 +34,7 @@ struct ContentView: View {
|
||||||
@Default(.showNetworkGraph) var showNetworkGraph
|
@Default(.showNetworkGraph) var showNetworkGraph
|
||||||
@Default(.showDiskGraph) var showDiskGraph
|
@Default(.showDiskGraph) var showDiskGraph
|
||||||
|
|
||||||
// Dynamic sizing based on view type and graph count with smooth transitions
|
// Dynamic sizing based on view type and graph count with smooth transitions
|
||||||
var dynamicNotchSize: CGSize {
|
var dynamicNotchSize: CGSize {
|
||||||
// Use minimalistic or normal size based on settings
|
// Use minimalistic or normal size based on settings
|
||||||
let baseSize = Defaults[.enableMinimalisticUI] ? minimalisticOpenNotchSize : openNotchSize
|
let baseSize = Defaults[.enableMinimalisticUI] ? minimalisticOpenNotchSize : openNotchSize
|
||||||
|
|
@ -107,6 +107,8 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
let interactionsEnabled = !lockScreenManager.isLocked
|
||||||
|
|
||||||
ZStack(alignment: .top) {
|
ZStack(alignment: .top) {
|
||||||
let mainLayout = NotchLayout()
|
let mainLayout = NotchLayout()
|
||||||
.frame(alignment: .top)
|
.frame(alignment: .top)
|
||||||
|
|
@ -149,12 +151,12 @@ struct ContentView: View {
|
||||||
.animation(notchStateAnimation, value: vm.notchState)
|
.animation(notchStateAnimation, value: vm.notchState)
|
||||||
.animation(viewTransitionAnimation, value: coordinator.currentView)
|
.animation(viewTransitionAnimation, value: coordinator.currentView)
|
||||||
}
|
}
|
||||||
.conditionalModifier(Defaults[.openNotchOnHover]) { view in
|
.conditionalModifier(Defaults[.openNotchOnHover] && interactionsEnabled) { view in
|
||||||
view.onHover { hovering in
|
view.onHover { hovering in
|
||||||
handleHover(hovering)
|
handleHover(hovering)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.conditionalModifier(!Defaults[.openNotchOnHover]) { view in
|
.conditionalModifier(!Defaults[.openNotchOnHover] && interactionsEnabled) { view in
|
||||||
view
|
view
|
||||||
.onHover { hovering in
|
.onHover { hovering in
|
||||||
handleSimpleHover(hovering)
|
handleSimpleHover(hovering)
|
||||||
|
|
@ -165,14 +167,14 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
doOpen()
|
doOpen()
|
||||||
}
|
}
|
||||||
.conditionalModifier(Defaults[.enableGestures]) { view in
|
.conditionalModifier(Defaults[.enableGestures] && interactionsEnabled) { view in
|
||||||
view
|
view
|
||||||
.panGesture(direction: .down) { translation, phase in
|
.panGesture(direction: .down) { translation, phase in
|
||||||
handleDownGesture(translation: translation, phase: phase)
|
handleDownGesture(translation: translation, phase: phase)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.conditionalModifier(Defaults[.closeGestureEnabled] && Defaults[.enableGestures]) { view in
|
.conditionalModifier(Defaults[.closeGestureEnabled] && Defaults[.enableGestures] && interactionsEnabled) { view in
|
||||||
view
|
view
|
||||||
.panGesture(direction: .up) { translation, phase in
|
.panGesture(direction: .up) { translation, phase in
|
||||||
handleUpGesture(translation: translation, phase: phase)
|
handleUpGesture(translation: translation, phase: phase)
|
||||||
|
|
@ -417,7 +419,7 @@ struct ContentView: View {
|
||||||
} else if coordinator.sneakPeek.show && Defaults[.inlineHUD] && (coordinator.sneakPeek.type != .music) && (coordinator.sneakPeek.type != .battery) {
|
} else if coordinator.sneakPeek.show && Defaults[.inlineHUD] && (coordinator.sneakPeek.type != .music) && (coordinator.sneakPeek.type != .battery) {
|
||||||
InlineHUD(type: $coordinator.sneakPeek.type, value: $coordinator.sneakPeek.value, icon: $coordinator.sneakPeek.icon, hoverAnimation: $isHovering, gestureProgress: $gestureProgress)
|
InlineHUD(type: $coordinator.sneakPeek.type, value: $coordinator.sneakPeek.value, icon: $coordinator.sneakPeek.icon, hoverAnimation: $isHovering, gestureProgress: $gestureProgress)
|
||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
} else if (!coordinator.expandingView.show || coordinator.expandingView.type == .music) && vm.notchState == .closed && (musicManager.isPlaying || !musicManager.isPlayerIdle) && coordinator.musicLiveActivityEnabled && !vm.hideOnClosed {
|
} else if (!coordinator.expandingView.show || coordinator.expandingView.type == .music) && vm.notchState == .closed && (musicManager.isPlaying || !musicManager.isPlayerIdle) && coordinator.musicLiveActivityEnabled && !vm.hideOnClosed && !lockScreenManager.isLocked {
|
||||||
MusicLiveActivity()
|
MusicLiveActivity()
|
||||||
} else if (!coordinator.expandingView.show || coordinator.expandingView.type == .timer) && vm.notchState == .closed && timerManager.isTimerActive && coordinator.timerLiveActivityEnabled && !vm.hideOnClosed {
|
} else if (!coordinator.expandingView.show || coordinator.expandingView.type == .timer) && vm.notchState == .closed && timerManager.isTimerActive && coordinator.timerLiveActivityEnabled && !vm.hideOnClosed {
|
||||||
TimerLiveActivity()
|
TimerLiveActivity()
|
||||||
|
|
@ -611,7 +613,9 @@ struct ContentView: View {
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var dragDetector: some View {
|
var dragDetector: some View {
|
||||||
if Defaults[.dynamicShelf] && !Defaults[.enableMinimalisticUI] {
|
if lockScreenManager.isLocked {
|
||||||
|
EmptyView()
|
||||||
|
} else if Defaults[.dynamicShelf] && !Defaults[.enableMinimalisticUI] {
|
||||||
Color.clear
|
Color.clear
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
|
|
|
||||||
|
|
@ -434,6 +434,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||||
|
|
||||||
coordinator.setupWorkersNotificationObservers()
|
coordinator.setupWorkersNotificationObservers()
|
||||||
|
LockScreenLiveActivityWindowManager.shared.configure(viewModel: vm)
|
||||||
|
LockScreenManager.shared.configure(viewModel: vm)
|
||||||
|
|
||||||
// Migrate legacy progress bar settings
|
// Migrate legacy progress bar settings
|
||||||
Defaults.Keys.migrateProgressBarStyle()
|
Defaults.Keys.migrateProgressBarStyle()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
// Legacy placeholder file superseded by LockScreenLiveActivityOverlay.swift.
|
||||||
|
|
@ -1,89 +1,57 @@
|
||||||
//
|
|
||||||
// LockScreenLiveActivityOverlay.swift
|
|
||||||
// DynamicIsland
|
|
||||||
//
|
|
||||||
// Mirrors the closed-notch live activity layout on the macOS lock screen.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
enum LockLiveActivityState: Equatable {
|
final class LockScreenLiveActivityOverlayModel: ObservableObject {
|
||||||
case locked
|
@Published var iconName: String = "lock.fill"
|
||||||
case unlocking
|
@Published var scale: CGFloat = 0.6
|
||||||
}
|
@Published var opacity: Double = 0
|
||||||
|
|
||||||
final class LockScreenLiveActivityOverlayViewModel: ObservableObject {
|
|
||||||
@Published var expansion: CGFloat = 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LockScreenLiveActivityOverlay: View {
|
struct LockScreenLiveActivityOverlay: View {
|
||||||
let state: LockLiveActivityState
|
@ObservedObject var model: LockScreenLiveActivityOverlayModel
|
||||||
let notchSize: CGSize
|
let notchSize: CGSize
|
||||||
@ObservedObject var viewModel: LockScreenLiveActivityOverlayViewModel
|
|
||||||
|
|
||||||
private var indicatorSize: CGFloat {
|
private var indicatorSize: CGFloat {
|
||||||
max(0, notchSize.height - 12)
|
max(0, notchSize.height - 12)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var horizontalPadding: CGFloat {
|
private var horizontalPadding: CGFloat {
|
||||||
cornerRadiusInsets.closed.bottom
|
cornerRadiusInsets.closed.bottom
|
||||||
}
|
}
|
||||||
|
|
||||||
private var baseWidth: CGFloat {
|
private var totalWidth: CGFloat {
|
||||||
notchSize.width + (indicatorSize * 2)
|
notchSize.width + (indicatorSize * 2) + (horizontalPadding * 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var totalWidth: CGFloat {
|
var body: some View {
|
||||||
baseWidth + (horizontalPadding * 2)
|
HStack(spacing: 0) {
|
||||||
}
|
Color.clear
|
||||||
|
.overlay(alignment: .leading) {
|
||||||
|
Image(systemName: model.iconName)
|
||||||
|
.font(.system(size: 12, weight: .semibold))
|
||||||
|
.foregroundStyle(.white)
|
||||||
|
.frame(width: indicatorSize, height: indicatorSize)
|
||||||
|
}
|
||||||
|
.frame(width: indicatorSize, height: notchSize.height)
|
||||||
|
|
||||||
private var iconName: String {
|
Rectangle()
|
||||||
state == .locked ? "lock.fill" : "lock.open.fill"
|
.fill(.black)
|
||||||
}
|
.frame(width: notchSize.width, height: notchSize.height)
|
||||||
|
|
||||||
private var iconColor: Color { .white }
|
Color.clear
|
||||||
|
.frame(width: indicatorSize, height: notchSize.height)
|
||||||
var body: some View {
|
}
|
||||||
HStack(alignment: .top, spacing: 0) {
|
.frame(width: notchSize.width + (indicatorSize * 2), height: notchSize.height)
|
||||||
Color.clear
|
.padding(.horizontal, horizontalPadding)
|
||||||
.overlay(alignment: .leading) {
|
.background(Color.black)
|
||||||
Image(systemName: iconName)
|
.clipShape(
|
||||||
.font(.system(size: 12, weight: .semibold))
|
NotchShape(
|
||||||
.foregroundStyle(iconColor)
|
topCornerRadius: cornerRadiusInsets.closed.top,
|
||||||
.frame(width: indicatorSize, height: indicatorSize)
|
bottomCornerRadius: cornerRadiusInsets.closed.bottom
|
||||||
}
|
)
|
||||||
.frame(width: indicatorSize, height: notchSize.height, alignment: .top)
|
)
|
||||||
|
.frame(width: totalWidth, height: notchSize.height)
|
||||||
Color.clear
|
.scaleEffect(x: model.scale, y: 1, anchor: .center)
|
||||||
.frame(width: notchSize.width, height: notchSize.height, alignment: .top)
|
.opacity(model.opacity)
|
||||||
|
.drawingGroup()
|
||||||
Color.clear
|
}
|
||||||
.frame(width: indicatorSize, height: notchSize.height, alignment: .top)
|
|
||||||
}
|
|
||||||
.frame(width: baseWidth, height: notchSize.height)
|
|
||||||
.padding(.horizontal, horizontalPadding)
|
|
||||||
.background(Color.black)
|
|
||||||
.clipShape(
|
|
||||||
NotchShape(
|
|
||||||
topCornerRadius: cornerRadiusInsets.closed.top,
|
|
||||||
bottomCornerRadius: cornerRadiusInsets.closed.bottom
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.frame(width: totalWidth, height: notchSize.height)
|
|
||||||
.scaleEffect(x: max(0.05, viewModel.expansion), y: 1, anchor: .center)
|
|
||||||
.opacity(min(1, max(0.2, viewModel.expansion * 5)))
|
|
||||||
.animation(.spring(response: 0.45, dampingFraction: 0.85), value: state)
|
|
||||||
.animation(.spring(response: 0.5, dampingFraction: 0.85), value: viewModel.expansion)
|
|
||||||
.drawingGroup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview {
|
|
||||||
let model = LockScreenLiveActivityOverlayViewModel()
|
|
||||||
return VStack(spacing: 24) {
|
|
||||||
LockScreenLiveActivityOverlay(state: .locked, notchSize: CGSize(width: 220, height: 32), viewModel: model)
|
|
||||||
LockScreenLiveActivityOverlay(state: .unlocking, notchSize: CGSize(width: 220, height: 32), viewModel: model)
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
.background(Color.gray.opacity(0.2))
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,10 @@ struct LockScreenMusicPanel: View {
|
||||||
@State private var lastDragged: Date = .distantPast
|
@State private var lastDragged: Date = .distantPast
|
||||||
@State private var isActive = true
|
@State private var isActive = true
|
||||||
|
|
||||||
|
private let panelWidth: CGFloat = 460
|
||||||
|
private let panelHeight: CGFloat = 180
|
||||||
|
private let cornerRadius: CGFloat = 28
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if isActive {
|
if isActive {
|
||||||
panelContent
|
panelContent
|
||||||
|
|
@ -23,14 +27,14 @@ struct LockScreenMusicPanel: View {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Color.clear
|
Color.clear
|
||||||
.frame(width: 420, height: 150)
|
.frame(width: panelWidth, height: panelHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var panelContent: some View {
|
private var panelContent: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 12) {
|
||||||
// Header area with album art
|
// Header area with album art
|
||||||
HStack(alignment: .center, spacing: 10) {
|
HStack(alignment: .center, spacing: 16) {
|
||||||
// Album art
|
// Album art
|
||||||
Button {
|
Button {
|
||||||
musicManager.openMusicApp()
|
musicManager.openMusicApp()
|
||||||
|
|
@ -38,8 +42,8 @@ struct LockScreenMusicPanel: View {
|
||||||
Image(nsImage: musicManager.albumArt)
|
Image(nsImage: musicManager.albumArt)
|
||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
.frame(width: 50, height: 50)
|
.frame(width: 60, height: 60)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
.clipShape(RoundedRectangle(cornerRadius: 16))
|
||||||
}
|
}
|
||||||
.buttonStyle(PlainButtonStyle())
|
.buttonStyle(PlainButtonStyle())
|
||||||
.opacity(musicManager.isPlaying ? 1 : 0.4)
|
.opacity(musicManager.isPlaying ? 1 : 0.4)
|
||||||
|
|
@ -71,30 +75,30 @@ struct LockScreenMusicPanel: View {
|
||||||
.frame(width: 20, height: 16)
|
.frame(width: 20, height: 16)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(height: 50)
|
.frame(height: 60)
|
||||||
|
|
||||||
// Progress bar
|
// Progress bar
|
||||||
progressBar
|
progressBar
|
||||||
.padding(.top, 6)
|
.padding(.top, 4)
|
||||||
|
|
||||||
// Playback controls
|
// Playback controls
|
||||||
playbackControls
|
playbackControls
|
||||||
.padding(.top, 4)
|
.padding(.top, 4)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 12)
|
.padding(.horizontal, 20)
|
||||||
.padding(.vertical, 8)
|
.padding(.vertical, 16)
|
||||||
.frame(width: 420, height: 150)
|
.frame(width: panelWidth, height: panelHeight)
|
||||||
.background {
|
.background {
|
||||||
if #available(macOS 26.0, *) {
|
if #available(macOS 26.0, *) {
|
||||||
RoundedRectangle(cornerRadius: 20)
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
.glassEffect(in: .rect(cornerRadius: 20))
|
.glassEffect(in: .rect(cornerRadius: cornerRadius))
|
||||||
.onAppear {
|
.onAppear {
|
||||||
let formatter = DateFormatter()
|
let formatter = DateFormatter()
|
||||||
formatter.dateFormat = "HH:mm:ss.SSS"
|
formatter.dateFormat = "HH:mm:ss.SSS"
|
||||||
print("[\(formatter.string(from: Date()))] LockScreenMusicPanel: ✨ Using macOS 26.0+ Liquid Glass effect")
|
print("[\(formatter.string(from: Date()))] LockScreenMusicPanel: ✨ Using macOS 26.0+ Liquid Glass effect")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
RoundedRectangle(cornerRadius: 20)
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
.fill(.ultraThinMaterial)
|
.fill(.ultraThinMaterial)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
let formatter = DateFormatter()
|
let formatter = DateFormatter()
|
||||||
|
|
@ -103,6 +107,10 @@ struct LockScreenMusicPanel: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.overlay {
|
||||||
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
|
.stroke(Color.white.opacity(0.35), lineWidth: 1.4)
|
||||||
|
}
|
||||||
.shadow(color: Color.black.opacity(0.3), radius: 20, x: 0, y: 10)
|
.shadow(color: Color.black.opacity(0.3), radius: 20, x: 0, y: 10)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
sliderValue = musicManager.elapsedTime
|
sliderValue = musicManager.elapsedTime
|
||||||
|
|
@ -233,7 +241,9 @@ struct LockScreenMusicPanel: View {
|
||||||
Image(systemName: musicManager.isPlaying ? "pause.fill" : "play.fill")
|
Image(systemName: musicManager.isPlaying ? "pause.fill" : "play.fill")
|
||||||
.font(.system(size: 24, weight: .semibold))
|
.font(.system(size: 24, weight: .semibold))
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
.frame(width: 50, height: 50)
|
.frame(width: 54, height: 54)
|
||||||
|
.background(Color.white.opacity(0.15))
|
||||||
|
.clipShape(Circle())
|
||||||
}
|
}
|
||||||
.buttonStyle(PlainButtonStyle())
|
.buttonStyle(PlainButtonStyle())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ class LockScreenLiveActivityWindowManager {
|
||||||
private var hasDelegated = false
|
private var hasDelegated = false
|
||||||
private var hideTask: Task<Void, Never>?
|
private var hideTask: Task<Void, Never>?
|
||||||
private var hostingView: NSHostingView<LockScreenLiveActivityOverlay>?
|
private var hostingView: NSHostingView<LockScreenLiveActivityOverlay>?
|
||||||
private let overlayModel = LockScreenLiveActivityOverlayViewModel()
|
private let overlayModel = LockScreenLiveActivityOverlayModel()
|
||||||
|
private weak var viewModel: DynamicIslandViewModel?
|
||||||
|
|
||||||
private init() {}
|
private init() {}
|
||||||
|
|
||||||
|
|
@ -28,25 +29,30 @@ class LockScreenLiveActivityWindowManager {
|
||||||
return formatter.string(from: Date())
|
return formatter.string(from: Date())
|
||||||
}
|
}
|
||||||
|
|
||||||
private func frame(for notchSize: CGSize, on screen: NSScreen) -> NSRect {
|
private func windowSize(for notchSize: CGSize) -> CGSize {
|
||||||
let indicatorSize = max(0, notchSize.height - 12)
|
let indicatorWidth = max(0, notchSize.height - 12)
|
||||||
let horizontalPadding = cornerRadiusInsets.closed.bottom
|
let horizontalPadding = cornerRadiusInsets.closed.bottom
|
||||||
let totalWidth = notchSize.width + (indicatorSize * 2) + (horizontalPadding * 2)
|
|
||||||
|
|
||||||
let screenFrame = screen.frame
|
let totalWidth = notchSize.width + (indicatorWidth * 2) + (horizontalPadding * 2)
|
||||||
let originX = screenFrame.origin.x + (screenFrame.width / 2) - (totalWidth / 2)
|
|
||||||
let originY = screenFrame.origin.y + screenFrame.height - notchSize.height
|
|
||||||
|
|
||||||
return NSRect(x: originX, y: originY, width: totalWidth, height: notchSize.height)
|
return CGSize(width: totalWidth, height: notchSize.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func ensureWindow(notchSize: CGSize, screen: NSScreen) -> NSWindow {
|
private func frame(for windowSize: CGSize, on screen: NSScreen) -> NSRect {
|
||||||
|
let screenFrame = screen.frame
|
||||||
|
let originX = screenFrame.origin.x + (screenFrame.width / 2) - (windowSize.width / 2)
|
||||||
|
let originY = screenFrame.origin.y + screenFrame.height - windowSize.height
|
||||||
|
|
||||||
|
return NSRect(x: originX, y: originY, width: windowSize.width, height: windowSize.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func ensureWindow(windowSize: CGSize, screen: NSScreen) -> NSWindow {
|
||||||
if let window {
|
if let window {
|
||||||
return window
|
return window
|
||||||
}
|
}
|
||||||
|
|
||||||
let window = NSWindow(
|
let window = NSWindow(
|
||||||
contentRect: frame(for: notchSize, on: screen),
|
contentRect: frame(for: windowSize, on: screen),
|
||||||
styleMask: [.borderless, .nonactivatingPanel],
|
styleMask: [.borderless, .nonactivatingPanel],
|
||||||
backing: .buffered,
|
backing: .buffered,
|
||||||
defer: false
|
defer: false
|
||||||
|
|
@ -67,29 +73,43 @@ class LockScreenLiveActivityWindowManager {
|
||||||
return window
|
return window
|
||||||
}
|
}
|
||||||
|
|
||||||
private func present(state: LockLiveActivityState) {
|
private func lockContext() -> (notchSize: CGSize, screen: NSScreen)? {
|
||||||
|
guard let screen = NSScreen.main else {
|
||||||
|
print("[\(timestamp())] LockScreenLiveActivityWindowManager: no main screen available")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let viewModel else {
|
||||||
|
print("[\(timestamp())] LockScreenLiveActivityWindowManager: no view model configured")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var notchSize = viewModel.closedNotchSize
|
||||||
|
if notchSize.width <= 0 || notchSize.height <= 0 {
|
||||||
|
notchSize = getClosedNotchSize(screen: screen.localizedName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (notchSize, screen)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func present(notchSize: CGSize, on screen: NSScreen) {
|
||||||
guard Defaults[.enableLockScreenLiveActivity] else {
|
guard Defaults[.enableLockScreenLiveActivity] else {
|
||||||
hideImmediately()
|
hideImmediately()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let screen = NSScreen.main else {
|
let windowSize = windowSize(for: notchSize)
|
||||||
print("[\(timestamp())] LockScreenLiveActivityWindowManager: no main screen available")
|
let window = ensureWindow(windowSize: windowSize, screen: screen)
|
||||||
return
|
let targetFrame = frame(for: windowSize, on: screen)
|
||||||
}
|
|
||||||
|
|
||||||
let notchSize = getClosedNotchSize(screen: screen.localizedName)
|
|
||||||
let window = ensureWindow(notchSize: notchSize, screen: screen)
|
|
||||||
let targetFrame = frame(for: notchSize, on: screen)
|
|
||||||
window.setFrame(targetFrame, display: true)
|
window.setFrame(targetFrame, display: true)
|
||||||
|
|
||||||
let overlay = LockScreenLiveActivityOverlay(state: state, notchSize: notchSize, viewModel: overlayModel)
|
let overlayView = LockScreenLiveActivityOverlay(model: overlayModel, notchSize: notchSize)
|
||||||
|
|
||||||
if let hostingView {
|
if let hostingView {
|
||||||
hostingView.rootView = overlay
|
hostingView.rootView = overlayView
|
||||||
hostingView.frame = CGRect(origin: .zero, size: targetFrame.size)
|
hostingView.frame = CGRect(origin: .zero, size: targetFrame.size)
|
||||||
} else {
|
} else {
|
||||||
let view = NSHostingView(rootView: overlay)
|
let view = NSHostingView(rootView: overlayView)
|
||||||
view.frame = CGRect(origin: .zero, size: targetFrame.size)
|
view.frame = CGRect(origin: .zero, size: targetFrame.size)
|
||||||
hostingView = view
|
hostingView = view
|
||||||
window.contentView = view
|
window.contentView = view
|
||||||
|
|
@ -107,40 +127,55 @@ class LockScreenLiveActivityWindowManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
window.orderFrontRegardless()
|
window.orderFrontRegardless()
|
||||||
|
window.alphaValue = 1
|
||||||
if state == .locked {
|
|
||||||
overlayModel.expansion = 0.2
|
|
||||||
withAnimation(.spring(response: 0.55, dampingFraction: 0.82)) {
|
|
||||||
overlayModel.expansion = 1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
overlayModel.expansion = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if window.alphaValue < 1 {
|
|
||||||
NSAnimationContext.runAnimationGroup { context in
|
|
||||||
context.duration = 0.12
|
|
||||||
window.animator().alphaValue = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func showLocked() {
|
func showLocked() {
|
||||||
hideTask?.cancel()
|
hideTask?.cancel()
|
||||||
present(state: .locked)
|
guard let context = lockContext() else { return }
|
||||||
|
|
||||||
|
overlayModel.iconName = "lock.fill"
|
||||||
|
overlayModel.scale = 0.6
|
||||||
|
overlayModel.opacity = 0
|
||||||
|
|
||||||
|
present(notchSize: context.notchSize, on: context.screen)
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
withAnimation(.spring(response: 0.45, dampingFraction: 0.82)) {
|
||||||
|
self.overlayModel.scale = 1
|
||||||
|
}
|
||||||
|
withAnimation(.easeOut(duration: 0.18)) {
|
||||||
|
self.overlayModel.opacity = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
print("[\(timestamp())] LockScreenLiveActivityWindowManager: showing locked state")
|
print("[\(timestamp())] LockScreenLiveActivityWindowManager: showing locked state")
|
||||||
}
|
}
|
||||||
|
|
||||||
func showUnlockAndScheduleHide() {
|
func showUnlockAndScheduleHide() {
|
||||||
hideTask?.cancel()
|
hideTask?.cancel()
|
||||||
present(state: .unlocking)
|
guard let context = lockContext() else { return }
|
||||||
print("[\(timestamp())] LockScreenLiveActivityWindowManager: showing unlock state")
|
|
||||||
|
overlayModel.iconName = "lock.open.fill"
|
||||||
|
overlayModel.scale = 0.7
|
||||||
|
overlayModel.opacity = 0
|
||||||
|
|
||||||
|
present(notchSize: context.notchSize, on: context.screen)
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
withAnimation(.spring(response: 0.45, dampingFraction: 0.85)) {
|
||||||
|
self.overlayModel.scale = 1
|
||||||
|
}
|
||||||
|
withAnimation(.easeOut(duration: 0.16)) {
|
||||||
|
self.overlayModel.opacity = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hideTask = Task { [weak self] in
|
hideTask = Task { [weak self] in
|
||||||
try? await Task.sleep(for: .seconds(max(Defaults[.waitInterval], 1.0)))
|
try? await Task.sleep(for: .seconds(max(Defaults[.waitInterval], 1.0)))
|
||||||
guard let self, !Task.isCancelled else { return }
|
guard let self, !Task.isCancelled else { return }
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
self.hideImmediately()
|
self.hideWithAnimation()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -149,17 +184,30 @@ class LockScreenLiveActivityWindowManager {
|
||||||
hideTask?.cancel()
|
hideTask?.cancel()
|
||||||
hideTask = nil
|
hideTask = nil
|
||||||
|
|
||||||
|
hideWithAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func hideWithAnimation() {
|
||||||
guard let window else { return }
|
guard let window else { return }
|
||||||
|
|
||||||
withAnimation(.easeOut(duration: 0.1)) {
|
withAnimation(.easeInOut(duration: 0.18)) {
|
||||||
overlayModel.expansion = 0.2
|
overlayModel.opacity = 0
|
||||||
|
overlayModel.scale = 0.7
|
||||||
}
|
}
|
||||||
|
|
||||||
NSAnimationContext.runAnimationGroup { context in
|
NSAnimationContext.runAnimationGroup { context in
|
||||||
context.duration = 0.1
|
context.duration = 0.2
|
||||||
window.animator().alphaValue = 0
|
window.animator().alphaValue = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.22) {
|
||||||
|
window.orderOut(nil)
|
||||||
|
}
|
||||||
|
|
||||||
print("[\(timestamp())] LockScreenLiveActivityWindowManager: HUD hidden")
|
print("[\(timestamp())] LockScreenLiveActivityWindowManager: HUD hidden")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func configure(viewModel: DynamicIslandViewModel) {
|
||||||
|
self.viewModel = viewModel
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ class LockScreenManager: ObservableObject {
|
||||||
|
|
||||||
// MARK: - Coordinator
|
// MARK: - Coordinator
|
||||||
private let coordinator = DynamicIslandViewCoordinator.shared
|
private let coordinator = DynamicIslandViewCoordinator.shared
|
||||||
|
private weak var viewModel: DynamicIslandViewModel?
|
||||||
|
|
||||||
// MARK: - Published Properties
|
// MARK: - Published Properties
|
||||||
@Published var isLocked: Bool = false
|
@Published var isLocked: Bool = false
|
||||||
|
|
@ -80,6 +81,17 @@ class LockScreenManager: ObservableObject {
|
||||||
// Set locked state immediately without animation wrapper
|
// Set locked state immediately without animation wrapper
|
||||||
isLocked = true
|
isLocked = true
|
||||||
|
|
||||||
|
viewModel?.closeForLockScreen()
|
||||||
|
|
||||||
|
if coordinator.expandingView.show {
|
||||||
|
let currentType = coordinator.expandingView.type
|
||||||
|
coordinator.toggleExpandingView(status: false, type: currentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if coordinator.sneakPeek.show {
|
||||||
|
coordinator.toggleSneakPeek(status: false, type: coordinator.sneakPeek.type)
|
||||||
|
}
|
||||||
|
|
||||||
// Show panel FIRST (creates and shows window on lock screen)
|
// Show panel FIRST (creates and shows window on lock screen)
|
||||||
print("[\(timestamp())] LockScreenManager: 🎵 Showing lock screen panel")
|
print("[\(timestamp())] LockScreenManager: 🎵 Showing lock screen panel")
|
||||||
LockScreenPanelManager.shared.showPanel()
|
LockScreenPanelManager.shared.showPanel()
|
||||||
|
|
@ -143,6 +155,10 @@ class LockScreenManager: ObservableObject {
|
||||||
// MARK: - Extensions
|
// MARK: - Extensions
|
||||||
|
|
||||||
extension LockScreenManager {
|
extension LockScreenManager {
|
||||||
|
func configure(viewModel: DynamicIslandViewModel) {
|
||||||
|
self.viewModel = viewModel
|
||||||
|
}
|
||||||
|
|
||||||
/// Get current lock status without async
|
/// Get current lock status without async
|
||||||
var currentLockStatus: Bool {
|
var currentLockStatus: Bool {
|
||||||
return isLocked
|
return isLocked
|
||||||
|
|
|
||||||
|
|
@ -41,11 +41,11 @@ class LockScreenPanelManager {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let panelWidth: CGFloat = 420
|
let panelWidth: CGFloat = 460
|
||||||
let panelHeight: CGFloat = 150
|
let panelHeight: CGFloat = 180
|
||||||
let screenFrame = screen.frame
|
let screenFrame = screen.frame
|
||||||
let originX = screenFrame.origin.x + (screenFrame.width / 2) - (panelWidth / 2)
|
let originX = screenFrame.origin.x + (screenFrame.width / 2) - (panelWidth / 2)
|
||||||
let originY = screenFrame.origin.y + (screenFrame.height / 2) - panelHeight - 40
|
let originY = screenFrame.origin.y + (screenFrame.height / 2) - panelHeight - 32
|
||||||
let targetFrame = NSRect(x: originX, y: originY, width: panelWidth, height: panelHeight)
|
let targetFrame = NSRect(x: originX, y: originY, width: panelWidth, height: panelHeight)
|
||||||
|
|
||||||
let window: NSWindow
|
let window: NSWindow
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,15 @@ class DynamicIslandViewModel: NSObject, ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func closeForLockScreen() {
|
||||||
|
let targetSize = getClosedNotchSize(screen: screen)
|
||||||
|
withAnimation(.none) {
|
||||||
|
notchSize = targetSize
|
||||||
|
closedNotchSize = targetSize
|
||||||
|
notchState = .closed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func closeHello() {
|
func closeHello() {
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3.5) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 3.5) { [weak self] in
|
||||||
self?.coordinator.firstLaunch = false
|
self?.coordinator.firstLaunch = false
|
||||||
|
|
|
||||||
1
DynamicIsland/utils/Environment+LockScreenReplica.swift
Normal file
1
DynamicIsland/utils/Environment+LockScreenReplica.swift
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
// Deprecated: lockScreenReplica environment key now lives in ContentView.swift.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue