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() + } }