Remove FeatureFlag.MediaCaptionCreation and FeatureFlag.MediaCaptionWarning

This commit is contained in:
Benoit Marty 2025-08-12 15:20:26 +02:00 committed by Benoit Marty
parent dcb6055783
commit 34efd2c3fa
13 changed files with 96 additions and 328 deletions

View file

@ -63,8 +63,6 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.permalink.PermalinkParser
@ -111,7 +109,6 @@ class MessagesPresenter @AssistedInject constructor(
private val snackbarDispatcher: SnackbarDispatcher, private val snackbarDispatcher: SnackbarDispatcher,
private val dispatchers: CoroutineDispatchers, private val dispatchers: CoroutineDispatchers,
private val clipboardHelper: ClipboardHelper, private val clipboardHelper: ClipboardHelper,
private val featureFlagsService: FeatureFlagService,
private val htmlConverterProvider: HtmlConverterProvider, private val htmlConverterProvider: HtmlConverterProvider,
private val buildMeta: BuildMeta, private val buildMeta: BuildMeta,
private val timelineController: TimelineController, private val timelineController: TimelineController,
@ -443,7 +440,6 @@ class MessagesPresenter @AssistedInject constructor(
val composerMode = MessageComposerMode.EditCaption( val composerMode = MessageComposerMode.EditCaption(
eventOrTransactionId = targetEvent.eventOrTransactionId, eventOrTransactionId = targetEvent.eventOrTransactionId,
content = "", content = "",
showCaptionCompatibilityWarning = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaCaptionWarning),
) )
composerState.eventSink( composerState.eventSink(
MessageComposerEvents.SetMode(composerMode) MessageComposerEvents.SetMode(composerMode)
@ -457,7 +453,6 @@ class MessagesPresenter @AssistedInject constructor(
val composerMode = MessageComposerMode.EditCaption( val composerMode = MessageComposerMode.EditCaption(
eventOrTransactionId = targetEvent.eventOrTransactionId, eventOrTransactionId = targetEvent.eventOrTransactionId,
content = (targetEvent.content as? TimelineItemEventContentWithAttachment)?.caption.orEmpty(), content = (targetEvent.content as? TimelineItemEventContentWithAttachment)?.caption.orEmpty(),
showCaptionCompatibilityWarning = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaCaptionWarning),
) )
composerState.eventSink( composerState.eventSink(
MessageComposerEvents.SetMode(composerMode) MessageComposerEvents.SetMode(composerMode)

View file

@ -39,8 +39,6 @@ import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.dateformatter.api.DateFormatter import io.element.android.libraries.dateformatter.api.DateFormatter
import io.element.android.libraries.dateformatter.api.DateFormatterMode import io.element.android.libraries.dateformatter.api.DateFormatterMode
import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.room.BaseRoom import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.preferences.api.store.AppPreferencesStore import io.element.android.libraries.preferences.api.store.AppPreferencesStore
@ -63,7 +61,6 @@ class DefaultActionListPresenter @AssistedInject constructor(
private val appPreferencesStore: AppPreferencesStore, private val appPreferencesStore: AppPreferencesStore,
private val room: BaseRoom, private val room: BaseRoom,
private val userSendFailureFactory: VerifiedUserSendFailureFactory, private val userSendFailureFactory: VerifiedUserSendFailureFactory,
private val featureFlagService: FeatureFlagService,
private val dateFormatter: DateFormatter, private val dateFormatter: DateFormatter,
) : ActionListPresenter { ) : ActionListPresenter {
@AssistedFactory @AssistedFactory
@ -166,9 +163,7 @@ class DefaultActionListPresenter @AssistedInject constructor(
if (timelineItem.content is TimelineItemEventContentWithAttachment) { if (timelineItem.content is TimelineItemEventContentWithAttachment) {
// Caption // Caption
if (timelineItem.content.caption == null) { if (timelineItem.content.caption == null) {
if (featureFlagService.isFeatureEnabled(FeatureFlags.MediaCaptionCreation)) { add(TimelineItemAction.AddCaption)
add(TimelineItemAction.AddCaption)
}
} else { } else {
add(TimelineItemAction.EditCaption) add(TimelineItemAction.EditCaption)
add(TimelineItemAction.RemoveCaption) add(TimelineItemAction.RemoveCaption)

View file

@ -10,7 +10,6 @@ package io.element.android.features.messages.impl.attachments.preview
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -86,13 +85,6 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
val ongoingSendAttachmentJob = remember { mutableStateOf<Job?>(null) } val ongoingSendAttachmentJob = remember { mutableStateOf<Job?>(null) }
val allowCaption by remember {
featureFlagService.isFeatureEnabledFlow(FeatureFlags.MediaCaptionCreation)
}.collectAsState(initial = false)
val showCaptionCompatibilityWarning by remember {
featureFlagService.isFeatureEnabledFlow(FeatureFlags.MediaCaptionWarning)
}.collectAsState(initial = false)
var useSendQueue by remember { mutableStateOf(false) } var useSendQueue by remember { mutableStateOf(false) }
var preprocessMediaJob by remember { mutableStateOf<Job?>(null) } var preprocessMediaJob by remember { mutableStateOf<Job?>(null) }
@ -238,8 +230,6 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
attachment = attachment, attachment = attachment,
sendActionState = sendActionState.value, sendActionState = sendActionState.value,
textEditorState = textEditorState, textEditorState = textEditorState,
allowCaption = allowCaption,
showCaptionCompatibilityWarning = showCaptionCompatibilityWarning,
mediaOptimizationSelectorState = mediaOptimizationSelectorState, mediaOptimizationSelectorState = mediaOptimizationSelectorState,
displayFileTooLargeError = displayFileTooLargeError, displayFileTooLargeError = displayFileTooLargeError,
eventSink = ::handleEvents eventSink = ::handleEvents

View file

@ -17,8 +17,6 @@ data class AttachmentsPreviewState(
val attachment: Attachment, val attachment: Attachment,
val sendActionState: SendActionState, val sendActionState: SendActionState,
val textEditorState: TextEditorState, val textEditorState: TextEditorState,
val allowCaption: Boolean,
val showCaptionCompatibilityWarning: Boolean,
val mediaOptimizationSelectorState: MediaOptimizationSelectorState, val mediaOptimizationSelectorState: MediaOptimizationSelectorState,
val displayFileTooLargeError: Boolean, val displayFileTooLargeError: Boolean,
val eventSink: (AttachmentsPreviewEvents) -> Unit val eventSink: (AttachmentsPreviewEvents) -> Unit

View file

@ -41,8 +41,6 @@ open class AttachmentsPreviewStateProvider : PreviewParameterProvider<Attachment
anAttachmentsPreviewState(sendActionState = SendActionState.Sending.ReadyToUpload(aMediaUploadInfo())), anAttachmentsPreviewState(sendActionState = SendActionState.Sending.ReadyToUpload(aMediaUploadInfo())),
anAttachmentsPreviewState(sendActionState = SendActionState.Sending.Uploading(0.5f, aMediaUploadInfo())), anAttachmentsPreviewState(sendActionState = SendActionState.Sending.Uploading(0.5f, aMediaUploadInfo())),
anAttachmentsPreviewState(sendActionState = SendActionState.Failure(RuntimeException("error"), aMediaUploadInfo())), anAttachmentsPreviewState(sendActionState = SendActionState.Failure(RuntimeException("error"), aMediaUploadInfo())),
anAttachmentsPreviewState(allowCaption = false),
anAttachmentsPreviewState(showCaptionCompatibilityWarning = true),
anAttachmentsPreviewState(displayFileTooLargeError = true), anAttachmentsPreviewState(displayFileTooLargeError = true),
anAttachmentsPreviewState( anAttachmentsPreviewState(
mediaInfo = aVideoMediaInfo(), mediaInfo = aVideoMediaInfo(),
@ -65,8 +63,6 @@ fun anAttachmentsPreviewState(
mediaInfo: MediaInfo = anImageMediaInfo(), mediaInfo: MediaInfo = anImageMediaInfo(),
textEditorState: TextEditorState = aTextEditorStateMarkdown(), textEditorState: TextEditorState = aTextEditorStateMarkdown(),
sendActionState: SendActionState = SendActionState.Idle, sendActionState: SendActionState = SendActionState.Idle,
allowCaption: Boolean = true,
showCaptionCompatibilityWarning: Boolean = true,
mediaOptimizationSelectorState: MediaOptimizationSelectorState = aMediaOptimisationSelectorState(), mediaOptimizationSelectorState: MediaOptimizationSelectorState = aMediaOptimisationSelectorState(),
displayFileTooLargeError: Boolean = false, displayFileTooLargeError: Boolean = false,
) = AttachmentsPreviewState( ) = AttachmentsPreviewState(
@ -75,8 +71,6 @@ fun anAttachmentsPreviewState(
), ),
sendActionState = sendActionState, sendActionState = sendActionState,
textEditorState = textEditorState, textEditorState = textEditorState,
allowCaption = allowCaption,
showCaptionCompatibilityWarning = showCaptionCompatibilityWarning,
mediaOptimizationSelectorState = mediaOptimizationSelectorState, mediaOptimizationSelectorState = mediaOptimizationSelectorState,
displayFileTooLargeError = displayFileTooLargeError, displayFileTooLargeError = displayFileTooLargeError,
eventSink = {} eventSink = {}

View file

@ -363,10 +363,7 @@ private fun AttachmentsPreviewBottomActions(
modifier = modifier, modifier = modifier,
state = state.textEditorState, state = state.textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
composerMode = MessageComposerMode.Attachment( composerMode = MessageComposerMode.Attachment,
allowCaption = state.allowCaption,
showCaptionCompatibilityWarning = state.showCaptionCompatibilityWarning,
),
onRequestFocus = {}, onRequestFocus = {},
onSendMessage = onSendClick, onSendMessage = onSendClick,
showTextFormatting = false, showTextFormatting = false,

View file

@ -46,9 +46,6 @@ 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.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher 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.EventId
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
@ -994,37 +991,6 @@ class MessagesPresenterTest {
composerMode = MessageComposerMode.EditCaption( composerMode = MessageComposerMode.EditCaption(
eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(), eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(),
content = A_CAPTION, 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.testWithLifecycleOwner {
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,
) )
) )
) )
@ -1051,37 +1017,6 @@ class MessagesPresenterTest {
composerMode = MessageComposerMode.EditCaption( composerMode = MessageComposerMode.EditCaption(
eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(), eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(),
content = "", 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.testWithLifecycleOwner {
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,
) )
) )
) )
@ -1234,7 +1169,6 @@ class MessagesPresenterTest {
typingNoticeResult = { Result.success(Unit) }, typingNoticeResult = { Result.success(Unit) },
), ),
navigator: FakeMessagesNavigator = FakeMessagesNavigator(), navigator: FakeMessagesNavigator = FakeMessagesNavigator(),
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(), clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(),
analyticsService: FakeAnalyticsService = FakeAnalyticsService(), analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
timelineEventSink: (TimelineEvents) -> Unit = {}, timelineEventSink: (TimelineEvents) -> Unit = {},
@ -1270,7 +1204,6 @@ class MessagesPresenterTest {
snackbarDispatcher = SnackbarDispatcher(), snackbarDispatcher = SnackbarDispatcher(),
navigator = navigator, navigator = navigator,
clipboardHelper = clipboardHelper, clipboardHelper = clipboardHelper,
featureFlagsService = featureFlagService,
buildMeta = aBuildMeta(), buildMeta = aBuildMeta(),
dispatchers = coroutineDispatchers, dispatchers = coroutineDispatchers,
htmlConverterProvider = FakeHtmlConverterProvider(), htmlConverterProvider = FakeHtmlConverterProvider(),

View file

@ -27,8 +27,6 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent
import io.element.android.features.poll.api.pollcontent.aPollAnswerItemList import io.element.android.features.poll.api.pollcontent.aPollAnswerItemList
import io.element.android.libraries.dateformatter.test.FakeDateFormatter import io.element.android.libraries.dateformatter.test.FakeDateFormatter
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.room.BaseRoom import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.AN_EVENT_ID
@ -603,57 +601,6 @@ class ActionListPresenterTest {
} }
} }
@Test
fun `present - compute for a media item - caption disabled`() = runTest {
val presenter = createActionListPresenter(
isDeveloperModeEnabled = true,
allowCaption = false,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
val messageEvent = aMessageEvent(
isMine = true,
isEditable = true,
content = aTimelineItemImageContent(),
)
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = messageEvent,
userEventPermissions = aUserEventPermissions(
canRedactOwn = true,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
canPinUnpin = true,
),
)
)
val successState = awaitItem()
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
event = messageEvent,
sentTimeFull = "0 Full true",
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
// Not here
// TimelineItemAction.AddCaption,
TimelineItemAction.CopyLink,
TimelineItemAction.Pin,
TimelineItemAction.ViewSource,
TimelineItemAction.Redact,
)
)
)
initialState.eventSink.invoke(ActionListEvents.Clear)
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
}
}
@Test @Test
fun `present - compute for a media with caption item`() = runTest { fun `present - compute for a media with caption item`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
@ -1298,7 +1245,6 @@ class ActionListPresenterTest {
private fun createActionListPresenter( private fun createActionListPresenter(
isDeveloperModeEnabled: Boolean, isDeveloperModeEnabled: Boolean,
room: BaseRoom = FakeBaseRoom(), room: BaseRoom = FakeBaseRoom(),
allowCaption: Boolean = true,
): ActionListPresenter { ): ActionListPresenter {
val preferencesStore = InMemoryAppPreferencesStore(isDeveloperModeEnabled = isDeveloperModeEnabled) val preferencesStore = InMemoryAppPreferencesStore(isDeveloperModeEnabled = isDeveloperModeEnabled)
return DefaultActionListPresenter( return DefaultActionListPresenter(
@ -1306,11 +1252,6 @@ private fun createActionListPresenter(
appPreferencesStore = preferencesStore, appPreferencesStore = preferencesStore,
room = room, room = room,
userSendFailureFactory = VerifiedUserSendFailureFactory(room), userSendFailureFactory = VerifiedUserSendFailureFactory(room),
featureFlagService = FakeFeatureFlagService(
initialState = mapOf(
FeatureFlags.MediaCaptionCreation.key to allowCaption,
),
),
dateFormatter = FakeDateFormatter(), dateFormatter = FakeDateFormatter(),
) )
} }

View file

@ -84,34 +84,8 @@ class AttachmentsPreviewPresenterTest {
@Test @Test
fun `present - initial state`() = runTest { fun `present - initial state`() = runTest {
createAttachmentsPreviewPresenter().test { createAttachmentsPreviewPresenter().test {
skipItems(1)
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) 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()
}
}
@Test
fun `present - initial state - caption not allowed`() = runTest {
createAttachmentsPreviewPresenter(
allowCaption = false,
).test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
assertThat(initialState.allowCaption).isFalse()
} }
} }
@ -144,7 +118,6 @@ class AttachmentsPreviewPresenterTest {
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = true)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = true))
@ -186,7 +159,6 @@ class AttachmentsPreviewPresenterTest {
// Pre-processing finishes // Pre-processing finishes
processLatch.complete(Unit) processLatch.complete(Unit)
advanceUntilIdle() advanceUntilIdle()
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo))
@ -221,7 +193,6 @@ class AttachmentsPreviewPresenterTest {
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
// Pre-processing finishes // Pre-processing finishes
@ -253,7 +224,6 @@ class AttachmentsPreviewPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
// Pre-processing finishes // Pre-processing finishes
processLatch.complete(Unit) processLatch.complete(Unit)
@ -282,7 +252,6 @@ class AttachmentsPreviewPresenterTest {
processLatch.complete(Unit) processLatch.complete(Unit)
advanceUntilIdle() advanceUntilIdle()
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Failure::class.java) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Failure::class.java)
} }
@ -304,7 +273,6 @@ class AttachmentsPreviewPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.CancelAndDismiss) initialState.eventSink(AttachmentsPreviewEvents.CancelAndDismiss)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
deleteCallback.assertions().isCalledOnce() deleteCallback.assertions().isCalledOnce()
onDoneListener.assertions().isCalledOnce() onDoneListener.assertions().isCalledOnce()
@ -339,7 +307,6 @@ class AttachmentsPreviewPresenterTest {
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.textEditorState.setMarkdown(A_CAPTION) initialState.textEditorState.setMarkdown(A_CAPTION)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.ReadyToUpload::class.java) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.ReadyToUpload::class.java)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
@ -383,7 +350,6 @@ class AttachmentsPreviewPresenterTest {
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.textEditorState.setMarkdown(A_CAPTION) initialState.textEditorState.setMarkdown(A_CAPTION)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.ReadyToUpload::class.java) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.ReadyToUpload::class.java)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
@ -425,7 +391,6 @@ class AttachmentsPreviewPresenterTest {
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.textEditorState.setMarkdown(A_CAPTION) initialState.textEditorState.setMarkdown(A_CAPTION)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.ReadyToUpload::class.java) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.ReadyToUpload::class.java)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
@ -460,7 +425,6 @@ class AttachmentsPreviewPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo))
val failureState = awaitItem() val failureState = awaitItem()
@ -492,7 +456,6 @@ class AttachmentsPreviewPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo))
@ -516,7 +479,6 @@ class AttachmentsPreviewPresenterTest {
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo))
@ -546,7 +508,6 @@ class AttachmentsPreviewPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo))
initialState.eventSink(AttachmentsPreviewEvents.CancelAndClearSendState) initialState.eventSink(AttachmentsPreviewEvents.CancelAndClearSendState)
@ -673,8 +634,6 @@ class AttachmentsPreviewPresenterTest {
temporaryUriDeleter: TemporaryUriDeleter = FakeTemporaryUriDeleter(), temporaryUriDeleter: TemporaryUriDeleter = FakeTemporaryUriDeleter(),
onDoneListener: OnDoneListener = OnDoneListener { lambdaError() }, onDoneListener: OnDoneListener = OnDoneListener { lambdaError() },
mediaUploadOnSendQueueEnabled: Boolean = true, mediaUploadOnSendQueueEnabled: Boolean = true,
allowCaption: Boolean = true,
showCaptionCompatibilityWarning: Boolean = true,
displayMediaQualitySelectorViews: Boolean = false, displayMediaQualitySelectorViews: Boolean = false,
mediaOptimizationSelectorPresenterFactory: FakeMediaOptimizationSelectorPresenterFactory = FakeMediaOptimizationSelectorPresenterFactory( mediaOptimizationSelectorPresenterFactory: FakeMediaOptimizationSelectorPresenterFactory = FakeMediaOptimizationSelectorPresenterFactory(
fakePresenter = { fakePresenter = {
@ -701,8 +660,6 @@ class AttachmentsPreviewPresenterTest {
featureFlagService = FakeFeatureFlagService( featureFlagService = FakeFeatureFlagService(
initialState = mapOf( initialState = mapOf(
FeatureFlags.MediaUploadOnSendQueue.key to mediaUploadOnSendQueueEnabled, FeatureFlags.MediaUploadOnSendQueue.key to mediaUploadOnSendQueueEnabled,
FeatureFlags.MediaCaptionCreation.key to allowCaption,
FeatureFlags.MediaCaptionWarning.key to showCaptionCompatibilityWarning,
), ),
), ),
sessionCoroutineScope = this, sessionCoroutineScope = this,

View file

@ -1585,11 +1585,9 @@ fun anEditMode(
fun anEditCaptionMode( fun anEditCaptionMode(
eventOrTransactionId: EventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(), eventOrTransactionId: EventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(),
caption: String = A_CAPTION, caption: String = A_CAPTION,
showCaptionCompatibilityWarning: Boolean = false,
) = MessageComposerMode.EditCaption( ) = MessageComposerMode.EditCaption(
eventOrTransactionId = eventOrTransactionId, eventOrTransactionId = eventOrTransactionId,
content = caption, content = caption,
showCaptionCompatibilityWarning = showCaptionCompatibilityWarning,
) )
fun aReplyMode() = MessageComposerMode.Reply( fun aReplyMode() = MessageComposerMode.Reply(

View file

@ -82,20 +82,6 @@ enum class FeatureFlags(
defaultValue = { true }, defaultValue = { true },
isFinished = true, isFinished = true,
), ),
MediaCaptionCreation(
key = "feature.media_caption_creation",
title = "Allow creation of media captions",
description = null,
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,
),
PrintLogsToLogcat( PrintLogsToLogcat(
key = "feature.print_logs_to_logcat", key = "feature.print_logs_to_logcat",
title = "Print logs to logcat", title = "Print logs to logcat",

View file

@ -140,8 +140,8 @@ fun TextComposer(
} }
val layoutModifier = modifier val layoutModifier = modifier
.fillMaxSize() .fillMaxSize()
.height(IntrinsicSize.Min) .height(IntrinsicSize.Min)
val composerOptionsButton: @Composable () -> Unit = remember(composerMode) { val composerOptionsButton: @Composable () -> Unit = remember(composerMode) {
@Composable { @Composable {
@ -170,22 +170,17 @@ fun TextComposer(
} else { } else {
stringResource(id = R.string.rich_text_editor_composer_placeholder) stringResource(id = R.string.rich_text_editor_composer_placeholder)
} }
val textInput: @Composable () -> Unit = if ((composerMode as? MessageComposerMode.Attachment)?.allowCaption == false) { val textInput: @Composable () -> Unit = when (state) {
{ is TextEditorState.Rich -> {
// No text input when in attachment mode and caption not allowed. val coroutineScope = rememberCoroutineScope()
} val view = LocalView.current
} else { remember(state.richTextEditorState, composerMode, onResetComposerMode, onError) {
when (state) { @Composable {
is TextEditorState.Rich -> { TextInputBox(
val coroutineScope = rememberCoroutineScope() modifier = Modifier
val view = LocalView.current
remember(state.richTextEditorState, composerMode, onResetComposerMode, onError) {
@Composable {
TextInputBox(
modifier = Modifier
.clickable( .clickable(
interactionSource = remember { MutableInteractionSource() }, interactionSource = remember { MutableInteractionSource() },
indication = null, indication = null,
) { ) {
coroutineScope.launch { coroutineScope.launch {
state.requestFocus() state.requestFocus()
@ -195,46 +190,45 @@ fun TextComposer(
.semantics { .semantics {
hideFromAccessibility() hideFromAccessibility()
}, },
composerMode = composerMode, composerMode = composerMode,
onResetComposerMode = onResetComposerMode, onResetComposerMode = onResetComposerMode,
isTextEmpty = state.richTextEditorState.messageHtml.isEmpty(), isTextEmpty = state.richTextEditorState.messageHtml.isEmpty(),
) { ) {
RichTextEditor( RichTextEditor(
state = state.richTextEditorState, state = state.richTextEditorState,
placeholder = placeholder, placeholder = placeholder,
registerStateUpdates = true, registerStateUpdates = true,
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.richTextEditorState.hasFocus), style = ElementRichTextEditorStyle.composerStyle(hasFocus = state.richTextEditorState.hasFocus),
resolveMentionDisplay = resolveMentionDisplay, resolveMentionDisplay = resolveMentionDisplay,
resolveRoomMentionDisplay = resolveAtRoomMentionDisplay, resolveRoomMentionDisplay = resolveAtRoomMentionDisplay,
onError = onError, onError = onError,
onRichContentSelected = onSelectRichContent, onRichContentSelected = onSelectRichContent,
onTyping = onTyping, onTyping = onTyping,
) )
}
} }
} }
} }
is TextEditorState.Markdown -> { }
@Composable { is TextEditorState.Markdown -> {
val style = ElementRichTextEditorStyle.composerStyle(hasFocus = state.hasFocus()) @Composable {
TextInputBox( val style = ElementRichTextEditorStyle.composerStyle(hasFocus = state.hasFocus())
composerMode = composerMode, TextInputBox(
onResetComposerMode = onResetComposerMode, composerMode = composerMode,
isTextEmpty = state.state.text.value().isEmpty(), onResetComposerMode = onResetComposerMode,
) { isTextEmpty = state.state.text.value().isEmpty(),
MarkdownTextInput( ) {
state = state.state, MarkdownTextInput(
placeholder = placeholder, state = state.state,
placeholderColor = ElementTheme.colors.textSecondary, placeholder = placeholder,
onTyping = onTyping, placeholderColor = ElementTheme.colors.textSecondary,
onReceiveSuggestion = onReceiveSuggestion, onTyping = onTyping,
richTextEditorStyle = style, onReceiveSuggestion = onReceiveSuggestion,
onSelectRichContent = onSelectRichContent, richTextEditorStyle = style,
) onSelectRichContent = onSelectRichContent,
} )
} }
} }
} }
@ -426,8 +420,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()
@ -437,8 +431,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()
} }
@ -451,17 +445,17 @@ 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)
.clearAndSetSemantics(endButtonA11y), .clearAndSetSemantics(endButtonA11y),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
endButton() endButton()
@ -512,8 +506,8 @@ private fun TextFormattingLayout(
} }
Box( Box(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.padding(horizontal = 12.dp) .padding(horizontal = 12.dp)
) { ) {
textInput() textInput()
} }
@ -532,11 +526,11 @@ private fun TextFormattingLayout(
} }
Box( Box(
modifier = Modifier modifier = Modifier
.padding( .padding(
start = 14.dp, start = 14.dp,
end = 6.dp, end = 6.dp,
) )
.clearAndSetSemantics(endButtonA11y) .clearAndSetSemantics(endButtonA11y)
) { ) {
sendButton() sendButton()
} }
@ -558,12 +552,12 @@ 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()
.then(modifier), .then(modifier),
) { ) {
if (composerMode is MessageComposerMode.Special) { if (composerMode is MessageComposerMode.Special) {
ComposerModeView( ComposerModeView(
@ -573,8 +567,8 @@ private fun TextInputBox(
} }
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)
.then(Modifier.testTag(TestTags.textEditor)), .then(Modifier.testTag(TestTags.textEditor)),
contentAlignment = Alignment.CenterStart, contentAlignment = Alignment.CenterStart,
) { ) {
textInput() textInput()
@ -582,9 +576,9 @@ private fun TextInputBox(
var showBottomSheet by remember { mutableStateOf(false) } var showBottomSheet by remember { mutableStateOf(false) }
Icon( Icon(
modifier = Modifier modifier = Modifier
.clickable { showBottomSheet = true } .clickable { showBottomSheet = true }
.padding(horizontal = 8.dp, vertical = 4.dp) .padding(horizontal = 8.dp, vertical = 4.dp)
.align(Alignment.CenterEnd), .align(Alignment.CenterEnd),
imageVector = CompoundIcons.InfoSolid(), imageVector = CompoundIcons.InfoSolid(),
tint = ElementTheme.colors.iconCriticalPrimary, tint = ElementTheme.colors.iconCriticalPrimary,
contentDescription = null, contentDescription = null,
@ -626,7 +620,7 @@ private fun aTextEditorStateRichList(isRoomEncrypted: Boolean? = null) = persist
internal fun TextComposerSimplePreview() = ElementPreview { internal fun TextComposerSimplePreview() = ElementPreview {
PreviewColumn( PreviewColumn(
items = aTextEditorStateMarkdownList() items = aTextEditorStateMarkdownList()
) { _, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
@ -640,7 +634,7 @@ internal fun TextComposerSimplePreview() = ElementPreview {
internal fun TextComposerSimpleNotEncryptedPreview() = ElementPreview { internal fun TextComposerSimpleNotEncryptedPreview() = ElementPreview {
PreviewColumn( PreviewColumn(
items = aTextEditorStateMarkdownList(isRoomEncrypted = false), items = aTextEditorStateMarkdownList(isRoomEncrypted = false),
) { _, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
@ -654,7 +648,7 @@ internal fun TextComposerSimpleNotEncryptedPreview() = ElementPreview {
internal fun TextComposerFormattingPreview() = ElementPreview { internal fun TextComposerFormattingPreview() = ElementPreview {
PreviewColumn( PreviewColumn(
items = aTextEditorStateRichList() items = aTextEditorStateRichList()
) { _, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
@ -669,7 +663,7 @@ internal fun TextComposerFormattingPreview() = ElementPreview {
internal fun TextComposerFormattingNotEncryptedPreview() = ElementPreview { internal fun TextComposerFormattingNotEncryptedPreview() = ElementPreview {
PreviewColumn( PreviewColumn(
items = aTextEditorStateRichList(isRoomEncrypted = false) items = aTextEditorStateRichList(isRoomEncrypted = false)
) { _, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
@ -684,7 +678,7 @@ internal fun TextComposerFormattingNotEncryptedPreview() = ElementPreview {
internal fun TextComposerEditPreview() = ElementPreview { internal fun TextComposerEditPreview() = ElementPreview {
PreviewColumn( PreviewColumn(
items = aTextEditorStateRichList() items = aTextEditorStateRichList()
) { _, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
@ -698,7 +692,7 @@ internal fun TextComposerEditPreview() = ElementPreview {
internal fun TextComposerEditNotEncryptedPreview() = ElementPreview { internal fun TextComposerEditNotEncryptedPreview() = ElementPreview {
PreviewColumn( PreviewColumn(
items = aTextEditorStateRichList(isRoomEncrypted = false) items = aTextEditorStateRichList(isRoomEncrypted = false)
) { _, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
@ -712,7 +706,7 @@ internal fun TextComposerEditNotEncryptedPreview() = ElementPreview {
internal fun TextComposerEditCaptionPreview() = ElementPreview { internal fun TextComposerEditCaptionPreview() = ElementPreview {
PreviewColumn( PreviewColumn(
items = aTextEditorStateRichList() items = aTextEditorStateRichList()
) { _, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
@ -729,14 +723,13 @@ internal fun TextComposerEditCaptionPreview() = ElementPreview {
internal fun TextComposerAddCaptionPreview() = ElementPreview { internal fun TextComposerAddCaptionPreview() = ElementPreview {
PreviewColumn( PreviewColumn(
items = aTextEditorStateRichList() items = aTextEditorStateRichList()
) { index, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
composerMode = aMessageComposerModeEditCaption( composerMode = aMessageComposerModeEditCaption(
// No caption so that the UI will be in add caption mode // No caption so that the UI will be in add caption mode
content = "", content = "",
showCompatibilityWarning = index == 0,
), ),
) )
} }
@ -747,7 +740,7 @@ internal fun TextComposerAddCaptionPreview() = ElementPreview {
internal fun MarkdownTextComposerEditPreview() = ElementPreview { internal fun MarkdownTextComposerEditPreview() = ElementPreview {
PreviewColumn( PreviewColumn(
items = aTextEditorStateMarkdownList() items = aTextEditorStateMarkdownList()
) { _, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
@ -761,7 +754,7 @@ internal fun MarkdownTextComposerEditPreview() = ElementPreview {
internal fun TextComposerReplyPreview(@PreviewParameter(InReplyToDetailsProvider::class) inReplyToDetails: InReplyToDetails) = ElementPreview { internal fun TextComposerReplyPreview(@PreviewParameter(InReplyToDetailsProvider::class) inReplyToDetails: InReplyToDetails) = ElementPreview {
PreviewColumn( PreviewColumn(
items = aTextEditorStateRichList() items = aTextEditorStateRichList()
) { _, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
@ -785,7 +778,7 @@ internal fun TextComposerReplyPreview(@PreviewParameter(InReplyToDetailsProvider
internal fun TextComposerReplyNotEncryptedPreview(@PreviewParameter(InReplyToDetailsProvider::class) inReplyToDetails: InReplyToDetails) = ElementPreview { internal fun TextComposerReplyNotEncryptedPreview(@PreviewParameter(InReplyToDetailsProvider::class) inReplyToDetails: InReplyToDetails) = ElementPreview {
PreviewColumn( PreviewColumn(
items = aTextEditorStateRichList(isRoomEncrypted = false) items = aTextEditorStateRichList(isRoomEncrypted = false)
) { _, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
@ -802,14 +795,11 @@ internal fun TextComposerCaptionPreview() = ElementPreview {
val list = aTextEditorStateMarkdownList() val list = aTextEditorStateMarkdownList()
PreviewColumn( PreviewColumn(
items = (list + aTextEditorStateMarkdown(initialText = "NO_CAPTION", initialFocus = true)).toPersistentList() items = (list + aTextEditorStateMarkdown(initialText = "NO_CAPTION", initialFocus = true)).toPersistentList()
) { index, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
composerMode = MessageComposerMode.Attachment( composerMode = MessageComposerMode.Attachment,
allowCaption = index < list.size,
showCaptionCompatibilityWarning = index == 0,
),
) )
} }
} }
@ -845,7 +835,7 @@ internal fun TextComposerVoicePreview() = ElementPreview {
playbackProgress = 0.0f playbackProgress = 0.0f
), ),
) )
) { _, voiceMessageState -> ) { voiceMessageState ->
ATextComposer( ATextComposer(
state = aTextEditorStateRich(initialFocus = true), state = aTextEditorStateRich(initialFocus = true),
voiceMessageState = voiceMessageState, voiceMessageState = voiceMessageState,
@ -885,7 +875,7 @@ internal fun TextComposerVoiceNotEncryptedPreview() = ElementPreview {
playbackProgress = 0.0f playbackProgress = 0.0f
), ),
) )
) { _, voiceMessageState -> ) { voiceMessageState ->
ATextComposer( ATextComposer(
state = aTextEditorStateRich(initialFocus = true, isRoomEncrypted = false), state = aTextEditorStateRich(initialFocus = true, isRoomEncrypted = false),
voiceMessageState = voiceMessageState, voiceMessageState = voiceMessageState,
@ -897,15 +887,15 @@ internal fun TextComposerVoiceNotEncryptedPreview() = ElementPreview {
@Composable @Composable
private fun <T> PreviewColumn( private fun <T> PreviewColumn(
items: ImmutableList<T>, items: ImmutableList<T>,
view: @Composable (Int, T) -> Unit, view: @Composable (T) -> Unit,
) { ) {
Column { Column {
items.forEachIndexed { index, item -> items.forEach { item ->
HorizontalDivider() HorizontalDivider()
Box( Box(
modifier = Modifier.height(IntrinsicSize.Min) modifier = Modifier.height(IntrinsicSize.Min)
) { ) {
view(index, item) view(item)
} }
} }
} }
@ -952,11 +942,9 @@ fun aMessageComposerModeEdit(
fun aMessageComposerModeEditCaption( fun aMessageComposerModeEditCaption(
eventOrTransactionId: EventOrTransactionId = EventId("$1234").toEventOrTransactionId(), eventOrTransactionId: EventOrTransactionId = EventId("$1234").toEventOrTransactionId(),
content: String, content: String,
showCompatibilityWarning: Boolean = false,
) = MessageComposerMode.EditCaption( ) = MessageComposerMode.EditCaption(
eventOrTransactionId = eventOrTransactionId, eventOrTransactionId = eventOrTransactionId,
content = content, content = content,
showCaptionCompatibilityWarning = showCompatibilityWarning,
) )
fun aMessageComposerModeReply( fun aMessageComposerModeReply(

View file

@ -18,10 +18,7 @@ import io.element.android.libraries.matrix.ui.messages.reply.eventId
sealed interface MessageComposerMode { sealed interface MessageComposerMode {
data object Normal : MessageComposerMode data object Normal : MessageComposerMode
data class Attachment( data object Attachment : MessageComposerMode
val allowCaption: Boolean,
val showCaptionCompatibilityWarning: Boolean,
) : MessageComposerMode
sealed interface Special : MessageComposerMode sealed interface Special : MessageComposerMode
@ -33,7 +30,6 @@ sealed interface MessageComposerMode {
data class EditCaption( data class EditCaption(
val eventOrTransactionId: EventOrTransactionId, val eventOrTransactionId: EventOrTransactionId,
val content: String, val content: String,
val showCaptionCompatibilityWarning: Boolean,
) : Special ) : Special
data class Reply( data class Reply(
@ -58,8 +54,8 @@ sealed interface MessageComposerMode {
fun MessageComposerMode.showCaptionCompatibilityWarning(): Boolean { fun MessageComposerMode.showCaptionCompatibilityWarning(): Boolean {
return when (this) { return when (this) {
is MessageComposerMode.Attachment -> showCaptionCompatibilityWarning is MessageComposerMode.Attachment -> true
is MessageComposerMode.EditCaption -> showCaptionCompatibilityWarning && content.isEmpty() is MessageComposerMode.EditCaption -> content.isEmpty()
else -> false else -> false
} }
} }