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 70cdb4a2fc
commit 6e9d19884c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
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())
}

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:377c36c5f1019bb6838749fd93c91dec703da825f75e5600ef028d2aaeaa79ac
size 5646

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6bbe1dbe62743ca682a48e9e777e8b2b09064afeb2ce36448a62452faaca5d67
size 5629

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bb2d844445877bdd615c4b9fcedd9759ccf4fa7e72b8a740a74af18f8b7d18f8
size 5925

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8e3fb36aa1c559c6d3ae8ca2399531b5090229131ba23b710d46639d3a26b6ec
size 5847

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:60410832f66d0c70166443d855743ef956b1b97a3684624ce5797805d71e04f6
size 8712

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:54c5208fd88e1f404667cdc0b39ff87824115a1c414dfdbd55a334fa6a445548
size 8619

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b43968125ef27b3b9918251590befe6e6a7d781342e8d915833fa1cba395f17d
size 7211

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a7ea766f311321684f8a387a7be9e2f88a4f27a4adb85a9b0f4d0b3e56854ee0
size 7034