diff --git a/DynamicIsland/ContentView.swift b/DynamicIsland/ContentView.swift index f7e268c..36032d9 100644 --- a/DynamicIsland/ContentView.swift +++ b/DynamicIsland/ContentView.swift @@ -851,7 +851,7 @@ struct ContentView: View { } private func shouldPreventAutoClose() -> Bool { - hasAnyActivePopovers() || SharingStateManager.shared.preventNotchClose + hasAnyActivePopovers() || vm.isAutoCloseSuppressed || SharingStateManager.shared.preventNotchClose } // Helper to prevent rapid haptic feedback diff --git a/DynamicIsland/Localizable.xcstrings b/DynamicIsland/Localizable.xcstrings index efa85a2..4d1b2d4 100644 --- a/DynamicIsland/Localizable.xcstrings +++ b/DynamicIsland/Localizable.xcstrings @@ -5295,6 +5295,10 @@ } } }, + "Clear Clipboard History?" : { + "comment" : "An alert title that asks the user to confirm clearing their clipboard history.", + "isCommentAutoGenerated" : true + }, "Clear Color History" : { "localizations" : { "fr" : { @@ -5331,6 +5335,10 @@ } } }, + "Clear History" : { + "comment" : "The text on a button that clears the clipboard history.", + "isCommentAutoGenerated" : true + }, "Clear Pinned Items" : { "localizations" : { "fr" : { @@ -6716,6 +6724,9 @@ } } } + }, + "Delete All Notes?" : { + }, "Delete Animation" : { "comment" : "The title of an alert that appears when trying to delete a custom animation.", @@ -18822,6 +18833,13 @@ "comment" : "A footer description for the custom timer settings, explaining their purpose.", "isCommentAutoGenerated" : true }, + "This permanently removes every saved note and its attachments." : { + + }, + "This removes every saved clipboard item from the notch tab." : { + "comment" : "A message displayed when they clear their clipboard history.", + "isCommentAutoGenerated" : true + }, "Time Range" : { "extractionState" : "stale", "localizations" : { diff --git a/DynamicIsland/components/Notch/DynamicIslandHeader.swift b/DynamicIsland/components/Notch/DynamicIslandHeader.swift index 0bef420..18027f1 100644 --- a/DynamicIsland/components/Notch/DynamicIslandHeader.swift +++ b/DynamicIsland/components/Notch/DynamicIslandHeader.swift @@ -22,6 +22,8 @@ struct DynamicIslandHeader: View { @State private var showTimerPopover = false @Default(.enableTimerFeature) var enableTimerFeature @Default(.timerDisplayMode) var timerDisplayMode + @Default(.showClipboardIcon) var showClipboardIcon + @Default(.clipboardDisplayMode) var clipboardDisplayMode var body: some View { HStack(spacing: 0) { @@ -68,10 +70,12 @@ struct DynamicIslandHeader: View { .buttonStyle(PlainButtonStyle()) } - if Defaults[.enableClipboardManager] && Defaults[.showClipboardIcon] { + if Defaults[.enableClipboardManager] + && showClipboardIcon + && clipboardDisplayMode != .separateTab { Button(action: { // Switch behavior based on display mode - switch Defaults[.clipboardDisplayMode] { + switch clipboardDisplayMode { case .panel: ClipboardPanelManager.shared.toggleClipboardPanel() case .popover: @@ -237,7 +241,7 @@ struct DynamicIslandHeader: View { .onChange(of: coordinator.shouldToggleClipboardPopover) { _ in // Only toggle if clipboard is enabled if Defaults[.enableClipboardManager] { - switch Defaults[.clipboardDisplayMode] { + switch clipboardDisplayMode { case .panel: ClipboardPanelManager.shared.toggleClipboardPanel() case .popover: @@ -253,7 +257,7 @@ struct DynamicIslandHeader: View { } .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("ToggleClipboardPopover"))) { _ in // Handle keyboard shortcut for popover mode - if Defaults[.enableClipboardManager] && Defaults[.clipboardDisplayMode] == .popover { + if Defaults[.enableClipboardManager] && clipboardDisplayMode == .popover { showClipboardPopover.toggle() } } diff --git a/DynamicIsland/components/Notch/NotchNotesView.swift b/DynamicIsland/components/Notch/NotchNotesView.swift index b823c7a..378216b 100644 --- a/DynamicIsland/components/Notch/NotchNotesView.swift +++ b/DynamicIsland/components/Notch/NotchNotesView.swift @@ -351,6 +351,8 @@ struct NotchClipboardList: View { @State private var justCopiedId: UUID? @State private var suppressionToken = UUID() @State private var isSuppressing = false + @State private var showClearHistoryAlert = false + @State private var autoCloseToken = UUID() var body: some View { VStack(spacing: 0) { @@ -363,10 +365,10 @@ struct NotchClipboardList: View { Spacer() if !clipboardManager.clipboardHistory.isEmpty { - Button(action: { clipboardManager.clearHistory() }) { + Button(action: { showClearHistoryAlert = true }) { Image(systemName: "trash") .font(.system(size: 14)) - .foregroundStyle(.white.opacity(0.6)) + .foregroundStyle(.red) .padding(6) .background(Color.white.opacity(0.1)) .clipShape(Circle()) @@ -432,6 +434,17 @@ struct NotchClipboardList: View { .onDisappear { updateSuppression(for: false) } + .alert("Clear Clipboard History?", isPresented: $showClearHistoryAlert) { + Button("Clear History", role: .destructive) { + clipboardManager.clearHistory() + } + Button("Cancel", role: .cancel) {} + } message: { + Text("This removes every saved clipboard item from the notch tab.") + } + .onChange(of: showClearHistoryAlert) { _, isShowing in + vm.setAutoCloseSuppression(isShowing, token: autoCloseToken) + } } private func updateSuppression(for hovering: Bool) { @@ -513,6 +526,8 @@ struct NoteListView: View { @State private var isSearchExpanded = false @State private var suppressionToken = UUID() @State private var isSuppressing = false + @State private var showClearNotesAlert = false + @State private var autoCloseToken = UUID() var sortedNotes: [NoteItem] { var filtered = searchText.isEmpty ? notes : notes.filter { @@ -575,11 +590,11 @@ struct NoteListView: View { } if !notes.isEmpty { - Button(action: onClearAll) { + Button(action: { showClearNotesAlert = true }) { Image(systemName: "trash") .font(.system(size: 14)) .frame(width: 16, height: 16) - .foregroundStyle(.white.opacity(0.6)) + .foregroundStyle(.red) .padding(5) .background(Color.white.opacity(0.1)) .clipShape(Circle()) @@ -710,6 +725,17 @@ struct NoteListView: View { .onDisappear { updateSuppression(for: false) } + .alert("Delete All Notes?", isPresented: $showClearNotesAlert) { + Button("Delete", role: .destructive) { + onClearAll() + } + Button("Cancel", role: .cancel) {} + } message: { + Text("This permanently removes every saved note and its attachments.") + } + .onChange(of: showClearNotesAlert) { _, isShowing in + vm.setAutoCloseSuppression(isShowing, token: autoCloseToken) + } } private func updateSuppression(for hovering: Bool) { diff --git a/DynamicIsland/components/Settings/SettingsView.swift b/DynamicIsland/components/Settings/SettingsView.swift index b1c24c7..bf9798b 100644 --- a/DynamicIsland/components/Settings/SettingsView.swift +++ b/DynamicIsland/components/Settings/SettingsView.swift @@ -727,6 +727,9 @@ struct SettingsView: View { private func isTabVisible(_ tab: SettingsTab) -> Bool { switch tab { + case .notes: + // Keep notes configuration visible even when other tabs are hidden in minimalistic mode + return true case .timer, .stats, .clipboard, .screenAssistant, .colorPicker, .shelf: return !enableMinimalisticUI default: diff --git a/DynamicIsland/managers/ClipboardManager.swift b/DynamicIsland/managers/ClipboardManager.swift index 9f3b640..949acd8 100644 --- a/DynamicIsland/managers/ClipboardManager.swift +++ b/DynamicIsland/managers/ClipboardManager.swift @@ -160,6 +160,7 @@ class ClipboardManager: ObservableObject { @Published var clipboardHistory: [ClipboardItem] = [] @Published var pinnedItems: [ClipboardItem] = [] @Published var isMonitoring: Bool = false + @Published private(set) var lastCopiedItemDate: Date? private var timer: Timer? private var lastChangeCount: Int = 0 @@ -450,6 +451,7 @@ class ClipboardManager: ObservableObject { // Add to beginning of array self.clipboardHistory.insert(item, at: 0) + self.lastCopiedItemDate = item.timestamp // Keep only the most recent items and clean up old files let itemsToDelete = Array(self.clipboardHistory.dropFirst(self.maxHistoryItems)) diff --git a/DynamicIsland/models/DynamicIslandViewModel.swift b/DynamicIsland/models/DynamicIslandViewModel.swift index 6d7f5cc..bdf8bf0 100644 --- a/DynamicIsland/models/DynamicIslandViewModel.swift +++ b/DynamicIsland/models/DynamicIslandViewModel.swift @@ -38,6 +38,9 @@ class DynamicIslandViewModel: NSObject, ObservableObject { @Published var shouldRecheckHover: Bool = false @Published var isScrollGestureActive: Bool = false private var scrollGestureSuppressionTokens: Set = [] + @Published private(set) var isAutoCloseSuppressed: Bool = false + private var autoCloseSuppressionTokens: Set = [] + private let clipboardFocusWindow: TimeInterval = 10 func setScrollGestureSuppression(_ active: Bool, token: UUID) { if active { @@ -56,6 +59,34 @@ class DynamicIslandViewModel: NSObject, ObservableObject { scrollGestureSuppressionTokens.removeAll() isScrollGestureActive = false } + + func setAutoCloseSuppression(_ active: Bool, token: UUID) { + if active { + let inserted = autoCloseSuppressionTokens.insert(token).inserted + if inserted { + isAutoCloseSuppressed = true + } + } else if autoCloseSuppressionTokens.remove(token) != nil { + isAutoCloseSuppressed = !autoCloseSuppressionTokens.isEmpty + } + } + + private func resetAutoCloseSuppression() { + autoCloseSuppressionTokens.removeAll() + isAutoCloseSuppressed = false + } + + private func focusClipboardTabIfNeeded() { + guard !Defaults[.enableMinimalisticUI] else { return } + guard Defaults[.enableClipboardManager] else { return } + guard Defaults[.clipboardDisplayMode] == .separateTab else { return } + guard let lastCopyDate = ClipboardManager.shared.lastCopiedItemDate else { return } + guard Date().timeIntervalSince(lastCopyDate) <= clipboardFocusWindow else { return } + guard coordinator.currentView != .notes else { return } + withAnimation(.smooth) { + coordinator.currentView = .notes + } + } let webcamManager = WebcamManager.shared @Published var isCameraExpanded: Bool = false @@ -305,6 +336,7 @@ class DynamicIslandViewModel: NSObject, ObservableObject { // Force music information update when notch is opened MusicManager.shared.forceUpdate() + focusClipboardTabIfNeeded() } private func calculateDynamicNotchSize() -> CGSize { @@ -330,6 +362,7 @@ class DynamicIslandViewModel: NSObject, ObservableObject { closedNotchSize = targetSize notchState = .closed resetScrollGestureSuppression() + resetAutoCloseSuppression() // 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 @@ -347,6 +380,7 @@ class DynamicIslandViewModel: NSObject, ObservableObject { closedNotchSize = targetSize notchState = .closed resetScrollGestureSuppression() + resetAutoCloseSuppression() } }