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 {
|
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 {
|
guard coordinator.currentView == .stats else {
|
||||||
|
|
@ -905,7 +907,7 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleUpGesture(translation: CGFloat, phase: NSEvent.Phase) {
|
private func handleUpGesture(translation: CGFloat, phase: NSEvent.Phase) {
|
||||||
if vm.notchState == .open && !vm.isHoveringCalendar {
|
if vm.notchState == .open && !vm.isHoveringCalendar && !vm.isScrollGestureActive {
|
||||||
withAnimation(.smooth) {
|
withAnimation(.smooth) {
|
||||||
gestureProgress = (translation / Defaults[.gestureSensitivity]) * -20
|
gestureProgress = (translation / Defaults[.gestureSensitivity]) * -20
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -284,7 +284,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
if coordinator.currentView == .timer {
|
if coordinator.currentView == .timer {
|
||||||
baseSize.height = 250 // Extra space for timer presets
|
baseSize.height = 250 // Extra space for timer presets
|
||||||
} else if coordinator.currentView == .notes || coordinator.currentView == .clipboard {
|
} 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(
|
let adjustedContentSize = statsAdjustedNotchSize(
|
||||||
|
|
@ -378,6 +379,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
coordinator.$currentView.sink { [weak self] newView in
|
coordinator.$currentView.sink { [weak self] newView in
|
||||||
self?.updateWindowSizeIfNeeded()
|
self?.updateWindowSizeIfNeeded()
|
||||||
}.store(in: &cancellables)
|
}.store(in: &cancellables)
|
||||||
|
|
||||||
|
coordinator.$notesLayoutState
|
||||||
|
.removeDuplicates()
|
||||||
|
.sink { [weak self] _ in
|
||||||
|
self?.updateWindowSizeIfNeeded()
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
// Observe stats settings changes - use debounced updates
|
// Observe stats settings changes - use debounced updates
|
||||||
Defaults.publisher(.enableStatsFeature, options: []).sink { [weak self] _ in
|
Defaults.publisher(.enableStatsFeature, options: []).sink { [weak self] _ in
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ class DynamicIslandViewCoordinator: ObservableObject {
|
||||||
private var statsSecondRowWorkItem: DispatchWorkItem?
|
private var statsSecondRowWorkItem: DispatchWorkItem?
|
||||||
private let statsSecondRowRevealDelay: TimeInterval = 0.5
|
private let statsSecondRowRevealDelay: TimeInterval = 0.5
|
||||||
private let statsSecondRowAnimationDuration: TimeInterval = 0.3
|
private let statsSecondRowAnimationDuration: TimeInterval = 0.3
|
||||||
|
@Published var notesLayoutState: NotesLayoutState = .list
|
||||||
|
|
||||||
|
|
||||||
@AppStorage("firstLaunch") var firstLaunch: Bool = true
|
@AppStorage("firstLaunch") var firstLaunch: Bool = true
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ import SwiftUI
|
||||||
import Defaults
|
import Defaults
|
||||||
|
|
||||||
struct NotchNotesView: View {
|
struct NotchNotesView: View {
|
||||||
|
@EnvironmentObject var vm: DynamicIslandViewModel
|
||||||
|
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
|
||||||
@FocusState private var isFocused: Bool
|
@FocusState private var isFocused: Bool
|
||||||
@Default(.savedNotes) var savedNotes
|
@Default(.savedNotes) var savedNotes
|
||||||
@Default(.clipboardDisplayMode) var clipboardDisplayMode
|
@Default(.clipboardDisplayMode) var clipboardDisplayMode
|
||||||
|
|
@ -85,6 +87,27 @@ struct NotchNotesView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxHeight: .infinity)
|
.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
|
// MARK: - Actions
|
||||||
|
|
@ -300,15 +323,34 @@ struct NotchNotesView: View {
|
||||||
editorTitle = ""
|
editorTitle = ""
|
||||||
editorContent = ""
|
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
|
// MARK: - Subviews
|
||||||
|
|
||||||
struct NotchClipboardList: View {
|
struct NotchClipboardList: View {
|
||||||
|
@EnvironmentObject var vm: DynamicIslandViewModel
|
||||||
@ObservedObject var clipboardManager = ClipboardManager.shared
|
@ObservedObject var clipboardManager = ClipboardManager.shared
|
||||||
@State private var hoveredItemId: UUID?
|
@State private var hoveredItemId: UUID?
|
||||||
@State private var justCopiedId: UUID?
|
@State private var justCopiedId: UUID?
|
||||||
|
@State private var suppressionToken = UUID()
|
||||||
|
@State private var isSuppressing = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
|
|
@ -384,6 +426,18 @@ struct NotchClipboardList: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxHeight: .infinity)
|
.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 {
|
struct NoteListView: View {
|
||||||
|
@EnvironmentObject var vm: DynamicIslandViewModel
|
||||||
let notes: [NoteItem]
|
let notes: [NoteItem]
|
||||||
let onSelect: (NoteItem) -> Void
|
let onSelect: (NoteItem) -> Void
|
||||||
let onCreate: () -> Void
|
let onCreate: () -> Void
|
||||||
|
|
@ -456,6 +511,8 @@ struct NoteListView: View {
|
||||||
@State private var searchText = ""
|
@State private var searchText = ""
|
||||||
@State private var selectedColorFilter: Int? = nil
|
@State private var selectedColorFilter: Int? = nil
|
||||||
@State private var isSearchExpanded = false
|
@State private var isSearchExpanded = false
|
||||||
|
@State private var suppressionToken = UUID()
|
||||||
|
@State private var isSuppressing = false
|
||||||
|
|
||||||
var sortedNotes: [NoteItem] {
|
var sortedNotes: [NoteItem] {
|
||||||
var filtered = searchText.isEmpty ? notes : notes.filter {
|
var filtered = searchText.isEmpty ? notes : notes.filter {
|
||||||
|
|
@ -647,6 +704,18 @@ struct NoteListView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxHeight: .infinity)
|
.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 Foundation
|
||||||
import Defaults
|
import Defaults
|
||||||
|
import CoreGraphics
|
||||||
|
|
||||||
public enum Style {
|
public enum Style {
|
||||||
case notch
|
case notch
|
||||||
|
|
@ -34,6 +35,23 @@ public enum NotchViews {
|
||||||
case clipboard
|
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 {
|
enum SettingsEnum {
|
||||||
case general
|
case general
|
||||||
case about
|
case about
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,26 @@ class DynamicIslandViewModel: NSObject, ObservableObject {
|
||||||
@Published var isMediaOutputPopoverActive: Bool = false
|
@Published var isMediaOutputPopoverActive: Bool = false
|
||||||
@Published var isTimerPopoverActive: Bool = false
|
@Published var isTimerPopoverActive: Bool = false
|
||||||
@Published var shouldRecheckHover: 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
|
let webcamManager = WebcamManager.shared
|
||||||
@Published var isCameraExpanded: Bool = false
|
@Published var isCameraExpanded: Bool = false
|
||||||
|
|
@ -150,6 +170,27 @@ class DynamicIslandViewModel: NSObject, ObservableObject {
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.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: [])
|
Defaults.publisher(.openNotchWidth, options: [])
|
||||||
.map { $0.newValue }
|
.map { $0.newValue }
|
||||||
.removeDuplicates()
|
.removeDuplicates()
|
||||||
|
|
@ -267,15 +308,18 @@ class DynamicIslandViewModel: NSObject, ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func calculateDynamicNotchSize() -> CGSize {
|
private func calculateDynamicNotchSize() -> CGSize {
|
||||||
// Use minimalistic size if minimalistic UI is enabled
|
|
||||||
let baseSize = Defaults[.enableMinimalisticUI] ? minimalisticOpenNotchSize : openNotchSize
|
let baseSize = Defaults[.enableMinimalisticUI] ? minimalisticOpenNotchSize : openNotchSize
|
||||||
if DynamicIslandViewCoordinator.shared.currentView == .notes {
|
var adjustedSize = baseSize
|
||||||
return CGSize(width: baseSize.width, height: 360)
|
|
||||||
|
if coordinator.currentView == .notes || coordinator.currentView == .clipboard {
|
||||||
|
let preferred = coordinator.notesLayoutState.preferredHeight
|
||||||
|
adjustedSize.height = max(adjustedSize.height, preferred)
|
||||||
|
return adjustedSize
|
||||||
}
|
}
|
||||||
|
|
||||||
return statsAdjustedNotchSize(
|
return statsAdjustedNotchSize(
|
||||||
from: baseSize,
|
from: adjustedSize,
|
||||||
isStatsTabActive: DynamicIslandViewCoordinator.shared.currentView == .stats,
|
isStatsTabActive: coordinator.currentView == .stats,
|
||||||
secondRowProgress: coordinator.statsSecondRowExpansion
|
secondRowProgress: coordinator.statsSecondRowExpansion
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -285,6 +329,7 @@ class DynamicIslandViewModel: NSObject, ObservableObject {
|
||||||
notchSize = targetSize
|
notchSize = targetSize
|
||||||
closedNotchSize = targetSize
|
closedNotchSize = targetSize
|
||||||
notchState = .closed
|
notchState = .closed
|
||||||
|
resetScrollGestureSuppression()
|
||||||
|
|
||||||
// Set the current view to shelf if it contains files and the user enables openShelfByDefault
|
// 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
|
// Otherwise, if the user has not enabled openLastShelfByDefault, set the view to home
|
||||||
|
|
@ -301,6 +346,7 @@ class DynamicIslandViewModel: NSObject, ObservableObject {
|
||||||
notchSize = targetSize
|
notchSize = targetSize
|
||||||
closedNotchSize = targetSize
|
closedNotchSize = targetSize
|
||||||
notchState = .closed
|
notchState = .closed
|
||||||
|
resetScrollGestureSuppression()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue