Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom Fields: Refactor AztecEditorView to be reusable #13925

Merged
merged 34 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
30df87d
Refactor EditorFactory to make it easier to create editor used in non…
hafizrahman Sep 11, 2024
f356cba
Re-add passing product to view controller and simple content paramete…
hafizrahman Sep 11, 2024
251d542
Update onContentSave
hafizrahman Sep 11, 2024
89bcbff
Add factory function for generating custom fields rich text editor.
hafizrahman Sep 11, 2024
d3b17e6
Put AztecEditorView into its own file so it's reusable in the future
hafizrahman Sep 11, 2024
53494ce
Try: Make editor's Done button work by passing the latest change to t…
hafizrahman Sep 11, 2024
3f75b67
Add comment to explain AztecEditorView usage.
hafizrahman Sep 11, 2024
7a9eb96
Revert "Try: Make editor's Done button work by passing the latest cha…
hafizrahman Sep 13, 2024
60b44a4
Update AztecEditorView by wrapping a navigation bar to Aztec so its n…
hafizrahman Sep 13, 2024
9fd2f57
Add `onDone` parameter for callback when Aztec's done button is tapped.
hafizrahman Sep 13, 2024
6d5855b
Update Custom List View's cancel button.
hafizrahman Sep 13, 2024
f32a3f3
Fix typo.
hafizrahman Sep 13, 2024
cc939db
Merge branch 'trunk' into feat/13493-refactor-aztec-make-more-generic
hafizrahman Sep 23, 2024
facdfd8
Revert "Fix typo."
hafizrahman Sep 23, 2024
133b3eb
Revert "Update Custom List View's cancel button."
hafizrahman Sep 23, 2024
e2f1da5
Revert "Add `onDone` parameter for callback when Aztec's done button …
hafizrahman Sep 23, 2024
5a7de7c
Revert "Update AztecEditorView by wrapping a navigation bar to Aztec …
hafizrahman Sep 23, 2024
31cf7e3
Add onContentChanged optional callback to Aztec Editor.
hafizrahman Sep 23, 2024
9d2ddac
Update UI to use segmented control to switch between Text and HTML
hafizrahman Sep 23, 2024
6b2b273
Add onContentChanged initialization to AztecEditorView
hafizrahman Sep 23, 2024
058fe3b
Implement onContentChanged in the View.
hafizrahman Sep 23, 2024
314a522
Handle "Modifying state during view update" issue
hafizrahman Sep 23, 2024
0b779dd
Refactor hasUnsavedChanges logic
hafizrahman Sep 23, 2024
38e4baf
Update UI to put the Picker on right side of Value label
hafizrahman Sep 23, 2024
3932d43
Input text styling
hafizrahman Sep 23, 2024
563dd83
Update logic to strip html in list view but not in editor view
hafizrahman Sep 23, 2024
1ee637a
Refactor AztecEditorView remove unneeded binding and update documenta…
hafizrahman Sep 23, 2024
a4c7771
Remove irrelevant unit test.
hafizrahman Sep 24, 2024
730eb2a
Make some strings translatable.
hafizrahman Sep 24, 2024
fc7952d
Simplify vars
hafizrahman Sep 24, 2024
adeb80d
Remove forced casting
hafizrahman Sep 24, 2024
fca268c
Avoid using fixedSize() and update the label's appearance instead to …
hafizrahman Sep 25, 2024
60d5f4b
Refactor the way the content in Aztec is bind to the value state in t…
hafizrahman Sep 30, 2024
13b7bec
Merge branch 'trunk' into feat/13493-refactor-aztec-make-more-generic
hafizrahman Sep 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
itsmeichigo marked this conversation as resolved.
Show resolved Hide resolved
.foregroundColor(Color(.text))
.subheadlineStyle()
.frame(minHeight: Layout.minimumEditorSize)
.padding(insets: Layout.inputInsets)
.background(Color(.listForeground(modal: false)))
itsmeichigo marked this conversation as resolved.
Show resolved Hide resolved
.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