Skip to content

Commit

Permalink
Custom Fields: Refactor AztecEditorView to be reusable (#13925)
Browse files Browse the repository at this point in the history
  • Loading branch information
hafizrahman authored Sep 30, 2024
2 parents 89d2c06 + 13b7bec commit 39c6935
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 116 deletions.
Original file line number Diff line number Diff line change
@@ -1,39 +1,18 @@
import SwiftUI

struct AztecEditorView: UIViewControllerRepresentable {
@Binding var html: String

func makeUIViewController(context: Context) -> AztecEditorViewController {
let viewProperties = EditorViewProperties(navigationTitle: "Navigation title", /* todo replace string */
placeholderText: "placeholder text", /* todo replace string */
showSaveChangesActionSheet: true)

let controller = AztecEditorViewController(
content: html,
product: nil,
viewProperties: viewProperties,
isAIGenerationEnabled: false
)

return controller
}

func updateUIViewController(_ uiViewController: AztecEditorViewController, context: Context) {
// Update the view controller if needed
}
}

struct CustomFieldEditorView: View {
@State private var key: String
@State private var value: String
@State private var hasUnsavedChanges: Bool = false
@State private var showRichTextEditor: Bool = false
@State private var showingActionSheet: Bool = false
@State private var showRichTextEditor = false
@State private var showingActionSheet = false

private let initialKey: String
private let initialValue: String
private let isReadOnlyValue: Bool

private var hasUnsavedChanges: Bool {
key != initialKey || value != initialValue
}

/// Initializer for custom field editor
/// - Parameters:
Expand All @@ -53,54 +32,59 @@ struct CustomFieldEditorView: View {
VStack(alignment: .leading, spacing: Layout.sectionSpacing) {
// Key Input
VStack(alignment: .leading, spacing: Layout.subSectionsSpacing) {
Text("Key") // todo-13493: set String to be translatable
Text(Localization.keyLabel)
.foregroundColor(Color(.text))
.subheadlineStyle()

TextField("Enter key", text: $key) // todo-13493: set String to be translatable
.bodyStyle()
TextField(Localization.keyPlaceholder, text: $key)
.foregroundColor(Color(.text))
.subheadlineStyle()
.padding(insets: Layout.inputInsets)
.background(Color(.listForeground(modal: false)))
.overlay(
RoundedRectangle(cornerRadius: Layout.cornerRadius).stroke(Color(.separator))
)
.onChange(of: key) { _ in
checkForModifications()
}
}

// Value Input
VStack(alignment: .leading, spacing: Layout.subSectionsSpacing) {
HStack {
Text("Value") // todo-13493: set String to be translatable
Text(Localization.valueLabel)
.foregroundColor(Color(.text))
.subheadlineStyle()
.frame(maxWidth: .infinity, alignment: .leading)

Spacer()

if !isReadOnlyValue {
Button(action: {
showRichTextEditor = true
}) {
Text("Edit in Rich Text Editor") // todo-13493: set String to be translatable
.font(.footnote)
Picker(Localization.editorPickerLabel, selection: $showRichTextEditor) {
Text(Localization.editorPickerText).tag(false)
Text(Localization.editorPickerHTML).tag(true)
}
.buttonStyle(.plain)
.foregroundColor(Color(uiColor: .accent))
.pickerStyle(.segmented)
}
}

TextEditor(text: isReadOnlyValue ? .constant(value) : $value)
.bodyStyle()
if showRichTextEditor {
AztecEditorView(content: $value)
.frame(minHeight: Layout.minimumEditorSize)
.clipped()
.padding(insets: Layout.inputInsets)
.background(Color(.listForeground(modal: false)))
.overlay(
RoundedRectangle(cornerRadius: Layout.cornerRadius).stroke(Color(.separator))
)
.onChange(of: value) { _ in
checkForModifications()
}
} else {
TextEditor(text: isReadOnlyValue ? .constant(value) : $value)
.foregroundColor(Color(.text))
.subheadlineStyle()
.frame(minHeight: Layout.minimumEditorSize)
.padding(insets: Layout.inputInsets)
.background(Color(.listForeground(modal: false)))
.overlay(
RoundedRectangle(cornerRadius: Layout.cornerRadius).stroke(Color(.separator))
)
}
}
}
.padding()
Expand Down Expand Up @@ -128,12 +112,6 @@ struct CustomFieldEditorView: View {
}
}
}
.sheet(isPresented: $showRichTextEditor) {
RichTextEditor(html: $value)
.onDisappear {
checkForModifications()
}
}
}

@ViewBuilder
Expand All @@ -153,45 +131,11 @@ struct CustomFieldEditorView: View {
}
}

private func checkForModifications() {
hasUnsavedChanges = key != initialKey || value != initialValue
}

private func saveChanges() {
// todo-13493: add save logic
}
}

private struct RichTextEditor: View {
@Binding var html: String
@State private var isModified: Bool = false
@Environment(\.presentationMode) var presentationMode

var body: some View {
NavigationView {
AztecEditorView(html: $html)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
presentationMode.wrappedValue.dismiss()
} label: {
Text("Cancel") // todo-13493: set String to be translatable
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button {
// todo-13493: implement save action
presentationMode.wrappedValue.dismiss()
} label: {
Text("Done") // todo-13493: set String to be translatable
}
.disabled(!isModified)
}
}
}
}
}

// MARK: Constants
private extension CustomFieldEditorView {
enum Layout {
Expand All @@ -201,6 +145,44 @@ private extension CustomFieldEditorView {
static let inputInsets = EdgeInsets(top: 8, leading: 5, bottom: 8, trailing: 5)
static let minimumEditorSize: CGFloat = 400
}

enum Localization {
static let keyLabel = NSLocalizedString(
"customFieldEditorView.keyLabel",
value: "Key",
comment: "Label for the Key field"
)

static let keyPlaceholder = NSLocalizedString(
"customFieldEditorView.keyPlaceholder",
value: "Enter key",
comment: "Placeholder for the Key field"
)

static let valueLabel = NSLocalizedString(
"customFieldEditorView.valueLabel",
value: "Value",
comment: "Label for the Value field"
)

static let editorPickerLabel = NSLocalizedString(
"customFieldEditorView.editorPickerLabel",
value: "Choose text editing mode",
comment: "Label for the Editor type picker"
)

static let editorPickerText = NSLocalizedString(
"customFieldEditorView.editorPickerText",
value: "Text",
comment: "Picker option for using Text Editor"
)

static let editorPickerHTML = NSLocalizedString(
"customFieldEditorView.editorPickerHTML",
value: "HTML",
comment: "Picker option for using Text Editor"
)
}
}

#Preview {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ struct CustomFieldViewModel: Identifiable {
self.init(
id: metadata.metadataID,
title: metadata.key,
content: metadata.value.removedHTMLTags,
content: metadata.value,
contentURL: contentURL
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ struct CustomFieldsListView: View {
) {
CustomFieldRow(isEditable: true,
title: customField.title,
content: customField.content,
content: customField.content.removedHTMLTags,
contentURL: customField.contentURL)
}
}
else {
CustomFieldRow(isEditable: false,
title: customField.title,
content: customField.content,
content: customField.content.removedHTMLTags,
contentURL: customField.contentURL)
}

Expand Down
34 changes: 34 additions & 0 deletions WooCommerce/Classes/ViewRelated/Editor/AztecEditorView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import SwiftUI

/// `UIViewControllerRepresentable` to use an Aztec Editor in SwiftUI.
/// Params:
/// - content: the content of the editor. It's a Binding so that the parent View can get the latest Editor content.
///
struct AztecEditorView: UIViewControllerRepresentable {
@Binding var content: String

func makeUIViewController(context: Context) -> AztecEditorViewController {
let controller = EditorFactory().customFieldRichTextEditor(initialValue: content)
var ignoreInitialContent = true

guard let aztecController = controller as? AztecEditorViewController else {
fatalError("EditorFactory must return an AztecEditorViewController, but returned \(type(of: controller))")
}

aztecController.onContentChanged = { text in
/// In addition to user's change action, this callback is invokeddssd during the View Controller's `viewDidLoad` too.
/// This check is needed to avoid setting the value back to the binding at that point, as doing so will trigger the
/// "Modifying state during view update, this will cause undefined behavior" issue.
if ignoreInitialContent {
ignoreInitialContent = false
return
}

content = text
}
return aztecController
}

func updateUIViewController(_ uiViewController: AztecEditorViewController, context: Context) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import WordPressEditor
/// Aztec's Native Editor!
final class AztecEditorViewController: UIViewController, Editor {
var onContentSave: OnContentSave?
var onContentChanged: ((String) -> Void)?

private var content: String
private var productName: String?
Expand Down Expand Up @@ -300,6 +301,7 @@ extension AztecEditorViewController: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
refreshPlaceholderVisibility()
formatBar.update(editorView: editorView)
onContentChanged?(getHTML())
}

func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
Expand Down
Loading

0 comments on commit 39c6935

Please sign in to comment.