mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-28 15:44:32 +00:00
Merge pull request #196 from Ebullioscopic/feature/clipboard
Updated Notes View and Clipboard View for tabbed mode
This commit is contained in:
commit
9f5e9eb6e5
13 changed files with 1467 additions and 21 deletions
|
|
@ -45,6 +45,16 @@ struct ContentView: View {
|
||||||
var dynamicNotchSize: CGSize {
|
var dynamicNotchSize: CGSize {
|
||||||
var baseSize = Defaults[.enableMinimalisticUI] ? minimalisticOpenNotchSize : openNotchSize
|
var baseSize = Defaults[.enableMinimalisticUI] ? minimalisticOpenNotchSize : openNotchSize
|
||||||
|
|
||||||
|
if coordinator.currentView == .timer {
|
||||||
|
return CGSize(width: baseSize.width, height: 250) // Extra height for timer presets
|
||||||
|
}
|
||||||
|
|
||||||
|
if coordinator.currentView == .notes || coordinator.currentView == .clipboard {
|
||||||
|
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 {
|
||||||
return baseSize
|
return baseSize
|
||||||
}
|
}
|
||||||
|
|
@ -580,6 +590,10 @@ struct ContentView: View {
|
||||||
NotchStatsView()
|
NotchStatsView()
|
||||||
case .colorPicker:
|
case .colorPicker:
|
||||||
NotchColorPickerView()
|
NotchColorPickerView()
|
||||||
|
case .notes:
|
||||||
|
NotchNotesView()
|
||||||
|
case .clipboard:
|
||||||
|
NotchNotesView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.transition(.asymmetric(
|
.transition(.asymmetric(
|
||||||
|
|
@ -837,7 +851,7 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func shouldPreventAutoClose() -> Bool {
|
private func shouldPreventAutoClose() -> Bool {
|
||||||
hasAnyActivePopovers() || SharingStateManager.shared.preventNotchClose
|
hasAnyActivePopovers() || vm.isAutoCloseSuppressed || SharingStateManager.shared.preventNotchClose
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to prevent rapid haptic feedback
|
// Helper to prevent rapid haptic feedback
|
||||||
|
|
@ -893,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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -277,8 +277,17 @@ 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, then adjust for stats layout
|
// Use minimalistic or normal size based on settings
|
||||||
let baseSize = Defaults[.enableMinimalisticUI] ? minimalisticOpenNotchSize : openNotchSize
|
var baseSize = Defaults[.enableMinimalisticUI] ? minimalisticOpenNotchSize : openNotchSize
|
||||||
|
|
||||||
|
// Use a consistent height for different view types
|
||||||
|
if coordinator.currentView == .timer {
|
||||||
|
baseSize.height = 250 // Extra space for timer presets
|
||||||
|
} else if coordinator.currentView == .notes || coordinator.currentView == .clipboard {
|
||||||
|
let preferredHeight = coordinator.notesLayoutState.preferredHeight
|
||||||
|
baseSize.height = max(baseSize.height, preferredHeight)
|
||||||
|
}
|
||||||
|
|
||||||
let adjustedContentSize = statsAdjustedNotchSize(
|
let adjustedContentSize = statsAdjustedNotchSize(
|
||||||
from: baseSize,
|
from: baseSize,
|
||||||
isStatsTabActive: coordinator.currentView == .stats,
|
isStatsTabActive: coordinator.currentView == .stats,
|
||||||
|
|
@ -366,10 +375,17 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
// Setup Privacy Indicator Manager (camera and microphone monitoring)
|
// Setup Privacy Indicator Manager (camera and microphone monitoring)
|
||||||
PrivacyIndicatorManager.shared.startMonitoring()
|
PrivacyIndicatorManager.shared.startMonitoring()
|
||||||
|
|
||||||
// Observe tab changes - use debounced updates
|
// Observe tab changes - use immediate updates to prevent clipping
|
||||||
coordinator.$currentView.sink { [weak self] newView in
|
coordinator.$currentView.sink { [weak self] newView in
|
||||||
self?.debouncedUpdateWindowSize()
|
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
|
||||||
|
|
@ -578,6 +594,17 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
} else {
|
} else {
|
||||||
NotificationCenter.default.post(name: NSNotification.Name("ToggleClipboardPopover"), object: nil)
|
NotificationCenter.default.post(name: NSNotification.Name("ToggleClipboardPopover"), object: nil)
|
||||||
}
|
}
|
||||||
|
case .separateTab:
|
||||||
|
if self.vm.notchState == .closed {
|
||||||
|
self.vm.open()
|
||||||
|
self.coordinator.currentView = .notes
|
||||||
|
} else {
|
||||||
|
if self.coordinator.currentView == .notes {
|
||||||
|
self.vm.close()
|
||||||
|
} else {
|
||||||
|
self.coordinator.currentView = .notes
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -884,6 +884,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"%lld chars" : {
|
||||||
|
"comment" : "A small, rounded badge displaying the number of characters in the note.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"%lld colors" : {
|
"%lld colors" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"fr" : {
|
"fr" : {
|
||||||
|
|
@ -2633,6 +2637,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"All" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"All-day" : {
|
"All-day" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -5288,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" : {
|
"Clear Color History" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"fr" : {
|
"fr" : {
|
||||||
|
|
@ -5324,6 +5335,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Clear History" : {
|
||||||
|
"comment" : "The text on a button that clears the clipboard history.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Clear Pinned Items" : {
|
"Clear Pinned Items" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"fr" : {
|
"fr" : {
|
||||||
|
|
@ -5921,6 +5936,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Copy text to see it here" : {
|
||||||
|
"comment" : "A description below the \"No copies yet\" text, instructing the user to copy text to see it in the clipboard.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Core %lld" : {
|
"Core %lld" : {
|
||||||
"comment" : "A label showing the ID of a CPU core. The argument is the ID of the CPU core.",
|
"comment" : "A label showing the ID of a CPU core. The argument is the ID of the CPU core.",
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
|
|
@ -5946,6 +5965,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Create from Clipboard" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Current custom timer:" : {
|
"Current custom timer:" : {
|
||||||
"extractionState" : "stale",
|
"extractionState" : "stale",
|
||||||
|
|
@ -6475,6 +6497,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Customize how you organize and create notes. Enabling color filtering and search helps manage large lists." : {
|
||||||
|
"comment" : "A footer text that describes the purpose of the \"Customize how you organize and create notes\" section in the settings view.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Customize in Settings" : {
|
"Customize in Settings" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ar" : {
|
"ar" : {
|
||||||
|
|
@ -6698,6 +6724,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Delete All Notes?" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Delete Animation" : {
|
"Delete Animation" : {
|
||||||
"comment" : "The title of an alert that appears when trying to delete a custom animation.",
|
"comment" : "The title of an alert that appears when trying to delete a custom animation.",
|
||||||
|
|
@ -7110,6 +7139,10 @@
|
||||||
},
|
},
|
||||||
"Do Not Disturb" : {
|
"Do Not Disturb" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Done" : {
|
||||||
|
"comment" : "A button label that indicates that the user has finished editing a note.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Download" : {
|
"Download" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -13693,6 +13726,10 @@
|
||||||
},
|
},
|
||||||
"No Colors Yet" : {
|
"No Colors Yet" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"No copies yet" : {
|
||||||
|
"comment" : "A message displayed when the clipboard is empty, indicating that there are no copied items.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"No custom animation available" : {
|
"No custom animation available" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -14123,6 +14160,9 @@
|
||||||
"No Graphs Enabled" : {
|
"No Graphs Enabled" : {
|
||||||
"comment" : "A label displayed when no graphs are enabled in the Stats tab.",
|
"comment" : "A label displayed when no graphs are enabled in the Stats tab.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
|
"No notes yet" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"No presets configured. Add a preset to make it appear in the timer popover." : {
|
"No presets configured. Add a preset to make it appear in the timer popover." : {
|
||||||
"comment" : "A message displayed when there are no timer presets configured, encouraging the user to add one.",
|
"comment" : "A message displayed when there are no timer presets configured, encouraging the user to add one.",
|
||||||
|
|
@ -14675,6 +14715,10 @@
|
||||||
"comment" : "A section header for adjusting the expanded notch width.",
|
"comment" : "A section header for adjusting the expanded notch width.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
|
"Notes" : {
|
||||||
|
"comment" : "The title of the Notes settings page.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Notify before" : {
|
"Notify before" : {
|
||||||
"comment" : "A label describing the slider that allows the user to set a reminder lead time.",
|
"comment" : "A label describing the slider that allows the user to set a reminder lead time.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
|
|
@ -14950,8 +14994,9 @@
|
||||||
"comment" : "A label indicating that the timer is running overtime.",
|
"comment" : "A label indicating that the timer is running overtime.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Panel mode shows clipboard in a floating window near the notch. Popover mode shows clipboard as a dropdown attached to the clipboard button." : {
|
"Panel mode shows clipboard in a floating window near the notch." : {
|
||||||
|
"comment" : "A description of the \"Panel\" clipboard display mode.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Panel mode shows color picker in a floating window. Popover mode shows color picker as a dropdown attached to the color picker button." : {
|
"Panel mode shows color picker in a floating window. Popover mode shows color picker as a dropdown attached to the color picker button." : {
|
||||||
|
|
||||||
|
|
@ -15103,8 +15148,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Popover mode shows clipboard as a dropdown attached to the clipboard button. Panel mode shows clipboard in a floating window near the notch." : {
|
"Popover mode shows clipboard as a dropdown attached to the clipboard button." : {
|
||||||
|
"comment" : "A description of the \"Popover mode\" in the Clipboard Settings view.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Popover mode shows color picker as a dropdown attached to the color picker button. Panel mode shows color picker in a floating window." : {
|
"Popover mode shows color picker as a dropdown attached to the color picker button. Panel mode shows color picker in a floating window." : {
|
||||||
|
|
||||||
|
|
@ -16234,6 +16280,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Search notes..." : {
|
||||||
|
"comment" : "A placeholder text for a search bar in the note list view.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Search Settings" : {
|
"Search Settings" : {
|
||||||
"comment" : "A placeholder text for a search bar in the settings view.",
|
"comment" : "A placeholder text for a search bar in the settings view.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
|
|
@ -16630,6 +16680,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Separate Tab mode integrates Copied Items and Notes into a single view. If both are enabled, Notes appear on the right and Clipboard on the left." : {
|
||||||
|
"comment" : "A description of the \"Separate Tab\" clipboard display mode.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Session · ↓ %@ · ↑ %@" : {
|
"Session · ↓ %@ · ↑ %@" : {
|
||||||
"comment" : "A row displaying the total downloaded and uploaded bytes for the network session.",
|
"comment" : "A row displaying the total downloaded and uploaded bytes for the network session.",
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
|
|
@ -18271,6 +18325,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Start typing..." : {
|
||||||
|
"comment" : "A placeholder text shown when a note's content is empty.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Starts a 5-minute demo timer to test the timer live activity feature. Only works when timer feature is enabled." : {
|
"Starts a 5-minute demo timer to test the timer live activity feature. Only works when timer feature is enabled." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
@ -18775,6 +18833,13 @@
|
||||||
"comment" : "A footer description for the custom timer settings, explaining their purpose.",
|
"comment" : "A footer description for the custom timer settings, explaining their purpose.",
|
||||||
"isCommentAutoGenerated" : true
|
"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" : {
|
"Time Range" : {
|
||||||
"extractionState" : "stale",
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -18951,6 +19016,10 @@
|
||||||
"comment" : "A section header for the timer widget settings.",
|
"comment" : "A section header for the timer widget settings.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
|
"Title" : {
|
||||||
|
"comment" : "A label for the title input field in the note editor.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Toggle Notch Open:" : {
|
"Toggle Notch Open:" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ar" : {
|
"ar" : {
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ struct DynamicIslandHeader: View {
|
||||||
@State private var showTimerPopover = false
|
@State private var showTimerPopover = false
|
||||||
@Default(.enableTimerFeature) var enableTimerFeature
|
@Default(.enableTimerFeature) var enableTimerFeature
|
||||||
@Default(.timerDisplayMode) var timerDisplayMode
|
@Default(.timerDisplayMode) var timerDisplayMode
|
||||||
|
@Default(.showClipboardIcon) var showClipboardIcon
|
||||||
|
@Default(.clipboardDisplayMode) var clipboardDisplayMode
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
|
|
@ -68,14 +70,18 @@ struct DynamicIslandHeader: View {
|
||||||
.buttonStyle(PlainButtonStyle())
|
.buttonStyle(PlainButtonStyle())
|
||||||
}
|
}
|
||||||
|
|
||||||
if Defaults[.enableClipboardManager] && Defaults[.showClipboardIcon] {
|
if Defaults[.enableClipboardManager]
|
||||||
|
&& showClipboardIcon
|
||||||
|
&& clipboardDisplayMode != .separateTab {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
// Switch behavior based on display mode
|
// Switch behavior based on display mode
|
||||||
switch Defaults[.clipboardDisplayMode] {
|
switch clipboardDisplayMode {
|
||||||
case .panel:
|
case .panel:
|
||||||
ClipboardPanelManager.shared.toggleClipboardPanel()
|
ClipboardPanelManager.shared.toggleClipboardPanel()
|
||||||
case .popover:
|
case .popover:
|
||||||
showClipboardPopover.toggle()
|
showClipboardPopover.toggle()
|
||||||
|
case .separateTab:
|
||||||
|
coordinator.currentView = .notes
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
Capsule()
|
Capsule()
|
||||||
|
|
@ -207,6 +213,8 @@ struct DynamicIslandHeader: View {
|
||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if Defaults[.showBatteryIndicator] {
|
if Defaults[.showBatteryIndicator] {
|
||||||
DynamicIslandBatteryView(
|
DynamicIslandBatteryView(
|
||||||
batteryWidth: 30,
|
batteryWidth: 30,
|
||||||
|
|
@ -233,17 +241,23 @@ struct DynamicIslandHeader: View {
|
||||||
.onChange(of: coordinator.shouldToggleClipboardPopover) { _ in
|
.onChange(of: coordinator.shouldToggleClipboardPopover) { _ in
|
||||||
// Only toggle if clipboard is enabled
|
// Only toggle if clipboard is enabled
|
||||||
if Defaults[.enableClipboardManager] {
|
if Defaults[.enableClipboardManager] {
|
||||||
switch Defaults[.clipboardDisplayMode] {
|
switch clipboardDisplayMode {
|
||||||
case .panel:
|
case .panel:
|
||||||
ClipboardPanelManager.shared.toggleClipboardPanel()
|
ClipboardPanelManager.shared.toggleClipboardPanel()
|
||||||
case .popover:
|
case .popover:
|
||||||
showClipboardPopover.toggle()
|
showClipboardPopover.toggle()
|
||||||
|
case .separateTab:
|
||||||
|
if coordinator.currentView == .notes {
|
||||||
|
coordinator.currentView = .home
|
||||||
|
} else {
|
||||||
|
coordinator.currentView = .notes
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("ToggleClipboardPopover"))) { _ in
|
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("ToggleClipboardPopover"))) { _ in
|
||||||
// Handle keyboard shortcut for popover mode
|
// Handle keyboard shortcut for popover mode
|
||||||
if Defaults[.enableClipboardManager] && Defaults[.clipboardDisplayMode] == .popover {
|
if Defaults[.enableClipboardManager] && clipboardDisplayMode == .popover {
|
||||||
showClipboardPopover.toggle()
|
showClipboardPopover.toggle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1075
DynamicIsland/components/Notch/NotchNotesView.swift
Normal file
1075
DynamicIsland/components/Notch/NotchNotesView.swift
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -35,6 +35,7 @@ private enum SettingsTab: String, CaseIterable, Identifiable {
|
||||||
case downloads
|
case downloads
|
||||||
case shelf
|
case shelf
|
||||||
case shortcuts
|
case shortcuts
|
||||||
|
case notes
|
||||||
case about
|
case about
|
||||||
|
|
||||||
var id: String { rawValue }
|
var id: String { rawValue }
|
||||||
|
|
@ -58,6 +59,7 @@ private enum SettingsTab: String, CaseIterable, Identifiable {
|
||||||
case .downloads: return "Downloads"
|
case .downloads: return "Downloads"
|
||||||
case .shelf: return "Shelf"
|
case .shelf: return "Shelf"
|
||||||
case .shortcuts: return "Shortcuts"
|
case .shortcuts: return "Shortcuts"
|
||||||
|
case .notes: return "Notes"
|
||||||
case .about: return "About"
|
case .about: return "About"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -81,6 +83,7 @@ private enum SettingsTab: String, CaseIterable, Identifiable {
|
||||||
case .downloads: return "square.and.arrow.down"
|
case .downloads: return "square.and.arrow.down"
|
||||||
case .shelf: return "books.vertical"
|
case .shelf: return "books.vertical"
|
||||||
case .shortcuts: return "keyboard"
|
case .shortcuts: return "keyboard"
|
||||||
|
case .notes: return "note.text"
|
||||||
case .about: return "info.circle"
|
case .about: return "info.circle"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -104,6 +107,7 @@ private enum SettingsTab: String, CaseIterable, Identifiable {
|
||||||
case .downloads: return .gray
|
case .downloads: return .gray
|
||||||
case .shelf: return .brown
|
case .shelf: return .brown
|
||||||
case .shortcuts: return .orange
|
case .shortcuts: return .orange
|
||||||
|
case .notes: return .yellow
|
||||||
case .about: return .secondary
|
case .about: return .secondary
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -387,6 +391,7 @@ struct SettingsView: View {
|
||||||
.hudAndOSD,
|
.hudAndOSD,
|
||||||
.battery,
|
.battery,
|
||||||
.stats,
|
.stats,
|
||||||
|
.notes,
|
||||||
.clipboard,
|
.clipboard,
|
||||||
.screenAssistant,
|
.screenAssistant,
|
||||||
.colorPicker,
|
.colorPicker,
|
||||||
|
|
@ -722,7 +727,7 @@ struct SettingsView: View {
|
||||||
|
|
||||||
private func isTabVisible(_ tab: SettingsTab) -> Bool {
|
private func isTabVisible(_ tab: SettingsTab) -> Bool {
|
||||||
switch tab {
|
switch tab {
|
||||||
case .timer, .stats, .clipboard, .screenAssistant, .colorPicker, .shelf:
|
case .timer, .stats, .clipboard, .screenAssistant, .colorPicker, .shelf, .notes:
|
||||||
return !enableMinimalisticUI
|
return !enableMinimalisticUI
|
||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
|
|
@ -800,6 +805,10 @@ struct SettingsView: View {
|
||||||
SettingsForm(tab: .shortcuts) {
|
SettingsForm(tab: .shortcuts) {
|
||||||
Shortcuts()
|
Shortcuts()
|
||||||
}
|
}
|
||||||
|
case .notes:
|
||||||
|
SettingsForm(tab: .notes) {
|
||||||
|
NotesSettingsView()
|
||||||
|
}
|
||||||
case .about:
|
case .about:
|
||||||
if let controller = updaterController {
|
if let controller = updaterController {
|
||||||
SettingsForm(tab: .about) {
|
SettingsForm(tab: .about) {
|
||||||
|
|
@ -4638,9 +4647,11 @@ struct ClipboardSettings: View {
|
||||||
} footer: {
|
} footer: {
|
||||||
switch clipboardDisplayMode {
|
switch clipboardDisplayMode {
|
||||||
case .popover:
|
case .popover:
|
||||||
Text("Popover mode shows clipboard as a dropdown attached to the clipboard button. Panel mode shows clipboard in a floating window near the notch.")
|
Text("Popover mode shows clipboard as a dropdown attached to the clipboard button.")
|
||||||
case .panel:
|
case .panel:
|
||||||
Text("Panel mode shows clipboard in a floating window near the notch. Popover mode shows clipboard as a dropdown attached to the clipboard button.")
|
Text("Panel mode shows clipboard in a floating window near the notch.")
|
||||||
|
case .separateTab:
|
||||||
|
Text("Separate Tab mode integrates Copied Items and Notes into a single view. If both are enabled, Notes appear on the right and Clipboard on the left.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5196,3 +5207,34 @@ struct SettingsPermissionCallout: View {
|
||||||
#Preview {
|
#Preview {
|
||||||
HUD()
|
HUD()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct NotesSettingsView: View {
|
||||||
|
@EnvironmentObject var vm: DynamicIslandViewModel
|
||||||
|
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
|
||||||
|
|
||||||
|
private func highlightID(_ title: String) -> String {
|
||||||
|
SettingsTab.notes.highlightID(for: title)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Form {
|
||||||
|
Section {
|
||||||
|
Defaults.Toggle("Enable Notes", key: .enableNotes)
|
||||||
|
if Defaults[.enableNotes] {
|
||||||
|
Defaults.Toggle("Enable Note Pinning", key: .enableNotePinning)
|
||||||
|
Defaults.Toggle("Enable Note Search", key: .enableNoteSearch)
|
||||||
|
Defaults.Toggle("Enable Color Filtering", key: .enableNoteColorFiltering)
|
||||||
|
Defaults.Toggle("Enable Create from Clipboard", key: .enableCreateFromClipboard)
|
||||||
|
Defaults.Toggle("Show Character Count", key: .enableNoteCharCount)
|
||||||
|
}
|
||||||
|
} header: {
|
||||||
|
Text("General")
|
||||||
|
} footer: {
|
||||||
|
Text("Customize how you organize and create notes. Enabling color filtering and search helps manage large lists.")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle("Notes")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,12 @@ struct TabSelectionView: View {
|
||||||
if Defaults[.enableStatsFeature] {
|
if Defaults[.enableStatsFeature] {
|
||||||
tabsArray.append(TabModel(label: "Stats", icon: "chart.xyaxis.line", view: .stats))
|
tabsArray.append(TabModel(label: "Stats", icon: "chart.xyaxis.line", view: .stats))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if Defaults[.enableNotes] || (Defaults[.enableClipboardManager] && Defaults[.clipboardDisplayMode] == .separateTab) {
|
||||||
|
let label = Defaults[.enableNotes] ? "Notes" : "Clipboard"
|
||||||
|
let icon = Defaults[.enableNotes] ? "note.text" : "doc.on.clipboard"
|
||||||
|
tabsArray.append(TabModel(label: label, icon: icon, view: .notes))
|
||||||
|
}
|
||||||
|
|
||||||
return tabsArray
|
return tabsArray
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Defaults
|
import Defaults
|
||||||
|
import CoreGraphics
|
||||||
|
|
||||||
public enum Style {
|
public enum Style {
|
||||||
case notch
|
case notch
|
||||||
|
|
@ -30,6 +31,25 @@ public enum NotchViews {
|
||||||
case timer
|
case timer
|
||||||
case stats
|
case stats
|
||||||
case colorPicker
|
case colorPicker
|
||||||
|
case notes
|
||||||
|
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 {
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,7 @@ class ClipboardManager: ObservableObject {
|
||||||
@Published var clipboardHistory: [ClipboardItem] = []
|
@Published var clipboardHistory: [ClipboardItem] = []
|
||||||
@Published var pinnedItems: [ClipboardItem] = []
|
@Published var pinnedItems: [ClipboardItem] = []
|
||||||
@Published var isMonitoring: Bool = false
|
@Published var isMonitoring: Bool = false
|
||||||
|
@Published private(set) var lastCopiedItemDate: Date?
|
||||||
|
|
||||||
private var timer: Timer?
|
private var timer: Timer?
|
||||||
private var lastChangeCount: Int = 0
|
private var lastChangeCount: Int = 0
|
||||||
|
|
@ -450,6 +451,7 @@ class ClipboardManager: ObservableObject {
|
||||||
|
|
||||||
// Add to beginning of array
|
// Add to beginning of array
|
||||||
self.clipboardHistory.insert(item, at: 0)
|
self.clipboardHistory.insert(item, at: 0)
|
||||||
|
self.lastCopiedItemDate = item.timestamp
|
||||||
|
|
||||||
// Keep only the most recent items and clean up old files
|
// Keep only the most recent items and clean up old files
|
||||||
let itemsToDelete = Array(self.clipboardHistory.dropFirst(self.maxHistoryItems))
|
let itemsToDelete = Array(self.clipboardHistory.dropFirst(self.maxHistoryItems))
|
||||||
|
|
|
||||||
|
|
@ -105,11 +105,13 @@ enum CalendarSelectionState: Codable, Defaults.Serializable {
|
||||||
enum ClipboardDisplayMode: String, CaseIterable, Codable, Defaults.Serializable {
|
enum ClipboardDisplayMode: String, CaseIterable, Codable, Defaults.Serializable {
|
||||||
case popover = "popover" // Traditional popover attached to button
|
case popover = "popover" // Traditional popover attached to button
|
||||||
case panel = "panel" // Floating panel near notch
|
case panel = "panel" // Floating panel near notch
|
||||||
|
case separateTab = "separateTab" // Separate tab in Dynamic Island
|
||||||
|
|
||||||
var displayName: String {
|
var displayName: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .popover: return "Popover"
|
case .popover: return "Popover"
|
||||||
case .panel: return "Panel"
|
case .panel: return "Panel"
|
||||||
|
case .separateTab: return "Separate Tab"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,6 +119,7 @@ enum ClipboardDisplayMode: String, CaseIterable, Codable, Defaults.Serializable
|
||||||
switch self {
|
switch self {
|
||||||
case .popover: return "Shows clipboard as a dropdown attached to the clipboard button"
|
case .popover: return "Shows clipboard as a dropdown attached to the clipboard button"
|
||||||
case .panel: return "Shows clipboard in a floating panel near the notch"
|
case .panel: return "Shows clipboard in a floating panel near the notch"
|
||||||
|
case .separateTab: return "Shows copied items in a separate tab within the Dynamic Island (merges with Notes if enabled)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -377,6 +380,86 @@ struct AIModel: Codable, Identifiable, Defaults.Serializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct NoteItem: Codable, Identifiable, Defaults.Serializable, Hashable {
|
||||||
|
var id: UUID = UUID()
|
||||||
|
var title: String
|
||||||
|
var content: String
|
||||||
|
var creationDate: Date
|
||||||
|
var colorIndex: Int // 0: Yellow, 1: Blue, 2: Red, 3: Green
|
||||||
|
var isPinned: Bool = false
|
||||||
|
var imageFileName: String? = nil // Store filename instead of raw data
|
||||||
|
|
||||||
|
// Internal property for migration
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case id, title, content, creationDate, colorIndex, isPinned, imageFileName, imageData
|
||||||
|
}
|
||||||
|
|
||||||
|
init(id: UUID = UUID(), title: String, content: String, creationDate: Date, colorIndex: Int, isPinned: Bool = false, imageFileName: String? = nil) {
|
||||||
|
self.id = id
|
||||||
|
self.title = title
|
||||||
|
self.content = content
|
||||||
|
self.creationDate = creationDate
|
||||||
|
self.colorIndex = colorIndex
|
||||||
|
self.isPinned = isPinned
|
||||||
|
self.imageFileName = imageFileName
|
||||||
|
}
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
id = try container.decode(UUID.self, forKey: .id)
|
||||||
|
title = try container.decode(String.self, forKey: .title)
|
||||||
|
content = try container.decode(String.self, forKey: .content)
|
||||||
|
creationDate = try container.decode(Date.self, forKey: .creationDate)
|
||||||
|
colorIndex = try container.decode(Int.self, forKey: .colorIndex)
|
||||||
|
isPinned = try container.decode(Bool.self, forKey: .isPinned)
|
||||||
|
|
||||||
|
// Migration logic: if imageData exists but imageFileName doesn't, save it to disk
|
||||||
|
if let data = try container.decodeIfPresent(Data.self, forKey: .imageData) {
|
||||||
|
let fileName = "note_image_\(id.uuidString).png"
|
||||||
|
let fileURL = NoteItem.noteImageDataDirectory.appendingPathComponent(fileName)
|
||||||
|
try? data.write(to: fileURL)
|
||||||
|
imageFileName = fileName
|
||||||
|
} else {
|
||||||
|
imageFileName = try container.decodeIfPresent(String.self, forKey: .imageFileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
|
try container.encode(id, forKey: .id)
|
||||||
|
try container.encode(title, forKey: .title)
|
||||||
|
try container.encode(content, forKey: .content)
|
||||||
|
try container.encode(creationDate, forKey: .creationDate)
|
||||||
|
try container.encode(colorIndex, forKey: .colorIndex)
|
||||||
|
try container.encode(isPinned, forKey: .isPinned)
|
||||||
|
try container.encode(imageFileName, forKey: .imageFileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
static let colors: [Color] = [.yellow, .blue, .red, .green, .purple, .orange]
|
||||||
|
|
||||||
|
// Directory for storing note image files
|
||||||
|
static let noteImageDataDirectory: URL = {
|
||||||
|
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||||
|
let notesDir = documentsPath.appendingPathComponent("NoteImages")
|
||||||
|
try? FileManager.default.createDirectory(at: notesDir, withIntermediateDirectories: true)
|
||||||
|
return notesDir
|
||||||
|
}()
|
||||||
|
|
||||||
|
var color: Color {
|
||||||
|
if colorIndex >= 0 && colorIndex < NoteItem.colors.count {
|
||||||
|
return NoteItem.colors[colorIndex]
|
||||||
|
}
|
||||||
|
return .yellow
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to get image data from file
|
||||||
|
func getImageData() -> Data? {
|
||||||
|
guard let fileName = imageFileName else { return nil }
|
||||||
|
let fileURL = NoteItem.noteImageDataDirectory.appendingPathComponent(fileName)
|
||||||
|
return try? Data(contentsOf: fileURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension Defaults.Keys {
|
extension Defaults.Keys {
|
||||||
// MARK: General
|
// MARK: General
|
||||||
static let menubarIcon = Key<Bool>("menubarIcon", default: true)
|
static let menubarIcon = Key<Bool>("menubarIcon", default: true)
|
||||||
|
|
@ -662,6 +745,15 @@ extension Defaults.Keys {
|
||||||
// MARK: Lyrics Feature
|
// MARK: Lyrics Feature
|
||||||
static let enableLyrics = Key<Bool>("enableLyrics", default: true)
|
static let enableLyrics = Key<Bool>("enableLyrics", default: true)
|
||||||
|
|
||||||
|
// MARK: Notes Feature
|
||||||
|
static let enableNotes = Key<Bool>("enableNotes", default: false)
|
||||||
|
static let enableNotePinning = Key<Bool>("enableNotePinning", default: true)
|
||||||
|
static let enableNoteSearch = Key<Bool>("enableNoteSearch", default: false)
|
||||||
|
static let enableNoteColorFiltering = Key<Bool>("enableNoteColorFiltering", default: false)
|
||||||
|
static let enableCreateFromClipboard = Key<Bool>("enableCreateFromClipboard", default: true)
|
||||||
|
static let enableNoteCharCount = Key<Bool>("enableNoteCharCount", default: true)
|
||||||
|
static let savedNotes = Key<[NoteItem]>("savedNotes", default: [])
|
||||||
|
|
||||||
// Helper to determine the default media controller based on macOS version
|
// Helper to determine the default media controller based on macOS version
|
||||||
static var defaultMediaController: MediaControllerType {
|
static var defaultMediaController: MediaControllerType {
|
||||||
if #available(macOS 15.4, *) {
|
if #available(macOS 15.4, *) {
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,57 @@ 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> = []
|
||||||
|
@Published private(set) var isAutoCloseSuppressed: Bool = false
|
||||||
|
private var autoCloseSuppressionTokens: Set<UUID> = []
|
||||||
|
private let clipboardFocusWindow: TimeInterval = 10
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
let webcamManager = WebcamManager.shared
|
||||||
@Published var isCameraExpanded: Bool = false
|
@Published var isCameraExpanded: Bool = false
|
||||||
|
|
@ -150,6 +201,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()
|
||||||
|
|
@ -264,14 +336,22 @@ class DynamicIslandViewModel: NSObject, ObservableObject {
|
||||||
|
|
||||||
// Force music information update when notch is opened
|
// Force music information update when notch is opened
|
||||||
MusicManager.shared.forceUpdate()
|
MusicManager.shared.forceUpdate()
|
||||||
|
focusClipboardTabIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
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(
|
return statsAdjustedNotchSize(
|
||||||
from: baseSize,
|
from: adjustedSize,
|
||||||
isStatsTabActive: DynamicIslandViewCoordinator.shared.currentView == .stats,
|
isStatsTabActive: coordinator.currentView == .stats,
|
||||||
secondRowProgress: coordinator.statsSecondRowExpansion
|
secondRowProgress: coordinator.statsSecondRowExpansion
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -281,6 +361,8 @@ class DynamicIslandViewModel: NSObject, ObservableObject {
|
||||||
notchSize = targetSize
|
notchSize = targetSize
|
||||||
closedNotchSize = targetSize
|
closedNotchSize = targetSize
|
||||||
notchState = .closed
|
notchState = .closed
|
||||||
|
resetScrollGestureSuppression()
|
||||||
|
resetAutoCloseSuppression()
|
||||||
|
|
||||||
// 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
|
||||||
|
|
@ -297,6 +379,8 @@ class DynamicIslandViewModel: NSObject, ObservableObject {
|
||||||
notchSize = targetSize
|
notchSize = targetSize
|
||||||
closedNotchSize = targetSize
|
closedNotchSize = targetSize
|
||||||
notchState = .closed
|
notchState = .closed
|
||||||
|
resetScrollGestureSuppression()
|
||||||
|
resetAutoCloseSuppression()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ let batterySneakSize: CGSize = .init(width: 160, height: 1)
|
||||||
|
|
||||||
var openNotchSize: CGSize {
|
var openNotchSize: CGSize {
|
||||||
let width = Defaults[.openNotchWidth]
|
let width = Defaults[.openNotchWidth]
|
||||||
return .init(width: width, height: 190)
|
return .init(width: width, height: 200) // Adjusted to a better middle ground
|
||||||
}
|
}
|
||||||
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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue