diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/compose/component/aztec/AztecEditor.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/compose/component/aztec/AztecEditor.kt
index 788b0d7517c..a7bea811853 100644
--- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/compose/component/aztec/AztecEditor.kt
+++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/compose/component/aztec/AztecEditor.kt
@@ -8,23 +8,27 @@ import android.widget.EditText
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.isImeVisible
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.relocation.bringIntoViewRequester
+import androidx.compose.material.Switch
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalTextInputService
@@ -53,7 +57,7 @@ import org.wordpress.aztec.AztecText
import org.wordpress.aztec.ITextFormat
import org.wordpress.aztec.glideloader.GlideImageLoader
import org.wordpress.aztec.source.SourceViewEditText
-import org.wordpress.aztec.toolbar.IAztecToolbar
+import org.wordpress.aztec.toolbar.AztecToolbar
import org.wordpress.aztec.toolbar.IAztecToolbarClickListener
/**
@@ -65,6 +69,7 @@ import org.wordpress.aztec.toolbar.IAztecToolbarClickListener
* @param label The label to display above the editor
* @param minLines The minimum number of lines the editor should have
* @param maxLines The maximum number of lines the editor should have
+ * @param enableSourceEditor Whether the the toolbar will have a button to enable the source editor
* @param calypsoMode Whether the editor should be in calypso mode, for more information on calypso mode see https://github.com/wordpress-mobile/AztecEditor-Android/pull/309
*/
@Composable
@@ -75,7 +80,8 @@ fun OutlinedAztecEditor(
label: String? = null,
minLines: Int = 1,
maxLines: Int = Int.MAX_VALUE,
- calypsoMode: Boolean = false
+ enableSourceEditor: Boolean = true,
+ calypsoMode: Boolean = false,
) {
val state = rememberAztecEditorState(initialContent = content)
val contentState by rememberUpdatedState(content)
@@ -96,6 +102,7 @@ fun OutlinedAztecEditor(
label = label,
minLines = minLines,
maxLines = maxLines,
+ enableSourceEditor = enableSourceEditor,
calypsoMode = calypsoMode
)
}
@@ -108,6 +115,7 @@ fun OutlinedAztecEditor(
* @param label The label to display above the editor
* @param minLines The minimum number of lines the editor should have
* @param maxLines The maximum number of lines the editor should have
+ * @param enableSourceEditor Whether the the toolbar will have a button to enable the source editor
* @param calypsoMode Whether the editor should be in calypso mode, for more information on calypso mode see https://github.com/wordpress-mobile/AztecEditor-Android/pull/309
*/
@Composable
@@ -117,6 +125,7 @@ fun OutlinedAztecEditor(
label: String? = null,
minLines: Int = 1,
maxLines: Int = Int.MAX_VALUE,
+ enableSourceEditor: Boolean = true,
calypsoMode: Boolean = false
) {
InternalAztecEditor(
@@ -138,6 +147,7 @@ fun OutlinedAztecEditor(
label = label,
minLines = minLines,
maxLines = maxLines,
+ enableSourceEditor = enableSourceEditor,
calypsoMode = calypsoMode
)
}
@@ -151,6 +161,7 @@ fun OutlinedAztecEditor(
* @param label The label to display above the editor
* @param minLines The minimum number of lines the editor should have
* @param maxLines The maximum number of lines the editor should have
+ * @param enableSourceEditor Whether the the toolbar will have a button to enable the source editor
* @param calypsoMode Whether the editor should be in calypso mode, for more information on calypso mode see https://github.com/wordpress-mobile/AztecEditor-Android/pull/309
*/
@Composable
@@ -161,6 +172,7 @@ fun AztecEditor(
label: String? = null,
minLines: Int = 1,
maxLines: Int = Int.MAX_VALUE,
+ enableSourceEditor: Boolean = true,
calypsoMode: Boolean = false
) {
val state = rememberAztecEditorState(initialContent = content)
@@ -182,6 +194,7 @@ fun AztecEditor(
label = label,
minLines = minLines,
maxLines = maxLines,
+ enableSourceEditor = enableSourceEditor,
calypsoMode = calypsoMode
)
}
@@ -194,6 +207,7 @@ fun AztecEditor(
* @param label The label to display above the editor
* @param minLines The minimum number of lines the editor should have
* @param maxLines The maximum number of lines the editor should have
+ * @param enableSourceEditor Whether the the toolbar will have a button to enable the source editor
* @param calypsoMode Whether the editor should be in calypso mode, for more information on calypso mode see https://github.com/wordpress-mobile/AztecEditor-Android/pull/309
*/
@Composable
@@ -203,6 +217,7 @@ fun AztecEditor(
label: String? = null,
minLines: Int = 1,
maxLines: Int = Int.MAX_VALUE,
+ enableSourceEditor: Boolean = true,
calypsoMode: Boolean = false
) {
InternalAztecEditor(
@@ -221,11 +236,13 @@ fun AztecEditor(
label = label,
minLines = minLines,
maxLines = maxLines,
+ enableSourceEditor = enableSourceEditor,
calypsoMode = calypsoMode
)
}
@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
+@Suppress("CyclomaticComplexMethod")
@Composable
private fun InternalAztecEditor(
state: AztecEditorState,
@@ -234,29 +251,45 @@ private fun InternalAztecEditor(
label: String? = null,
minLines: Int = 1,
maxLines: Int = Int.MAX_VALUE,
+ enableSourceEditor: Boolean,
calypsoMode: Boolean = false
) {
val localContext = LocalContext.current
val bringIntoViewRequester = remember { BringIntoViewRequester() }
val textInputService = LocalTextInputService.current
- val viewsHolder = remember(localContext) { aztecViewsProvider(localContext) }
+ val viewsHolder = remember(localContext, enableSourceEditor) { aztecViewsProvider(localContext) }
val listener = remember { createToolbarListener { state.toggleHtmlEditor() } }
- val aztec = remember(localContext) {
- Aztec.with(viewsHolder.visualEditor, viewsHolder.sourceEditor, viewsHolder.toolbar, listener)
- .setImageGetter(GlideImageLoader(localContext))
+ val aztec = remember(viewsHolder) {
+ if (!enableSourceEditor && !state.isHtmlEditorEnabled) {
+ // Make sure we have the correct state when the source editor is disabled, they could go out of sync
+ // if [enableSourceEditor] was changed when the source editor was the one being used
+ state.toggleHtmlEditor()
+ }
+
+ val aztec = if (enableSourceEditor) {
+ Aztec.with(viewsHolder.visualEditor, viewsHolder.sourceEditor, viewsHolder.toolbar, listener)
+ } else {
+ Aztec.with(viewsHolder.visualEditor, viewsHolder.toolbar, listener)
+ }
+
+ aztec.setImageGetter(GlideImageLoader(localContext))
}
+
var sourceEditorMinHeight by rememberSaveable { mutableStateOf(0) }
// Toggle the editor mode when the state changes
- LaunchedEffect(Unit) {
+ LaunchedEffect(aztec, enableSourceEditor) {
snapshotFlow { state.isHtmlEditorEnabled }
.drop(1) // Skip the initial value to avoid toggling the editor when it's first created
- .collect { aztec.toolbar.toggleEditorMode() }
+ .collect {
+ if (!enableSourceEditor) error("The source editor is disabled, the editor mode cannot be toggled")
+ aztec.toolbar.toggleEditorMode()
+ }
}
// Update the content of the editor when the state changes
- LaunchedEffect(state.content) {
+ LaunchedEffect(state.content, aztec) {
if (state.isHtmlEditorEnabled) {
if (aztec.visualEditor.toHtml() != state.content) {
aztec.visualEditor.fromHtml(state.content)
@@ -280,61 +313,64 @@ private fun InternalAztecEditor(
)
}
- AndroidView(
- factory = {
- // Set initial content
- aztec.visualEditor.fromHtml(state.content)
- aztec.sourceEditor?.displayStyledAndFormattedHtml(state.content)
-
- aztec.visualEditor.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
- // Because the editors could have different number of lines, we don't set the minLines
- // of the source editor, so we set the minHeight instead to match the visual editor
- sourceEditorMinHeight = aztec.visualEditor.height
- }
-
- aztec.visualEditor.doAfterTextChanged {
- if (!state.isHtmlEditorEnabled) return@doAfterTextChanged
- state.updateContent(aztec.visualEditor.toHtml())
- }
- aztec.sourceEditor?.doAfterTextChanged {
- val sourceEditor = aztec.sourceEditor
- if (state.isHtmlEditorEnabled || sourceEditor == null) return@doAfterTextChanged
- state.updateContent(sourceEditor.getPureHtml())
- }
-
- val focusChangeListener = OnFocusChangeListener { _, focused ->
- focusState.value = focused
- }
- aztec.visualEditor.onFocusChangeListener = focusChangeListener
- aztec.sourceEditor?.onFocusChangeListener = focusChangeListener
-
- viewsHolder.layout
- },
- update = {
- if (aztec.visualEditor.isInCalypsoMode != calypsoMode) {
- aztec.visualEditor.isInCalypsoMode = calypsoMode
- aztec.sourceEditor?.setCalypsoMode(calypsoMode)
- }
-
- if (sourceEditorMinHeight != aztec.sourceEditor?.minHeight) {
- aztec.sourceEditor?.minHeight = sourceEditorMinHeight
- }
- if (minLines != -1 && minLines != aztec.visualEditor.minLines) {
- aztec.visualEditor.minLines = minLines
- }
- if (maxLines != Int.MAX_VALUE && maxLines != aztec.visualEditor.maxLines) {
- aztec.visualEditor.maxLines = maxLines
- aztec.sourceEditor?.maxLines = maxLines
- }
+ // `key` is needed to force re-creating the AndroidView when a new Aztec instance is created
+ key(aztec) {
+ AndroidView(
+ factory = {
+ // Set initial content
+ aztec.visualEditor.fromHtml(state.content)
+ aztec.sourceEditor?.displayStyledAndFormattedHtml(state.content)
- if (aztec.visualEditor.label != label) {
- aztec.visualEditor.label = label
- aztec.sourceEditor?.label = label
- }
- },
- modifier = modifier
- .bringIntoViewRequester(bringIntoViewRequester)
- )
+ aztec.visualEditor.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
+ // Because the editors could have different number of lines, we don't set the minLines
+ // of the source editor, so we set the minHeight instead to match the visual editor
+ sourceEditorMinHeight = aztec.visualEditor.height
+ }
+
+ aztec.visualEditor.doAfterTextChanged {
+ if (!state.isHtmlEditorEnabled) return@doAfterTextChanged
+ state.updateContent(aztec.visualEditor.toHtml())
+ }
+ aztec.sourceEditor?.doAfterTextChanged {
+ val sourceEditor = aztec.sourceEditor
+ if (state.isHtmlEditorEnabled || sourceEditor == null) return@doAfterTextChanged
+ state.updateContent(sourceEditor.getPureHtml())
+ }
+
+ val focusChangeListener = OnFocusChangeListener { _, focused ->
+ focusState.value = focused
+ }
+ aztec.visualEditor.onFocusChangeListener = focusChangeListener
+ aztec.sourceEditor?.onFocusChangeListener = focusChangeListener
+
+ viewsHolder.layout
+ },
+ update = {
+ if (aztec.visualEditor.isInCalypsoMode != calypsoMode) {
+ aztec.visualEditor.isInCalypsoMode = calypsoMode
+ aztec.sourceEditor?.setCalypsoMode(calypsoMode)
+ }
+
+ if (sourceEditorMinHeight != aztec.sourceEditor?.minHeight) {
+ aztec.sourceEditor?.minHeight = sourceEditorMinHeight
+ }
+ if (minLines != -1 && minLines != aztec.visualEditor.minLines) {
+ aztec.visualEditor.minLines = minLines
+ }
+ if (maxLines != Int.MAX_VALUE && maxLines != aztec.visualEditor.maxLines) {
+ aztec.visualEditor.maxLines = maxLines
+ aztec.sourceEditor?.maxLines = maxLines
+ }
+
+ if (aztec.visualEditor.label != label) {
+ aztec.visualEditor.label = label
+ aztec.sourceEditor?.label = label
+ }
+ },
+ modifier = modifier
+ .bringIntoViewRequester(bringIntoViewRequester)
+ )
+ }
}
@OptIn(ExperimentalFoundationApi::class, InternalTextApi::class)
@@ -407,7 +443,7 @@ private data class AztecViewsHolder(
val layout: ViewGroup,
val visualEditor: AztecText,
val sourceEditor: SourceViewEditText,
- val toolbar: IAztecToolbar
+ val toolbar: AztecToolbar
)
@Composable
@@ -436,16 +472,34 @@ private fun OutlinedAztecEditorPreview() {
private fun AztecEditorPreview() {
val state = rememberAztecEditorState("")
+ var enableSourceEditor by remember { mutableStateOf(true) }
+
WooThemeWithBackground {
Column {
AztecEditor(
state = state,
label = "Label",
+ enableSourceEditor = enableSourceEditor,
)
- TextButton(onClick = { state.toggleHtmlEditor() }) {
+ TextButton(
+ onClick = { state.toggleHtmlEditor() },
+ enabled = enableSourceEditor
+ ) {
Text("Toggle Html Mode")
}
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.padding(8.dp)
+ ) {
+ Text("Enable Source Editor")
+ Switch(
+ checked = enableSourceEditor,
+ onCheckedChange = { enableSourceEditor = it },
+ modifier = Modifier.padding(start = 8.dp)
+ )
+ }
}
}
}
diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorScreen.kt
index 1670091a063..0e7fb9ec6b5 100644
--- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorScreen.kt
+++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorScreen.kt
@@ -1,20 +1,59 @@
package com.woocommerce.android.ui.customfields.editor
import androidx.activity.compose.BackHandler
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.Colors
import androidx.compose.material.LocalContentColor
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
+import androidx.compose.material.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.listSaver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.stateDescription
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.max
import com.woocommerce.android.R
import com.woocommerce.android.ui.compose.component.DiscardChangesDialog
import com.woocommerce.android.ui.compose.component.Toolbar
@@ -38,6 +77,7 @@ fun CustomFieldsEditorScreen(viewModel: CustomFieldsEditorViewModel) {
onDeleteClicked = viewModel::onDeleteClicked,
onCopyKeyClicked = viewModel::onCopyKeyClicked,
onCopyValueClicked = viewModel::onCopyValueClicked,
+ onEditorModeChanged = viewModel::onEditorModeChanged,
onBackButtonClick = viewModel::onBackClick,
)
}
@@ -52,6 +92,7 @@ private fun CustomFieldsEditorScreen(
onDeleteClicked: () -> Unit,
onCopyKeyClicked: () -> Unit,
onCopyValueClicked: () -> Unit,
+ onEditorModeChanged: (Boolean) -> Unit,
onBackButtonClick: () -> Unit,
) {
BackHandler { onBackButtonClick() }
@@ -95,61 +136,203 @@ private fun CustomFieldsEditorScreen(
},
backgroundColor = MaterialTheme.colors.surface
) { paddingValues ->
- Column(
+ BoxWithConstraints(
modifier = Modifier
- .verticalScroll(rememberScrollState())
.padding(paddingValues)
.padding(16.dp)
) {
- WCOutlinedTextField(
- value = state.customField.key,
- onValueChange = onKeyChanged,
- label = stringResource(R.string.custom_fields_editor_key_label),
- helperText = state.keyErrorMessage?.getText(),
- isError = state.keyErrorMessage != null,
- singleLine = true
- )
+ Column(
+ modifier = Modifier
+ .verticalScroll(rememberScrollState())
+ .heightIn(max = max(maxHeight, 320.dp))
+ ) {
+ WCOutlinedTextField(
+ value = state.customField.key,
+ onValueChange = onKeyChanged,
+ label = stringResource(R.string.custom_fields_editor_key_label),
+ helperText = state.keyErrorMessage?.getText(),
+ isError = state.keyErrorMessage != null,
+ singleLine = true
+ )
- Spacer(modifier = Modifier.height(16.dp))
+ Spacer(modifier = Modifier.height(16.dp))
- if (state.isHtml) {
- OutlinedAztecEditor(
- content = state.customField.value,
- onContentChanged = onValueChanged,
- label = stringResource(R.string.custom_fields_editor_value_label),
- minLines = 5
+ Toggle(
+ useHtmlEditor = state.useHtmlEditor,
+ onToggle = onEditorModeChanged,
+ modifier = Modifier.align(Alignment.CenterHorizontally)
)
- } else {
- WCOutlinedTextField(
- value = state.customField.value,
- onValueChange = onValueChanged,
- label = stringResource(R.string.custom_fields_editor_value_label),
- minLines = 5
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Box {
+ androidx.compose.animation.AnimatedVisibility(
+ visible = state.useHtmlEditor,
+ enter = fadeIn(),
+ exit = fadeOut()
+ ) {
+ OutlinedAztecEditor(
+ content = state.customField.value,
+ onContentChanged = onValueChanged,
+ label = stringResource(R.string.custom_fields_editor_value_label),
+ enableSourceEditor = false,
+ minLines = 5
+ )
+ }
+ androidx.compose.animation.AnimatedVisibility(
+ visible = !state.useHtmlEditor,
+ enter = fadeIn(),
+ exit = fadeOut()
+ ) {
+ WCOutlinedTextField(
+ value = state.customField.value,
+ onValueChange = onValueChanged,
+ label = stringResource(R.string.custom_fields_editor_value_label),
+ minLines = 5
+ )
+ }
+ }
+ }
+
+ state.discardChangesDialogState?.let {
+ DiscardChangesDialog(
+ discardButton = it.onDiscard,
+ dismissButton = it.onCancel
)
}
}
+ }
+}
+
+@Composable
+private fun Toggle(
+ useHtmlEditor: Boolean,
+ onToggle: (Boolean) -> Unit,
+ modifier: Modifier = Modifier
+) {
+ val contentDescription = stringResource(R.string.custom_fields_editor_toggle_accessibility_description)
+ val state = if (useHtmlEditor) {
+ stringResource(R.string.custom_fields_editor_html_toggle)
+ } else {
+ stringResource(R.string.custom_fields_editor_text_toggle)
+ }
+
+ Box(
+ modifier = modifier
+ .toggleable(
+ value = useHtmlEditor,
+ onValueChange = onToggle
+ )
+ .clearAndSetSemantics {
+ this.contentDescription = contentDescription
+ this.stateDescription = state
+ }
+ .background(
+ MaterialTheme.colors.toggleBackgroundColor,
+ MaterialTheme.shapes.medium
+ )
+ ) {
+ var size by rememberSaveable(stateSaver = DpSize.Saver) {
+ mutableStateOf(DpSize.Zero)
+ }
- state.discardChangesDialogState?.let {
- DiscardChangesDialog(
- discardButton = it.onDiscard,
- dismissButton = it.onCancel
+ val offset by animateDpAsState(
+ targetValue = if (useHtmlEditor) size.width else 0.dp,
+ animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
+ label = "offset"
+ )
+ val textAlpha by animateFloatAsState(
+ targetValue = if (useHtmlEditor) 1f else 0.5f,
+ animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
+ label = "text alpha"
+ )
+ val htmlTextColor by animateColorAsState(
+ targetValue = if (useHtmlEditor) MaterialTheme.colors.onPrimary else LocalContentColor.current,
+ animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
+ label = "html text color"
+ )
+ val textTextColor by animateColorAsState(
+ targetValue = if (useHtmlEditor) LocalContentColor.current else MaterialTheme.colors.onPrimary,
+ animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
+ label = "regular text color"
+ )
+
+ val density = LocalDensity.current
+
+ val boxShape = MaterialTheme.shapes.medium
+ Box(
+ modifier = Modifier
+ .size(size)
+ .offset(x = offset)
+ .shadow(1.dp, MaterialTheme.shapes.medium)
+ .background(
+ MaterialTheme.colors.primary,
+ boxShape
+ )
+ )
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .width(IntrinsicSize.Max)
+ ) {
+ Text(
+ text = stringResource(R.string.custom_fields_editor_text_toggle),
+ color = textTextColor.copy(alpha = 1.5f - textAlpha),
+ textAlign = TextAlign.Center,
+ modifier = Modifier
+ .defaultMinSize(minWidth = 128.dp)
+ .onSizeChanged {
+ size = with(density) {
+ DpSize(it.width.toDp(), it.height.toDp())
+ }
+ }
+ .clickable(onClick = { onToggle(false) })
+ .padding(horizontal = 16.dp, vertical = 4.dp)
+ .weight(1f)
+ )
+ Text(
+ text = stringResource(R.string.custom_fields_editor_html_toggle),
+ color = htmlTextColor.copy(alpha = textAlpha),
+ textAlign = TextAlign.Center,
+ modifier = Modifier
+ .clickable(onClick = { onToggle(true) })
+ .padding(horizontal = 16.dp, vertical = 4.dp)
+ .weight(1f)
)
}
}
}
+private val Colors.toggleBackgroundColor: Color
+ @Composable
+ get() = if (isLight) MaterialTheme.colors.background else Color.DarkGray
+
+private val DpSize.Companion.Saver by lazy {
+ listSaver(
+ save = { listOf(it.width.value, it.height.value) },
+ restore = { DpSize((it[0] as Float).dp, (it[1] as Float).dp) }
+ )
+}
+
@LightDarkThemePreviews
+@Preview
@Composable
private fun CustomFieldsEditorScreenPreview() {
+ var useHtmlEditor by remember { mutableStateOf(false) }
WooThemeWithBackground {
CustomFieldsEditorScreen(
- CustomFieldsEditorViewModel.UiState(customField = CustomFieldUiModel("key", "value")),
+ CustomFieldsEditorViewModel.UiState(
+ customField = CustomFieldUiModel("key", "value"),
+ useHtmlEditor = useHtmlEditor,
+ ),
onKeyChanged = {},
onValueChanged = {},
onDoneClicked = {},
onDeleteClicked = {},
onCopyKeyClicked = {},
onCopyValueClicked = {},
+ onEditorModeChanged = { useHtmlEditor = it },
onBackButtonClick = {}
)
}
diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt
index c2172cb9d29..7340aecaaf6 100644
--- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt
+++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModel.kt
@@ -52,19 +52,24 @@ class CustomFieldsEditorViewModel @Inject constructor(
clazz = UiString::class.java,
key = "keyErrorMessage"
)
+ private val useHtmlEditor = savedStateHandle.getStateFlow(
+ scope = viewModelScope,
+ initialValue = false,
+ key = "useHtmlEditor"
+ )
private val storedValue = navArgs.customField
- private val isHtml = storedValue?.valueStrippedHtml != storedValue?.value
val state = combine(
customFieldDraft,
showDiscardChangesDialog.mapToState(),
- keyErrorMessage
- ) { customField, discardChangesDialogState, keyErrorMessage ->
+ keyErrorMessage,
+ useHtmlEditor
+ ) { customField, discardChangesDialogState, keyErrorMessage, useHtmlEditor ->
UiState(
customField = customField,
hasChanges = storedValue?.key.orEmpty() != customField.key ||
storedValue?.value.orEmpty() != customField.value,
- isHtml = isHtml,
+ useHtmlEditor = useHtmlEditor,
discardChangesDialogState = discardChangesDialogState,
keyErrorMessage = keyErrorMessage,
isCreatingNewItem = storedValue == null
@@ -123,6 +128,10 @@ class CustomFieldsEditorViewModel @Inject constructor(
triggerEvent(CopyContentToClipboard(R.string.custom_fields_editor_value_label, customFieldDraft.value.value))
}
+ fun onEditorModeChanged(useHtmlEditor: Boolean) {
+ this.useHtmlEditor.update { useHtmlEditor }
+ }
+
fun onBackClick() {
if (state.value?.hasChanges == true) {
showDiscardChangesDialog.value = true
@@ -145,10 +154,10 @@ class CustomFieldsEditorViewModel @Inject constructor(
data class UiState(
val customField: CustomFieldUiModel = CustomFieldUiModel("", ""),
val hasChanges: Boolean = false,
- val isHtml: Boolean = false,
+ val useHtmlEditor: Boolean = false,
val discardChangesDialogState: DiscardChangesDialogState? = null,
val keyErrorMessage: UiString? = null,
- val isCreatingNewItem: Boolean = false
+ val isCreatingNewItem: Boolean = false,
) {
val showDoneButton
get() = customField.key.isNotEmpty() && hasChanges && keyErrorMessage == null
diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml
index beb06df356e..8705b38cd39 100644
--- a/WooCommerce/src/main/res/values/strings.xml
+++ b/WooCommerce/src/main/res/values/strings.xml
@@ -4303,4 +4303,7 @@
Invalid key: please remove the \"_\" character from the beginning.
Copy Key
Copy Value
+ Toggle between text and HTML editors
+ Text
+ HTML
diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModelTest.kt
index 59b333a3da0..942ea191f33 100644
--- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModelTest.kt
+++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/customfields/editor/CustomFieldsEditorViewModelTest.kt
@@ -301,4 +301,15 @@ class CustomFieldsEditorViewModelTest : BaseUnitTest() {
)
)
}
+
+ @Test
+ fun `when toggle editor mode, then update the state`() = testBlocking {
+ setup(editing = true)
+
+ val state = viewModel.state.runAndCaptureValues {
+ viewModel.onEditorModeChanged(true)
+ }.last()
+
+ assertThat(state.useHtmlEditor).isTrue()
+ }
}