diff --git a/ShadowsocksX-NG2/App/Resources/Localizable.xcstrings b/ShadowsocksX-NG2/App/Resources/Localizable.xcstrings index 8a70085d..abbe2b3a 100644 --- a/ShadowsocksX-NG2/App/Resources/Localizable.xcstrings +++ b/ShadowsocksX-NG2/App/Resources/Localizable.xcstrings @@ -1100,6 +1100,28 @@ } } } + }, + "名称": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Name" + } + } + } + }, + "重命名分组": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Group" + } + } + } } }, "version": "1.0" diff --git a/ShadowsocksX-NG2/App/UI/Servers/RenameGroupSheet.swift b/ShadowsocksX-NG2/App/UI/Servers/RenameGroupSheet.swift new file mode 100644 index 00000000..ab3466f9 --- /dev/null +++ b/ShadowsocksX-NG2/App/UI/Servers/RenameGroupSheet.swift @@ -0,0 +1,53 @@ +import SwiftUI + +/// 重命名分组表单:名称初值取现名;失败保留名称,成功才关闭。 +struct RenameGroupSheet: View { + let workflow: CatalogWorkflow + let errors: ErrorAlertPresenter + let nodeID: NodeID + @Environment(\.dismiss) private var dismiss + @State private var name: String + @State private var isSubmitting = false + @FocusState private var nameFocused: Bool + + init(workflow: CatalogWorkflow, errors: ErrorAlertPresenter, nodeID: NodeID) { + self.workflow = workflow + self.errors = errors + self.nodeID = nodeID + // 打开时的现名仅作为初值,后续编辑由本表单持有。 + _name = State(initialValue: workflow.displayName(for: nodeID)) + } + + var body: some View { + VStack(alignment: .leading, spacing: 16) { + Text("重命名分组").font(.title2) + TextField("名称", text: $name) + .focused($nameFocused) + HStack { + Spacer() + Button("取消", role: .cancel) { dismiss() } + Button("保存") { rename() } + .keyboardShortcut(.defaultAction) + } + } + .padding(20) + .frame(width: 360) + .disabled(isSubmitting) + .interactiveDismissDisabled(isSubmitting) + .defaultFocus($nameFocused, true) + } + + private func rename() { + guard !isSubmitting else { return } + isSubmitting = true + Task { + defer { isSubmitting = false } + do { + try await workflow.renameGroup(nodeID, to: name) + dismiss() + } catch { + errors.present(error) + } + } + } +} diff --git a/ShadowsocksX-NG2/App/UI/Servers/ServersView.swift b/ShadowsocksX-NG2/App/UI/Servers/ServersView.swift index ae41d261..4ee729f1 100644 --- a/ShadowsocksX-NG2/App/UI/Servers/ServersView.swift +++ b/ShadowsocksX-NG2/App/UI/Servers/ServersView.swift @@ -26,7 +26,6 @@ struct ServersView: View { // 纯窗口状态(issue #41):selection/sheet/alert 不进目录 module。 @State private var renameTarget: NodeID? - @State private var renameText = "" @State private var newGroupParent: NodeID? @State private var isPresentingNewGroup = false @State private var serverFormOperation: ServerFormSheet.Operation? @@ -45,15 +44,12 @@ struct ServersView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) } .frame(minHeight: 420) - .alert( - "重命名分组", - isPresented: Binding( - get: { renameTarget != nil }, - set: { if !$0 { renameTarget = nil } }) - ) { - TextField("名称", text: $renameText) - Button("确定") { commitRename() } - Button("取消", role: .cancel) { renameTarget = nil } + .sheet( + item: Binding( + get: { renameTarget.map(NodeContext.init) }, + set: { renameTarget = $0?.nodeID }) + ) { context in + RenameGroupSheet(workflow: workflow, errors: errors, nodeID: context.nodeID) } .sheet(isPresented: $isPresentingNewGroup) { NewGroupSheet( @@ -75,7 +71,7 @@ struct ServersView: View { } .sheet( item: Binding( - get: { moveTarget.map(MoveContext.init) }, + get: { moveTarget.map(NodeContext.init) }, set: { moveTarget = $0?.nodeID }) ) { context in MoveNodeSheet(workflow: workflow, errors: errors, nodeID: context.nodeID) @@ -146,10 +142,7 @@ struct ServersView: View { activeTargetID: activeTargetID, errors: errors, onEdit: presentEditServer, - onRename: { id in - renameTarget = id - renameText = workflow.displayName(for: id) - }, + onRename: { renameTarget = $0 }, onNewGroup: { parent in presentNewGroup(in: parent) }, @@ -362,18 +355,6 @@ extension ServersView { } } - private func commitRename() { - guard let id = renameTarget else { return } - renameTarget = nil - Task { - do { - try await workflow.renameGroup(id, to: renameText) - } catch { - errors.present(error) - } - } - } - /// 删除确认文案:档位与规模事实来自 seam;句子由 UI 拼(Q3-A)。 private var deleteTitle: String { guard let id = deleteTarget else { return "" } @@ -413,8 +394,8 @@ extension ServersView { } } -/// moveTarget 的 sheet(item:) 适配壳。 -private struct MoveContext: Identifiable { +/// moveTarget/renameTarget 的 sheet(item:) 适配壳。 +private struct NodeContext: Identifiable { let nodeID: NodeID var id: NodeID { nodeID } }