Skip to content

Commit b816765

Browse files
Prachi Gauriarprachigauriar
authored andcommitted
Require the consumer to provide a dismiss closure to the editor
- Rename ConfigVariableEditor.onSave to dismiss - If no dismiss is provided, default to dismissing the view - Remove requiresRelaunch metadata key, as we don’t want to encourage requiring relaunch vs. responding to the change live
1 parent 6cef800 commit b816765

11 files changed

Lines changed: 149 additions & 127 deletions

File tree

App/Sources/App/ContentView.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ struct ContentView: View {
3333
Button("Do something", role: .destructive) {
3434
print("Did something!")
3535
}
36-
} onSave: { variables in
36+
} dismiss: { variables in
3737
print(variables)
38+
isPresentingConfigEditor = false
3839
}
3940
}
4041
}

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ metadata.
4242
`ConfigVariableContent`, `CodableValueRepresentation`, `RegisteredConfigVariable`,
4343
and `ConfigVariableSecrecy`
4444
- **Sources/DevConfiguration/Metadata/**: `ConfigVariableMetadata` and metadata key types
45-
(`DisplayNameMetadataKey`, `RequiresRelaunchMetadataKey`)
45+
(`DisplayNameMetadataKey`)
4646
- **Sources/DevConfiguration/Access Reporting/**: EventBus-based access and decoding events
4747

4848
### Key Documents

Sources/DevConfiguration/Editor/Config Variable List/ConfigVariableListView.swift

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,12 @@ struct ConfigVariableListView<ViewModel: ConfigVariableListViewModeling, CustomS
7171
.toolbar { toolbarContent }
7272
.alert(localizedStringResource("editorView.saveAlert.title"), isPresented: $viewModel.isShowingSaveAlert) {
7373
Button(localizedStringResource("editorView.saveAlert.saveButton")) {
74-
viewModel.save()
75-
dismiss()
74+
viewModel.saveAndDismiss { dismiss() }
7675
}
7776
.keyboardShortcut(.defaultAction)
7877

7978
Button(localizedStringResource("editorView.saveAlert.dontSaveButton"), role: .destructive) {
80-
dismiss()
79+
viewModel.dismissWithoutSaving { dismiss() }
8180
}
8281

8382
Button(localizedStringResource("editorView.saveAlert.cancelButton"), role: .cancel) {}
@@ -116,8 +115,7 @@ extension ConfigVariableListView {
116115

117116
ToolbarItem(placement: .confirmationAction) {
118117
Button {
119-
viewModel.save()
120-
dismiss()
118+
viewModel.saveAndDismiss { dismiss() }
121119
} label: {
122120
Label(localizedStringResource("editorView.saveButton"), systemImage: "checkmark")
123121
}

Sources/DevConfiguration/Editor/Config Variable List/ConfigVariableListViewModel.swift

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@ final class ConfigVariableListViewModel: ConfigVariableListViewModeling {
2020
/// The document that owns the variable data.
2121
private let document: EditorDocument
2222

23-
/// The closure to call with the changed variables when the user saves.
24-
private let onSave: ([RegisteredConfigVariable]) -> Void
23+
/// An optional closure to call when the editor is dismissed.
24+
///
25+
/// It receives the registered variables whose overrides changed, or an empty array if the user dismissed without
26+
/// saving.
27+
private let dismiss: (([RegisteredConfigVariable]) -> Void)?
2528

2629
var searchText = ""
2730
var isShowingSaveAlert = false
@@ -32,10 +35,12 @@ final class ConfigVariableListViewModel: ConfigVariableListViewModeling {
3235
///
3336
/// - Parameters:
3437
/// - document: The editor document.
35-
/// - onSave: A closure called with the registered variables whose overrides changed when the user saves.
36-
init(document: EditorDocument, onSave: @escaping ([RegisteredConfigVariable]) -> Void) {
38+
/// - dismiss: An optional closure called when the editor is dismissed. It receives the registered variables whose
39+
/// overrides changed, or an empty array if the user dismissed without saving. If `nil`, the view's environment
40+
/// dismiss action is used instead.
41+
init(document: EditorDocument, dismiss: (([RegisteredConfigVariable]) -> Void)?) {
3742
self.document = document
38-
self.onSave = onSave
43+
self.dismiss = dismiss
3944
}
4045

4146

@@ -95,15 +100,30 @@ final class ConfigVariableListViewModel: ConfigVariableListViewModeling {
95100
if isDirty {
96101
isShowingSaveAlert = true
97102
} else {
98-
dismiss()
103+
dismissWithoutSaving(dismiss)
99104
}
100105
}
101106

102107

103-
func save() {
108+
func saveAndDismiss(_ dismiss: () -> Void) {
104109
let changedKeys = document.changedKeys
105110
document.save()
106-
onSave(changedKeys.compactMap { document.registeredVariables[$0] })
111+
let changedVariables = changedKeys.compactMap { document.registeredVariables[$0] }
112+
113+
if let dismiss = self.dismiss {
114+
dismiss(changedVariables)
115+
} else {
116+
dismiss()
117+
}
118+
}
119+
120+
121+
func dismissWithoutSaving(_ dismiss: () -> Void) {
122+
if let dismiss = self.dismiss {
123+
dismiss([])
124+
} else {
125+
dismiss()
126+
}
107127
}
108128

109129

Sources/DevConfiguration/Editor/Config Variable List/ConfigVariableListViewModeling.swift

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,26 @@ protocol ConfigVariableListViewModeling: Observable {
4343

4444
/// Requests dismissal of the editor.
4545
///
46-
/// If the working copy has unsaved changes, this presents the save alert. Otherwise, it calls the dismiss closure
47-
/// immediately.
46+
/// If the working copy has unsaved changes, this presents the save alert. Otherwise, it dismisses without saving.
4847
///
49-
/// - Parameter dismiss: A closure that dismisses the editor view.
48+
/// - Parameter dismiss: A closure that dismisses the editor view using the environment's dismiss action.
5049
func requestDismiss(_ dismiss: () -> Void)
5150

52-
/// Saves the working copy to the editor override provider.
53-
func save()
51+
/// Saves the working copy and dismisses the editor.
52+
///
53+
/// This saves the document and calls the consumer's dismiss closure with the changed variables. If no consumer
54+
/// dismiss closure was provided, it calls the provided `dismiss` closure instead.
55+
///
56+
/// - Parameter dismiss: A closure that dismisses the editor view using the environment's dismiss action.
57+
func saveAndDismiss(_ dismiss: () -> Void)
58+
59+
/// Dismisses the editor without saving.
60+
///
61+
/// This calls the consumer's dismiss closure with an empty array. If no consumer dismiss closure was provided, it
62+
/// calls the provided `dismiss` closure instead.
63+
///
64+
/// - Parameter dismiss: A closure that dismisses the editor view using the environment's dismiss action.
65+
func dismissWithoutSaving(_ dismiss: () -> Void)
5466

5567
/// Requests clearing all overrides by presenting the clear confirmation alert.
5668
func requestClearAllOverrides()

Sources/DevConfiguration/Editor/ConfigVariableEditor.swift

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ import SwiftUI
1212
/// A SwiftUI view that presents the configuration variable editor.
1313
///
1414
/// `ConfigVariableEditor` is initialized with a ``ConfigVariableReader`` that has editor support enabled and an
15-
/// `onSave` closure that receives the registered variables whose overrides changed.
15+
/// optional `dismiss` closure that receives the registered variables whose overrides changed.
1616
///
17-
/// The consumer is responsible for presentation (sheet, full-screen cover, navigation push, etc.).
17+
/// The consumer is responsible for presentation (sheet, full-screen cover, navigation push, etc.). If no `dismiss`
18+
/// closure is provided, the editor uses the environment's dismiss action.
1819
///
1920
/// .sheet(isPresented: $isEditorPresented) {
2021
/// ConfigVariableEditor(reader: reader) { changedVariables in
21-
/// // Handle changed variables
22+
/// // Handle changed variables and dismiss
2223
/// }
2324
/// }
2425
///
@@ -29,8 +30,8 @@ import SwiftUI
2930
/// customSectionTitle: "Actions"
3031
/// ) {
3132
/// Button("Reset All") { … }
32-
/// } onSave: { changedVariables in
33-
/// // Handle changed variables
33+
/// } dismiss: { changedVariables in
34+
/// // Handle changed variables and dismiss
3435
/// }
3536
public struct ConfigVariableEditor<CustomSection: View>: View {
3637
/// The list view model created from the reader.
@@ -50,18 +51,20 @@ public struct ConfigVariableEditor<CustomSection: View>: View {
5051
/// `true`, the view is empty.
5152
/// - customSectionTitle: The title for the custom section.
5253
/// - customSection: A view builder that produces custom content to display in a section at the top of the list.
53-
/// - onSave: A closure called with the registered variables whose overrides changed when the user saves.
54+
/// - dismiss: An optional closure called when the editor is dismissed. It receives the registered variables whose
55+
/// overrides changed, or an empty array if dismissed without saving. If `nil`, the environment's dismiss action
56+
/// is used.
5457
public init(
5558
reader: ConfigVariableReader,
5659
customSectionTitle: LocalizedStringKey,
5760
@ViewBuilder customSection: () -> CustomSection,
58-
onSave: @escaping ([RegisteredConfigVariable]) -> Void,
61+
dismiss: (([RegisteredConfigVariable]) -> Void)? = nil,
5962
) {
6063
self.init(
6164
reader: reader,
6265
customSectionTitle: Text(customSectionTitle),
6366
customSection: customSection,
64-
onSave: onSave,
67+
dismiss: dismiss,
6568
)
6669
}
6770

@@ -73,16 +76,18 @@ public struct ConfigVariableEditor<CustomSection: View>: View {
7376
/// `true`, the view is empty.
7477
/// - customSectionTitle: A `Text` view to use as the title for the custom section.
7578
/// - customSection: A view builder that produces custom content to display in a section at the top of the list.
76-
/// - onSave: A closure called with the registered variables whose overrides changed when the user saves.
79+
/// - dismiss: An optional closure called when the editor is dismissed. It receives the registered variables whose
80+
/// overrides changed, or an empty array if dismissed without saving. If `nil`, the environment's dismiss action
81+
/// is used.
7782
public init(
7883
reader: ConfigVariableReader,
7984
customSectionTitle: Text,
8085
@ViewBuilder customSection: () -> CustomSection,
81-
onSave: @escaping ([RegisteredConfigVariable]) -> Void,
86+
dismiss: (([RegisteredConfigVariable]) -> Void)? = nil,
8287
) {
8388
self.customSectionTitle = customSectionTitle
8489
self.customSection = customSection()
85-
self._viewModel = Self.makeViewModel(reader: reader, onSave: onSave)
90+
self._viewModel = Self.makeViewModel(reader: reader, dismiss: dismiss)
8691
}
8792

8893

@@ -99,7 +104,7 @@ public struct ConfigVariableEditor<CustomSection: View>: View {
99104

100105
private static func makeViewModel(
101106
reader: ConfigVariableReader,
102-
onSave: @escaping ([RegisteredConfigVariable]) -> Void,
107+
dismiss: (([RegisteredConfigVariable]) -> Void)?,
103108
) -> State<ConfigVariableListViewModel?> {
104109
guard let editorOverrideProvider = reader.editorOverrideProvider else {
105110
return State(initialValue: nil)
@@ -117,7 +122,7 @@ public struct ConfigVariableEditor<CustomSection: View>: View {
117122
undoManager: UndoManager(),
118123
)
119124

120-
return State(initialValue: ConfigVariableListViewModel(document: document, onSave: onSave))
125+
return State(initialValue: ConfigVariableListViewModel(document: document, dismiss: dismiss))
121126
}
122127
}
123128

@@ -128,14 +133,16 @@ extension ConfigVariableEditor where CustomSection == EmptyView {
128133
/// - Parameters:
129134
/// - reader: The configuration variable reader. If the reader was not created with `isEditorEnabled` set to
130135
/// `true`, the view is empty.
131-
/// - onSave: A closure called with the registered variables whose overrides changed when the user saves.
136+
/// - dismiss: An optional closure called when the editor is dismissed. It receives the registered variables whose
137+
/// overrides changed, or an empty array if dismissed without saving. If `nil`, the environment's dismiss action
138+
/// is used.
132139
public init(
133140
reader: ConfigVariableReader,
134-
onSave: @escaping ([RegisteredConfigVariable]) -> Void,
141+
dismiss: (([RegisteredConfigVariable]) -> Void)? = nil,
135142
) {
136143
self.customSectionTitle = Text(verbatim: "")
137144
self.customSection = EmptyView()
138-
self._viewModel = Self.makeViewModel(reader: reader, onSave: onSave)
145+
self._viewModel = Self.makeViewModel(reader: reader, dismiss: dismiss)
139146
}
140147
}
141148

Sources/DevConfiguration/Metadata/RequiresRelaunchMetadataKey.swift

Lines changed: 0 additions & 26 deletions
This file was deleted.

Sources/DevConfiguration/Resources/Localizable.xcstrings

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -370,16 +370,6 @@
370370
}
371371
}
372372
}
373-
},
374-
"requiresRelaunchMetadata.keyDisplayText" : {
375-
"localizations" : {
376-
"en" : {
377-
"stringUnit" : {
378-
"state" : "translated",
379-
"value" : "Requires Relaunch"
380-
}
381-
}
382-
}
383373
}
384374
},
385375
"version" : "1.0"

Tests/DevConfigurationTests/Unit Tests/Editor/Config Variable Detail/ConfigVariableDetailViewModelTests.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ struct ConfigVariableDetailViewModelTests: RandomValueGenerating {
6060
// set up
6161
var metadata = ConfigVariableMetadata()
6262
metadata.displayName = randomAlphanumericString()
63-
metadata.requiresRelaunch = randomBool()
6463
metadata.isEditable = randomBool()
6564
let destinationTypeName = randomAlphanumericString()
6665
let isSecret = randomBool()

0 commit comments

Comments
 (0)