Merge pull request #3977 from element-hq/feature/bma/captionWarning
Add warning when adding a caption.
This commit is contained in:
@@ -402,26 +402,28 @@ class MessagesPresenter @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleActionAddCaption(
|
||||
private suspend fun handleActionAddCaption(
|
||||
targetEvent: TimelineItem.Event,
|
||||
composerState: MessageComposerState,
|
||||
) {
|
||||
val composerMode = MessageComposerMode.EditCaption(
|
||||
eventOrTransactionId = targetEvent.eventOrTransactionId,
|
||||
content = "",
|
||||
showCaptionCompatibilityWarning = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaCaptionWarning),
|
||||
)
|
||||
composerState.eventSink(
|
||||
MessageComposerEvents.SetMode(composerMode)
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleActionEditCaption(
|
||||
private suspend fun handleActionEditCaption(
|
||||
targetEvent: TimelineItem.Event,
|
||||
composerState: MessageComposerState,
|
||||
) {
|
||||
val composerMode = MessageComposerMode.EditCaption(
|
||||
eventOrTransactionId = targetEvent.eventOrTransactionId,
|
||||
content = (targetEvent.content as? TimelineItemEventContentWithAttachment)?.caption.orEmpty(),
|
||||
showCaptionCompatibilityWarning = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaCaptionWarning),
|
||||
)
|
||||
composerState.eventSink(
|
||||
MessageComposerEvents.SetMode(composerMode)
|
||||
|
||||
@@ -74,6 +74,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
|
||||
|
||||
val userSentAttachment = remember { mutableStateOf(false) }
|
||||
val allowCaption by featureFlagService.isFeatureEnabledFlow(FeatureFlags.MediaCaptionCreation).collectAsState(initial = false)
|
||||
val showCaptionCompatibilityWarning by featureFlagService.isFeatureEnabledFlow(FeatureFlags.MediaCaptionWarning).collectAsState(initial = false)
|
||||
|
||||
val mediaUploadInfoState = remember { mutableStateOf<AsyncData<MediaUploadInfo>>(AsyncData.Uninitialized) }
|
||||
LaunchedEffect(Unit) {
|
||||
@@ -145,6 +146,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
|
||||
sendActionState = sendActionState.value,
|
||||
textEditorState = textEditorState,
|
||||
allowCaption = allowCaption,
|
||||
showCaptionCompatibilityWarning = showCaptionCompatibilityWarning,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ data class AttachmentsPreviewState(
|
||||
val sendActionState: SendActionState,
|
||||
val textEditorState: TextEditorState,
|
||||
val allowCaption: Boolean,
|
||||
val showCaptionCompatibilityWarning: Boolean,
|
||||
val eventSink: (AttachmentsPreviewEvents) -> Unit
|
||||
)
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ open class AttachmentsPreviewStateProvider : PreviewParameterProvider<Attachment
|
||||
anAttachmentsPreviewState(sendActionState = SendActionState.Sending.Uploading(0.5f)),
|
||||
anAttachmentsPreviewState(sendActionState = SendActionState.Failure(RuntimeException("error"))),
|
||||
anAttachmentsPreviewState(allowCaption = false),
|
||||
anAttachmentsPreviewState(showCaptionCompatibilityWarning = true),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -32,6 +33,7 @@ fun anAttachmentsPreviewState(
|
||||
textEditorState: TextEditorState = aTextEditorStateMarkdown(),
|
||||
sendActionState: SendActionState = SendActionState.Idle,
|
||||
allowCaption: Boolean = true,
|
||||
showCaptionCompatibilityWarning: Boolean = true,
|
||||
) = AttachmentsPreviewState(
|
||||
attachment = Attachment.Media(
|
||||
localMedia = LocalMedia("file://path".toUri(), mediaInfo),
|
||||
@@ -39,5 +41,6 @@ fun anAttachmentsPreviewState(
|
||||
sendActionState = sendActionState,
|
||||
textEditorState = textEditorState,
|
||||
allowCaption = allowCaption,
|
||||
showCaptionCompatibilityWarning = showCaptionCompatibilityWarning,
|
||||
eventSink = {}
|
||||
)
|
||||
|
||||
@@ -173,7 +173,10 @@ private fun AttachmentsPreviewBottomActions(
|
||||
modifier = modifier,
|
||||
state = state.textEditorState,
|
||||
voiceMessageState = VoiceMessageState.Idle,
|
||||
composerMode = MessageComposerMode.Attachment(state.allowCaption),
|
||||
composerMode = MessageComposerMode.Attachment(
|
||||
allowCaption = state.allowCaption,
|
||||
showCaptionCompatibilityWarning = state.showCaptionCompatibilityWarning,
|
||||
),
|
||||
onRequestFocus = {},
|
||||
onSendMessage = onSendClick,
|
||||
showTextFormatting = false,
|
||||
|
||||
@@ -45,6 +45,8 @@ import io.element.android.libraries.core.mimetype.MimeTypes
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
@@ -1007,6 +1009,37 @@ class MessagesPresenterTest {
|
||||
composerMode = MessageComposerMode.EditCaption(
|
||||
eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(),
|
||||
content = A_CAPTION,
|
||||
showCaptionCompatibilityWarning = true,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle action edit caption without warning`() = runTest {
|
||||
val messageEvent = aMessageEvent(
|
||||
content = aTimelineItemImageContent(
|
||||
caption = A_CAPTION,
|
||||
)
|
||||
)
|
||||
val composerRecorder = EventsRecorder<MessageComposerEvents>()
|
||||
val presenter = createMessagesPresenter(
|
||||
messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) },
|
||||
featureFlagService = FakeFeatureFlagService(
|
||||
initialState = mapOf(FeatureFlags.MediaCaptionWarning.key to false)
|
||||
)
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.EditCaption, messageEvent))
|
||||
awaitItem()
|
||||
composerRecorder.assertSingle(
|
||||
MessageComposerEvents.SetMode(
|
||||
composerMode = MessageComposerMode.EditCaption(
|
||||
eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(),
|
||||
content = A_CAPTION,
|
||||
showCaptionCompatibilityWarning = false,
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1033,6 +1066,37 @@ class MessagesPresenterTest {
|
||||
composerMode = MessageComposerMode.EditCaption(
|
||||
eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(),
|
||||
content = "",
|
||||
showCaptionCompatibilityWarning = true,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle action add caption without warning`() = runTest {
|
||||
val composerRecorder = EventsRecorder<MessageComposerEvents>()
|
||||
val presenter = createMessagesPresenter(
|
||||
messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) },
|
||||
featureFlagService = FakeFeatureFlagService(
|
||||
initialState = mapOf(FeatureFlags.MediaCaptionWarning.key to false)
|
||||
)
|
||||
)
|
||||
val messageEvent = aMessageEvent(
|
||||
content = aTimelineItemImageContent(
|
||||
caption = null,
|
||||
)
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.AddCaption, messageEvent))
|
||||
awaitItem()
|
||||
composerRecorder.assertSingle(
|
||||
MessageComposerEvents.SetMode(
|
||||
composerMode = MessageComposerMode.EditCaption(
|
||||
eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(),
|
||||
content = "",
|
||||
showCaptionCompatibilityWarning = false,
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1097,6 +1161,7 @@ class MessagesPresenterTest {
|
||||
givenRoomInfo(aRoomInfo(id = roomId, name = ""))
|
||||
},
|
||||
navigator: FakeMessagesNavigator = FakeMessagesNavigator(),
|
||||
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
|
||||
clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(),
|
||||
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
||||
timelineEventSink: (TimelineEvents) -> Unit = {},
|
||||
@@ -1109,7 +1174,6 @@ class MessagesPresenterTest {
|
||||
},
|
||||
actionListEventSink: (ActionListEvents) -> Unit = {},
|
||||
): MessagesPresenter {
|
||||
val featureFlagService = FakeFeatureFlagService()
|
||||
return MessagesPresenter(
|
||||
room = matrixRoom,
|
||||
composerPresenter = messageComposerPresenter,
|
||||
|
||||
@@ -67,10 +67,22 @@ class AttachmentsPreviewPresenterTest {
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
createAttachmentsPreviewPresenter().test {
|
||||
skipItems(1)
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
||||
assertThat(initialState.allowCaption).isTrue()
|
||||
assertThat(initialState.showCaptionCompatibilityWarning).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - initial state no caption warning`() = runTest {
|
||||
createAttachmentsPreviewPresenter(
|
||||
showCaptionCompatibilityWarning = false,
|
||||
).test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.showCaptionCompatibilityWarning).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,6 +455,7 @@ class AttachmentsPreviewPresenterTest {
|
||||
onDoneListener: OnDoneListener = OnDoneListener { lambdaError() },
|
||||
mediaUploadOnSendQueueEnabled: Boolean = true,
|
||||
allowCaption: Boolean = true,
|
||||
showCaptionCompatibilityWarning: Boolean = true,
|
||||
): AttachmentsPreviewPresenter {
|
||||
return AttachmentsPreviewPresenter(
|
||||
attachment = aMediaAttachment(localMedia),
|
||||
@@ -454,6 +467,7 @@ class AttachmentsPreviewPresenterTest {
|
||||
initialState = mapOf(
|
||||
FeatureFlags.MediaUploadOnSendQueue.key to mediaUploadOnSendQueueEnabled,
|
||||
FeatureFlags.MediaCaptionCreation.key to allowCaption,
|
||||
FeatureFlags.MediaCaptionWarning.key to showCaptionCompatibilityWarning,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1586,7 +1586,12 @@ fun anEditMode(
|
||||
fun anEditCaptionMode(
|
||||
eventOrTransactionId: EventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(),
|
||||
caption: String = A_CAPTION,
|
||||
) = MessageComposerMode.EditCaption(eventOrTransactionId, caption)
|
||||
showCaptionCompatibilityWarning: Boolean = false,
|
||||
) = MessageComposerMode.EditCaption(
|
||||
eventOrTransactionId = eventOrTransactionId,
|
||||
content = caption,
|
||||
showCaptionCompatibilityWarning = showCaptionCompatibilityWarning,
|
||||
)
|
||||
|
||||
fun aReplyMode() = MessageComposerMode.Reply(
|
||||
replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID),
|
||||
|
||||
@@ -144,7 +144,14 @@ enum class FeatureFlags(
|
||||
key = "feature.media_caption_creation",
|
||||
title = "Allow creation of media captions",
|
||||
description = null,
|
||||
defaultValue = { buildMeta -> buildMeta.buildType != BuildType.RELEASE },
|
||||
defaultValue = { true },
|
||||
isFinished = false,
|
||||
),
|
||||
MediaCaptionWarning(
|
||||
key = "feature.media_caption_creation_warning",
|
||||
title = "Show a compatibility warning on media captions creation",
|
||||
description = null,
|
||||
defaultValue = { true },
|
||||
isFinished = false,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.textcomposer
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.designsystem.components.BigIcon
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet
|
||||
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun CaptionWarningBottomSheet(
|
||||
onDismiss: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
ModalBottomSheet(
|
||||
modifier = modifier,
|
||||
onDismissRequest = onDismiss,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
BigIcon(
|
||||
style = BigIcon.Style.AlertSolid,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(CommonStrings.screen_media_upload_preview_caption_warning),
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
OutlinedButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp),
|
||||
onClick = onDismiss,
|
||||
text = stringResource(CommonStrings.action_ok),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun CaptionWarningBottomSheetPreview() = ElementPreview {
|
||||
CaptionWarningBottomSheet(
|
||||
onDismiss = {},
|
||||
)
|
||||
}
|
||||
@@ -10,8 +10,10 @@ package io.element.android.libraries.textcomposer
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -26,8 +28,10 @@ import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
@@ -36,10 +40,12 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.libraries.designsystem.components.media.createFakeWaveform
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||
@@ -66,6 +72,7 @@ import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent
|
||||
import io.element.android.libraries.textcomposer.model.VoiceMessageState
|
||||
import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown
|
||||
import io.element.android.libraries.textcomposer.model.aTextEditorStateRich
|
||||
import io.element.android.libraries.textcomposer.model.showCaptionCompatibilityWarning
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.wysiwyg.compose.RichTextEditor
|
||||
import io.element.android.wysiwyg.compose.RichTextEditorState
|
||||
@@ -121,8 +128,8 @@ fun TextComposer(
|
||||
}
|
||||
|
||||
val layoutModifier = modifier
|
||||
.fillMaxSize()
|
||||
.height(IntrinsicSize.Min)
|
||||
.fillMaxSize()
|
||||
.height(IntrinsicSize.Min)
|
||||
|
||||
val composerOptionsButton: @Composable () -> Unit = remember(composerMode) {
|
||||
@Composable {
|
||||
@@ -146,7 +153,7 @@ fun TextComposer(
|
||||
|
||||
val placeholder = if (composerMode.inThread) {
|
||||
stringResource(id = CommonStrings.action_reply_in_thread)
|
||||
} else if (composerMode is MessageComposerMode.Attachment) {
|
||||
} else if (composerMode is MessageComposerMode.Attachment || composerMode is MessageComposerMode.EditCaption) {
|
||||
stringResource(id = R.string.rich_text_editor_composer_caption_placeholder)
|
||||
} else {
|
||||
stringResource(id = R.string.rich_text_editor_composer_placeholder)
|
||||
@@ -182,7 +189,7 @@ fun TextComposer(
|
||||
composerMode = composerMode,
|
||||
onResetComposerMode = onResetComposerMode,
|
||||
placeholder = placeholder,
|
||||
showPlaceholder = { state.state.text.value().isEmpty() },
|
||||
showPlaceholder = state.state.text.value().isEmpty(),
|
||||
subcomposing = subcomposing,
|
||||
) {
|
||||
MarkdownTextInput(
|
||||
@@ -337,8 +344,8 @@ private fun StandardLayout(
|
||||
if (voiceMessageState is VoiceMessageState.Preview || voiceMessageState is VoiceMessageState.Recording) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 5.dp, top = 5.dp, end = 3.dp, start = 3.dp)
|
||||
.size(48.dp),
|
||||
.padding(bottom = 5.dp, top = 5.dp, end = 3.dp, start = 3.dp)
|
||||
.size(48.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
voiceDeleteButton()
|
||||
@@ -348,8 +355,8 @@ private fun StandardLayout(
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 8.dp, top = 8.dp)
|
||||
.weight(1f)
|
||||
.padding(bottom = 8.dp, top = 8.dp)
|
||||
.weight(1f)
|
||||
) {
|
||||
voiceRecording()
|
||||
}
|
||||
@@ -362,16 +369,16 @@ private fun StandardLayout(
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 8.dp, top = 8.dp)
|
||||
.weight(1f)
|
||||
.padding(bottom = 8.dp, top = 8.dp)
|
||||
.weight(1f)
|
||||
) {
|
||||
textInput()
|
||||
}
|
||||
}
|
||||
Box(
|
||||
Modifier
|
||||
.padding(bottom = 5.dp, top = 5.dp, end = 6.dp, start = 6.dp)
|
||||
.size(48.dp),
|
||||
Modifier
|
||||
.padding(bottom = 5.dp, top = 5.dp, end = 6.dp, start = 6.dp)
|
||||
.size(48.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
endButton()
|
||||
@@ -393,8 +400,8 @@ private fun TextFormattingLayout(
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(horizontal = 12.dp)
|
||||
.weight(1f)
|
||||
.padding(horizontal = 12.dp)
|
||||
) {
|
||||
textInput()
|
||||
}
|
||||
@@ -428,9 +435,9 @@ private fun TextInputBox(
|
||||
composerMode: MessageComposerMode,
|
||||
onResetComposerMode: () -> Unit,
|
||||
placeholder: String,
|
||||
showPlaceholder: () -> Boolean,
|
||||
showPlaceholder: Boolean,
|
||||
subcomposing: Boolean,
|
||||
textInput: @Composable () -> Unit,
|
||||
textInput: @Composable BoxScope.() -> Unit,
|
||||
) {
|
||||
val bgColor = ElementTheme.colors.bgSubtleSecondary
|
||||
val borderColor = ElementTheme.colors.borderDisabled
|
||||
@@ -438,11 +445,11 @@ private fun TextInputBox(
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.clip(roundedCorners)
|
||||
.border(0.5.dp, borderColor, roundedCorners)
|
||||
.background(color = bgColor)
|
||||
.requiredHeightIn(min = 42.dp)
|
||||
.fillMaxSize(),
|
||||
.clip(roundedCorners)
|
||||
.border(0.5.dp, borderColor, roundedCorners)
|
||||
.background(color = bgColor)
|
||||
.requiredHeightIn(min = 42.dp)
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
if (composerMode is MessageComposerMode.Special) {
|
||||
ComposerModeView(
|
||||
@@ -453,15 +460,15 @@ private fun TextInputBox(
|
||||
val defaultTypography = ElementTheme.typography.fontBodyLgRegular
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.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
|
||||
.then(if (!subcomposing) Modifier.testTag(TestTags.textEditor) else Modifier),
|
||||
.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
|
||||
.then(if (!subcomposing) Modifier.testTag(TestTags.textEditor) else Modifier),
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
) {
|
||||
// Placeholder
|
||||
if (showPlaceholder()) {
|
||||
if (showPlaceholder) {
|
||||
Text(
|
||||
placeholder,
|
||||
text = placeholder,
|
||||
style = defaultTypography.copy(
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
),
|
||||
@@ -471,6 +478,24 @@ private fun TextInputBox(
|
||||
}
|
||||
|
||||
textInput()
|
||||
|
||||
if (showPlaceholder && composerMode.showCaptionCompatibilityWarning()) {
|
||||
var showBottomSheet by remember { mutableStateOf(false) }
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.clickable { showBottomSheet = true }
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||
.align(Alignment.CenterEnd),
|
||||
imageVector = CompoundIcons.InfoSolid(),
|
||||
tint = ElementTheme.colors.iconCriticalPrimary,
|
||||
contentDescription = null,
|
||||
)
|
||||
if (showBottomSheet) {
|
||||
CaptionWarningBottomSheet(
|
||||
onDismiss = { showBottomSheet = false },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -492,7 +517,7 @@ private fun TextInput(
|
||||
composerMode = composerMode,
|
||||
onResetComposerMode = onResetComposerMode,
|
||||
placeholder = placeholder,
|
||||
showPlaceholder = { state.messageHtml.isEmpty() },
|
||||
showPlaceholder = state.messageHtml.isEmpty(),
|
||||
subcomposing = subcomposing,
|
||||
) {
|
||||
RichTextEditor(
|
||||
@@ -501,8 +526,8 @@ private fun TextInput(
|
||||
// This prevents it gaining focus and mutating the state.
|
||||
registerStateUpdates = !subcomposing,
|
||||
modifier = Modifier
|
||||
.padding(top = 6.dp, bottom = 6.dp)
|
||||
.fillMaxWidth(),
|
||||
.padding(top = 6.dp, bottom = 6.dp)
|
||||
.fillMaxWidth(),
|
||||
style = ElementRichTextEditorStyle.composerStyle(hasFocus = state.hasFocus),
|
||||
resolveMentionDisplay = resolveMentionDisplay,
|
||||
resolveRoomMentionDisplay = resolveRoomMentionDisplay,
|
||||
@@ -602,13 +627,14 @@ internal fun TextComposerEditCaptionPreview() = ElementPreview {
|
||||
internal fun TextComposerAddCaptionPreview() = ElementPreview {
|
||||
PreviewColumn(
|
||||
items = aTextEditorStateRichList()
|
||||
) { _, textEditorState ->
|
||||
) { index, textEditorState ->
|
||||
ATextComposer(
|
||||
state = textEditorState,
|
||||
voiceMessageState = VoiceMessageState.Idle,
|
||||
composerMode = aMessageComposerModeEditCaption(
|
||||
// No caption so that the UI will be in add caption mode
|
||||
content = "",
|
||||
showCompatibilityWarning = index == 0,
|
||||
),
|
||||
enableVoiceMessages = false,
|
||||
)
|
||||
@@ -657,7 +683,10 @@ internal fun TextComposerCaptionPreview() = ElementPreview {
|
||||
ATextComposer(
|
||||
state = textEditorState,
|
||||
voiceMessageState = VoiceMessageState.Idle,
|
||||
composerMode = MessageComposerMode.Attachment(allowCaption = index < list.size),
|
||||
composerMode = MessageComposerMode.Attachment(
|
||||
allowCaption = index < list.size,
|
||||
showCaptionCompatibilityWarning = index == 0,
|
||||
),
|
||||
enableVoiceMessages = false,
|
||||
)
|
||||
}
|
||||
@@ -762,9 +791,11 @@ fun aMessageComposerModeEdit(
|
||||
fun aMessageComposerModeEditCaption(
|
||||
eventOrTransactionId: EventOrTransactionId = EventId("$1234").toEventOrTransactionId(),
|
||||
content: String,
|
||||
showCompatibilityWarning: Boolean = false,
|
||||
) = MessageComposerMode.EditCaption(
|
||||
eventOrTransactionId = eventOrTransactionId,
|
||||
content = content
|
||||
content = content,
|
||||
showCaptionCompatibilityWarning = showCompatibilityWarning,
|
||||
)
|
||||
|
||||
fun aMessageComposerModeReply(
|
||||
|
||||
@@ -18,7 +18,10 @@ import io.element.android.libraries.matrix.ui.messages.reply.eventId
|
||||
sealed interface MessageComposerMode {
|
||||
data object Normal : MessageComposerMode
|
||||
|
||||
data class Attachment(val allowCaption: Boolean) : MessageComposerMode
|
||||
data class Attachment(
|
||||
val allowCaption: Boolean,
|
||||
val showCaptionCompatibilityWarning: Boolean,
|
||||
) : MessageComposerMode
|
||||
|
||||
sealed interface Special : MessageComposerMode
|
||||
|
||||
@@ -29,7 +32,8 @@ sealed interface MessageComposerMode {
|
||||
|
||||
data class EditCaption(
|
||||
val eventOrTransactionId: EventOrTransactionId,
|
||||
val content: String
|
||||
val content: String,
|
||||
val showCaptionCompatibilityWarning: Boolean,
|
||||
) : Special
|
||||
|
||||
data class Reply(
|
||||
@@ -51,3 +55,11 @@ sealed interface MessageComposerMode {
|
||||
replyToDetails.eventContent is MessageContent &&
|
||||
(replyToDetails.eventContent as MessageContent).isThreaded
|
||||
}
|
||||
|
||||
fun MessageComposerMode.showCaptionCompatibilityWarning(): Boolean {
|
||||
return when (this) {
|
||||
is MessageComposerMode.Attachment -> showCaptionCompatibilityWarning
|
||||
is MessageComposerMode.EditCaption -> showCaptionCompatibilityWarning && content.isEmpty()
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<string name="rich_text_editor_bullet_list">"Toggle bullet list"</string>
|
||||
<string name="rich_text_editor_close_formatting_options">"Close formatting options"</string>
|
||||
<string name="rich_text_editor_code_block">"Toggle code block"</string>
|
||||
<string name="rich_text_editor_composer_caption_placeholder">"Optional caption…"</string>
|
||||
<string name="rich_text_editor_composer_caption_placeholder">"Add a caption"</string>
|
||||
<string name="rich_text_editor_composer_placeholder">"Message…"</string>
|
||||
<string name="rich_text_editor_create_link">"Create a link"</string>
|
||||
<string name="rich_text_editor_edit_link">"Edit link"</string>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user