Fix Scroll Effect on the notch notes and clipboard

This commit is contained in:
Hariharan Mudaliar 2025-12-24 23:56:08 +05:30
commit adc28fcf2f
No known key found for this signature in database
GPG key ID: 025583EB0BE5640E
6 changed files with 152 additions and 8 deletions

View file

@ -50,7 +50,9 @@ struct ContentView: View {
}
if coordinator.currentView == .notes || coordinator.currentView == .clipboard {
return CGSize(width: baseSize.width, height: 230) // Consistent height for split views
let preferredHeight = coordinator.notesLayoutState.preferredHeight
let resolvedHeight = max(baseSize.height, preferredHeight)
return CGSize(width: baseSize.width, height: resolvedHeight)
}
guard coordinator.currentView == .stats else {
@ -905,7 +907,7 @@ struct ContentView: View {
}
private func handleUpGesture(translation: CGFloat, phase: NSEvent.Phase) {
if vm.notchState == .open && !vm.isHoveringCalendar {
if vm.notchState == .open && !vm.isHoveringCalendar && !vm.isScrollGestureActive {
withAnimation(.smooth) {
gestureProgress = (translation / Defaults[.gestureSensitivity]) * -20
}

View file

@ -284,7 +284,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
if coordinator.currentView == .timer {
baseSize.height = 250 // Extra space for timer presets
} else if coordinator.currentView == .notes || coordinator.currentView == .clipboard {
baseSize.height = 230
let preferredHeight = coordinator.notesLayoutState.preferredHeight
baseSize.height = max(baseSize.height, preferredHeight)
}
let adjustedContentSize = statsAdjustedNotchSize(
@ -378,6 +379,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
coordinator.$currentView.sink { [weak self] newView in
self?.updateWindowSizeIfNeeded()
}.store(in: &cancellables)
coordinator.$notesLayoutState
.removeDuplicates()
.sink { [weak self] _ in
self?.updateWindowSizeIfNeeded()
}
.store(in: &cancellables)
// Observe stats settings changes - use debounced updates
Defaults.publisher(.enableStatsFeature, options: []).sink { [weak self] _ in

View file

@ -63,6 +63,7 @@ class DynamicIslandViewCoordinator: ObservableObject {
private var statsSecondRowWorkItem: DispatchWorkItem?
private let statsSecondRowRevealDelay: TimeInterval = 0.5
private let statsSecondRowAnimationDuration: TimeInterval = 0.3
@Published var notesLayoutState: NotesLayoutState = .list
@AppStorage("firstLaunch") var firstLaunch: Bool = true

View file

@ -9,6 +9,8 @@ import SwiftUI
import Defaults
struct NotchNotesView: View {
@EnvironmentObject var vm: DynamicIslandViewModel
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
@FocusState private var isFocused: Bool
@Default(.savedNotes) var savedNotes
@Default(.clipboardDisplayMode) var clipboardDisplayMode
@ -85,6 +87,27 @@ struct NotchNotesView: View {
}
}
.frame(maxHeight: .infinity)
.onAppear {
updateLayoutState()
}
.onDisappear {
coordinator.notesLayoutState = .list
}
.onChange(of: isEditingNewNote) { _, _ in
updateLayoutState()
}
.onChange(of: selectedNoteId) { _, _ in
updateLayoutState()
}
.onChange(of: enableClipboardManager) { _, _ in
updateLayoutState()
}
.onChange(of: clipboardDisplayMode) { _, _ in
updateLayoutState()
}
.onChange(of: enableNotes) { _, _ in
updateLayoutState()
}
}
// MARK: - Actions
@ -300,15 +323,34 @@ struct NotchNotesView: View {
editorTitle = ""
editorContent = ""
}
updateLayoutState()
}
private func updateLayoutState() {
let newState: NotesLayoutState
if enableNotes && (isEditingNewNote || selectedNoteId != nil) {
newState = .editor
} else if showSplitView {
newState = .split
} else {
newState = .list
}
if coordinator.notesLayoutState != newState {
coordinator.notesLayoutState = newState
}
}
}
// MARK: - Subviews
struct NotchClipboardList: View {
@EnvironmentObject var vm: DynamicIslandViewModel
@ObservedObject var clipboardManager = ClipboardManager.shared
@State private var hoveredItemId: UUID?
@State private var justCopiedId: UUID?
@State private var suppressionToken = UUID()
@State private var isSuppressing = false
var body: some View {
VStack(spacing: 0) {
@ -384,6 +426,18 @@ struct NotchClipboardList: View {
}
}
.frame(maxHeight: .infinity)
.onHover { hovering in
updateSuppression(for: hovering)
}
.onDisappear {
updateSuppression(for: false)
}
}
private func updateSuppression(for hovering: Bool) {
guard hovering != isSuppressing else { return }
isSuppressing = hovering
vm.setScrollGestureSuppression(hovering, token: suppressionToken)
}
}
@ -440,6 +494,7 @@ struct NotchClipboardItemRow: View {
struct NoteListView: View {
@EnvironmentObject var vm: DynamicIslandViewModel
let notes: [NoteItem]
let onSelect: (NoteItem) -> Void
let onCreate: () -> Void
@ -456,6 +511,8 @@ struct NoteListView: View {
@State private var searchText = ""
@State private var selectedColorFilter: Int? = nil
@State private var isSearchExpanded = false
@State private var suppressionToken = UUID()
@State private var isSuppressing = false
var sortedNotes: [NoteItem] {
var filtered = searchText.isEmpty ? notes : notes.filter {
@ -647,6 +704,18 @@ struct NoteListView: View {
}
}
.frame(maxHeight: .infinity)
.onHover { hovering in
updateSuppression(for: hovering)
}
.onDisappear {
updateSuppression(for: false)
}
}
private func updateSuppression(for hovering: Bool) {
guard hovering != isSuppressing else { return }
isSuppressing = hovering
vm.setScrollGestureSuppression(hovering, token: suppressionToken)
}
}

View file

@ -7,6 +7,7 @@
import Foundation
import Defaults
import CoreGraphics
public enum Style {
case notch
@ -34,6 +35,23 @@ public enum NotchViews {
case clipboard
}
enum NotesLayoutState: Equatable {
case list
case split
case editor
var preferredHeight: CGFloat {
switch self {
case .list:
return 240
case .split:
return 260
case .editor:
return 320
}
}
}
enum SettingsEnum {
case general
case about

View file

@ -36,6 +36,26 @@ class DynamicIslandViewModel: NSObject, ObservableObject {
@Published var isMediaOutputPopoverActive: Bool = false
@Published var isTimerPopoverActive: Bool = false
@Published var shouldRecheckHover: Bool = false
@Published var isScrollGestureActive: Bool = false
private var scrollGestureSuppressionTokens: Set<UUID> = []
func setScrollGestureSuppression(_ active: Bool, token: UUID) {
if active {
let inserted = scrollGestureSuppressionTokens.insert(token).inserted
if inserted {
isScrollGestureActive = true
}
} else {
if scrollGestureSuppressionTokens.remove(token) != nil {
isScrollGestureActive = !scrollGestureSuppressionTokens.isEmpty
}
}
}
private func resetScrollGestureSuppression() {
scrollGestureSuppressionTokens.removeAll()
isScrollGestureActive = false
}
let webcamManager = WebcamManager.shared
@Published var isCameraExpanded: Bool = false
@ -150,6 +170,27 @@ class DynamicIslandViewModel: NSObject, ObservableObject {
}
.store(in: &cancellables)
coordinator.$notesLayoutState
.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.25)) {
self.notchSize = updatedTarget
}
if let delegate = AppDelegate.shared {
delegate.ensureWindowSize(
addShadowPadding(to: updatedTarget, isMinimalistic: Defaults[.enableMinimalisticUI]),
animated: true,
force: false
)
}
}
.store(in: &cancellables)
Defaults.publisher(.openNotchWidth, options: [])
.map { $0.newValue }
.removeDuplicates()
@ -267,15 +308,18 @@ class DynamicIslandViewModel: NSObject, ObservableObject {
}
private func calculateDynamicNotchSize() -> CGSize {
// Use minimalistic size if minimalistic UI is enabled
let baseSize = Defaults[.enableMinimalisticUI] ? minimalisticOpenNotchSize : openNotchSize
if DynamicIslandViewCoordinator.shared.currentView == .notes {
return CGSize(width: baseSize.width, height: 360)
var adjustedSize = baseSize
if coordinator.currentView == .notes || coordinator.currentView == .clipboard {
let preferred = coordinator.notesLayoutState.preferredHeight
adjustedSize.height = max(adjustedSize.height, preferred)
return adjustedSize
}
return statsAdjustedNotchSize(
from: baseSize,
isStatsTabActive: DynamicIslandViewCoordinator.shared.currentView == .stats,
from: adjustedSize,
isStatsTabActive: coordinator.currentView == .stats,
secondRowProgress: coordinator.statsSecondRowExpansion
)
}
@ -285,6 +329,7 @@ class DynamicIslandViewModel: NSObject, ObservableObject {
notchSize = targetSize
closedNotchSize = targetSize
notchState = .closed
resetScrollGestureSuppression()
// Set the current view to shelf if it contains files and the user enables openShelfByDefault
// Otherwise, if the user has not enabled openLastShelfByDefault, set the view to home
@ -301,6 +346,7 @@ class DynamicIslandViewModel: NSObject, ObservableObject {
notchSize = targetSize
closedNotchSize = targetSize
notchState = .closed
resetScrollGestureSuppression()
}
}