Temporary fix for glitch, TODO: remove push up animation

This commit is contained in:
Hariharan Mudaliar 2025-11-26 22:40:23 +05:30
commit 80d132a85f
5 changed files with 177 additions and 64 deletions

View file

@ -276,33 +276,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
return CGSize(width: inlineSneakPeekWidth, height: vm.effectiveClosedNotchHeight) return CGSize(width: inlineSneakPeekWidth, height: vm.effectiveClosedNotchHeight)
} }
// Use minimalistic or normal size based on settings // Use minimalistic or normal size based on settings, then adjust for stats layout
var baseSize = Defaults[.enableMinimalisticUI] ? minimalisticOpenNotchSize : openNotchSize let baseSize = Defaults[.enableMinimalisticUI] ? minimalisticOpenNotchSize : openNotchSize
return statsAdjustedNotchSize(
// Only apply dynamic sizing when on stats tab and stats are enabled from: baseSize,
guard coordinator.currentView == .stats && Defaults[.enableStatsFeature] else { isStatsTabActive: coordinator.currentView == .stats,
return baseSize secondRowProgress: coordinator.statsSecondRowExpansion
} )
let enabledGraphsCount = [
Defaults[.showCpuGraph],
Defaults[.showMemoryGraph],
Defaults[.showGpuGraph],
Defaults[.showNetworkGraph],
Defaults[.showDiskGraph]
].filter { $0 }.count
// Calculate height based on layout: 1-3 graphs = single row, 4+ graphs = two rows
var requiredHeight = baseSize.height
if enabledGraphsCount >= 4 {
// Two rows needed - add height for second row plus spacing
let extraHeight: CGFloat = 120 + 12 // Graph height + spacing
requiredHeight = baseSize.height + extraHeight
}
// Width stays constant - no horizontal expansion
return CGSize(width: baseSize.width, height: requiredHeight)
} }
func ensureWindowSize(_ size: CGSize, animated: Bool, force: Bool = false) { func ensureWindowSize(_ size: CGSize, animated: Bool, force: Bool = false) {

View file

@ -48,7 +48,16 @@ struct ExpandedItem {
class DynamicIslandViewCoordinator: ObservableObject { class DynamicIslandViewCoordinator: ObservableObject {
static let shared = DynamicIslandViewCoordinator() static let shared = DynamicIslandViewCoordinator()
@Published var currentView: NotchViews = .home @Published var currentView: NotchViews = .home {
didSet {
handleStatsTabTransition(from: oldValue, to: currentView)
}
}
@Published var statsSecondRowExpansion: CGFloat = 1
private var statsSecondRowWorkItem: DispatchWorkItem?
private let statsSecondRowRevealDelay: TimeInterval = 0.5
private let statsSecondRowAnimationDuration: TimeInterval = 0.3
@AppStorage("firstLaunch") var firstLaunch: Bool = true @AppStorage("firstLaunch") var firstLaunch: Bool = true
@ -91,6 +100,27 @@ class DynamicIslandViewCoordinator: ObservableObject {
private init() { private init() {
selectedScreen = preferredScreen selectedScreen = preferredScreen
} }
private func handleStatsTabTransition(from oldValue: NotchViews, to newValue: NotchViews) {
guard oldValue != newValue else { return }
statsSecondRowWorkItem?.cancel()
if newValue == .stats && Defaults[.enableStatsFeature] {
statsSecondRowExpansion = 0
let workItem = DispatchWorkItem { [weak self] in
guard let self else { return }
withAnimation(.easeInOut(duration: self.statsSecondRowAnimationDuration)) {
self.statsSecondRowExpansion = 1
}
self.statsSecondRowWorkItem = nil
}
statsSecondRowWorkItem = workItem
DispatchQueue.main.asyncAfter(deadline: .now() + statsSecondRowRevealDelay, execute: workItem)
} else {
withAnimation(.easeInOut(duration: 0.2)) {
statsSecondRowExpansion = 0
}
}
}
func toggleSneakPeek(status: Bool, type: SneakContentType, duration: TimeInterval = 1.5, value: CGFloat = 0, icon: String = "") { func toggleSneakPeek(status: Bool, type: SneakContentType, duration: TimeInterval = 1.5, value: CGFloat = 0, icon: String = "") {
let resolvedDuration: TimeInterval let resolvedDuration: TimeInterval

View file

@ -67,6 +67,7 @@ struct NotchStatsView: View {
@State private var statsHoverGraceActive = false @State private var statsHoverGraceActive = false
@State private var statsHoverGraceWorkItem: DispatchWorkItem? @State private var statsHoverGraceWorkItem: DispatchWorkItem?
@EnvironmentObject var vm: DynamicIslandViewModel @EnvironmentObject var vm: DynamicIslandViewModel
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
var availableGraphs: [GraphData] { var availableGraphs: [GraphData] {
var graphs: [GraphData] = [] var graphs: [GraphData] = []
@ -148,18 +149,35 @@ struct NotchStatsView: View {
} }
} }
} else if graphCount == 4 { } else if graphCount == 4 {
// 4 graphs: 2x2 grid // 4 graphs: second row scales from zero height on tab open
LazyVGrid( VStack(spacing: 0) {
columns: Array(repeating: GridItem(.flexible(), spacing: 8), count: 2), LazyVGrid(
spacing: 12 columns: Array(repeating: GridItem(.flexible(), spacing: 8), count: 2),
) { spacing: 12
ForEach(0..<graphCount, id: \.self) { index in ) {
graphViewForIndex(index) ForEach(0..<2, id: \.self) { index in
graphViewForIndex(index)
}
}
StatsCollapsibleRow(
expansion: coordinator.statsSecondRowExpansion,
height: statsSecondRowContentHeight + statsGridSpacingHeight
) {
LazyVGrid(
columns: Array(repeating: GridItem(.flexible(), spacing: 8), count: 2),
spacing: 12
) {
ForEach(2..<graphCount, id: \.self) { index in
graphViewForIndex(index)
}
}
.padding(.top, statsGridSpacingHeight)
} }
} }
} else { } else {
// 5 graphs: First row 3 graphs, second row 2 graphs (half-width each) // 5 graphs: First row 3 graphs, second row 2 graphs (half-width each)
VStack(spacing: 12) { VStack(spacing: 0) {
// First row: 3 graphs // First row: 3 graphs
LazyVGrid( LazyVGrid(
columns: Array(repeating: GridItem(.flexible(), spacing: 8), count: 3), columns: Array(repeating: GridItem(.flexible(), spacing: 8), count: 3),
@ -169,15 +187,20 @@ struct NotchStatsView: View {
graphViewForIndex(index) graphViewForIndex(index)
} }
} }
// Second row: 2 graphs (half-width each) // Second row: 2 graphs (half-width each)
LazyVGrid( StatsCollapsibleRow(
columns: Array(repeating: GridItem(.flexible(), spacing: 8), count: 2), expansion: coordinator.statsSecondRowExpansion,
spacing: 8 height: statsSecondRowContentHeight + statsGridSpacingHeight
) { ) {
ForEach(3..<graphCount, id: \.self) { index in LazyVGrid(
graphViewForIndex(index) columns: Array(repeating: GridItem(.flexible(), spacing: 8), count: 2),
spacing: 8
) {
ForEach(3..<graphCount, id: \.self) { index in
graphViewForIndex(index)
}
} }
.padding(.top, statsGridSpacingHeight)
} }
} }
} }
@ -438,8 +461,53 @@ struct NotchStatsView: View {
.onChange(of: isHoveringDiskPopover) { _, _ in .onChange(of: isHoveringDiskPopover) { _, _ in
updateStatsPopoverState() updateStatsPopoverState()
} }
.onChange(of: coordinator.statsSecondRowExpansion) { _, newValue in
let shouldHold = availableGraphs.count >= 4 && newValue < 0.999
if shouldHold {
isResizingForStats = true
} else {
DispatchQueue.main.async {
isResizingForStats = false
}
}
}
} }
private struct StatsCollapsibleRow<Content: View>: View {
let expansion: CGFloat
let height: CGFloat
let content: () -> Content
private let minVisibleHeight: CGFloat = 0.5
init(expansion: CGFloat, height: CGFloat, @ViewBuilder content: @escaping () -> Content) {
self.expansion = expansion
self.height = height
self.content = content
}
var body: some View {
let clamped = max(0, min(expansion, 1))
let targetHeight = clamped == 0 ? 0 : max(height * clamped, minVisibleHeight)
return Group {
if targetHeight == 0 {
Color.clear.frame(height: 0)
} else {
content()
.frame(height: height, alignment: .top)
.clipped()
.mask(
Rectangle()
.frame(height: targetHeight)
.frame(maxWidth: .infinity, alignment: .top)
)
.frame(height: targetHeight, alignment: .top)
}
}
.animation(.easeInOut(duration: 0.3), value: clamped)
}
}
private func activateStatsHoverGrace(duration: TimeInterval = 0.45) { private func activateStatsHoverGrace(duration: TimeInterval = 0.45) {
statsHoverGraceWorkItem?.cancel() statsHoverGraceWorkItem?.cancel()
statsHoverGraceActive = true statsHoverGraceActive = true

View file

@ -110,6 +110,23 @@ class DynamicIslandViewModel: NSObject, ObservableObject {
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
coordinator.$statsSecondRowExpansion
.removeDuplicates()
.receive(on: RunLoop.main)
.sink { [weak self] _ in
guard let self else { return }
guard self.notchState == .open else { return }
let updatedTarget = self.calculateDynamicNotchSize()
guard self.notchSize != updatedTarget else { return }
withAnimation(.easeInOut(duration: 0.3)) {
self.notchSize = updatedTarget
}
if let delegate = AppDelegate.shared {
delegate.ensureWindowSize(updatedTarget, animated: false, force: false)
}
}
.store(in: &cancellables)
} }
private func setupDetectorObserver() { private func setupDetectorObserver() {
@ -187,28 +204,12 @@ class DynamicIslandViewModel: NSObject, ObservableObject {
private func calculateDynamicNotchSize() -> CGSize { private func calculateDynamicNotchSize() -> CGSize {
// Use minimalistic size if minimalistic UI is enabled // Use minimalistic size if minimalistic UI is enabled
var baseSize = Defaults[.enableMinimalisticUI] ? minimalisticOpenNotchSize : openNotchSize let baseSize = Defaults[.enableMinimalisticUI] ? minimalisticOpenNotchSize : openNotchSize
return statsAdjustedNotchSize(
// Only apply dynamic sizing when on stats tab and stats are enabled from: baseSize,
guard DynamicIslandViewCoordinator.shared.currentView == .stats && Defaults[.enableStatsFeature] else { isStatsTabActive: DynamicIslandViewCoordinator.shared.currentView == .stats,
return baseSize secondRowProgress: coordinator.statsSecondRowExpansion
} )
let enabledGraphsCount = [
Defaults[.showCpuGraph],
Defaults[.showMemoryGraph],
Defaults[.showGpuGraph],
Defaults[.showNetworkGraph],
Defaults[.showDiskGraph]
].filter { $0 }.count
// If 4+ graphs are enabled, increase width
if enabledGraphsCount >= 4 {
let extraWidth: CGFloat = CGFloat(enabledGraphsCount - 3) * 120
return CGSize(width: baseSize.width + extraWidth, height: baseSize.height)
}
return baseSize
} }
func close() { func close() {

View file

@ -17,6 +17,8 @@ let batterySneakSize: CGSize = .init(width: 160, height: 1)
let openNotchSize: CGSize = .init(width: 640, height: 190) let openNotchSize: CGSize = .init(width: 640, height: 190)
private let minimalisticBaseOpenNotchSize: CGSize = .init(width: 420, height: 180) private let minimalisticBaseOpenNotchSize: CGSize = .init(width: 420, height: 180)
private let minimalisticLyricsExtraHeight: CGFloat = 40 private let minimalisticLyricsExtraHeight: CGFloat = 40
let statsSecondRowContentHeight: CGFloat = 120
let statsGridSpacingHeight: CGFloat = 12
@MainActor @MainActor
var minimalisticOpenNotchSize: CGSize { var minimalisticOpenNotchSize: CGSize {
@ -37,6 +39,38 @@ var minimalisticOpenNotchSize: CGSize {
let cornerRadiusInsets: (opened: (top: CGFloat, bottom: CGFloat), closed: (top: CGFloat, bottom: CGFloat)) = (opened: (top: 19, bottom: 24), closed: (top: 6, bottom: 14)) let cornerRadiusInsets: (opened: (top: CGFloat, bottom: CGFloat), closed: (top: CGFloat, bottom: CGFloat)) = (opened: (top: 19, bottom: 24), closed: (top: 6, bottom: 14))
let minimalisticCornerRadiusInsets: (opened: (top: CGFloat, bottom: CGFloat), closed: (top: CGFloat, bottom: CGFloat)) = (opened: (top: 35, bottom: 35), closed: cornerRadiusInsets.closed) let minimalisticCornerRadiusInsets: (opened: (top: CGFloat, bottom: CGFloat), closed: (top: CGFloat, bottom: CGFloat)) = (opened: (top: 35, bottom: 35), closed: cornerRadiusInsets.closed)
func statsAdjustedNotchSize(
from baseSize: CGSize,
isStatsTabActive: Bool,
secondRowProgress: CGFloat
) -> CGSize {
guard isStatsTabActive, Defaults[.enableStatsFeature] else {
return baseSize
}
let enabledGraphsCount = [
Defaults[.showCpuGraph],
Defaults[.showMemoryGraph],
Defaults[.showGpuGraph],
Defaults[.showNetworkGraph],
Defaults[.showDiskGraph]
].filter { $0 }.count
guard enabledGraphsCount >= 4 else {
return baseSize
}
let clampedProgress = max(0, min(secondRowProgress, 1))
guard clampedProgress > 0 else {
return baseSize
}
var adjustedSize = baseSize
let extraHeight = (statsSecondRowContentHeight + statsGridSpacingHeight) * clampedProgress
adjustedSize.height += extraHeight
return adjustedSize
}
enum MusicPlayerImageSizes { enum MusicPlayerImageSizes {
static let cornerRadiusInset: (opened: CGFloat, closed: CGFloat) = (opened: 13.0, closed: 4.0) static let cornerRadiusInset: (opened: CGFloat, closed: CGFloat) = (opened: 13.0, closed: 4.0)
static let size = (opened: CGSize(width: 90, height: 90), closed: CGSize(width: 20, height: 20)) static let size = (opened: CGSize(width: 90, height: 90), closed: CGSize(width: 20, height: 20))