Refactor composer UI components to separate files (#1506)
--------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
parent
70cdb4a2fc
commit
6e9d19884c
15 changed files with 461 additions and 271 deletions
|
|
@ -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())
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:377c36c5f1019bb6838749fd93c91dec703da825f75e5600ef028d2aaeaa79ac
|
||||
size 5646
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6bbe1dbe62743ca682a48e9e777e8b2b09064afeb2ce36448a62452faaca5d67
|
||||
size 5629
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bb2d844445877bdd615c4b9fcedd9759ccf4fa7e72b8a740a74af18f8b7d18f8
|
||||
size 5925
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8e3fb36aa1c559c6d3ae8ca2399531b5090229131ba23b710d46639d3a26b6ec
|
||||
size 5847
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:60410832f66d0c70166443d855743ef956b1b97a3684624ce5797805d71e04f6
|
||||
size 8712
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:54c5208fd88e1f404667cdc0b39ff87824115a1c414dfdbd55a334fa6a445548
|
||||
size 8619
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b43968125ef27b3b9918251590befe6e6a7d781342e8d915833fa1cba395f17d
|
||||
size 7211
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a7ea766f311321684f8a387a7be9e2f88a4f27a4adb85a9b0f4d0b3e56854ee0
|
||||
size 7034
|
||||
Loading…
Add table
Add a link
Reference in a new issue