Add warning when adding a caption.
This commit is contained in:
parent
6592e3e939
commit
94ebac7e74
13 changed files with 260 additions and 43 deletions
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue