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 a0629d03fd
commit d025ae63ee
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.SnackbarMessage
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.identity.IdentityState
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
@@ -111,7 +109,6 @@ class MessagesPresenter @AssistedInject constructor(
private val snackbarDispatcher: SnackbarDispatcher,
private val dispatchers: CoroutineDispatchers,
private val clipboardHelper: ClipboardHelper,
private val featureFlagsService: FeatureFlagService,
private val htmlConverterProvider: HtmlConverterProvider,
private val buildMeta: BuildMeta,
private val timelineController: TimelineController,
@@ -443,7 +440,6 @@ class MessagesPresenter @AssistedInject constructor(
val composerMode = MessageComposerMode.EditCaption(
eventOrTransactionId = targetEvent.eventOrTransactionId,
content = "",
showCaptionCompatibilityWarning = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaCaptionWarning),
)
composerState.eventSink(
MessageComposerEvents.SetMode(composerMode)
@@ -457,7 +453,6 @@ class MessagesPresenter @AssistedInject constructor(
val composerMode = MessageComposerMode.EditCaption(
eventOrTransactionId = targetEvent.eventOrTransactionId,
content = (targetEvent.content as? TimelineItemEventContentWithAttachment)?.caption.orEmpty(),
showCaptionCompatibilityWarning = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaCaptionWarning),
)
composerState.eventSink(
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.DateFormatterMode
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.room.BaseRoom
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
@@ -63,7 +61,6 @@ class DefaultActionListPresenter @AssistedInject constructor(
private val appPreferencesStore: AppPreferencesStore,
private val room: BaseRoom,
private val userSendFailureFactory: VerifiedUserSendFailureFactory,
private val featureFlagService: FeatureFlagService,
private val dateFormatter: DateFormatter,
) : ActionListPresenter {
@AssistedFactory
@@ -166,9 +163,7 @@ class DefaultActionListPresenter @AssistedInject constructor(
if (timelineItem.content is TimelineItemEventContentWithAttachment) {
// Caption
if (timelineItem.content.caption == null) {
if (featureFlagService.isFeatureEnabled(FeatureFlags.MediaCaptionCreation)) {
add(TimelineItemAction.AddCaption)
}
add(TimelineItemAction.AddCaption)
} else {
add(TimelineItemAction.EditCaption)
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.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -86,13 +85,6 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
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 preprocessMediaJob by remember { mutableStateOf<Job?>(null) }
@@ -238,8 +230,6 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
attachment = attachment,
sendActionState = sendActionState.value,
textEditorState = textEditorState,
allowCaption = allowCaption,
showCaptionCompatibilityWarning = showCaptionCompatibilityWarning,
mediaOptimizationSelectorState = mediaOptimizationSelectorState,
displayFileTooLargeError = displayFileTooLargeError,
eventSink = ::handleEvents

View File

@@ -17,8 +17,6 @@ data class AttachmentsPreviewState(
val attachment: Attachment,
val sendActionState: SendActionState,
val textEditorState: TextEditorState,
val allowCaption: Boolean,
val showCaptionCompatibilityWarning: Boolean,
val mediaOptimizationSelectorState: MediaOptimizationSelectorState,
val displayFileTooLargeError: Boolean,
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.Uploading(0.5f, aMediaUploadInfo())),
anAttachmentsPreviewState(sendActionState = SendActionState.Failure(RuntimeException("error"), aMediaUploadInfo())),
anAttachmentsPreviewState(allowCaption = false),
anAttachmentsPreviewState(showCaptionCompatibilityWarning = true),
anAttachmentsPreviewState(displayFileTooLargeError = true),
anAttachmentsPreviewState(
mediaInfo = aVideoMediaInfo(),
@@ -65,8 +63,6 @@ fun anAttachmentsPreviewState(
mediaInfo: MediaInfo = anImageMediaInfo(),
textEditorState: TextEditorState = aTextEditorStateMarkdown(),
sendActionState: SendActionState = SendActionState.Idle,
allowCaption: Boolean = true,
showCaptionCompatibilityWarning: Boolean = true,
mediaOptimizationSelectorState: MediaOptimizationSelectorState = aMediaOptimisationSelectorState(),
displayFileTooLargeError: Boolean = false,
) = AttachmentsPreviewState(
@@ -75,8 +71,6 @@ fun anAttachmentsPreviewState(
),
sendActionState = sendActionState,
textEditorState = textEditorState,
allowCaption = allowCaption,
showCaptionCompatibilityWarning = showCaptionCompatibilityWarning,
mediaOptimizationSelectorState = mediaOptimizationSelectorState,
displayFileTooLargeError = displayFileTooLargeError,
eventSink = {}

View File

@@ -363,10 +363,7 @@ private fun AttachmentsPreviewBottomActions(
modifier = modifier,
state = state.textEditorState,
voiceMessageState = VoiceMessageState.Idle,
composerMode = MessageComposerMode.Attachment(
allowCaption = state.allowCaption,
showCaptionCompatibilityWarning = state.showCaptionCompatibilityWarning,
),
composerMode = MessageComposerMode.Attachment,
onRequestFocus = {},
onSendMessage = onSendClick,
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.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.RoomId
import io.element.android.libraries.matrix.api.core.UserId
@@ -994,37 +991,6 @@ 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.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(
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.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) },
),
navigator: FakeMessagesNavigator = FakeMessagesNavigator(),
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(),
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
timelineEventSink: (TimelineEvents) -> Unit = {},
@@ -1270,7 +1204,6 @@ class MessagesPresenterTest {
snackbarDispatcher = SnackbarDispatcher(),
navigator = navigator,
clipboardHelper = clipboardHelper,
featureFlagsService = featureFlagService,
buildMeta = aBuildMeta(),
dispatchers = coroutineDispatchers,
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.poll.api.pollcontent.aPollAnswerItemList
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.timeline.item.event.LocalEventSendState
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
fun `present - compute for a media with caption item`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
@@ -1298,7 +1245,6 @@ class ActionListPresenterTest {
private fun createActionListPresenter(
isDeveloperModeEnabled: Boolean,
room: BaseRoom = FakeBaseRoom(),
allowCaption: Boolean = true,
): ActionListPresenter {
val preferencesStore = InMemoryAppPreferencesStore(isDeveloperModeEnabled = isDeveloperModeEnabled)
return DefaultActionListPresenter(
@@ -1306,11 +1252,6 @@ private fun createActionListPresenter(
appPreferencesStore = preferencesStore,
room = room,
userSendFailureFactory = VerifiedUserSendFailureFactory(room),
featureFlagService = FakeFeatureFlagService(
initialState = mapOf(
FeatureFlags.MediaCaptionCreation.key to allowCaption,
),
),
dateFormatter = FakeDateFormatter(),
)
}

View File

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

View File

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

View File

@@ -82,20 +82,6 @@ enum class FeatureFlags(
defaultValue = { 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(
key = "feature.print_logs_to_logcat",
title = "Print logs to logcat",

View File

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

View File

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