mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Fix Scroll Effect on the notch notes and clipboard
This commit is contained in:
parent
722363cdca
commit
adc28fcf2f
6 changed files with 152 additions and 8 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue