mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Temporary fix for glitch, TODO: remove push up animation
This commit is contained in:
parent
3eb9122b9f
commit
80d132a85f
5 changed files with 177 additions and 64 deletions
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue