mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Things work - but animation is still janky
This commit is contained in:
parent
29b9f7b460
commit
ba27aa3d0f
4 changed files with 114 additions and 74 deletions
|
|
@ -87,6 +87,12 @@ struct ContentView: View {
|
|||
@Default(.showNotHumanFace) var showNotHumanFace
|
||||
@Default(.useModernCloseAnimation) var useModernCloseAnimation
|
||||
@Default(.enableMinimalisticUI) var enableMinimalisticUI
|
||||
private var dynamicNotchResizeAnimation: Animation? {
|
||||
if enableMinimalisticUI && reminderManager.activeWindowReminders.isEmpty == false {
|
||||
return nil
|
||||
}
|
||||
return .easeInOut(duration: 0.4)
|
||||
}
|
||||
|
||||
private let extendedHoverPadding: CGFloat = 30
|
||||
private let zeroHeightHoverPadding: CGFloat = 10
|
||||
|
|
@ -344,8 +350,8 @@ struct ContentView: View {
|
|||
// .keyboardShortcut("E", modifiers: .command)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: dynamicNotchSize.width, maxHeight: dynamicNotchSize.height, alignment: .top)
|
||||
.animation(.easeInOut(duration: 0.4), value: dynamicNotchSize)
|
||||
.frame(maxWidth: dynamicNotchSize.width, maxHeight: dynamicNotchSize.height, alignment: .top)
|
||||
.animation(dynamicNotchResizeAnimation, value: dynamicNotchSize)
|
||||
.animation(.easeInOut(duration: 0.4), value: coordinator.currentView)
|
||||
.environmentObject(privacyManager)
|
||||
.onChange(of: dynamicNotchSize) { oldSize, newSize in
|
||||
|
|
@ -357,9 +363,11 @@ struct ContentView: View {
|
|||
isViewTransitioning = true
|
||||
let workItem = DispatchWorkItem {
|
||||
isViewTransitioning = false
|
||||
vm.shouldRecheckHover.toggle()
|
||||
}
|
||||
sizeChangeWorkItem = workItem
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: workItem) // Frame animation + buffer
|
||||
let delay: TimeInterval = dynamicNotchResizeAnimation == nil ? 0.25 : 1.0
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: workItem)
|
||||
}
|
||||
}
|
||||
.shadow(color: ((vm.notchState == .open || isHovering) && Defaults[.enableShadow]) ? .black.opacity(0.6) : .clear, radius: Defaults[.cornerRadiusScaling] ? 10 : 5)
|
||||
|
|
@ -736,11 +744,15 @@ struct ContentView: View {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
if vm.notchState == .open && isViewTransitioning {
|
||||
return
|
||||
}
|
||||
hoverTask = Task {
|
||||
try? await Task.sleep(for: .milliseconds(100))
|
||||
guard !Task.isCancelled else { return }
|
||||
|
||||
await MainActor.run {
|
||||
guard !self.isViewTransitioning else { return }
|
||||
withAnimation(.bouncy.speed(1.2)) {
|
||||
self.isHovering = false
|
||||
}
|
||||
|
|
@ -800,7 +812,7 @@ struct ContentView: View {
|
|||
let stillNotHovering = !isHovering
|
||||
let stillOpen = vm.notchState == .open
|
||||
let stillNoPopovers = !hasAnyActivePopovers()
|
||||
let notTransitioning = !isStatsTransitioning && !isSwitchingToStats
|
||||
let notTransitioning = !isStatsTransitioning && !isSwitchingToStats && !isViewTransitioning
|
||||
|
||||
if stillNotHovering && stillOpen && stillNoPopovers && notTransitioning {
|
||||
vm.close()
|
||||
|
|
|
|||
|
|
@ -79,6 +79,12 @@ struct DynamicNotchApp: App {
|
|||
}
|
||||
}
|
||||
|
||||
extension AppDelegate {
|
||||
static var shared: AppDelegate? {
|
||||
NSApplication.shared.delegate as? AppDelegate
|
||||
}
|
||||
}
|
||||
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
var statusItem: NSStatusItem?
|
||||
var windows: [NSScreen: NSWindow] = [:]
|
||||
|
|
@ -249,69 +255,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
private func updateWindowSizeIfNeeded() {
|
||||
// Calculate required size based on current state
|
||||
let requiredSize = calculateRequiredNotchSize()
|
||||
|
||||
if Defaults[.showOnAllDisplays] {
|
||||
// Update all windows if size has changed (multi-display mode)
|
||||
for (screen, window) in windows {
|
||||
if window.frame.size != requiredSize {
|
||||
// Calculate center position BEFORE any changes
|
||||
let screenFrame = screen.frame
|
||||
let centerX = screenFrame.origin.x + (screenFrame.width / 2)
|
||||
let newX = centerX - (requiredSize.width / 2)
|
||||
let newY = screenFrame.origin.y + screenFrame.height - requiredSize.height
|
||||
|
||||
// Stop any existing animations first
|
||||
NSAnimationContext.runAnimationGroup { _ in
|
||||
window.animator().setFrame(window.frame, display: false)
|
||||
}
|
||||
|
||||
// Animate window resize smoothly
|
||||
NSAnimationContext.runAnimationGroup { context in
|
||||
context.duration = 0.25
|
||||
context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
||||
context.allowsImplicitAnimation = true
|
||||
window.animator().setFrame(NSRect(
|
||||
x: newX,
|
||||
y: newY,
|
||||
width: requiredSize.width,
|
||||
height: requiredSize.height
|
||||
), display: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Update single window if size has changed (single display mode)
|
||||
if let window = window, window.frame.size != requiredSize {
|
||||
// Find the screen this window is on
|
||||
let currentScreen = NSScreen.screens.first { screen in
|
||||
screen.frame.intersects(window.frame)
|
||||
} ?? NSScreen.main ?? NSScreen.screens.first!
|
||||
|
||||
// Calculate center position BEFORE any changes
|
||||
let screenFrame = currentScreen.frame
|
||||
let centerX = screenFrame.origin.x + (screenFrame.width / 2)
|
||||
let newX = centerX - (requiredSize.width / 2)
|
||||
let newY = screenFrame.origin.y + screenFrame.height - requiredSize.height
|
||||
|
||||
// Stop any existing animations first
|
||||
NSAnimationContext.runAnimationGroup { _ in
|
||||
window.animator().setFrame(window.frame, display: false)
|
||||
}
|
||||
|
||||
// Animate window resize smoothly
|
||||
NSAnimationContext.runAnimationGroup { context in
|
||||
context.duration = 0.25
|
||||
context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
||||
context.allowsImplicitAnimation = true
|
||||
window.animator().setFrame(NSRect(
|
||||
x: newX,
|
||||
y: newY,
|
||||
width: requiredSize.width,
|
||||
height: requiredSize.height
|
||||
), display: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
let animateResize = shouldAnimateResize(for: requiredSize)
|
||||
resizeWindows(to: requiredSize, animated: animateResize, force: false)
|
||||
}
|
||||
|
||||
private func calculateRequiredNotchSize() -> CGSize {
|
||||
|
|
@ -364,6 +309,54 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
// Width stays constant - no horizontal expansion
|
||||
return CGSize(width: baseSize.width, height: requiredHeight)
|
||||
}
|
||||
|
||||
func ensureWindowSize(_ size: CGSize, animated: Bool, force: Bool = false) {
|
||||
resizeWindows(to: size, animated: animated, force: force)
|
||||
}
|
||||
|
||||
private func resizeWindows(to size: CGSize, animated: Bool, force: Bool) {
|
||||
guard size.width > 0, size.height > 0 else { return }
|
||||
|
||||
if Defaults[.showOnAllDisplays] {
|
||||
for (screen, window) in windows {
|
||||
if force || window.frame.size != size {
|
||||
resizeWindow(window, on: screen, to: size, animated: animated)
|
||||
}
|
||||
}
|
||||
} else if let window {
|
||||
let screen = window.screen ?? NSScreen.screens.first { $0.frame.intersects(window.frame) } ?? NSScreen.main ?? NSScreen.screens.first
|
||||
guard let screen else { return }
|
||||
if force || window.frame.size != size {
|
||||
resizeWindow(window, on: screen, to: size, animated: animated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func resizeWindow(_ window: NSWindow, on screen: NSScreen, to size: CGSize, animated: Bool) {
|
||||
let screenFrame = screen.frame
|
||||
let centerX = screenFrame.midX
|
||||
let newX = centerX - (size.width / 2)
|
||||
let newY = screenFrame.origin.y + screenFrame.height - size.height
|
||||
let targetFrame = NSRect(x: newX, y: newY, width: size.width, height: size.height)
|
||||
|
||||
if animated {
|
||||
NSAnimationContext.runAnimationGroup { context in
|
||||
context.duration = 0.25
|
||||
context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
||||
context.allowsImplicitAnimation = true
|
||||
window.animator().setFrame(targetFrame, display: true)
|
||||
}
|
||||
} else {
|
||||
window.setFrame(targetFrame, display: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func shouldAnimateResize(for newSize: CGSize) -> Bool {
|
||||
if Defaults[.enableMinimalisticUI] && !ReminderLiveActivityManager.shared.activeWindowReminders.isEmpty {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
|
||||
|
|
|
|||
|
|
@ -65,10 +65,7 @@ struct MinimalisticMusicPlayerView: View {
|
|||
playbackControls
|
||||
.padding(.top, 4)
|
||||
|
||||
if shouldShowReminderList {
|
||||
MinimalisticReminderEventListView(reminders: reminderEntries, currentDate: reminderManager.currentDate)
|
||||
.transition(.opacity.combined(with: .move(edge: .bottom)))
|
||||
}
|
||||
reminderList
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.top, -15)
|
||||
|
|
@ -83,6 +80,18 @@ struct MinimalisticMusicPlayerView: View {
|
|||
private var shouldShowReminderList: Bool {
|
||||
enableReminderLiveActivity && !reminderEntries.isEmpty
|
||||
}
|
||||
|
||||
private var reminderListHeight: CGFloat {
|
||||
ReminderLiveActivityManager.additionalHeight(forRowCount: reminderEntries.count)
|
||||
}
|
||||
|
||||
private var reminderList: some View {
|
||||
MinimalisticReminderEventListView(reminders: reminderEntries, currentDate: reminderManager.currentDate)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.frame(height: reminderListHeight, alignment: .top)
|
||||
.opacity(shouldShowReminderList ? 1 : 0)
|
||||
.animation(.easeInOut(duration: 0.18), value: shouldShowReminderList)
|
||||
}
|
||||
|
||||
|
||||
private struct MinimalisticReminderEventListView: View {
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ class DynamicIslandViewModel: NSObject, ObservableObject {
|
|||
|
||||
@Published var notchSize: CGSize = getClosedNotchSize()
|
||||
@Published var closedNotchSize: CGSize = getClosedNotchSize()
|
||||
private var minimalisticReminderRowCount: Int = 0
|
||||
|
||||
deinit {
|
||||
destroy()
|
||||
|
|
@ -71,6 +72,19 @@ class DynamicIslandViewModel: NSObject, ObservableObject {
|
|||
.store(in: &cancellables)
|
||||
|
||||
setupDetectorObserver()
|
||||
|
||||
ReminderLiveActivityManager.shared.$activeWindowReminders
|
||||
.removeDuplicates()
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak self] entries in
|
||||
guard let self else { return }
|
||||
self.minimalisticReminderRowCount = entries.count
|
||||
let updatedTarget = self.calculateDynamicNotchSize()
|
||||
guard self.notchState == .open else { return }
|
||||
guard self.notchSize != updatedTarget else { return }
|
||||
self.notchSize = updatedTarget
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
private func setupDetectorObserver() {
|
||||
|
|
@ -124,8 +138,21 @@ class DynamicIslandViewModel: NSObject, ObservableObject {
|
|||
}
|
||||
|
||||
func open() {
|
||||
let targetSize = calculateDynamicNotchSize()
|
||||
|
||||
let applyWindowResize: () -> Void = {
|
||||
guard let delegate = AppDelegate.shared else { return }
|
||||
delegate.ensureWindowSize(targetSize, animated: false, force: true)
|
||||
}
|
||||
|
||||
if Thread.isMainThread {
|
||||
applyWindowResize()
|
||||
} else {
|
||||
DispatchQueue.main.async(execute: applyWindowResize)
|
||||
}
|
||||
|
||||
withAnimation(.bouncy) {
|
||||
self.notchSize = calculateDynamicNotchSize()
|
||||
self.notchSize = targetSize
|
||||
self.notchState = .open
|
||||
}
|
||||
|
||||
|
|
@ -138,8 +165,7 @@ class DynamicIslandViewModel: NSObject, ObservableObject {
|
|||
var baseSize = Defaults[.enableMinimalisticUI] ? minimalisticOpenNotchSize : openNotchSize
|
||||
|
||||
if Defaults[.enableMinimalisticUI] {
|
||||
let reminderCount = ReminderLiveActivityManager.shared.activeWindowReminders.count
|
||||
let extraHeight = ReminderLiveActivityManager.additionalHeight(forRowCount: reminderCount)
|
||||
let extraHeight = ReminderLiveActivityManager.additionalHeight(forRowCount: minimalisticReminderRowCount)
|
||||
baseSize.height += extraHeight
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue