@@ -19,7 +19,6 @@ package io.element.android.libraries.textcomposer
1919import androidx.compose.foundation.background
2020import androidx.compose.foundation.border
2121import androidx.compose.foundation.clickable
22- import androidx.compose.foundation.horizontalScroll
2322import androidx.compose.foundation.interaction.MutableInteractionSource
2423import androidx.compose.foundation.layout.Arrangement
2524import androidx.compose.foundation.layout.Box
@@ -34,32 +33,22 @@ import androidx.compose.foundation.layout.padding
3433import androidx.compose.foundation.layout.requiredHeightIn
3534import androidx.compose.foundation.layout.size
3635import androidx.compose.foundation.layout.width
37- import androidx.compose.foundation.rememberScrollState
38- import androidx.compose.foundation.shape.CircleShape
3936import androidx.compose.foundation.shape.RoundedCornerShape
4037import androidx.compose.material.ripple.rememberRipple
4138import androidx.compose.material3.MaterialTheme
4239import androidx.compose.runtime.Composable
43- import androidx.compose.runtime.getValue
44- import androidx.compose.runtime.mutableStateOf
4540import androidx.compose.runtime.remember
46- import androidx.compose.runtime.rememberCoroutineScope
47- import androidx.compose.runtime.setValue
4841import androidx.compose.ui.Alignment
4942import androidx.compose.ui.Modifier
5043import androidx.compose.ui.draw.clip
51- import androidx.compose.ui.graphics.Color
52- import androidx.compose.ui.graphics.vector.ImageVector
5344import androidx.compose.ui.res.stringResource
54- import androidx.compose.ui.res.vectorResource
5545import androidx.compose.ui.text.style.TextAlign
5646import androidx.compose.ui.text.style.TextOverflow
5747import androidx.compose.ui.unit.dp
5848import io.element.android.libraries.designsystem.preview.PreviewsDayNight
5949import io.element.android.libraries.designsystem.preview.ElementPreview
6050import io.element.android.libraries.designsystem.text.applyScaleUp
6151import io.element.android.libraries.designsystem.theme.components.Icon
62- import io.element.android.libraries.designsystem.theme.components.IconButton
6352import io.element.android.libraries.designsystem.theme.components.Text
6453import io.element.android.libraries.designsystem.utils.CommonDrawables
6554import io.element.android.libraries.matrix.api.core.EventId
@@ -70,20 +59,17 @@ import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo
7059import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType
7160import io.element.android.libraries.testtags.TestTags
7261import 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
7566import io.element.android.libraries.textcomposer.components.textInputRoundedCornerShape
7667import io.element.android.libraries.theme.ElementTheme
7768import io.element.android.libraries.ui.strings.CommonStrings
7869import io.element.android.wysiwyg.compose.RichTextEditor
7970import io.element.android.wysiwyg.compose.RichTextEditorState
80- import io.element.android.wysiwyg.view.models.InlineFormat
81- import io.element.android.wysiwyg.view.models.LinkAction
8271import kotlinx.collections.immutable.ImmutableList
8372import kotlinx.collections.immutable.persistentListOf
84- import kotlinx.coroutines.launch
85- import uniffi.wysiwyg_composer.ActionState
86- import uniffi.wysiwyg_composer.ComposerAction
8773
8874@Composable
8975fun 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
520303private 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
703436internal fun TextComposerSimplePreview () = ElementPreview {
0 commit comments