Edit / Add / Remove caption

This commit is contained in:
Benoit Marty 2024-11-19 11:55:46 +01:00
parent f353ecdd45
commit d3408c8f25
12 changed files with 246 additions and 69 deletions

View file

@ -43,6 +43,7 @@ import io.element.android.features.messages.impl.timeline.components.customreact
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContentWithAttachment
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
@ -273,6 +274,9 @@ class MessagesPresenter @AssistedInject constructor(
TimelineItemAction.CopyLink -> handleCopyLink(targetEvent) TimelineItemAction.CopyLink -> handleCopyLink(targetEvent)
TimelineItemAction.Redact -> handleActionRedact(targetEvent) TimelineItemAction.Redact -> handleActionRedact(targetEvent)
TimelineItemAction.Edit -> handleActionEdit(targetEvent, composerState, enableTextFormatting) TimelineItemAction.Edit -> handleActionEdit(targetEvent, composerState, enableTextFormatting)
TimelineItemAction.AddCaption -> handleActionAddCaption(targetEvent, composerState)
TimelineItemAction.EditCaption -> handleActionEditCaption(targetEvent, composerState)
TimelineItemAction.RemoveCaption -> handleRemoveCaption(targetEvent)
TimelineItemAction.Reply, TimelineItemAction.Reply,
TimelineItemAction.ReplyInThread -> handleActionReply(targetEvent, composerState, timelineProtectionState) TimelineItemAction.ReplyInThread -> handleActionReply(targetEvent, composerState, timelineProtectionState)
TimelineItemAction.ViewSource -> handleShowDebugInfoAction(targetEvent) TimelineItemAction.ViewSource -> handleShowDebugInfoAction(targetEvent)
@ -285,6 +289,16 @@ class MessagesPresenter @AssistedInject constructor(
} }
} }
private suspend fun handleRemoveCaption(targetEvent: TimelineItem.Event) {
timelineController.invokeOnCurrentTimeline {
editCaption(
eventOrTransactionId = targetEvent.eventOrTransactionId,
caption = null,
formattedCaption = null,
)
}
}
private suspend fun handlePinAction(targetEvent: TimelineItem.Event) { private suspend fun handlePinAction(targetEvent: TimelineItem.Event) {
if (targetEvent.eventId == null) return if (targetEvent.eventId == null) return
analyticsService.capture( analyticsService.capture(
@ -387,6 +401,32 @@ class MessagesPresenter @AssistedInject constructor(
} }
} }
private fun handleActionAddCaption(
targetEvent: TimelineItem.Event,
composerState: MessageComposerState,
) {
val composerMode = MessageComposerMode.EditCaption(
eventOrTransactionId = targetEvent.eventOrTransactionId,
content = "",
)
composerState.eventSink(
MessageComposerEvents.SetMode(composerMode)
)
}
private fun handleActionEditCaption(
targetEvent: TimelineItem.Event,
composerState: MessageComposerState,
) {
val composerMode = MessageComposerMode.EditCaption(
eventOrTransactionId = targetEvent.eventOrTransactionId,
content = (targetEvent.content as? TimelineItemEventContentWithAttachment)?.caption.orEmpty(),
)
composerState.eventSink(
MessageComposerEvents.SetMode(composerMode)
)
}
private suspend fun handleActionReply( private suspend fun handleActionReply(
targetEvent: TimelineItem.Event, targetEvent: TimelineItem.Event,
composerState: MessageComposerState, composerState: MessageComposerState,

View file

@ -27,6 +27,7 @@ import io.element.android.features.messages.impl.crypto.sendfailure.VerifiedUser
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContentWithAttachment
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
@ -154,6 +155,16 @@ class DefaultActionListPresenter @AssistedInject constructor(
} }
if (timelineItem.isEditable) { if (timelineItem.isEditable) {
add(TimelineItemAction.Edit) add(TimelineItemAction.Edit)
} else {
// Caption
if (timelineItem.isMine && timelineItem.content is TimelineItemEventContentWithAttachment) {
if (timelineItem.content.caption == null) {
add(TimelineItemAction.AddCaption)
} else {
add(TimelineItemAction.EditCaption)
add(TimelineItemAction.RemoveCaption)
}
}
} }
if (canRedact && timelineItem.content is TimelineItemPollContent && !timelineItem.content.isEnded) { if (canRedact && timelineItem.content is TimelineItemPollContent && !timelineItem.content.isEnded) {
add(TimelineItemAction.EndPoll) add(TimelineItemAction.EndPoll)

View file

@ -28,6 +28,9 @@ sealed class TimelineItemAction(
data object Reply : TimelineItemAction(CommonStrings.action_reply, CompoundDrawables.ic_compound_reply) data object Reply : TimelineItemAction(CommonStrings.action_reply, CompoundDrawables.ic_compound_reply)
data object ReplyInThread : TimelineItemAction(CommonStrings.action_reply_in_thread, CompoundDrawables.ic_compound_reply) data object ReplyInThread : TimelineItemAction(CommonStrings.action_reply_in_thread, CompoundDrawables.ic_compound_reply)
data object Edit : TimelineItemAction(CommonStrings.action_edit, CompoundDrawables.ic_compound_edit) data object Edit : TimelineItemAction(CommonStrings.action_edit, CompoundDrawables.ic_compound_edit)
data object EditCaption : TimelineItemAction(CommonStrings.action_edit_caption, CompoundDrawables.ic_compound_edit)
data object AddCaption : TimelineItemAction(CommonStrings.action_add_caption, CompoundDrawables.ic_compound_edit)
data object RemoveCaption : TimelineItemAction(CommonStrings.action_remove_caption, CompoundDrawables.ic_compound_delete, destructive = true)
data object ViewSource : TimelineItemAction(CommonStrings.action_view_source, CommonDrawables.ic_developer_options) data object ViewSource : TimelineItemAction(CommonStrings.action_view_source, CommonDrawables.ic_developer_options)
data object ReportContent : TimelineItemAction(CommonStrings.action_report_content, CompoundDrawables.ic_compound_chat_problem, destructive = true) data object ReportContent : TimelineItemAction(CommonStrings.action_report_content, CompoundDrawables.ic_compound_chat_problem, destructive = true)
data object EndPoll : TimelineItemAction(CommonStrings.action_end_poll, CompoundDrawables.ic_compound_polls_end) data object EndPoll : TimelineItemAction(CommonStrings.action_end_poll, CompoundDrawables.ic_compound_polls_end)

View file

@ -254,7 +254,7 @@ class MessageComposerPresenter @Inject constructor(
when (event) { when (event) {
MessageComposerEvents.ToggleFullScreenState -> isFullScreen.value = !isFullScreen.value MessageComposerEvents.ToggleFullScreenState -> isFullScreen.value = !isFullScreen.value
MessageComposerEvents.CloseSpecialMode -> { MessageComposerEvents.CloseSpecialMode -> {
if (messageComposerContext.composerMode is MessageComposerMode.Edit) { if (messageComposerContext.composerMode.isEditing) {
localCoroutineScope.launch { localCoroutineScope.launch {
resetComposer(markdownTextEditorState, richTextEditorState, fromEdit = true) resetComposer(markdownTextEditorState, richTextEditorState, fromEdit = true)
} }
@ -431,7 +431,15 @@ class MessageComposerPresenter @Inject constructor(
} }
} }
} }
is MessageComposerMode.EditCaption -> {
timelineController.invokeOnCurrentTimeline {
editCaption(
capturedMode.eventOrTransactionId,
caption = message.markdown,
formattedCaption = message.html
)
}
}
is MessageComposerMode.Reply -> { is MessageComposerMode.Reply -> {
timelineController.invokeOnCurrentTimeline { timelineController.invokeOnCurrentTimeline {
replyMessage(capturedMode.eventId, message.markdown, message.html, message.intentionalMentions) replyMessage(capturedMode.eventId, message.markdown, message.html, message.intentionalMentions)
@ -570,6 +578,10 @@ class MessageComposerPresenter @Inject constructor(
mode.eventOrTransactionId.eventId?.let { eventId -> ComposerDraftType.Edit(eventId) } mode.eventOrTransactionId.eventId?.let { eventId -> ComposerDraftType.Edit(eventId) }
} }
is MessageComposerMode.Reply -> ComposerDraftType.Reply(mode.eventId) is MessageComposerMode.Reply -> ComposerDraftType.Reply(mode.eventId)
is MessageComposerMode.EditCaption -> {
// TODO Need a new type to save caption in the SDK
null
}
} }
return if (draftType == null || message.markdown.isBlank()) { return if (draftType == null || message.markdown.isBlank()) {
null null
@ -644,7 +656,14 @@ class MessageComposerPresenter @Inject constructor(
val currentComposerMode = messageComposerContext.composerMode val currentComposerMode = messageComposerContext.composerMode
when (newComposerMode) { when (newComposerMode) {
is MessageComposerMode.Edit -> { is MessageComposerMode.Edit -> {
if (currentComposerMode !is MessageComposerMode.Edit) { if (currentComposerMode.isEditing.not()) {
val draft = createDraftFromState(markdownTextEditorState, richTextEditorState)
updateDraft(draft, isVolatile = true).join()
}
setText(newComposerMode.content, markdownTextEditorState, richTextEditorState)
}
is MessageComposerMode.EditCaption -> {
if (currentComposerMode.isEditing.not()) {
val draft = createDraftFromState(markdownTextEditorState, richTextEditorState) val draft = createDraftFromState(markdownTextEditorState, richTextEditorState)
updateDraft(draft, isVolatile = true).join() updateDraft(draft, isVolatile = true).join()
} }
@ -652,7 +671,7 @@ class MessageComposerPresenter @Inject constructor(
} }
else -> { else -> {
// When coming from edit, just clear the composer as it'd be weird to reset a volatile draft in this scenario. // When coming from edit, just clear the composer as it'd be weird to reset a volatile draft in this scenario.
if (currentComposerMode is MessageComposerMode.Edit) { if (currentComposerMode.isEditing) {
setText("", markdownTextEditorState, richTextEditorState) setText("", markdownTextEditorState, richTextEditorState)
} }
} }

View file

@ -59,10 +59,17 @@ interface Timeline : AutoCloseable {
suspend fun editMessage( suspend fun editMessage(
eventOrTransactionId: EventOrTransactionId, eventOrTransactionId: EventOrTransactionId,
body: String, htmlBody: String?, body: String,
htmlBody: String?,
intentionalMentions: List<IntentionalMention>, intentionalMentions: List<IntentionalMention>,
): Result<Unit> ): Result<Unit>
suspend fun editCaption(
eventOrTransactionId: EventOrTransactionId,
caption: String?,
formattedCaption: String?,
): Result<Unit>
suspend fun replyMessage( suspend fun replyMessage(
eventId: EventId, eventId: EventId,
body: String, body: String,

View file

@ -295,22 +295,40 @@ class RustTimeline(
body: String, body: String,
htmlBody: String?, htmlBody: String?,
intentionalMentions: List<IntentionalMention>, intentionalMentions: List<IntentionalMention>,
): Result<Unit> = ): Result<Unit> = withContext(dispatcher) {
withContext(dispatcher) { runCatching<Unit> {
runCatching<Unit> { val editedContent = EditedContent.RoomMessage(
val editedContent = EditedContent.RoomMessage( content = MessageEventContent.from(
content = MessageEventContent.from( body = body,
body = body, htmlBody = htmlBody,
htmlBody = htmlBody, intentionalMentions = intentionalMentions
intentionalMentions = intentionalMentions ),
), )
) inner.edit(
inner.edit( newContent = editedContent,
newContent = editedContent, eventOrTransactionId = eventOrTransactionId.toRustEventOrTransactionId(),
eventOrTransactionId = eventOrTransactionId.toRustEventOrTransactionId(), )
)
}
} }
}
override suspend fun editCaption(
eventOrTransactionId: EventOrTransactionId,
caption: String?,
formattedCaption: String?,
): Result<Unit> = withContext(dispatcher) {
runCatching<Unit> {
val editedContent = EditedContent.MediaCaption(
caption = caption,
formattedCaption = formattedCaption?.let {
FormattedBody(body = it, format = MessageFormat.Html)
},
)
inner.edit(
newContent = editedContent,
eventOrTransactionId = eventOrTransactionId.toRustEventOrTransactionId(),
)
}
}
override suspend fun replyMessage( override suspend fun replyMessage(
eventId: EventId, eventId: EventId,

View file

@ -92,6 +92,24 @@ class FakeTimeline(
intentionalMentions intentionalMentions
) )
var editCaptionLambda: (
eventOrTransactionId: EventOrTransactionId,
caption: String?,
formattedCaption: String?,
) -> Result<Unit> = { _, _, _ ->
lambdaError()
}
override suspend fun editCaption(
eventOrTransactionId: EventOrTransactionId,
caption: String?,
formattedCaption: String?,
): Result<Unit> = editCaptionLambda(
eventOrTransactionId,
caption,
formattedCaption,
)
var replyMessageLambda: ( var replyMessageLambda: (
eventId: EventId, eventId: EventId,
body: String, body: String,

View file

@ -47,6 +47,16 @@ internal fun ComposerModeView(
when (composerMode) { when (composerMode) {
is MessageComposerMode.Edit -> { is MessageComposerMode.Edit -> {
EditingModeView( EditingModeView(
text = stringResource(CommonStrings.common_editing),
modifier = modifier,
onResetComposerMode = onResetComposerMode,
)
}
is MessageComposerMode.EditCaption -> {
EditingModeView(
text = stringResource(
if (composerMode.content.isEmpty()) CommonStrings.common_adding_caption else CommonStrings.common_editing_caption
),
modifier = modifier, modifier = modifier,
onResetComposerMode = onResetComposerMode, onResetComposerMode = onResetComposerMode,
) )
@ -65,6 +75,7 @@ internal fun ComposerModeView(
@Composable @Composable
private fun EditingModeView( private fun EditingModeView(
onResetComposerMode: () -> Unit, onResetComposerMode: () -> Unit,
text: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
Row( Row(
@ -76,14 +87,14 @@ private fun EditingModeView(
) { ) {
Icon( Icon(
imageVector = CompoundIcons.Edit(), imageVector = CompoundIcons.Edit(),
contentDescription = stringResource(CommonStrings.common_editing), contentDescription = null,
tint = ElementTheme.materialColors.secondary, tint = ElementTheme.materialColors.secondary,
modifier = Modifier modifier = Modifier
.padding(vertical = 8.dp) .padding(vertical = 8.dp)
.size(16.dp), .size(16.dp),
) )
Text( Text(
stringResource(CommonStrings.common_editing), text = text,
style = ElementTheme.typography.fontBodySmRegular, style = ElementTheme.typography.fontBodySmRegular,
textAlign = TextAlign.Start, textAlign = TextAlign.Start,
color = ElementTheme.materialColors.secondary, color = ElementTheme.materialColors.secondary,

View file

@ -121,19 +121,25 @@ fun TextComposer(
} }
val layoutModifier = modifier val layoutModifier = modifier
.fillMaxSize() .fillMaxSize()
.height(IntrinsicSize.Min) .height(IntrinsicSize.Min)
val composerOptionsButton: @Composable () -> Unit = remember { val composerOptionsButton: @Composable () -> Unit = remember(composerMode) {
@Composable { @Composable {
if (composerMode is MessageComposerMode.Attachment) { when (composerMode) {
Spacer(modifier = Modifier.width(9.dp)) is MessageComposerMode.Attachment -> {
} else { Spacer(modifier = Modifier.width(9.dp))
ComposerOptionsButton( }
modifier = Modifier is MessageComposerMode.EditCaption -> {
.size(48.dp), Spacer(modifier = Modifier.width(16.dp))
onClick = onAddAttachment }
) else -> {
ComposerOptionsButton(
modifier = Modifier
.size(48.dp),
onClick = onAddAttachment
)
}
} }
} }
} }
@ -331,8 +337,8 @@ private fun StandardLayout(
if (voiceMessageState is VoiceMessageState.Preview || voiceMessageState is VoiceMessageState.Recording) { if (voiceMessageState is VoiceMessageState.Preview || voiceMessageState is VoiceMessageState.Recording) {
Box( Box(
modifier = Modifier modifier = Modifier
.padding(bottom = 5.dp, top = 5.dp, end = 3.dp, start = 3.dp) .padding(bottom = 5.dp, top = 5.dp, end = 3.dp, start = 3.dp)
.size(48.dp), .size(48.dp),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
voiceDeleteButton() voiceDeleteButton()
@ -342,8 +348,8 @@ private fun StandardLayout(
} }
Box( Box(
modifier = Modifier modifier = Modifier
.padding(bottom = 8.dp, top = 8.dp) .padding(bottom = 8.dp, top = 8.dp)
.weight(1f) .weight(1f)
) { ) {
voiceRecording() voiceRecording()
} }
@ -356,16 +362,16 @@ private fun StandardLayout(
} }
Box( Box(
modifier = Modifier modifier = Modifier
.padding(bottom = 8.dp, top = 8.dp) .padding(bottom = 8.dp, top = 8.dp)
.weight(1f) .weight(1f)
) { ) {
textInput() textInput()
} }
} }
Box( Box(
Modifier Modifier
.padding(bottom = 5.dp, top = 5.dp, end = 6.dp, start = 6.dp) .padding(bottom = 5.dp, top = 5.dp, end = 6.dp, start = 6.dp)
.size(48.dp), .size(48.dp),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
endButton() endButton()
@ -387,8 +393,8 @@ private fun TextFormattingLayout(
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.padding(horizontal = 12.dp) .padding(horizontal = 12.dp)
) { ) {
textInput() textInput()
} }
@ -432,11 +438,11 @@ private fun TextInputBox(
Column( Column(
modifier = Modifier modifier = Modifier
.clip(roundedCorners) .clip(roundedCorners)
.border(0.5.dp, borderColor, roundedCorners) .border(0.5.dp, borderColor, roundedCorners)
.background(color = bgColor) .background(color = bgColor)
.requiredHeightIn(min = 42.dp) .requiredHeightIn(min = 42.dp)
.fillMaxSize(), .fillMaxSize(),
) { ) {
if (composerMode is MessageComposerMode.Special) { if (composerMode is MessageComposerMode.Special) {
ComposerModeView( ComposerModeView(
@ -447,9 +453,9 @@ private fun TextInputBox(
val defaultTypography = ElementTheme.typography.fontBodyLgRegular val defaultTypography = ElementTheme.typography.fontBodyLgRegular
Box( Box(
modifier = Modifier modifier = Modifier
.padding(top = 4.dp, bottom = 4.dp, start = 12.dp, end = 12.dp) .padding(top = 4.dp, bottom = 4.dp, start = 12.dp, end = 12.dp)
// Apply test tag only once, otherwise 2 nodes will have it (both the normal and subcomposing one) and tests will fail // Apply test tag only once, otherwise 2 nodes will have it (both the normal and subcomposing one) and tests will fail
.then(if (!subcomposing) Modifier.testTag(TestTags.textEditor) else Modifier), .then(if (!subcomposing) Modifier.testTag(TestTags.textEditor) else Modifier),
contentAlignment = Alignment.CenterStart, contentAlignment = Alignment.CenterStart,
) { ) {
// Placeholder // Placeholder
@ -495,8 +501,8 @@ private fun TextInput(
// This prevents it gaining focus and mutating the state. // This prevents it gaining focus and mutating the state.
registerStateUpdates = !subcomposing, registerStateUpdates = !subcomposing,
modifier = Modifier modifier = Modifier
.padding(top = 6.dp, bottom = 6.dp) .padding(top = 6.dp, bottom = 6.dp)
.fillMaxWidth(), .fillMaxWidth(),
style = ElementRichTextEditorStyle.composerStyle(hasFocus = state.hasFocus), style = ElementRichTextEditorStyle.composerStyle(hasFocus = state.hasFocus),
resolveMentionDisplay = resolveMentionDisplay, resolveMentionDisplay = resolveMentionDisplay,
resolveRoomMentionDisplay = resolveRoomMentionDisplay, resolveRoomMentionDisplay = resolveRoomMentionDisplay,
@ -573,6 +579,40 @@ internal fun TextComposerEditPreview() = ElementPreview {
} }
} }
@PreviewsDayNight
@Composable
internal fun TextComposerEditCaptionPreview() = ElementPreview {
PreviewColumn(
items = aTextEditorStateRichList()
) { _, textEditorState ->
ATextComposer(
state = textEditorState,
voiceMessageState = VoiceMessageState.Idle,
composerMode = aMessageComposerModeEditCaption(
content = "A caption",
),
enableVoiceMessages = false,
)
}
}
@PreviewsDayNight
@Composable
internal fun TextComposerAddCaptionPreview() = ElementPreview {
PreviewColumn(
items = aTextEditorStateRichList()
) { _, textEditorState ->
ATextComposer(
state = textEditorState,
voiceMessageState = VoiceMessageState.Idle,
composerMode = aMessageComposerModeEditCaption(
content = "",
),
enableVoiceMessages = false,
)
}
}
@PreviewsDayNight @PreviewsDayNight
@Composable @Composable
internal fun MarkdownTextComposerEditPreview() = ElementPreview { internal fun MarkdownTextComposerEditPreview() = ElementPreview {
@ -717,6 +757,14 @@ fun aMessageComposerModeEdit(
content = content content = content
) )
fun aMessageComposerModeEditCaption(
eventOrTransactionId: EventOrTransactionId = EventId("$1234").toEventOrTransactionId(),
content: String = "Some caption",
) = MessageComposerMode.EditCaption(
eventOrTransactionId = eventOrTransactionId,
content = content
)
fun aMessageComposerModeReply( fun aMessageComposerModeReply(
replyToDetails: InReplyToDetails, replyToDetails: InReplyToDetails,
hideImage: Boolean = false, hideImage: Boolean = false,

View file

@ -53,16 +53,16 @@ internal fun SendButton(
onClick = onClick, onClick = onClick,
enabled = canSendMessage, enabled = canSendMessage,
) { ) {
val iconVector = when (composerMode) { val iconVector = when {
is MessageComposerMode.Edit -> CompoundIcons.Check() composerMode.isEditing -> CompoundIcons.Check()
else -> CompoundIcons.SendSolid() else -> CompoundIcons.SendSolid()
} }
val iconStartPadding = when (composerMode) { val iconStartPadding = when {
is MessageComposerMode.Edit -> 0.dp composerMode.isEditing -> 0.dp
else -> 2.dp else -> 2.dp
} }
val contentDescription = when (composerMode) { val contentDescription = when {
is MessageComposerMode.Edit -> stringResource(CommonStrings.action_edit) composerMode.isEditing -> stringResource(CommonStrings.action_edit)
else -> stringResource(CommonStrings.action_send) else -> stringResource(CommonStrings.action_send)
} }
Box( Box(

View file

@ -27,6 +27,11 @@ sealed interface MessageComposerMode {
val content: String val content: String
) : Special ) : Special
data class EditCaption(
val eventOrTransactionId: EventOrTransactionId,
val content: String
) : Special
data class Reply( data class Reply(
val replyToDetails: InReplyToDetails, val replyToDetails: InReplyToDetails,
val hideImage: Boolean, val hideImage: Boolean,
@ -34,16 +39,8 @@ sealed interface MessageComposerMode {
val eventId: EventId = replyToDetails.eventId() val eventId: EventId = replyToDetails.eventId()
} }
val relatedEventId: EventId?
get() = when (this) {
is Normal,
is Attachment -> null
is Edit -> eventOrTransactionId.eventId
is Reply -> eventId
}
val isEditing: Boolean val isEditing: Boolean
get() = this is Edit get() = this is Edit || this is EditCaption
val isReply: Boolean val isReply: Boolean
get() = this is Reply get() = this is Reply

View file

@ -32,6 +32,7 @@
<string name="a11y_voice_message_record">"Record voice message."</string> <string name="a11y_voice_message_record">"Record voice message."</string>
<string name="a11y_voice_message_stop_recording">"Stop recording"</string> <string name="a11y_voice_message_stop_recording">"Stop recording"</string>
<string name="action_accept">"Accept"</string> <string name="action_accept">"Accept"</string>
<string name="action_add_caption">"Add caption"</string>
<string name="action_add_to_timeline">"Add to timeline"</string> <string name="action_add_to_timeline">"Add to timeline"</string>
<string name="action_back">"Back"</string> <string name="action_back">"Back"</string>
<string name="action_call">"Call"</string> <string name="action_call">"Call"</string>
@ -57,6 +58,7 @@
<string name="action_discard">"Discard"</string> <string name="action_discard">"Discard"</string>
<string name="action_done">"Done"</string> <string name="action_done">"Done"</string>
<string name="action_edit">"Edit"</string> <string name="action_edit">"Edit"</string>
<string name="action_edit_caption">"Edit caption"</string>
<string name="action_edit_poll">"Edit poll"</string> <string name="action_edit_poll">"Edit poll"</string>
<string name="action_enable">"Enable"</string> <string name="action_enable">"Enable"</string>
<string name="action_end_poll">"End poll"</string> <string name="action_end_poll">"End poll"</string>
@ -91,6 +93,7 @@
<string name="action_react">"React"</string> <string name="action_react">"React"</string>
<string name="action_reject">"Reject"</string> <string name="action_reject">"Reject"</string>
<string name="action_remove">"Remove"</string> <string name="action_remove">"Remove"</string>
<string name="action_remove_caption">"Remove caption"</string>
<string name="action_reply">"Reply"</string> <string name="action_reply">"Reply"</string>
<string name="action_reply_in_thread">"Reply in thread"</string> <string name="action_reply_in_thread">"Reply in thread"</string>
<string name="action_report_bug">"Report bug"</string> <string name="action_report_bug">"Report bug"</string>
@ -123,6 +126,7 @@
<string name="action_yes">"Yes"</string> <string name="action_yes">"Yes"</string>
<string name="common_about">"About"</string> <string name="common_about">"About"</string>
<string name="common_acceptable_use_policy">"Acceptable use policy"</string> <string name="common_acceptable_use_policy">"Acceptable use policy"</string>
<string name="common_adding_caption">"Adding caption"</string>
<string name="common_advanced_settings">"Advanced settings"</string> <string name="common_advanced_settings">"Advanced settings"</string>
<string name="common_analytics">"Analytics"</string> <string name="common_analytics">"Analytics"</string>
<string name="common_appearance">"Appearance"</string> <string name="common_appearance">"Appearance"</string>
@ -143,6 +147,7 @@
<string name="common_do_not_show_this_again">"Do not show this again"</string> <string name="common_do_not_show_this_again">"Do not show this again"</string>
<string name="common_edited_suffix">"(edited)"</string> <string name="common_edited_suffix">"(edited)"</string>
<string name="common_editing">"Editing"</string> <string name="common_editing">"Editing"</string>
<string name="common_editing_caption">"Editing caption"</string>
<string name="common_emote">"* %1$s %2$s"</string> <string name="common_emote">"* %1$s %2$s"</string>
<string name="common_encryption">"Encryption"</string> <string name="common_encryption">"Encryption"</string>
<string name="common_encryption_enabled">"Encryption enabled"</string> <string name="common_encryption_enabled">"Encryption enabled"</string>