Skip to content

Commit 719f005

Browse files
authored
refactor(services): inject dependencies into AppSettingsManager and add four singletons to AppServices (#1157)
1 parent 12b7b2a commit 719f005

3 files changed

Lines changed: 67 additions & 33 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3333
- Internal: drop four 1-to-1 PassthroughSubject buses (`saveAsFavoriteRequested`, `focusConnectionFormWindowRequested`, `openSampleDatabaseRequested`, `resetSampleDatabaseRequested`) for direct ownership. The favorite-edit dialog query is now observable state on `MainContentCoordinator`, the connection-form focus lookup is inlined in `WelcomeViewModel`, and the sample-database menu items call `SampleDatabaseLauncher` directly. `AppDelegate` no longer needs `commandCancellables`.
3434
- Internal: tighten dependency injection in the four files that already accept `services: AppServices`. `ConnectionFormCoordinator`, `MainContentCoordinator`, `WelcomeViewModel`, and `ERDiagramViewModel` now read `services.appEvents.*` instead of mixing in raw `AppEvents.shared.*` for events they were already supposed to thread through the container.
3535
- Internal: extend `AppServices` with `appSettingsStorage`, `schemaProviderRegistry`, `aiKeyStorage`, `groupStorage`, `favoritesExpansionState`, and `linkedFolderWatcher`. Convert the raw `.shared` reads of those types in `AIChatViewModel`, `FavoritesSidebarViewModel`, `WelcomeViewModel`, and `MainContentCoordinator` to `services.*`.
36+
- Internal: extend `AppServices` with `queryHistoryManager`, `dateFormattingService`, `copilotService`, and `mcpServerManager`. `AppSettingsManager` now takes its eight cross-singleton dependencies (`AppSettingsStorage`, `ThemeEngine`, `SyncChangeTracker`, `AppEvents`, `DateFormattingService`, `QueryHistoryManager`, `MCPServerManager`, `CopilotService`) via init with `.shared` defaults. The 32 raw `.shared` reads inside `AppSettingsManager` are gone, including the four `Task { ... }` capture closures that previously dialed `CopilotService.shared` / `MCPServerManager.shared` mid-`didSet`. The 49 caller-side `AppSettingsManager.shared` reads are unchanged in this PR; they will be threaded as their owners adopt `services` in subsequent waves.
3637
- Internal: extend `AppServices` with `tagStorage`, `sshProfileStorage`, `licenseManager`, `conflictResolver`, and `syncMetadataStorage`. `SyncCoordinator` now takes `services: AppServices` in init (default `.live`); 34 raw `.shared` reads of those types inside `SyncCoordinator` are routed through `services.*`.
3738
- Internal: Redis sidebar key tree uses SwiftUI `OutlineGroup` instead of recursive `DisclosureGroup` + `ForEach` wrapped in `AnyView`. Expansion state is now managed natively per branch identifier; the explicit `expandedPrefixes` set is gone.
3839
- Result-grid cells render via direct `draw(_:)` on a layer-backed `NSView` instead of an `NSTableCellView` wrapping an `NSTextField` plus an `NSButton` accessory. Per cell during scroll there is no Auto Layout solving, no `NSTextField` re-layout, and no `NSButton` tracking-area work. Editing for plain-text columns now opens the overlay editor (the same surface previously used for multi-line cells) rather than an inline text field.

TablePro/Core/Services/AppServices.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ struct AppServices {
2727
let syncMetadataStorage: SyncMetadataStorage
2828
let favoritesExpansionState: FavoritesExpansionState
2929
let linkedFolderWatcher: LinkedFolderWatcher
30+
let queryHistoryManager: QueryHistoryManager
31+
let dateFormattingService: DateFormattingService
32+
let copilotService: CopilotService
33+
let mcpServerManager: MCPServerManager
3034
let syncTracker: SyncChangeTracker
3135
let themeEngine: ThemeEngine
3236
let feedbackAPIClient: FeedbackAPIClient
@@ -52,6 +56,10 @@ struct AppServices {
5256
syncMetadataStorage: .shared,
5357
favoritesExpansionState: .shared,
5458
linkedFolderWatcher: .shared,
59+
queryHistoryManager: .shared,
60+
dateFormattingService: .shared,
61+
copilotService: .shared,
62+
mcpServerManager: .shared,
5563
syncTracker: .shared,
5664
themeEngine: .shared,
5765
feedbackAPIClient: .shared

TablePro/Core/Storage/AppSettingsManager.swift

Lines changed: 58 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -19,34 +19,34 @@ final class AppSettingsManager {
1919
didSet {
2020
general.language.apply()
2121
storage.saveGeneral(general)
22-
SyncChangeTracker.shared.markDirty(.settings, id: "general")
22+
syncTracker.markDirty(.settings, id: "general")
2323
}
2424
}
2525

2626
var appearance: AppearanceSettings {
2727
didSet {
2828
storage.saveAppearance(appearance)
29-
ThemeEngine.shared.updateAppearanceAndTheme(
29+
themeEngine.updateAppearanceAndTheme(
3030
mode: appearance.appearanceMode,
3131
lightThemeId: appearance.preferredLightThemeId,
3232
darkThemeId: appearance.preferredDarkThemeId
3333
)
34-
SyncChangeTracker.shared.markDirty(.settings, id: "appearance")
34+
syncTracker.markDirty(.settings, id: "appearance")
3535
}
3636
}
3737

3838
var editor: EditorSettings {
3939
didSet {
4040
storage.saveEditor(editor)
41-
ThemeEngine.shared.updateEditorSettings(
41+
themeEngine.updateEditorSettings(
4242
highlightCurrentLine: editor.highlightCurrentLine,
4343
showLineNumbers: editor.showLineNumbers,
4444
tabWidth: editor.clampedTabWidth,
4545

4646
wordWrap: editor.wordWrap
4747
)
48-
AppEvents.shared.editorSettingsChanged.send(())
49-
SyncChangeTracker.shared.markDirty(.settings, id: "editor")
48+
appEvents.editorSettingsChanged.send(())
49+
syncTracker.markDirty(.settings, id: "editor")
5050
}
5151
}
5252

@@ -64,9 +64,9 @@ final class AppSettingsManager {
6464
}
6565

6666
storage.saveDataGrid(validated)
67-
DateFormattingService.shared.updateFormat(validated.dateFormat)
68-
AppEvents.shared.dataGridSettingsChanged.send(())
69-
SyncChangeTracker.shared.markDirty(.settings, id: "dataGrid")
67+
dateFormattingService.updateFormat(validated.dateFormat)
68+
appEvents.dataGridSettingsChanged.send(())
69+
syncTracker.markDirty(.settings, id: "dataGrid")
7070
}
7171
}
7272

@@ -85,37 +85,37 @@ final class AppSettingsManager {
8585

8686
storage.saveHistory(validated)
8787
Task { await applyHistorySettingsImmediately() }
88-
SyncChangeTracker.shared.markDirty(.settings, id: "history")
88+
syncTracker.markDirty(.settings, id: "history")
8989
}
9090
}
9191

9292
var tabs: TabSettings {
9393
didSet {
9494
storage.saveTabs(tabs)
95-
SyncChangeTracker.shared.markDirty(.settings, id: "tabs")
95+
syncTracker.markDirty(.settings, id: "tabs")
9696
}
9797
}
9898

9999
var keyboard: KeyboardSettings {
100100
didSet {
101101
storage.saveKeyboard(keyboard)
102-
SyncChangeTracker.shared.markDirty(.settings, id: "keyboard")
102+
syncTracker.markDirty(.settings, id: "keyboard")
103103
}
104104
}
105105

106106
var ai: AISettings {
107107
didSet {
108108
storage.saveAI(ai)
109-
SyncChangeTracker.shared.markDirty(.settings, id: "ai")
110-
AppEvents.shared.aiSettingsChanged.send(())
109+
syncTracker.markDirty(.settings, id: "ai")
110+
appEvents.aiSettingsChanged.send(())
111111
let hadCopilot = oldValue.providers.contains(where: { $0.type == .copilot })
112112
let hasCopilot = ai.providers.contains(where: { $0.type == .copilot })
113113
if hasCopilot != hadCopilot {
114-
Task {
114+
Task { [copilotService] in
115115
if hasCopilot {
116-
await CopilotService.shared.start()
116+
await copilotService.start()
117117
} else {
118-
await CopilotService.shared.stop()
118+
await copilotService.stop()
119119
}
120120
}
121121
}
@@ -125,15 +125,15 @@ final class AppSettingsManager {
125125
var sync: SyncSettings {
126126
didSet {
127127
storage.saveSync(sync)
128-
SyncChangeTracker.shared.markDirty(.settings, id: "sync")
128+
syncTracker.markDirty(.settings, id: "sync")
129129
}
130130
}
131131

132132
var terminal: TerminalSettings {
133133
didSet {
134134
storage.saveTerminal(terminal)
135-
AppEvents.shared.terminalSettingsChanged.send(())
136-
SyncChangeTracker.shared.markDirty(.settings, id: "terminal")
135+
appEvents.terminalSettingsChanged.send(())
136+
syncTracker.markDirty(.settings, id: "terminal")
137137
}
138138
}
139139

@@ -148,30 +148,55 @@ final class AppSettingsManager {
148148
}
149149

150150
storage.saveMCP(mcp)
151-
SyncChangeTracker.shared.markDirty(.settings, id: "mcp")
151+
syncTracker.markDirty(.settings, id: "mcp")
152152
let enabledChanged = mcp.enabled != oldValue.enabled
153153
let portChanged = mcp.port != oldValue.port
154154
let remoteChanged = mcp.allowRemoteConnections != oldValue.allowRemoteConnections
155155
let authChanged = mcp.requireAuthentication != oldValue.requireAuthentication
156156
if enabledChanged || portChanged || remoteChanged || authChanged {
157157
let settings = mcp
158-
Task {
158+
Task { [mcpServerManager] in
159159
if settings.enabled {
160-
await MCPServerManager.shared.restart(port: UInt16(clamping: settings.port))
160+
await mcpServerManager.restart(port: UInt16(clamping: settings.port))
161161
} else {
162-
await MCPServerManager.shared.stop()
162+
await mcpServerManager.stop()
163163
}
164164
}
165165
}
166166
}
167167
}
168168

169-
@ObservationIgnored private let storage = AppSettingsStorage.shared
169+
@ObservationIgnored private let storage: AppSettingsStorage
170+
@ObservationIgnored private let themeEngine: ThemeEngine
171+
@ObservationIgnored private let syncTracker: SyncChangeTracker
172+
@ObservationIgnored private let appEvents: AppEvents
173+
@ObservationIgnored private let dateFormattingService: DateFormattingService
174+
@ObservationIgnored private let queryHistoryManager: QueryHistoryManager
175+
@ObservationIgnored private let mcpServerManager: MCPServerManager
176+
@ObservationIgnored private let copilotService: CopilotService
170177
@ObservationIgnored private var isValidating = false
171178
@ObservationIgnored private var accessibilityTextSizeObserver: NSObjectProtocol?
172179
@ObservationIgnored private var lastAccessibilityScale: CGFloat = 1.0
173180

174-
private init() {
181+
init(
182+
storage: AppSettingsStorage = .shared,
183+
themeEngine: ThemeEngine = .shared,
184+
syncTracker: SyncChangeTracker = .shared,
185+
appEvents: AppEvents = .shared,
186+
dateFormattingService: DateFormattingService = .shared,
187+
queryHistoryManager: QueryHistoryManager = .shared,
188+
mcpServerManager: MCPServerManager = .shared,
189+
copilotService: CopilotService = .shared
190+
) {
191+
self.storage = storage
192+
self.themeEngine = themeEngine
193+
self.syncTracker = syncTracker
194+
self.appEvents = appEvents
195+
self.dateFormattingService = dateFormattingService
196+
self.queryHistoryManager = queryHistoryManager
197+
self.mcpServerManager = mcpServerManager
198+
self.copilotService = copilotService
199+
175200
self.general = storage.loadGeneral()
176201
self.appearance = storage.loadAppearance()
177202
self.editor = storage.loadEditor()
@@ -186,25 +211,25 @@ final class AppSettingsManager {
186211

187212
general.language.apply()
188213

189-
ThemeEngine.shared.updateAppearanceAndTheme(
214+
themeEngine.updateAppearanceAndTheme(
190215
mode: appearance.appearanceMode,
191216
lightThemeId: appearance.preferredLightThemeId,
192217
darkThemeId: appearance.preferredDarkThemeId
193218
)
194219

195-
ThemeEngine.shared.updateEditorSettings(
220+
themeEngine.updateEditorSettings(
196221
highlightCurrentLine: editor.highlightCurrentLine,
197222
showLineNumbers: editor.showLineNumbers,
198223
tabWidth: editor.clampedTabWidth,
199224
wordWrap: editor.wordWrap
200225
)
201226

202-
DateFormattingService.shared.updateFormat(dataGrid.dateFormat)
227+
dateFormattingService.updateFormat(dataGrid.dateFormat)
203228

204229
observeAccessibilityTextSizeChanges()
205230

206231
if ai.enabled, ai.providers.contains(where: { $0.type == .copilot }) {
207-
Task { await CopilotService.shared.start() }
232+
Task { [copilotService] in await copilotService.start() }
208233
}
209234
}
210235

@@ -236,14 +261,14 @@ final class AppSettingsManager {
236261
guard abs(newScale - lastAccessibilityScale) > 0.01 else { return }
237262
lastAccessibilityScale = newScale
238263
Self.logger.debug("Accessibility text size changed, scale: \(newScale, format: .fixed(precision: 2))")
239-
ThemeEngine.shared.reloadFontCaches()
240-
AppEvents.shared.accessibilityTextSizeChanged.send(())
264+
themeEngine.reloadFontCaches()
265+
appEvents.accessibilityTextSizeChanged.send(())
241266
}
242267
}
243268
}
244269

245270
private func applyHistorySettingsImmediately() async {
246-
await QueryHistoryManager.shared.applySettingsChange()
271+
await queryHistoryManager.applySettingsChange()
247272
}
248273

249274
func resetToDefaults() {

0 commit comments

Comments
 (0)