Refactor composer UI components to separate files (#1506)
--------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 = {})
|
||||
}
|
||||
|
||||
@@ -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 = {})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user