Skip to content

Commit 6e9d198

Browse files
jonnyandrewElementBot
andauthored
Refactor composer UI components to separate files (#1506)
--------- Co-authored-by: ElementBot <[email protected]>
1 parent 70cdb4a commit 6e9d198

15 files changed

+461
-271
lines changed

libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt

Lines changed: 4 additions & 271 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package io.element.android.libraries.textcomposer
1919
import androidx.compose.foundation.background
2020
import androidx.compose.foundation.border
2121
import androidx.compose.foundation.clickable
22-
import androidx.compose.foundation.horizontalScroll
2322
import androidx.compose.foundation.interaction.MutableInteractionSource
2423
import androidx.compose.foundation.layout.Arrangement
2524
import androidx.compose.foundation.layout.Box
@@ -34,32 +33,22 @@ import androidx.compose.foundation.layout.padding
3433
import androidx.compose.foundation.layout.requiredHeightIn
3534
import androidx.compose.foundation.layout.size
3635
import androidx.compose.foundation.layout.width
37-
import androidx.compose.foundation.rememberScrollState
38-
import androidx.compose.foundation.shape.CircleShape
3936
import androidx.compose.foundation.shape.RoundedCornerShape
4037
import androidx.compose.material.ripple.rememberRipple
4138
import androidx.compose.material3.MaterialTheme
4239
import androidx.compose.runtime.Composable
43-
import androidx.compose.runtime.getValue
44-
import androidx.compose.runtime.mutableStateOf
4540
import androidx.compose.runtime.remember
46-
import androidx.compose.runtime.rememberCoroutineScope
47-
import androidx.compose.runtime.setValue
4841
import androidx.compose.ui.Alignment
4942
import androidx.compose.ui.Modifier
5043
import androidx.compose.ui.draw.clip
51-
import androidx.compose.ui.graphics.Color
52-
import androidx.compose.ui.graphics.vector.ImageVector
5344
import androidx.compose.ui.res.stringResource
54-
import androidx.compose.ui.res.vectorResource
5545
import androidx.compose.ui.text.style.TextAlign
5646
import androidx.compose.ui.text.style.TextOverflow
5747
import androidx.compose.ui.unit.dp
5848
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
5949
import io.element.android.libraries.designsystem.preview.ElementPreview
6050
import io.element.android.libraries.designsystem.text.applyScaleUp
6151
import io.element.android.libraries.designsystem.theme.components.Icon
62-
import io.element.android.libraries.designsystem.theme.components.IconButton
6352
import io.element.android.libraries.designsystem.theme.components.Text
6453
import io.element.android.libraries.designsystem.utils.CommonDrawables
6554
import io.element.android.libraries.matrix.api.core.EventId
@@ -70,20 +59,17 @@ import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo
7059
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType
7160
import io.element.android.libraries.testtags.TestTags
7261
import io.element.android.libraries.testtags.testTag
73-
import io.element.android.libraries.textcomposer.components.FormattingOption
74-
import io.element.android.libraries.textcomposer.components.FormattingOptionState
62+
import io.element.android.libraries.textcomposer.components.ComposerOptionsButton
63+
import io.element.android.libraries.textcomposer.components.DismissTextFormattingButton
64+
import io.element.android.libraries.textcomposer.components.SendButton
65+
import io.element.android.libraries.textcomposer.components.TextFormatting
7566
import io.element.android.libraries.textcomposer.components.textInputRoundedCornerShape
7667
import io.element.android.libraries.theme.ElementTheme
7768
import io.element.android.libraries.ui.strings.CommonStrings
7869
import io.element.android.wysiwyg.compose.RichTextEditor
7970
import io.element.android.wysiwyg.compose.RichTextEditorState
80-
import io.element.android.wysiwyg.view.models.InlineFormat
81-
import io.element.android.wysiwyg.view.models.LinkAction
8271
import kotlinx.collections.immutable.ImmutableList
8372
import kotlinx.collections.immutable.persistentListOf
84-
import kotlinx.coroutines.launch
85-
import uniffi.wysiwyg_composer.ActionState
86-
import uniffi.wysiwyg_composer.ComposerAction
8773

8874
@Composable
8975
fun TextComposer(
@@ -313,209 +299,6 @@ private fun TextInput(
313299
}
314300
}
315301

316-
@Composable
317-
private fun ComposerOptionsButton(
318-
onClick: () -> Unit,
319-
modifier: Modifier = Modifier,
320-
) {
321-
IconButton(
322-
modifier = modifier
323-
.size(48.dp),
324-
onClick = onClick
325-
) {
326-
Icon(
327-
modifier = Modifier.size(30.dp.applyScaleUp()),
328-
resourceId = CommonDrawables.ic_plus,
329-
contentDescription = stringResource(R.string.rich_text_editor_a11y_add_attachment),
330-
tint = ElementTheme.colors.iconPrimary,
331-
)
332-
}
333-
}
334-
335-
@Composable
336-
private fun DismissTextFormattingButton(
337-
onClick: () -> Unit,
338-
modifier: Modifier = Modifier
339-
) {
340-
IconButton(
341-
modifier = modifier
342-
.size(48.dp),
343-
onClick = onClick
344-
) {
345-
Icon(
346-
modifier = Modifier.size(30.dp.applyScaleUp()),
347-
resourceId = CommonDrawables.ic_cancel,
348-
contentDescription = stringResource(CommonStrings.action_close),
349-
tint = ElementTheme.colors.iconPrimary,
350-
)
351-
}
352-
}
353-
354-
@Composable
355-
private fun TextFormatting(
356-
state: RichTextEditorState,
357-
modifier: Modifier = Modifier,
358-
) {
359-
360-
val scrollState = rememberScrollState()
361-
val coroutineScope = rememberCoroutineScope()
362-
363-
fun onInlineFormatClick(inlineFormat: InlineFormat) {
364-
coroutineScope.launch {
365-
state.toggleInlineFormat(inlineFormat)
366-
}
367-
}
368-
369-
fun onToggleListClick(ordered: Boolean) {
370-
coroutineScope.launch {
371-
state.toggleList(ordered)
372-
}
373-
}
374-
375-
fun onIndentClick() {
376-
coroutineScope.launch {
377-
state.indent()
378-
}
379-
}
380-
381-
fun onUnindentClick() {
382-
coroutineScope.launch {
383-
state.unindent()
384-
}
385-
}
386-
387-
fun onCodeBlockClick() {
388-
coroutineScope.launch {
389-
state.toggleCodeBlock()
390-
}
391-
}
392-
393-
fun onQuoteClick() {
394-
coroutineScope.launch {
395-
state.toggleQuote()
396-
}
397-
}
398-
399-
fun onCreateLinkRequest(url: String, text: String) {
400-
coroutineScope.launch {
401-
state.insertLink(url, text)
402-
}
403-
}
404-
405-
fun onSaveLinkRequest(url: String) {
406-
coroutineScope.launch {
407-
state.setLink(url)
408-
}
409-
}
410-
411-
fun onRemoveLinkRequest() {
412-
coroutineScope.launch {
413-
state.removeLink()
414-
}
415-
}
416-
417-
Row(
418-
modifier = modifier
419-
.horizontalScroll(scrollState),
420-
verticalAlignment = Alignment.CenterVertically,
421-
horizontalArrangement = Arrangement.spacedBy(4.dp),
422-
) {
423-
FormattingOption(
424-
state = state.actions[ComposerAction.BOLD].toButtonState(),
425-
onClick = { onInlineFormatClick(InlineFormat.Bold) },
426-
imageVector = ImageVector.vectorResource(CommonDrawables.ic_bold),
427-
contentDescription = stringResource(R.string.rich_text_editor_format_bold)
428-
)
429-
FormattingOption(
430-
state = state.actions[ComposerAction.ITALIC].toButtonState(),
431-
onClick = { onInlineFormatClick(InlineFormat.Italic) },
432-
imageVector = ImageVector.vectorResource(CommonDrawables.ic_italic),
433-
contentDescription = stringResource(R.string.rich_text_editor_format_italic)
434-
)
435-
FormattingOption(
436-
state = state.actions[ComposerAction.UNDERLINE].toButtonState(),
437-
onClick = { onInlineFormatClick(InlineFormat.Underline) },
438-
imageVector = ImageVector.vectorResource(CommonDrawables.ic_underline),
439-
contentDescription = stringResource(R.string.rich_text_editor_format_underline)
440-
)
441-
FormattingOption(
442-
state = state.actions[ComposerAction.STRIKE_THROUGH].toButtonState(),
443-
onClick = { onInlineFormatClick(InlineFormat.StrikeThrough) },
444-
imageVector = ImageVector.vectorResource(CommonDrawables.ic_strikethrough),
445-
contentDescription = stringResource(R.string.rich_text_editor_format_strikethrough)
446-
)
447-
448-
var linkDialogAction by remember { mutableStateOf<LinkAction?>(null) }
449-
450-
linkDialogAction?.let {
451-
TextComposerLinkDialog(
452-
onDismissRequest = { linkDialogAction = null },
453-
onCreateLinkRequest = ::onCreateLinkRequest,
454-
onSaveLinkRequest = ::onSaveLinkRequest,
455-
onRemoveLinkRequest = ::onRemoveLinkRequest,
456-
linkAction = it,
457-
)
458-
}
459-
460-
FormattingOption(
461-
state = state.actions[ComposerAction.LINK].toButtonState(),
462-
onClick = { linkDialogAction = state.linkAction },
463-
imageVector = ImageVector.vectorResource(CommonDrawables.ic_link),
464-
contentDescription = stringResource(R.string.rich_text_editor_link)
465-
)
466-
467-
FormattingOption(
468-
state = state.actions[ComposerAction.UNORDERED_LIST].toButtonState(),
469-
onClick = { onToggleListClick(ordered = false) },
470-
imageVector = ImageVector.vectorResource(CommonDrawables.ic_bullet_list),
471-
contentDescription = stringResource(R.string.rich_text_editor_bullet_list)
472-
)
473-
FormattingOption(
474-
state = state.actions[ComposerAction.ORDERED_LIST].toButtonState(),
475-
onClick = { onToggleListClick(ordered = true) },
476-
imageVector = ImageVector.vectorResource(CommonDrawables.ic_numbered_list),
477-
contentDescription = stringResource(R.string.rich_text_editor_numbered_list)
478-
)
479-
FormattingOption(
480-
state = state.actions[ComposerAction.INDENT].toButtonState(),
481-
onClick = { onIndentClick() },
482-
imageVector = ImageVector.vectorResource(CommonDrawables.ic_indent_increase),
483-
contentDescription = stringResource(R.string.rich_text_editor_indent)
484-
)
485-
FormattingOption(
486-
state = state.actions[ComposerAction.UNINDENT].toButtonState(),
487-
onClick = { onUnindentClick() },
488-
imageVector = ImageVector.vectorResource(CommonDrawables.ic_indent_decrease),
489-
contentDescription = stringResource(R.string.rich_text_editor_unindent)
490-
)
491-
FormattingOption(
492-
state = state.actions[ComposerAction.INLINE_CODE].toButtonState(),
493-
onClick = { onInlineFormatClick(InlineFormat.InlineCode) },
494-
imageVector = ImageVector.vectorResource(CommonDrawables.ic_inline_code),
495-
contentDescription = stringResource(R.string.rich_text_editor_inline_code)
496-
)
497-
FormattingOption(
498-
state = state.actions[ComposerAction.CODE_BLOCK].toButtonState(),
499-
onClick = { onCodeBlockClick() },
500-
imageVector = ImageVector.vectorResource(CommonDrawables.ic_code_block),
501-
contentDescription = stringResource(R.string.rich_text_editor_code_block)
502-
)
503-
FormattingOption(
504-
state = state.actions[ComposerAction.QUOTE].toButtonState(),
505-
onClick = { onQuoteClick() },
506-
imageVector = ImageVector.vectorResource(CommonDrawables.ic_quote),
507-
contentDescription = stringResource(R.string.rich_text_editor_quote)
508-
)
509-
}
510-
}
511-
512-
private fun ActionState?.toButtonState(): FormattingOptionState =
513-
when (this) {
514-
ActionState.ENABLED -> FormattingOptionState.Default
515-
ActionState.REVERSED -> FormattingOptionState.Selected
516-
ActionState.DISABLED, null -> FormattingOptionState.Disabled
517-
}
518-
519302
@Composable
520303
private fun ComposerModeView(
521304
composerMode: MessageComposerMode,
@@ -648,56 +431,6 @@ private fun ReplyToModeView(
648431
}
649432
}
650433

651-
@Composable
652-
private fun SendButton(
653-
canSendMessage: Boolean,
654-
onClick: () -> Unit,
655-
composerMode: MessageComposerMode,
656-
modifier: Modifier = Modifier,
657-
) {
658-
IconButton(
659-
modifier = modifier
660-
.size(48.dp.applyScaleUp()),
661-
onClick = onClick,
662-
enabled = canSendMessage,
663-
) {
664-
val iconId = when (composerMode) {
665-
is MessageComposerMode.Edit -> CommonDrawables.ic_compound_check
666-
else -> CommonDrawables.ic_september_send
667-
}
668-
val iconSize = when (composerMode) {
669-
is MessageComposerMode.Edit -> 24.dp
670-
// CommonDrawables.ic_september_send is too big... reduce its size.
671-
else -> 18.dp
672-
}
673-
val iconStartPadding = when (composerMode) {
674-
is MessageComposerMode.Edit -> 0.dp
675-
else -> 2.dp
676-
}
677-
val contentDescription = when (composerMode) {
678-
is MessageComposerMode.Edit -> stringResource(CommonStrings.action_edit)
679-
else -> stringResource(CommonStrings.action_send)
680-
}
681-
Box(
682-
modifier = Modifier
683-
.clip(CircleShape)
684-
.size(36.dp.applyScaleUp())
685-
.background(if (canSendMessage) ElementTheme.colors.iconAccentTertiary else Color.Transparent)
686-
) {
687-
Icon(
688-
modifier = Modifier
689-
.height(iconSize.applyScaleUp())
690-
.padding(start = iconStartPadding)
691-
.align(Alignment.Center),
692-
resourceId = iconId,
693-
contentDescription = contentDescription,
694-
// Exception here, we use Color.White instead of ElementTheme.colors.iconOnSolidPrimary
695-
tint = if (canSendMessage) Color.White else ElementTheme.colors.iconDisabled
696-
)
697-
}
698-
}
699-
}
700-
701434
@PreviewsDayNight
702435
@Composable
703436
internal fun TextComposerSimplePreview() = ElementPreview {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright (c) 2023 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.element.android.libraries.textcomposer.components
18+
19+
import androidx.compose.foundation.layout.size
20+
import androidx.compose.runtime.Composable
21+
import androidx.compose.ui.Modifier
22+
import androidx.compose.ui.res.stringResource
23+
import androidx.compose.ui.unit.dp
24+
import io.element.android.libraries.designsystem.preview.ElementPreview
25+
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
26+
import io.element.android.libraries.designsystem.text.applyScaleUp
27+
import io.element.android.libraries.designsystem.theme.components.Icon
28+
import io.element.android.libraries.designsystem.theme.components.IconButton
29+
import io.element.android.libraries.designsystem.utils.CommonDrawables
30+
import io.element.android.libraries.textcomposer.R
31+
import io.element.android.libraries.theme.ElementTheme
32+
33+
@Composable
34+
internal fun ComposerOptionsButton(
35+
onClick: () -> Unit,
36+
modifier: Modifier = Modifier,
37+
) {
38+
IconButton(
39+
modifier = modifier
40+
.size(48.dp),
41+
onClick = onClick
42+
) {
43+
Icon(
44+
modifier = Modifier.size(30.dp.applyScaleUp()),
45+
resourceId = CommonDrawables.ic_plus,
46+
contentDescription = stringResource(R.string.rich_text_editor_a11y_add_attachment),
47+
tint = ElementTheme.colors.iconPrimary,
48+
)
49+
}
50+
}
51+
52+
@PreviewsDayNight
53+
@Composable
54+
internal fun ComposerOptionsButtonPreview() = ElementPreview {
55+
ComposerOptionsButton(onClick = {})
56+
}
57+

0 commit comments

Comments
 (0)