Skip to content

Commit 19eb4c8

Browse files
committed
Do not allow caption on audio files.
Regular files are not previewed, but prevent caption as well there.
1 parent 39ab2f8 commit 19eb4c8

File tree

5 files changed

+92
-77
lines changed

5 files changed

+92
-77
lines changed

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewState.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,21 @@ package io.element.android.features.messages.impl.attachments.preview
99

1010
import androidx.compose.runtime.Immutable
1111
import io.element.android.features.messages.impl.attachments.Attachment
12+
import io.element.android.libraries.core.bool.orFalse
13+
import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeImage
14+
import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeVideo
1215
import io.element.android.libraries.textcomposer.model.TextEditorState
1316

1417
data class AttachmentsPreviewState(
1518
val attachment: Attachment,
1619
val sendActionState: SendActionState,
1720
val textEditorState: TextEditorState,
1821
val eventSink: (AttachmentsPreviewEvents) -> Unit
19-
)
22+
) {
23+
val allowCaption: Boolean = (attachment as? Attachment.Media)?.localMedia?.info?.mimeType?.let {
24+
it.isMimeTypeImage() || it.isMimeTypeVideo()
25+
}.orFalse()
26+
}
2027

2128
@Immutable
2229
sealed interface SendActionState {

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewView.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ private fun AttachmentsPreviewBottomActions(
176176
modifier = modifier,
177177
state = state.textEditorState,
178178
voiceMessageState = VoiceMessageState.Idle,
179-
composerMode = MessageComposerMode.Caption,
179+
composerMode = MessageComposerMode.Attachment(state.allowCaption),
180180
onRequestFocus = {},
181181
onSendMessage = onSendClick,
182182
showTextFormatting = false,

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ class MessageComposerPresenter @Inject constructor(
436436
// Reset composer right away
437437
resetComposer(markdownTextEditorState, richTextEditorState, fromEdit = capturedMode is MessageComposerMode.Edit)
438438
when (capturedMode) {
439-
is MessageComposerMode.Caption,
439+
is MessageComposerMode.Attachment,
440440
is MessageComposerMode.Normal -> room.sendMessage(
441441
body = message.markdown,
442442
htmlBody = message.html,
@@ -606,7 +606,7 @@ class MessageComposerPresenter @Inject constructor(
606606
): ComposerDraft? {
607607
val message = currentComposerMessage(markdownTextEditorState, richTextEditorState, withMentions = false)
608608
val draftType = when (val mode = messageComposerContext.composerMode) {
609-
is MessageComposerMode.Caption,
609+
is MessageComposerMode.Attachment,
610610
is MessageComposerMode.Normal -> ComposerDraftType.NewMessage
611611
is MessageComposerMode.Edit -> {
612612
mode.eventOrTransactionId.eventId?.let { eventId -> ComposerDraftType.Edit(eventId) }

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

Lines changed: 79 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ import io.element.android.wysiwyg.compose.RichTextEditorState
7272
import io.element.android.wysiwyg.display.TextDisplay
7373
import kotlinx.collections.immutable.ImmutableList
7474
import kotlinx.collections.immutable.persistentListOf
75+
import kotlinx.collections.immutable.toPersistentList
7576
import uniffi.wysiwyg_composer.MenuAction
7677
import 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(
525532
internal 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 {
540547
internal 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 {
556563
internal 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 {
571578
internal 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 {
586593
internal 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
603610
internal 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
658666
private 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
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import io.element.android.libraries.matrix.ui.messages.reply.eventId
1818
sealed interface MessageComposerMode {
1919
data object Normal : MessageComposerMode
2020

21-
data object Caption : MessageComposerMode
21+
data class Attachment(val allowCaption: Boolean) : MessageComposerMode
2222

2323
sealed interface Special : MessageComposerMode
2424

@@ -37,7 +37,7 @@ sealed interface MessageComposerMode {
3737
val relatedEventId: EventId?
3838
get() = when (this) {
3939
is Normal,
40-
is Caption -> null
40+
is Attachment -> null
4141
is Edit -> eventOrTransactionId.eventId
4242
is Reply -> eventId
4343
}

0 commit comments

Comments
 (0)