Skip to content

Commit 8ba85ce

Browse files
authored
Merge pull request #282 from datlechin/refactor/remove-dead-notifications
refactor: replace settings notifications with @observable and direct calls
2 parents 585ab90 + da970ff commit 8ba85ce

7 files changed

Lines changed: 25 additions & 120 deletions

File tree

TablePro/Core/Services/Infrastructure/SettingsNotifications.swift

Lines changed: 4 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,78 +2,22 @@
22
// SettingsNotifications.swift
33
// TablePro
44
//
5-
// Notification names and payload structures for settings changes.
6-
// Follows existing NotificationCenter pattern used throughout the app.
5+
// Notification names for settings changes that require AppKit bridging.
6+
// SwiftUI views observe @Observable AppSettingsManager directly instead.
77
//
88

99
import Foundation
1010

11-
// MARK: - Settings Notification Names
12-
1311
extension Notification.Name {
14-
// MARK: - Domain-Specific Notifications
15-
1612
/// Posted when data grid settings change (row height, date format, etc.)
13+
/// Used by AppKit components that cannot observe @Observable directly.
1714
static let dataGridSettingsDidChange = Notification.Name("dataGridSettingsDidChange")
1815

19-
/// Posted when history settings change (retention, auto-cleanup, etc.)
20-
static let historySettingsDidChange = Notification.Name("historySettingsDidChange")
21-
2216
/// Posted when editor settings change (font, line numbers, etc.)
17+
/// Used by AppKit components that cannot observe @Observable directly.
2318
static let editorSettingsDidChange = Notification.Name("editorSettingsDidChange")
2419

25-
/// Posted when appearance settings change (theme, accent color)
26-
static let appearanceSettingsDidChange = Notification.Name("appearanceSettingsDidChange")
27-
28-
/// Posted when general settings change (startup behavior, confirmations)
29-
static let generalSettingsDidChange = Notification.Name("generalSettingsDidChange")
30-
31-
/// Posted when tab settings change (reuse behavior, etc.)
32-
static let tabSettingsDidChange = Notification.Name("tabSettingsDidChange")
33-
34-
/// Posted when keyboard shortcut settings change
35-
static let keyboardSettingsDidChange = Notification.Name("keyboardSettingsDidChange")
36-
37-
/// Posted when AI settings change (providers, routing, context options)
38-
static let aiSettingsDidChange = Notification.Name("aiSettingsDidChange")
39-
40-
// MARK: - System Notifications
41-
4220
/// Posted when the system accessibility text size preference changes.
4321
/// Observers should reload fonts via SQLEditorTheme.reloadFromSettings().
4422
static let accessibilityTextSizeDidChange = Notification.Name("accessibilityTextSizeDidChange")
45-
46-
// MARK: - Generic Notification
47-
48-
/// Posted for any settings change (in addition to domain-specific notification)
49-
/// Use this to listen for all settings changes regardless of domain
50-
static let settingsDidChange = Notification.Name("settingsDidChange")
51-
}
52-
53-
// MARK: - Settings Change Info
54-
55-
/// Information about a settings change included in notification userInfo
56-
struct SettingsChangeInfo {
57-
/// The settings domain that changed (e.g., "general", "dataGrid", "history")
58-
let domain: String
59-
60-
/// Optional set of specific keys that changed within the domain
61-
/// If nil, assume all settings in the domain may have changed
62-
let changedKeys: Set<String>?
63-
64-
/// User info dictionary key for accessing SettingsChangeInfo
65-
static let userInfoKey = "changeInfo"
66-
}
67-
68-
// MARK: - Convenience Extensions
69-
70-
extension Notification {
71-
/// Extract SettingsChangeInfo from notification's userInfo
72-
var settingsChangeInfo: SettingsChangeInfo? {
73-
guard let userInfo = userInfo,
74-
let changeInfo = userInfo[SettingsChangeInfo.userInfoKey] as? SettingsChangeInfo else {
75-
return nil
76-
}
77-
return changeInfo
78-
}
7923
}

TablePro/Core/Storage/AppSettingsManager.swift

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,13 @@ final class AppSettingsManager {
2323
didSet {
2424
general.language.apply()
2525
storage.saveGeneral(general)
26-
notifyChange(domain: "general", notification: .generalSettingsDidChange)
2726
}
2827
}
2928

3029
var appearance: AppearanceSettings {
3130
didSet {
3231
storage.saveAppearance(appearance)
3332
appearance.theme.apply()
34-
notifyChange(domain: "appearance", notification: .appearanceSettingsDidChange)
3533
}
3634
}
3735

@@ -40,7 +38,7 @@ final class AppSettingsManager {
4038
storage.saveEditor(editor)
4139
// Update cached theme values for thread-safe access
4240
SQLEditorTheme.reloadFromSettings(editor)
43-
notifyChange(domain: "editor", notification: .editorSettingsDidChange)
41+
notifyChange(.editorSettingsDidChange)
4442
}
4543
}
4644

@@ -62,7 +60,7 @@ final class AppSettingsManager {
6260
storage.saveDataGrid(validated)
6361
// Update date formatting service with new format
6462
DateFormattingService.shared.updateFormat(validated.dateFormat)
65-
notifyChange(domain: "dataGrid", notification: .dataGridSettingsDidChange)
63+
notifyChange(.dataGridSettingsDidChange)
6664
}
6765
}
6866

@@ -84,28 +82,24 @@ final class AppSettingsManager {
8482
storage.saveHistory(validated)
8583
// Apply history settings immediately (cleanup if auto-cleanup enabled)
8684
Task { await applyHistorySettingsImmediately() }
87-
notifyChange(domain: "history", notification: .historySettingsDidChange)
8885
}
8986
}
9087

9188
var tabs: TabSettings {
9289
didSet {
9390
storage.saveTabs(tabs)
94-
notifyChange(domain: "tabs", notification: .tabSettingsDidChange)
9591
}
9692
}
9793

9894
var keyboard: KeyboardSettings {
9995
didSet {
10096
storage.saveKeyboard(keyboard)
101-
notifyChange(domain: "keyboard", notification: .keyboardSettingsDidChange)
10297
}
10398
}
10499

105100
var ai: AISettings {
106101
didSet {
107102
storage.saveAI(ai)
108-
notifyChange(domain: "ai", notification: .aiSettingsDidChange)
109103
}
110104
}
111105

@@ -147,24 +141,8 @@ final class AppSettingsManager {
147141

148142
// MARK: - Notification Propagation
149143

150-
/// Notify listeners that settings have changed
151-
/// Posts both domain-specific and generic notifications
152-
private func notifyChange(domain: String, notification: Notification.Name) {
153-
let changeInfo = SettingsChangeInfo(domain: domain, changedKeys: nil)
154-
155-
// Post domain-specific notification
156-
NotificationCenter.default.post(
157-
name: notification,
158-
object: self,
159-
userInfo: [SettingsChangeInfo.userInfoKey: changeInfo]
160-
)
161-
162-
// Post generic notification for listeners that want all settings changes
163-
NotificationCenter.default.post(
164-
name: .settingsDidChange,
165-
object: self,
166-
userInfo: [SettingsChangeInfo.userInfoKey: changeInfo]
167-
)
144+
private func notifyChange(_ notification: Notification.Name) {
145+
NotificationCenter.default.post(name: notification, object: self)
168146
}
169147

170148
// MARK: - Accessibility Text Size
@@ -197,11 +175,8 @@ final class AppSettingsManager {
197175
}
198176
}
199177

200-
/// Apply history settings immediately (triggered on settings change)
201178
private func applyHistorySettingsImmediately() async {
202-
// This will be called by QueryHistoryManager
203-
// We post a notification and let the manager handle the actual cleanup
204-
// This keeps the settings manager decoupled from history storage implementation
179+
QueryHistoryManager.shared.applySettingsChange()
205180
}
206181

207182
// MARK: - Actions

TablePro/Core/Storage/QueryHistoryManager.swift

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@
33
// TablePro
44
//
55
// Thread-safe coordinator for query history
6-
// Communicates via NotificationCenter (NOT ObservableObject)
76
//
87

9-
import Combine
108
import Foundation
119

1210
/// Thread-safe manager for query history
@@ -16,32 +14,13 @@ final class QueryHistoryManager {
1614

1715
private let storage: QueryHistoryStorage
1816

19-
// Settings observer for immediate cleanup when settings change
20-
private var settingsObserver: AnyCancellable?
21-
2217
/// Creates an isolated manager with its own storage. For testing only.
2318
init(isolatedStorage: QueryHistoryStorage) {
2419
self.storage = isolatedStorage
2520
}
2621

2722
private init() {
2823
self.storage = QueryHistoryStorage.shared
29-
// Subscribe to history settings changes for immediate cleanup
30-
settingsObserver = NotificationCenter.default.publisher(for: .historySettingsDidChange)
31-
.receive(on: DispatchQueue.main)
32-
.sink { [weak self] _ in
33-
guard let self else { return }
34-
35-
MainActor.assumeIsolated {
36-
// Update settings cache
37-
self.storage.updateSettingsCache()
38-
39-
// Perform cleanup if auto-cleanup is enabled
40-
if AppSettingsManager.shared.history.autoCleanup {
41-
self.storage.cleanup()
42-
}
43-
}
44-
}
4524
}
4625

4726
/// Perform cleanup if auto-cleanup is enabled in settings
@@ -58,6 +37,15 @@ final class QueryHistoryManager {
5837
storage.cleanup()
5938
}
6039

40+
/// Apply settings changes directly (called by AppSettingsManager)
41+
@MainActor
42+
func applySettingsChange() {
43+
storage.updateSettingsCache()
44+
if AppSettingsManager.shared.history.autoCleanup {
45+
storage.cleanup()
46+
}
47+
}
48+
6149
// MARK: - History Capture
6250

6351
/// Record a query execution (fire-and-forget background write)

TablePro/Resources/Localizable.xcstrings

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2226,6 +2226,7 @@
22262226
}
22272227
},
22282228
"Are you sure you want to disconnect from this database?" : {
2229+
"extractionState" : "stale",
22292230
"localizations" : {
22302231
"vi" : {
22312232
"stringUnit" : {
@@ -5602,6 +5603,7 @@
56025603
}
56035604
},
56045605
"Disconnect" : {
5606+
"extractionState" : "stale",
56055607
"localizations" : {
56065608
"vi" : {
56075609
"stringUnit" : {

TablePro/Views/Editor/QueryEditorView.swift

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ struct QueryEditorView: View {
2424
var onExecuteQuery: (() -> Void)?
2525

2626
@State private var vimMode: VimMode = .normal
27-
@State private var isVimEnabled = AppSettingsManager.shared.editor.vimModeEnabled
2827

2928
var body: some View {
3029
let hasQuery = appState.hasQueryText
@@ -50,9 +49,6 @@ struct QueryEditorView: View {
5049
.clipped()
5150
}
5251
.background(Color(nsColor: .textBackgroundColor))
53-
.onReceive(NotificationCenter.default.publisher(for: .editorSettingsDidChange)) { _ in
54-
isVimEnabled = AppSettingsManager.shared.editor.vimModeEnabled
55-
}
5652
}
5753

5854
// MARK: - Toolbar
@@ -63,7 +59,7 @@ struct QueryEditorView: View {
6359
.font(.headline)
6460
.foregroundStyle(.secondary)
6561

66-
if isVimEnabled {
62+
if AppSettingsManager.shared.editor.vimModeEnabled {
6763
VimModeIndicatorView(mode: vimMode)
6864
}
6965

TablePro/Views/Editor/SQLEditorView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ struct SQLEditorView: View {
7777
.onChange(of: colorScheme) {
7878
editorConfiguration = Self.makeConfiguration()
7979
}
80-
.onReceive(NotificationCenter.default.publisher(for: .editorSettingsDidChange)) { _ in
80+
.onChange(of: AppSettingsManager.shared.editor) {
8181
editorConfiguration = Self.makeConfiguration()
8282
}
8383
.onReceive(NotificationCenter.default.publisher(for: .accessibilityTextSizeDidChange)) { _ in

docs/development/plugin-settings-tracking.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ Plugin enable/disable state lives in `UserDefaults["com.TablePro.disabledPlugins
1818
| Feature | Status | File | Notes |
1919
| -------------------------------------------------------- | ------ | --------------------------------------------------- | ----------------------------------------------------------- |
2020
| Installed plugins list with toggle | Done | `Views/Settings/Plugins/InstalledPluginsView.swift` | One row per `PluginEntry`, inline detail expansion |
21-
| Enable/disable toggle (live) | Done | `Core/Plugins/PluginManager.swift:365` | Immediate capability register/unregister, no restart needed |
21+
| Enable/disable toggle (live) | Done | `Core/Plugins/PluginManager.swift:382` | Immediate capability register/unregister, no restart needed |
2222
| Plugin detail (version, bundle ID, source, capabilities) | Done | `Views/Settings/Plugins/InstalledPluginsView.swift` | Shown on row expansion |
2323
| Install from file (.tableplugin, .zip) | Done | `Views/Settings/Plugins/InstalledPluginsView.swift` | NSOpenPanel + drag-and-drop |
2424
| Uninstall user plugins | Done | `Views/Settings/Plugins/InstalledPluginsView.swift` | Destructive button with AlertHelper.confirmDestructive |
2525
| Restart-required banner | Done | `Views/Settings/Plugins/InstalledPluginsView.swift` | Orange dismissible banner after uninstall |
2626
| Browse registry | Done | `Views/Settings/Plugins/BrowsePluginsView.swift` | Remote manifest from GitHub, search + category filter |
2727
| Registry install with progress | Done | `Views/Settings/Plugins/RegistryPluginRow.swift` | Download + SHA-256 verification, multi-phase progress |
2828
| Contextual install prompt (connection flow) | Done | `Views/Connection/PluginInstallModifier.swift` | Alert when opening DB type with missing driver |
29-
| Code signature verification | Done | `Core/Plugins/PluginManager.swift:558` | Team ID `D7HJ5TFYCU` for user-installed plugins |
29+
| Code signature verification | Done | `Core/Plugins/PluginManager.swift:586` | Team ID `D7HJ5TFYCU` for user-installed plugins |
3030

3131
## Per-Plugin Configuration
3232

@@ -50,7 +50,7 @@ All export plugins conform to `SettablePlugin` which provides automatic `loadSet
5050

5151
### Driver Plugins
5252

53-
All 8 driver plugins have zero per-plugin settings. They can adopt `SettablePlugin` when settings are needed.
53+
All 9 driver plugins have zero per-plugin settings. They can adopt `SettablePlugin` when settings are needed.
5454

5555
---
5656

0 commit comments

Comments
 (0)