Skip to content

Commit 5610b19

Browse files
authored
Merge pull request #3953 from element-hq/feature/bma/mediaCaptionFeatureFlag
Add feature flag to temporary disable sending caption by default in production
2 parents 6b9eb99 + ae30b9f commit 5610b19

File tree

8 files changed

+115
-11
lines changed

8 files changed

+115
-11
lines changed

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ import io.element.android.features.messages.impl.timeline.model.event.canBeForwa
3737
import io.element.android.features.messages.impl.timeline.model.event.canReact
3838
import io.element.android.libraries.architecture.Presenter
3939
import io.element.android.libraries.di.RoomScope
40+
import io.element.android.libraries.featureflag.api.FeatureFlagService
41+
import io.element.android.libraries.featureflag.api.FeatureFlags
4042
import io.element.android.libraries.matrix.api.core.EventId
4143
import io.element.android.libraries.matrix.api.room.MatrixRoom
4244
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
@@ -60,6 +62,7 @@ class DefaultActionListPresenter @AssistedInject constructor(
6062
private val isPinnedMessagesFeatureEnabled: IsPinnedMessagesFeatureEnabled,
6163
private val room: MatrixRoom,
6264
private val userSendFailureFactory: VerifiedUserSendFailureFactory,
65+
private val featureFlagService: FeatureFlagService,
6366
) : ActionListPresenter {
6467
@AssistedFactory
6568
@ContributesBinding(RoomScope::class)
@@ -134,7 +137,7 @@ class DefaultActionListPresenter @AssistedInject constructor(
134137
}
135138
}
136139

137-
private fun buildActions(
140+
private suspend fun buildActions(
138141
timelineItem: TimelineItem.Event,
139142
usersEventPermissions: UserEventPermissions,
140143
isDeveloperModeEnabled: Boolean,
@@ -157,7 +160,9 @@ class DefaultActionListPresenter @AssistedInject constructor(
157160
if (timelineItem.content is TimelineItemEventContentWithAttachment) {
158161
// Caption
159162
if (timelineItem.content.caption == null) {
160-
add(TimelineItemAction.AddCaption)
163+
if (featureFlagService.isFeatureEnabled(FeatureFlags.MediaCaptionCreation)) {
164+
add(TimelineItemAction.AddCaption)
165+
}
161166
} else {
162167
add(TimelineItemAction.EditCaption)
163168
add(TimelineItemAction.RemoveCaption)

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package io.element.android.features.messages.impl.attachments.preview
1010
import androidx.compose.runtime.Composable
1111
import androidx.compose.runtime.LaunchedEffect
1212
import androidx.compose.runtime.MutableState
13+
import androidx.compose.runtime.collectAsState
1314
import androidx.compose.runtime.getValue
1415
import androidx.compose.runtime.mutableStateOf
1516
import androidx.compose.runtime.remember
@@ -46,7 +47,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
4647
private val mediaSender: MediaSender,
4748
private val permalinkBuilder: PermalinkBuilder,
4849
private val temporaryUriDeleter: TemporaryUriDeleter,
49-
private val featureFlagsService: FeatureFlagService,
50+
private val featureFlagService: FeatureFlagService,
5051
) : Presenter<AttachmentsPreviewState> {
5152
@AssistedFactory
5253
interface Factory {
@@ -72,6 +73,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
7273
val ongoingSendAttachmentJob = remember { mutableStateOf<Job?>(null) }
7374

7475
val userSentAttachment = remember { mutableStateOf(false) }
76+
val allowCaption by featureFlagService.isFeatureEnabledFlow(FeatureFlags.MediaCaptionCreation).collectAsState(initial = false)
7577

7678
val mediaUploadInfoState = remember { mutableStateOf<AsyncData<MediaUploadInfo>>(AsyncData.Uninitialized) }
7779
LaunchedEffect(Unit) {
@@ -112,7 +114,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
112114
fun handleEvents(attachmentsPreviewEvents: AttachmentsPreviewEvents) {
113115
when (attachmentsPreviewEvents) {
114116
is AttachmentsPreviewEvents.SendAttachment -> coroutineScope.launch {
115-
val useSendQueue = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue)
117+
val useSendQueue = featureFlagService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue)
116118
userSentAttachment.value = true
117119
val instantSending = mediaUploadInfoState.value.isReady() && useSendQueue
118120
sendActionState.value = if (instantSending) {
@@ -142,6 +144,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
142144
attachment = attachment,
143145
sendActionState = sendActionState.value,
144146
textEditorState = textEditorState,
147+
allowCaption = allowCaption,
145148
eventSink = ::handleEvents
146149
)
147150
}

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewState.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,9 @@ data class AttachmentsPreviewState(
1515
val attachment: Attachment,
1616
val sendActionState: SendActionState,
1717
val textEditorState: TextEditorState,
18+
val allowCaption: Boolean,
1819
val eventSink: (AttachmentsPreviewEvents) -> Unit
19-
) {
20-
// Keep the val to eventually set to false for some mimetypes.
21-
val allowCaption: Boolean = true
22-
}
20+
)
2321

2422
@Immutable
2523
sealed interface SendActionState {

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,21 @@ open class AttachmentsPreviewStateProvider : PreviewParameterProvider<Attachment
2929
anAttachmentsPreviewState(sendActionState = SendActionState.Sending.Processing),
3030
anAttachmentsPreviewState(sendActionState = SendActionState.Sending.Uploading(0.5f)),
3131
anAttachmentsPreviewState(sendActionState = SendActionState.Failure(RuntimeException("error"))),
32+
anAttachmentsPreviewState(allowCaption = false),
3233
)
3334
}
3435

3536
fun anAttachmentsPreviewState(
3637
mediaInfo: MediaInfo = anImageMediaInfo(),
3738
textEditorState: TextEditorState = aTextEditorStateMarkdown(),
3839
sendActionState: SendActionState = SendActionState.Idle,
40+
allowCaption: Boolean = true,
3941
) = AttachmentsPreviewState(
4042
attachment = Attachment.Media(
4143
localMedia = LocalMedia("file://path".toUri(), mediaInfo),
4244
),
4345
sendActionState = sendActionState,
4446
textEditorState = textEditorState,
47+
allowCaption = allowCaption,
4548
eventSink = {}
4649
)

features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI
2626
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent
2727
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent
2828
import io.element.android.features.poll.api.pollcontent.aPollAnswerItemList
29+
import io.element.android.libraries.featureflag.api.FeatureFlags
30+
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
2931
import io.element.android.libraries.matrix.api.room.MatrixRoom
3032
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
3133
import io.element.android.libraries.matrix.test.AN_EVENT_ID
@@ -558,6 +560,57 @@ class ActionListPresenterTest {
558560
}
559561
}
560562

563+
@Test
564+
fun `present - compute for a media item - caption disabled`() = runTest {
565+
val presenter = createActionListPresenter(
566+
isDeveloperModeEnabled = true,
567+
isPinFeatureEnabled = true,
568+
allowCaption = false,
569+
)
570+
moleculeFlow(RecompositionMode.Immediate) {
571+
presenter.present()
572+
}.test {
573+
val initialState = awaitItem()
574+
val messageEvent = aMessageEvent(
575+
isMine = true,
576+
isEditable = true,
577+
content = aTimelineItemImageContent(),
578+
)
579+
initialState.eventSink.invoke(
580+
ActionListEvents.ComputeForMessage(
581+
event = messageEvent,
582+
userEventPermissions = aUserEventPermissions(
583+
canRedactOwn = true,
584+
canRedactOther = false,
585+
canSendMessage = true,
586+
canSendReaction = true,
587+
canPinUnpin = true,
588+
),
589+
)
590+
)
591+
val successState = awaitItem()
592+
assertThat(successState.target).isEqualTo(
593+
ActionListState.Target.Success(
594+
event = messageEvent,
595+
displayEmojiReactions = true,
596+
verifiedUserSendFailure = VerifiedUserSendFailure.None,
597+
actions = persistentListOf(
598+
TimelineItemAction.Reply,
599+
TimelineItemAction.Forward,
600+
// Not here
601+
// TimelineItemAction.AddCaption,
602+
TimelineItemAction.Pin,
603+
TimelineItemAction.CopyLink,
604+
TimelineItemAction.ViewSource,
605+
TimelineItemAction.Redact,
606+
)
607+
)
608+
)
609+
initialState.eventSink.invoke(ActionListEvents.Clear)
610+
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
611+
}
612+
}
613+
561614
@Test
562615
fun `present - compute for a media with caption item`() = runTest {
563616
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true)
@@ -1151,13 +1204,19 @@ private fun createActionListPresenter(
11511204
isDeveloperModeEnabled: Boolean,
11521205
isPinFeatureEnabled: Boolean,
11531206
room: MatrixRoom = FakeMatrixRoom(),
1207+
allowCaption: Boolean = true,
11541208
): ActionListPresenter {
11551209
val preferencesStore = InMemoryAppPreferencesStore(isDeveloperModeEnabled = isDeveloperModeEnabled)
11561210
return DefaultActionListPresenter(
11571211
postProcessor = TimelineItemActionPostProcessor.Default,
11581212
appPreferencesStore = preferencesStore,
11591213
isPinnedMessagesFeatureEnabled = { isPinFeatureEnabled },
11601214
room = room,
1161-
userSendFailureFactory = VerifiedUserSendFailureFactory(room)
1215+
userSendFailureFactory = VerifiedUserSendFailureFactory(room),
1216+
featureFlagService = FakeFeatureFlagService(
1217+
initialState = mapOf(
1218+
FeatureFlags.MediaCaptionCreation.key to allowCaption,
1219+
),
1220+
)
11621221
)
11631222
}

features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,28 @@ class AttachmentsPreviewPresenterTest {
6464

6565
private val mockMediaUrl: Uri = mockk("localMediaUri")
6666

67+
@Test
68+
fun `present - initial state`() = runTest {
69+
createAttachmentsPreviewPresenter().test {
70+
skipItems(1)
71+
val initialState = awaitItem()
72+
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
73+
assertThat(initialState.allowCaption).isTrue()
74+
}
75+
}
76+
77+
@Test
78+
fun `present - initial state - caption not allowed`() = runTest {
79+
createAttachmentsPreviewPresenter(
80+
allowCaption = false,
81+
).test {
82+
skipItems(1)
83+
val initialState = awaitItem()
84+
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
85+
assertThat(initialState.allowCaption).isFalse()
86+
}
87+
}
88+
6789
@Test
6890
fun `present - send media success scenario`() = runTest {
6991
val sendFileResult = lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, Result<FakeMediaUploadHandler>> { _, _, _, _, _ ->
@@ -420,15 +442,19 @@ class AttachmentsPreviewPresenterTest {
420442
temporaryUriDeleter: TemporaryUriDeleter = FakeTemporaryUriDeleter(),
421443
onDoneListener: OnDoneListener = OnDoneListener { lambdaError() },
422444
mediaUploadOnSendQueueEnabled: Boolean = true,
445+
allowCaption: Boolean = true,
423446
): AttachmentsPreviewPresenter {
424447
return AttachmentsPreviewPresenter(
425448
attachment = aMediaAttachment(localMedia),
426449
onDoneListener = onDoneListener,
427450
mediaSender = MediaSender(mediaPreProcessor, room, InMemorySessionPreferencesStore()),
428451
permalinkBuilder = permalinkBuilder,
429452
temporaryUriDeleter = temporaryUriDeleter,
430-
featureFlagsService = FakeFeatureFlagService(
431-
initialState = mapOf(FeatureFlags.MediaUploadOnSendQueue.key to mediaUploadOnSendQueueEnabled),
453+
featureFlagService = FakeFeatureFlagService(
454+
initialState = mapOf(
455+
FeatureFlags.MediaUploadOnSendQueue.key to mediaUploadOnSendQueueEnabled,
456+
FeatureFlags.MediaCaptionCreation.key to allowCaption,
457+
),
432458
)
433459
)
434460
}

libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,11 @@ enum class FeatureFlags(
140140
defaultValue = { buildMeta -> buildMeta.buildType != BuildType.RELEASE },
141141
isFinished = false,
142142
),
143+
MediaCaptionCreation(
144+
key = "feature.media_caption_creation",
145+
title = "Allow creation of media captions",
146+
description = null,
147+
defaultValue = { buildMeta -> buildMeta.buildType != BuildType.RELEASE },
148+
isFinished = false,
149+
),
143150
}
Lines changed: 3 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)