Add edit functionality to word replacements
This commit is contained in:
parent
080f985059
commit
2a9ead63de
134
VoiceInk/Views/Dictionary/EditReplacementSheet.swift
Normal file
134
VoiceInk/Views/Dictionary/EditReplacementSheet.swift
Normal file
@ -0,0 +1,134 @@
|
||||
import SwiftUI
|
||||
|
||||
/// A reusable sheet for editing an existing word replacement entry.
|
||||
/// Mirrors the UI of `AddReplacementSheet` for consistency while pre-populating
|
||||
/// the fields with the existing values.
|
||||
struct EditReplacementSheet: View {
|
||||
@ObservedObject var manager: WordReplacementManager
|
||||
let originalKey: String
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@State private var originalWord: String
|
||||
@State private var replacementWord: String
|
||||
|
||||
// MARK: – Initialiser
|
||||
init(manager: WordReplacementManager, originalKey: String) {
|
||||
self.manager = manager
|
||||
self.originalKey = originalKey
|
||||
_originalWord = State(initialValue: originalKey)
|
||||
_replacementWord = State(initialValue: manager.replacements[originalKey] ?? "")
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
header
|
||||
Divider()
|
||||
formContent
|
||||
}
|
||||
.frame(width: 460, height: 480)
|
||||
}
|
||||
|
||||
// MARK: – Subviews
|
||||
private var header: some View {
|
||||
HStack {
|
||||
Button("Cancel", role: .cancel) { dismiss() }
|
||||
.buttonStyle(.borderless)
|
||||
.keyboardShortcut(.escape, modifiers: [])
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("Edit Word Replacement")
|
||||
.font(.headline)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button("Save") { saveChanges() }
|
||||
.buttonStyle(.borderedProminent)
|
||||
.controlSize(.small)
|
||||
.disabled(originalWord.isEmpty || replacementWord.isEmpty)
|
||||
.keyboardShortcut(.return, modifiers: [])
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.vertical, 12)
|
||||
.background(CardBackground(isSelected: false))
|
||||
}
|
||||
|
||||
private var formContent: some View {
|
||||
ScrollView {
|
||||
VStack(spacing: 20) {
|
||||
descriptionSection
|
||||
inputSection
|
||||
}
|
||||
.padding(.vertical)
|
||||
}
|
||||
}
|
||||
|
||||
private var descriptionSection: some View {
|
||||
Text("Update the word or phrase that should be automatically replaced during AI enhancement.")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.horizontal)
|
||||
.padding(.top, 8)
|
||||
}
|
||||
|
||||
private var inputSection: some View {
|
||||
VStack(spacing: 16) {
|
||||
// Original Text Field
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
HStack {
|
||||
Text("Original Text")
|
||||
.font(.headline)
|
||||
Text("Required")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
TextField("Enter word or phrase to replace", text: $originalWord)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
// Replacement Text Field
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
HStack {
|
||||
Text("Replacement Text")
|
||||
.font(.headline)
|
||||
Text("Required")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
TextEditor(text: $replacementWord)
|
||||
.font(.body)
|
||||
.frame(height: 100)
|
||||
.padding(8)
|
||||
.background(Color(.textBackgroundColor))
|
||||
.cornerRadius(6)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 6)
|
||||
.stroke(Color(.separatorColor), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: – Actions
|
||||
private func saveChanges() {
|
||||
let newOriginal = originalWord.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let newReplacement = replacementWord.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !newOriginal.isEmpty, !newReplacement.isEmpty else { return }
|
||||
|
||||
manager.updateReplacement(oldOriginal: originalKey, newOriginal: newOriginal, newReplacement: newReplacement)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: – Preview
|
||||
#if DEBUG
|
||||
struct EditReplacementSheet_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
EditReplacementSheet(manager: WordReplacementManager(), originalKey: "hello")
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -1,5 +1,9 @@
|
||||
import SwiftUI
|
||||
|
||||
extension String: Identifiable {
|
||||
public var id: String { self }
|
||||
}
|
||||
|
||||
class WordReplacementManager: ObservableObject {
|
||||
@Published var replacements: [String: String] {
|
||||
didSet {
|
||||
@ -25,12 +29,23 @@ class WordReplacementManager: ObservableObject {
|
||||
func removeReplacement(original: String) {
|
||||
replacements.removeValue(forKey: original)
|
||||
}
|
||||
|
||||
func updateReplacement(oldOriginal: String, newOriginal: String, newReplacement: String) {
|
||||
// Remove the old key if the original text has changed
|
||||
if oldOriginal != newOriginal {
|
||||
replacements.removeValue(forKey: oldOriginal)
|
||||
}
|
||||
// Update (or insert) the new key/value pair
|
||||
replacements[newOriginal] = newReplacement
|
||||
}
|
||||
}
|
||||
|
||||
struct WordReplacementView: View {
|
||||
@StateObject private var manager = WordReplacementManager()
|
||||
@State private var showAddReplacementModal = false
|
||||
@State private var showAlert = false
|
||||
@State private var editingOriginal: String? = nil
|
||||
|
||||
@State private var alertMessage = ""
|
||||
|
||||
var body: some View {
|
||||
@ -88,7 +103,8 @@ struct WordReplacementView: View {
|
||||
ReplacementRow(
|
||||
original: original,
|
||||
replacement: manager.replacements[original] ?? "",
|
||||
onDelete: { manager.removeReplacement(original: original) }
|
||||
onDelete: { manager.removeReplacement(original: original) },
|
||||
onEdit: { editingOriginal = original }
|
||||
)
|
||||
|
||||
if original != manager.replacements.keys.sorted().last {
|
||||
@ -106,6 +122,11 @@ struct WordReplacementView: View {
|
||||
.sheet(isPresented: $showAddReplacementModal) {
|
||||
AddReplacementSheet(manager: manager)
|
||||
}
|
||||
// Edit existing replacement
|
||||
.sheet(item: $editingOriginal) { original in
|
||||
EditReplacementSheet(manager: manager, originalKey: original)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -287,6 +308,7 @@ struct ReplacementRow: View {
|
||||
let original: String
|
||||
let replacement: String
|
||||
let onDelete: () -> Void
|
||||
let onEdit: () -> Void
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 16) {
|
||||
@ -321,6 +343,16 @@ struct ReplacementRow: View {
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
// Edit Button
|
||||
Button(action: onEdit) {
|
||||
Image(systemName: "pencil.circle.fill")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.system(size: 16))
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
.help("Edit replacement")
|
||||
|
||||
// Delete Button
|
||||
Button(action: onDelete) {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user