@@ -72,6 +72,7 @@ import io.element.android.wysiwyg.compose.RichTextEditorState
7272import io.element.android.wysiwyg.display.TextDisplay
7373import kotlinx.collections.immutable.ImmutableList
7474import kotlinx.collections.immutable.persistentListOf
75+ import kotlinx.collections.immutable.toPersistentList
7576import uniffi.wysiwyg_composer.MenuAction
7677import kotlin.time.Duration.Companion.seconds
7778
@@ -120,12 +121,12 @@ fun TextComposer(
120121 }
121122
122123 val layoutModifier = modifier
123- .fillMaxSize()
124- .height(IntrinsicSize .Min )
124+ .fillMaxSize()
125+ .height(IntrinsicSize .Min )
125126
126127 val composerOptionsButton: @Composable () -> Unit = remember {
127128 @Composable {
128- if (composerMode == MessageComposerMode .Caption ) {
129+ if (composerMode is MessageComposerMode .Attachment ) {
129130 Spacer (modifier = Modifier .width(9 .dp))
130131 } else {
131132 ComposerOptionsButton (
@@ -139,54 +140,60 @@ fun TextComposer(
139140
140141 val placeholder = if (composerMode.inThread) {
141142 stringResource(id = CommonStrings .action_reply_in_thread)
142- } else if (composerMode == MessageComposerMode .Caption ) {
143+ } else if (composerMode is MessageComposerMode .Attachment ) {
143144 stringResource(id = R .string.rich_text_editor_composer_caption_placeholder)
144145 } else {
145146 stringResource(id = R .string.rich_text_editor_composer_placeholder)
146147 }
147- val textInput: @Composable () -> Unit = when (state) {
148- is TextEditorState .Rich -> {
149- remember(state.richTextEditorState, subcomposing, composerMode, onResetComposerMode, onError) {
148+ val textInput: @Composable () -> Unit = if ((composerMode as ? MessageComposerMode .Attachment )?.allowCaption == false ) {
149+ {
150+ // No text input when in attachment mode and caption not allowed.
151+ }
152+ } else {
153+ when (state) {
154+ is TextEditorState .Rich -> {
155+ remember(state.richTextEditorState, subcomposing, composerMode, onResetComposerMode, onError) {
156+ @Composable {
157+ TextInput (
158+ state = state.richTextEditorState,
159+ subcomposing = subcomposing,
160+ placeholder = placeholder,
161+ composerMode = composerMode,
162+ onResetComposerMode = onResetComposerMode,
163+ resolveMentionDisplay = resolveMentionDisplay,
164+ resolveRoomMentionDisplay = { resolveMentionDisplay(" @room" , " #" ) },
165+ onError = onError,
166+ onTyping = onTyping,
167+ onSelectRichContent = onSelectRichContent,
168+ )
169+ }
170+ }
171+ }
172+ is TextEditorState .Markdown -> {
150173 @Composable {
151- TextInput (
152- state = state.richTextEditorState,
153- subcomposing = subcomposing,
154- placeholder = placeholder,
174+ val style = ElementRichTextEditorStyle .composerStyle(hasFocus = state.hasFocus())
175+ TextInputBox (
155176 composerMode = composerMode,
156177 onResetComposerMode = onResetComposerMode,
157- resolveMentionDisplay = resolveMentionDisplay,
158- resolveRoomMentionDisplay = { resolveMentionDisplay(" @room" , " #" ) },
159- onError = onError,
160- onTyping = onTyping,
161- onSelectRichContent = onSelectRichContent,
162- )
163- }
164- }
165- }
166- is TextEditorState .Markdown -> {
167- @Composable {
168- val style = ElementRichTextEditorStyle .composerStyle(hasFocus = state.hasFocus())
169- TextInputBox (
170- composerMode = composerMode,
171- onResetComposerMode = onResetComposerMode,
172- placeholder = placeholder,
173- showPlaceholder = { state.state.text.value().isEmpty() },
174- subcomposing = subcomposing,
175- ) {
176- MarkdownTextInput (
177- state = state.state,
178+ placeholder = placeholder,
179+ showPlaceholder = { state.state.text.value().isEmpty() },
178180 subcomposing = subcomposing,
179- onTyping = onTyping,
180- onReceiveSuggestion = onReceiveSuggestion,
181- richTextEditorStyle = style,
182- onSelectRichContent = onSelectRichContent,
183- )
181+ ) {
182+ MarkdownTextInput (
183+ state = state.state,
184+ subcomposing = subcomposing,
185+ onTyping = onTyping,
186+ onReceiveSuggestion = onReceiveSuggestion,
187+ richTextEditorStyle = style,
188+ onSelectRichContent = onSelectRichContent,
189+ )
190+ }
184191 }
185192 }
186193 }
187194 }
188195
189- val canSendMessage = markdown.isNotBlank() || composerMode == MessageComposerMode .Caption
196+ val canSendMessage = markdown.isNotBlank() || composerMode is MessageComposerMode .Attachment
190197 val sendButton = @Composable {
191198 SendButton (
192199 canSendMessage = canSendMessage,
@@ -324,8 +331,8 @@ private fun StandardLayout(
324331 if (voiceMessageState is VoiceMessageState .Preview || voiceMessageState is VoiceMessageState .Recording ) {
325332 Box (
326333 modifier = Modifier
327- .padding(bottom = 5 .dp, top = 5 .dp, end = 3 .dp, start = 3 .dp)
328- .size(48 .dp),
334+ .padding(bottom = 5 .dp, top = 5 .dp, end = 3 .dp, start = 3 .dp)
335+ .size(48 .dp),
329336 contentAlignment = Alignment .Center ,
330337 ) {
331338 voiceDeleteButton()
@@ -335,8 +342,8 @@ private fun StandardLayout(
335342 }
336343 Box (
337344 modifier = Modifier
338- .padding(bottom = 8 .dp, top = 8 .dp)
339- .weight(1f )
345+ .padding(bottom = 8 .dp, top = 8 .dp)
346+ .weight(1f )
340347 ) {
341348 voiceRecording()
342349 }
@@ -349,16 +356,16 @@ private fun StandardLayout(
349356 }
350357 Box (
351358 modifier = Modifier
352- .padding(bottom = 8 .dp, top = 8 .dp)
353- .weight(1f )
359+ .padding(bottom = 8 .dp, top = 8 .dp)
360+ .weight(1f )
354361 ) {
355362 textInput()
356363 }
357364 }
358365 Box (
359- Modifier
360- .padding(bottom = 5 .dp, top = 5 .dp, end = 6 .dp, start = 6 .dp)
361- .size(48 .dp),
366+ Modifier
367+ .padding(bottom = 5 .dp, top = 5 .dp, end = 6 .dp, start = 6 .dp)
368+ .size(48 .dp),
362369 contentAlignment = Alignment .Center ,
363370 ) {
364371 endButton()
@@ -380,8 +387,8 @@ private fun TextFormattingLayout(
380387 ) {
381388 Box (
382389 modifier = Modifier
383- .weight(1f )
384- .padding(horizontal = 12 .dp)
390+ .weight(1f )
391+ .padding(horizontal = 12 .dp)
385392 ) {
386393 textInput()
387394 }
@@ -425,11 +432,11 @@ private fun TextInputBox(
425432
426433 Column (
427434 modifier = Modifier
428- .clip(roundedCorners)
429- .border(0.5 .dp, borderColor, roundedCorners)
430- .background(color = bgColor)
431- .requiredHeightIn(min = 42 .dp)
432- .fillMaxSize(),
435+ .clip(roundedCorners)
436+ .border(0.5 .dp, borderColor, roundedCorners)
437+ .background(color = bgColor)
438+ .requiredHeightIn(min = 42 .dp)
439+ .fillMaxSize(),
433440 ) {
434441 if (composerMode is MessageComposerMode .Special ) {
435442 ComposerModeView (
@@ -440,9 +447,9 @@ private fun TextInputBox(
440447 val defaultTypography = ElementTheme .typography.fontBodyLgRegular
441448 Box (
442449 modifier = Modifier
443- .padding(top = 4 .dp, bottom = 4 .dp, start = 12 .dp, end = 12 .dp)
444- // Apply test tag only once, otherwise 2 nodes will have it (both the normal and subcomposing one) and tests will fail
445- .then(if (! subcomposing) Modifier .testTag(TestTags .textEditor) else Modifier ),
450+ .padding(top = 4 .dp, bottom = 4 .dp, start = 12 .dp, end = 12 .dp)
451+ // Apply test tag only once, otherwise 2 nodes will have it (both the normal and subcomposing one) and tests will fail
452+ .then(if (! subcomposing) Modifier .testTag(TestTags .textEditor) else Modifier ),
446453 contentAlignment = Alignment .CenterStart ,
447454 ) {
448455 // Placeholder
@@ -488,8 +495,8 @@ private fun TextInput(
488495 // This prevents it gaining focus and mutating the state.
489496 registerStateUpdates = ! subcomposing,
490497 modifier = Modifier
491- .padding(top = 6 .dp, bottom = 6 .dp)
492- .fillMaxWidth(),
498+ .padding(top = 6 .dp, bottom = 6 .dp)
499+ .fillMaxWidth(),
493500 style = ElementRichTextEditorStyle .composerStyle(hasFocus = state.hasFocus),
494501 resolveMentionDisplay = resolveMentionDisplay,
495502 resolveRoomMentionDisplay = resolveRoomMentionDisplay,
@@ -525,7 +532,7 @@ private fun aTextEditorStateRichList() = persistentListOf(
525532internal fun TextComposerSimplePreview () = ElementPreview {
526533 PreviewColumn (
527534 items = aTextEditorStateMarkdownList()
528- ) { textEditorState ->
535+ ) { _, textEditorState ->
529536 ATextComposer (
530537 state = textEditorState,
531538 voiceMessageState = VoiceMessageState .Idle ,
@@ -540,7 +547,7 @@ internal fun TextComposerSimplePreview() = ElementPreview {
540547internal fun TextComposerFormattingPreview () = ElementPreview {
541548 PreviewColumn (
542549 items = aTextEditorStateRichList()
543- ) { textEditorState ->
550+ ) { _, textEditorState ->
544551 ATextComposer (
545552 state = textEditorState,
546553 voiceMessageState = VoiceMessageState .Idle ,
@@ -556,7 +563,7 @@ internal fun TextComposerFormattingPreview() = ElementPreview {
556563internal fun TextComposerEditPreview () = ElementPreview {
557564 PreviewColumn (
558565 items = aTextEditorStateRichList()
559- ) { textEditorState ->
566+ ) { _, textEditorState ->
560567 ATextComposer (
561568 state = textEditorState,
562569 voiceMessageState = VoiceMessageState .Idle ,
@@ -571,7 +578,7 @@ internal fun TextComposerEditPreview() = ElementPreview {
571578internal fun MarkdownTextComposerEditPreview () = ElementPreview {
572579 PreviewColumn (
573580 items = aTextEditorStateMarkdownList()
574- ) { textEditorState ->
581+ ) { _, textEditorState ->
575582 ATextComposer (
576583 state = textEditorState,
577584 voiceMessageState = VoiceMessageState .Idle ,
@@ -586,7 +593,7 @@ internal fun MarkdownTextComposerEditPreview() = ElementPreview {
586593internal fun TextComposerReplyPreview (@PreviewParameter(InReplyToDetailsProvider ::class ) inReplyToDetails : InReplyToDetails ) = ElementPreview {
587594 PreviewColumn (
588595 items = aTextEditorStateRichList()
589- ) { textEditorState ->
596+ ) { _, textEditorState ->
590597 ATextComposer (
591598 state = textEditorState,
592599 voiceMessageState = VoiceMessageState .Idle ,
@@ -601,13 +608,14 @@ internal fun TextComposerReplyPreview(@PreviewParameter(InReplyToDetailsProvider
601608@PreviewsDayNight
602609@Composable
603610internal fun TextComposerCaptionPreview () = ElementPreview {
611+ val list = aTextEditorStateMarkdownList()
604612 PreviewColumn (
605- items = aTextEditorStateMarkdownList ()
606- ) { textEditorState ->
613+ items = (list + aTextEditorStateMarkdown(initialText = " NO_CAPTION " , initialFocus = true )).toPersistentList ()
614+ ) { index, textEditorState ->
607615 ATextComposer (
608616 state = textEditorState,
609617 voiceMessageState = VoiceMessageState .Idle ,
610- composerMode = MessageComposerMode .Caption ,
618+ composerMode = MessageComposerMode .Attachment (allowCaption = index < list.size) ,
611619 enableVoiceMessages = false ,
612620 )
613621 }
@@ -644,7 +652,7 @@ internal fun TextComposerVoicePreview() = ElementPreview {
644652 playbackProgress = 0.0f
645653 ),
646654 )
647- ) { voiceMessageState ->
655+ ) { _, voiceMessageState ->
648656 ATextComposer (
649657 state = aTextEditorStateRich(initialFocus = true ),
650658 voiceMessageState = voiceMessageState,
@@ -657,14 +665,14 @@ internal fun TextComposerVoicePreview() = ElementPreview {
657665@Composable
658666private fun <T > PreviewColumn (
659667 items : ImmutableList <T >,
660- view : @Composable (T ) -> Unit ,
668+ view : @Composable (Int , T ) -> Unit ,
661669) {
662670 Column {
663- items.forEach { item ->
671+ items.forEachIndexed { index, item ->
664672 Box (
665673 modifier = Modifier .height(IntrinsicSize .Min )
666674 ) {
667- view(item)
675+ view(index, item)
668676 }
669677 }
670678 }
0 commit comments