Refactor composer UI components to separate files (#1506)

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
jonnyandrew
2023-10-10 09:31:36 +01:00
committed by GitHub
parent 69ac38ed10
commit 7e91e7824e
15 changed files with 461 additions and 271 deletions

View File

@@ -19,7 +19,6 @@ package io.element.android.libraries.textcomposer
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -34,24 +33,15 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeightIn
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@@ -59,7 +49,6 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.text.applyScaleUp
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.utils.CommonDrawables
import io.element.android.libraries.matrix.api.core.EventId
@@ -70,20 +59,17 @@ import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType
import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.textcomposer.components.FormattingOption
import io.element.android.libraries.textcomposer.components.FormattingOptionState
import io.element.android.libraries.textcomposer.components.ComposerOptionsButton
import io.element.android.libraries.textcomposer.components.DismissTextFormattingButton
import io.element.android.libraries.textcomposer.components.SendButton
import io.element.android.libraries.textcomposer.components.TextFormatting
import io.element.android.libraries.textcomposer.components.textInputRoundedCornerShape
import io.element.android.libraries.theme.ElementTheme
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.wysiwyg.compose.RichTextEditor
import io.element.android.wysiwyg.compose.RichTextEditorState
import io.element.android.wysiwyg.view.models.InlineFormat
import io.element.android.wysiwyg.view.models.LinkAction
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.launch
import uniffi.wysiwyg_composer.ActionState
import uniffi.wysiwyg_composer.ComposerAction
@Composable
fun TextComposer(
@@ -313,209 +299,6 @@ private fun TextInput(
}
}
@Composable
private fun ComposerOptionsButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
IconButton(
modifier = modifier
.size(48.dp),
onClick = onClick
) {
Icon(
modifier = Modifier.size(30.dp.applyScaleUp()),
resourceId = CommonDrawables.ic_plus,
contentDescription = stringResource(R.string.rich_text_editor_a11y_add_attachment),
tint = ElementTheme.colors.iconPrimary,
)
}
}
@Composable
private fun DismissTextFormattingButton(
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
IconButton(
modifier = modifier
.size(48.dp),
onClick = onClick
) {
Icon(
modifier = Modifier.size(30.dp.applyScaleUp()),
resourceId = CommonDrawables.ic_cancel,
contentDescription = stringResource(CommonStrings.action_close),
tint = ElementTheme.colors.iconPrimary,
)
}
}
@Composable
private fun TextFormatting(
state: RichTextEditorState,
modifier: Modifier = Modifier,
) {
val scrollState = rememberScrollState()
val coroutineScope = rememberCoroutineScope()
fun onInlineFormatClick(inlineFormat: InlineFormat) {
coroutineScope.launch {
state.toggleInlineFormat(inlineFormat)
}
}
fun onToggleListClick(ordered: Boolean) {
coroutineScope.launch {
state.toggleList(ordered)
}
}
fun onIndentClick() {
coroutineScope.launch {
state.indent()
}
}
fun onUnindentClick() {
coroutineScope.launch {
state.unindent()
}
}
fun onCodeBlockClick() {
coroutineScope.launch {
state.toggleCodeBlock()
}
}
fun onQuoteClick() {
coroutineScope.launch {
state.toggleQuote()
}
}
fun onCreateLinkRequest(url: String, text: String) {
coroutineScope.launch {
state.insertLink(url, text)
}
}
fun onSaveLinkRequest(url: String) {
coroutineScope.launch {
state.setLink(url)
}
}
fun onRemoveLinkRequest() {
coroutineScope.launch {
state.removeLink()
}
}
Row(
modifier = modifier
.horizontalScroll(scrollState),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
FormattingOption(
state = state.actions[ComposerAction.BOLD].toButtonState(),
onClick = { onInlineFormatClick(InlineFormat.Bold) },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_bold),
contentDescription = stringResource(R.string.rich_text_editor_format_bold)
)
FormattingOption(
state = state.actions[ComposerAction.ITALIC].toButtonState(),
onClick = { onInlineFormatClick(InlineFormat.Italic) },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_italic),
contentDescription = stringResource(R.string.rich_text_editor_format_italic)
)
FormattingOption(
state = state.actions[ComposerAction.UNDERLINE].toButtonState(),
onClick = { onInlineFormatClick(InlineFormat.Underline) },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_underline),
contentDescription = stringResource(R.string.rich_text_editor_format_underline)
)
FormattingOption(
state = state.actions[ComposerAction.STRIKE_THROUGH].toButtonState(),
onClick = { onInlineFormatClick(InlineFormat.StrikeThrough) },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_strikethrough),
contentDescription = stringResource(R.string.rich_text_editor_format_strikethrough)
)
var linkDialogAction by remember { mutableStateOf<LinkAction?>(null) }
linkDialogAction?.let {
TextComposerLinkDialog(
onDismissRequest = { linkDialogAction = null },
onCreateLinkRequest = ::onCreateLinkRequest,
onSaveLinkRequest = ::onSaveLinkRequest,
onRemoveLinkRequest = ::onRemoveLinkRequest,
linkAction = it,
)
}
FormattingOption(
state = state.actions[ComposerAction.LINK].toButtonState(),
onClick = { linkDialogAction = state.linkAction },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_link),
contentDescription = stringResource(R.string.rich_text_editor_link)
)
FormattingOption(
state = state.actions[ComposerAction.UNORDERED_LIST].toButtonState(),
onClick = { onToggleListClick(ordered = false) },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_bullet_list),
contentDescription = stringResource(R.string.rich_text_editor_bullet_list)
)
FormattingOption(
state = state.actions[ComposerAction.ORDERED_LIST].toButtonState(),
onClick = { onToggleListClick(ordered = true) },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_numbered_list),
contentDescription = stringResource(R.string.rich_text_editor_numbered_list)
)
FormattingOption(
state = state.actions[ComposerAction.INDENT].toButtonState(),
onClick = { onIndentClick() },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_indent_increase),
contentDescription = stringResource(R.string.rich_text_editor_indent)
)
FormattingOption(
state = state.actions[ComposerAction.UNINDENT].toButtonState(),
onClick = { onUnindentClick() },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_indent_decrease),
contentDescription = stringResource(R.string.rich_text_editor_unindent)
)
FormattingOption(
state = state.actions[ComposerAction.INLINE_CODE].toButtonState(),
onClick = { onInlineFormatClick(InlineFormat.InlineCode) },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_inline_code),
contentDescription = stringResource(R.string.rich_text_editor_inline_code)
)
FormattingOption(
state = state.actions[ComposerAction.CODE_BLOCK].toButtonState(),
onClick = { onCodeBlockClick() },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_code_block),
contentDescription = stringResource(R.string.rich_text_editor_code_block)
)
FormattingOption(
state = state.actions[ComposerAction.QUOTE].toButtonState(),
onClick = { onQuoteClick() },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_quote),
contentDescription = stringResource(R.string.rich_text_editor_quote)
)
}
}
private fun ActionState?.toButtonState(): FormattingOptionState =
when (this) {
ActionState.ENABLED -> FormattingOptionState.Default
ActionState.REVERSED -> FormattingOptionState.Selected
ActionState.DISABLED, null -> FormattingOptionState.Disabled
}
@Composable
private fun ComposerModeView(
composerMode: MessageComposerMode,
@@ -648,56 +431,6 @@ private fun ReplyToModeView(
}
}
@Composable
private fun SendButton(
canSendMessage: Boolean,
onClick: () -> Unit,
composerMode: MessageComposerMode,
modifier: Modifier = Modifier,
) {
IconButton(
modifier = modifier
.size(48.dp.applyScaleUp()),
onClick = onClick,
enabled = canSendMessage,
) {
val iconId = when (composerMode) {
is MessageComposerMode.Edit -> CommonDrawables.ic_compound_check
else -> CommonDrawables.ic_september_send
}
val iconSize = when (composerMode) {
is MessageComposerMode.Edit -> 24.dp
// CommonDrawables.ic_september_send is too big... reduce its size.
else -> 18.dp
}
val iconStartPadding = when (composerMode) {
is MessageComposerMode.Edit -> 0.dp
else -> 2.dp
}
val contentDescription = when (composerMode) {
is MessageComposerMode.Edit -> stringResource(CommonStrings.action_edit)
else -> stringResource(CommonStrings.action_send)
}
Box(
modifier = Modifier
.clip(CircleShape)
.size(36.dp.applyScaleUp())
.background(if (canSendMessage) ElementTheme.colors.iconAccentTertiary else Color.Transparent)
) {
Icon(
modifier = Modifier
.height(iconSize.applyScaleUp())
.padding(start = iconStartPadding)
.align(Alignment.Center),
resourceId = iconId,
contentDescription = contentDescription,
// Exception here, we use Color.White instead of ElementTheme.colors.iconOnSolidPrimary
tint = if (canSendMessage) Color.White else ElementTheme.colors.iconDisabled
)
}
}
}
@PreviewsDayNight
@Composable
internal fun TextComposerSimplePreview() = ElementPreview {

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.textcomposer.components
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.text.applyScaleUp
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.utils.CommonDrawables
import io.element.android.libraries.textcomposer.R
import io.element.android.libraries.theme.ElementTheme
@Composable
internal fun ComposerOptionsButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
IconButton(
modifier = modifier
.size(48.dp),
onClick = onClick
) {
Icon(
modifier = Modifier.size(30.dp.applyScaleUp()),
resourceId = CommonDrawables.ic_plus,
contentDescription = stringResource(R.string.rich_text_editor_a11y_add_attachment),
tint = ElementTheme.colors.iconPrimary,
)
}
}
@PreviewsDayNight
@Composable
internal fun ComposerOptionsButtonPreview() = ElementPreview {
ComposerOptionsButton(onClick = {})
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.textcomposer.components
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.text.applyScaleUp
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.utils.CommonDrawables
import io.element.android.libraries.theme.ElementTheme
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
internal fun DismissTextFormattingButton(
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
IconButton(
modifier = modifier
.size(48.dp),
onClick = onClick
) {
Icon(
modifier = Modifier.size(30.dp.applyScaleUp()),
resourceId = CommonDrawables.ic_cancel,
contentDescription = stringResource(CommonStrings.action_close),
tint = ElementTheme.colors.iconPrimary,
)
}
}
@PreviewsDayNight
@Composable
internal fun DismissTextFormattingButtonPreview() = ElementPreview {
DismissTextFormattingButton(onClick = {})
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.textcomposer.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.text.applyScaleUp
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.utils.CommonDrawables
import io.element.android.libraries.textcomposer.MessageComposerMode
import io.element.android.libraries.theme.ElementTheme
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
internal fun SendButton(
canSendMessage: Boolean,
onClick: () -> Unit,
composerMode: MessageComposerMode,
modifier: Modifier = Modifier,
) {
IconButton(
modifier = modifier
.size(48.dp.applyScaleUp()),
onClick = onClick,
enabled = canSendMessage,
) {
val iconId = when (composerMode) {
is MessageComposerMode.Edit -> CommonDrawables.ic_compound_check
else -> CommonDrawables.ic_september_send
}
val iconSize = when (composerMode) {
is MessageComposerMode.Edit -> 24.dp
// CommonDrawables.ic_september_send is too big... reduce its size.
else -> 18.dp
}
val iconStartPadding = when (composerMode) {
is MessageComposerMode.Edit -> 0.dp
else -> 2.dp
}
val contentDescription = when (composerMode) {
is MessageComposerMode.Edit -> stringResource(CommonStrings.action_edit)
else -> stringResource(CommonStrings.action_send)
}
Box(
modifier = Modifier
.clip(CircleShape)
.size(36.dp.applyScaleUp())
.background(if (canSendMessage) ElementTheme.colors.iconAccentTertiary else Color.Transparent)
) {
Icon(
modifier = Modifier
.height(iconSize.applyScaleUp())
.padding(start = iconStartPadding)
.align(Alignment.Center),
resourceId = iconId,
contentDescription = contentDescription,
// Exception here, we use Color.White instead of ElementTheme.colors.iconOnSolidPrimary
tint = if (canSendMessage) Color.White else ElementTheme.colors.iconDisabled
)
}
}
}
@PreviewsDayNight
@Composable
internal fun SendButtonPreview() = ElementPreview {
val normalMode = MessageComposerMode.Normal("")
val editMode = MessageComposerMode.Edit(null, "", null)
Row {
SendButton(canSendMessage = true, onClick = {}, composerMode = normalMode)
SendButton(canSendMessage = false, onClick = {}, composerMode = normalMode)
SendButton(canSendMessage = true, onClick = {}, composerMode = editMode)
SendButton(canSendMessage = false, onClick = {}, composerMode = editMode)
}
}

View File

@@ -0,0 +1,215 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.textcomposer.components
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.rememberScrollState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.utils.CommonDrawables
import io.element.android.libraries.textcomposer.R
import io.element.android.libraries.textcomposer.TextComposerLinkDialog
import io.element.android.wysiwyg.compose.RichTextEditorState
import io.element.android.wysiwyg.view.models.InlineFormat
import io.element.android.wysiwyg.view.models.LinkAction
import kotlinx.coroutines.launch
import uniffi.wysiwyg_composer.ActionState
import uniffi.wysiwyg_composer.ComposerAction
@Composable
internal fun TextFormatting(
state: RichTextEditorState,
modifier: Modifier = Modifier,
) {
val scrollState = rememberScrollState()
val coroutineScope = rememberCoroutineScope()
fun onInlineFormatClick(inlineFormat: InlineFormat) {
coroutineScope.launch {
state.toggleInlineFormat(inlineFormat)
}
}
fun onToggleListClick(ordered: Boolean) {
coroutineScope.launch {
state.toggleList(ordered)
}
}
fun onIndentClick() {
coroutineScope.launch {
state.indent()
}
}
fun onUnindentClick() {
coroutineScope.launch {
state.unindent()
}
}
fun onCodeBlockClick() {
coroutineScope.launch {
state.toggleCodeBlock()
}
}
fun onQuoteClick() {
coroutineScope.launch {
state.toggleQuote()
}
}
fun onCreateLinkRequest(url: String, text: String) {
coroutineScope.launch {
state.insertLink(url, text)
}
}
fun onSaveLinkRequest(url: String) {
coroutineScope.launch {
state.setLink(url)
}
}
fun onRemoveLinkRequest() {
coroutineScope.launch {
state.removeLink()
}
}
Row(
modifier = modifier
.horizontalScroll(scrollState),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
FormattingOption(
state = state.actions[ComposerAction.BOLD].toButtonState(),
onClick = { onInlineFormatClick(InlineFormat.Bold) },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_bold),
contentDescription = stringResource(R.string.rich_text_editor_format_bold)
)
FormattingOption(
state = state.actions[ComposerAction.ITALIC].toButtonState(),
onClick = { onInlineFormatClick(InlineFormat.Italic) },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_italic),
contentDescription = stringResource(R.string.rich_text_editor_format_italic)
)
FormattingOption(
state = state.actions[ComposerAction.UNDERLINE].toButtonState(),
onClick = { onInlineFormatClick(InlineFormat.Underline) },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_underline),
contentDescription = stringResource(R.string.rich_text_editor_format_underline)
)
FormattingOption(
state = state.actions[ComposerAction.STRIKE_THROUGH].toButtonState(),
onClick = { onInlineFormatClick(InlineFormat.StrikeThrough) },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_strikethrough),
contentDescription = stringResource(R.string.rich_text_editor_format_strikethrough)
)
var linkDialogAction by remember { mutableStateOf<LinkAction?>(null) }
linkDialogAction?.let {
TextComposerLinkDialog(
onDismissRequest = { linkDialogAction = null },
onCreateLinkRequest = ::onCreateLinkRequest,
onSaveLinkRequest = ::onSaveLinkRequest,
onRemoveLinkRequest = ::onRemoveLinkRequest,
linkAction = it,
)
}
FormattingOption(
state = state.actions[ComposerAction.LINK].toButtonState(),
onClick = { linkDialogAction = state.linkAction },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_link),
contentDescription = stringResource(R.string.rich_text_editor_link)
)
FormattingOption(
state = state.actions[ComposerAction.UNORDERED_LIST].toButtonState(),
onClick = { onToggleListClick(ordered = false) },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_bullet_list),
contentDescription = stringResource(R.string.rich_text_editor_bullet_list)
)
FormattingOption(
state = state.actions[ComposerAction.ORDERED_LIST].toButtonState(),
onClick = { onToggleListClick(ordered = true) },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_numbered_list),
contentDescription = stringResource(R.string.rich_text_editor_numbered_list)
)
FormattingOption(
state = state.actions[ComposerAction.INDENT].toButtonState(),
onClick = { onIndentClick() },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_indent_increase),
contentDescription = stringResource(R.string.rich_text_editor_indent)
)
FormattingOption(
state = state.actions[ComposerAction.UNINDENT].toButtonState(),
onClick = { onUnindentClick() },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_indent_decrease),
contentDescription = stringResource(R.string.rich_text_editor_unindent)
)
FormattingOption(
state = state.actions[ComposerAction.INLINE_CODE].toButtonState(),
onClick = { onInlineFormatClick(InlineFormat.InlineCode) },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_inline_code),
contentDescription = stringResource(R.string.rich_text_editor_inline_code)
)
FormattingOption(
state = state.actions[ComposerAction.CODE_BLOCK].toButtonState(),
onClick = { onCodeBlockClick() },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_code_block),
contentDescription = stringResource(R.string.rich_text_editor_code_block)
)
FormattingOption(
state = state.actions[ComposerAction.QUOTE].toButtonState(),
onClick = { onQuoteClick() },
imageVector = ImageVector.vectorResource(CommonDrawables.ic_quote),
contentDescription = stringResource(R.string.rich_text_editor_quote)
)
}
}
private fun ActionState?.toButtonState(): FormattingOptionState =
when (this) {
ActionState.ENABLED -> FormattingOptionState.Default
ActionState.REVERSED -> FormattingOptionState.Selected
ActionState.DISABLED, null -> FormattingOptionState.Disabled
}
@PreviewsDayNight
@Composable
internal fun TextFormattingPreview() = ElementPreview {
TextFormatting(state = RichTextEditorState())
}