Skip to content

[Rich text editor] Add feature flag for rich text editor #1289

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/1289.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[Rich text editor] Add feature flag for rich text editor. Markdown support can now be enabled by disabling the rich text editor.
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
import io.element.android.libraries.designsystem.utils.SnackbarMessage
import io.element.android.libraries.designsystem.utils.collectSnackbarMessageAsState
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.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
Expand Down Expand Up @@ -95,6 +97,7 @@ class MessagesPresenter @AssistedInject constructor(
private val dispatchers: CoroutineDispatchers,
private val clipboardHelper: ClipboardHelper,
private val analyticsService: AnalyticsService,
private val featureFlagService: FeatureFlagService,
@Assisted private val navigator: MessagesNavigator,
) : Presenter<MessagesState> {

Expand Down Expand Up @@ -143,6 +146,11 @@ class MessagesPresenter @AssistedInject constructor(
timelineState.eventSink(TimelineEvents.SetHighlightedEvent(composerState.mode.relatedEventId))
}

var enableTextFormatting by remember { mutableStateOf(true) }
LaunchedEffect(Unit) {
enableTextFormatting = featureFlagService.isFeatureEnabled(FeatureFlags.RichTextEditor)
}
Comment on lines +149 to +152
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming reflects that the rich text editor is not actually toggled; it is still always running under the hood. Only the text formatting capabilities of the RTE may be disabled (i.e. HTML output ignored and formatting buttons hidden)


fun handleEvents(event: MessagesEvents) {
when (event) {
is MessagesEvents.HandleAction -> {
Expand Down Expand Up @@ -178,6 +186,7 @@ class MessagesPresenter @AssistedInject constructor(
snackbarMessage = snackbarMessage,
showReinvitePrompt = showReinvitePrompt,
inviteProgress = inviteProgress.value,
enableTextFormatting = enableTextFormatting,
eventSink = { handleEvents(it) }
)
}
Expand Down Expand Up @@ -250,11 +259,15 @@ class MessagesPresenter @AssistedInject constructor(
}
}

private fun handleActionEdit(targetEvent: TimelineItem.Event, composerState: MessageComposerState) {
private suspend fun handleActionEdit(targetEvent: TimelineItem.Event, composerState: MessageComposerState) {
val composerMode = MessageComposerMode.Edit(
targetEvent.eventId,
(targetEvent.content as? TimelineItemTextBasedContent)?.let {
it.htmlBody ?: it.body
if (featureFlagService.isFeatureEnabled(FeatureFlags.RichTextEditor)) {
it.htmlBody ?: it.body
} else {
it.body
}
}.orEmpty(),
targetEvent.transactionId,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,6 @@ data class MessagesState(
val snackbarMessage: SnackbarMessage?,
val inviteProgress: Async<Unit>,
val showReinvitePrompt: Boolean,
val enableTextFormatting: Boolean,
val eventSink: (MessagesEvents) -> Unit
)
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,6 @@ fun aMessagesState() = MessagesState(
snackbarMessage = null,
inviteProgress = Async.Uninitialized,
showReinvitePrompt = false,
enableTextFormatting = true,
eventSink = {}
)
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ private fun MessagesViewContent(
state = state.composerState,
onSendLocationClicked = onSendLocationClicked,
onCreatePollClicked = onCreatePollClicked,
enableTextFormatting = state.enableTextFormatting,
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(Alignment.Bottom)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ internal fun AttachmentsBottomSheet(
state: MessageComposerState,
onSendLocationClicked: () -> Unit,
onCreatePollClicked: () -> Unit,
enableTextFormatting: Boolean,
modifier: Modifier = Modifier,
) {
val localView = LocalView.current
Expand Down Expand Up @@ -87,6 +88,7 @@ internal fun AttachmentsBottomSheet(
) {
AttachmentSourcePickerMenu(
state = state,
enableTextFormatting = enableTextFormatting,
onSendLocationClicked = onSendLocationClicked,
onCreatePollClicked = onCreatePollClicked,
)
Expand All @@ -100,6 +102,7 @@ internal fun AttachmentSourcePickerMenu(
state: MessageComposerState,
onSendLocationClicked: () -> Unit,
onCreatePollClicked: () -> Unit,
enableTextFormatting: Boolean,
modifier: Modifier = Modifier,
) {
Column(
Expand Down Expand Up @@ -146,11 +149,13 @@ internal fun AttachmentSourcePickerMenu(
text = { Text(stringResource(R.string.screen_room_attachment_source_poll)) },
)
}
ListItem(
modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.ToggleTextFormatting(enabled = true)) },
icon = { Icon(Icons.Default.FormatColorText, null) },
text = { Text(stringResource(R.string.screen_room_attachment_text_formatting)) },
)
if (enableTextFormatting) {
ListItem(
modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.ToggleTextFormatting(enabled = true)) },
icon = { Icon(Icons.Default.FormatColorText, null) },
text = { Text(stringResource(R.string.screen_room_attachment_text_formatting)) },
)
}
}
}

Expand All @@ -163,5 +168,6 @@ internal fun AttachmentSourcePickerMenuPreview() = ElementPreview {
),
onSendLocationClicked = {},
onCreatePollClicked = {},
enableTextFormatting = true,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ fun MessageComposerView(
state: MessageComposerState,
onSendLocationClicked: () -> Unit,
onCreatePollClicked: () -> Unit,
enableTextFormatting: Boolean,
modifier: Modifier = Modifier,
) {
fun onFullscreenToggle() {
Expand Down Expand Up @@ -62,6 +63,7 @@ fun MessageComposerView(
state = state,
onSendLocationClicked = onSendLocationClicked,
onCreatePollClicked = onCreatePollClicked,
enableTextFormatting = enableTextFormatting,
)

TextComposer(
Expand All @@ -74,6 +76,7 @@ fun MessageComposerView(
onResetComposerMode = ::onCloseSpecialMode,
onAddAttachment = ::onAddAttachment,
onDismissTextFormatting = ::onDismissTextFormatting,
enableTextFormatting = enableTextFormatting,
onError = ::onError,
)
}
Expand All @@ -95,5 +98,6 @@ private fun ContentToPreview(state: MessageComposerState) {
state = state,
onSendLocationClicked = {},
onCreatePollClicked = {},
enableTextFormatting = true,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,7 @@ class MessagesPresenterTest {
val customReactionPresenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider())
val reactionSummaryPresenter = ReactionSummaryPresenter(room = matrixRoom)
val retrySendMenuPresenter = RetrySendMenuPresenter(room = matrixRoom)
val featureFlagsService = FakeFeatureFlagService(mapOf(FeatureFlags.RichTextEditor.key to true))
return MessagesPresenter(
room = matrixRoom,
composerPresenter = messageComposerPresenter,
Expand All @@ -642,6 +643,7 @@ class MessagesPresenterTest {
navigator = navigator,
clipboardHelper = clipboardHelper,
analyticsService = analyticsService,
featureFlagService = featureFlagsService,
dispatchers = coroutineDispatchers,
)
}
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ dependencyanalysis = "1.21.0"
stem = "2.3.0"
sqldelight = "1.5.5"
telephoto = "0.6.0"
wysiwyg = "2.9.0"
wysiwyg = "2.10.0"

# DI
dagger = "2.48"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,9 @@ enum class FeatureFlags(
// Do not forget to edit StaticFeatureFlagProvider when enabling the feature.
defaultValue = false,
),
RichTextEditor(
key = "feature.richtexteditor",
title = "Enable rich text editor",
defaultValue = true,
),
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class StaticFeatureFlagProvider @Inject constructor() :
FeatureFlags.LocationSharing -> true
FeatureFlags.Polls -> true
FeatureFlags.NotificationSettings -> false
FeatureFlags.RichTextEditor -> true
}
} else {
false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,11 @@ interface MatrixRoom : Closeable {

suspend fun userAvatarUrl(userId: UserId): Result<String?>

suspend fun sendMessage(body: String, htmlBody: String): Result<Unit>
suspend fun sendMessage(body: String, htmlBody: String?): Result<Unit>

suspend fun editMessage(originalEventId: EventId?, transactionId: TransactionId?, body: String, htmlBody: String): Result<Unit>
suspend fun editMessage(originalEventId: EventId?, transactionId: TransactionId?, body: String, htmlBody: String?): Result<Unit>

suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String): Result<Unit>
suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?): Result<Unit>

suspend fun redactEvent(eventId: EventId, reason: String? = null): Result<Unit>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,12 @@ import org.matrix.rustcomponents.sdk.RequiredState
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.RoomMember
import org.matrix.rustcomponents.sdk.RoomMessageEventContentWithoutRelation
import org.matrix.rustcomponents.sdk.RoomSubscription
import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle
import org.matrix.rustcomponents.sdk.genTransactionId
import org.matrix.rustcomponents.sdk.messageEventContentFromHtml
import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown
import timber.log.Timber
import java.io.File

Expand Down Expand Up @@ -227,32 +229,32 @@ class RustMatrixRoom(
}
}

override suspend fun sendMessage(body: String, htmlBody: String): Result<Unit> = withContext(roomDispatcher) {
override suspend fun sendMessage(body: String, htmlBody: String?): Result<Unit> = withContext(roomDispatcher) {
val transactionId = genTransactionId()
messageEventContentFromHtml(body, htmlBody).use { content ->
messageEventContentFromParts(body, htmlBody).use { content ->
runCatching {
innerRoom.send(content, transactionId)
}
}
}

override suspend fun editMessage(originalEventId: EventId?, transactionId: TransactionId?, body: String, htmlBody: String): Result<Unit> =
override suspend fun editMessage(originalEventId: EventId?, transactionId: TransactionId?, body: String, htmlBody: String?): Result<Unit> =
withContext(roomDispatcher) {
if (originalEventId != null) {
runCatching {
innerRoom.edit(messageEventContentFromHtml(body, htmlBody), originalEventId.value, transactionId?.value)
innerRoom.edit(messageEventContentFromParts(body, htmlBody), originalEventId.value, transactionId?.value)
}
} else {
runCatching {
transactionId?.let { cancelSend(it) }
innerRoom.send(messageEventContentFromHtml(body, htmlBody), genTransactionId())
innerRoom.send(messageEventContentFromParts(body, htmlBody), genTransactionId())
}
}
}

override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String): Result<Unit> = withContext(roomDispatcher) {
override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?): Result<Unit> = withContext(roomDispatcher) {
runCatching {
innerRoom.sendReply(messageEventContentFromHtml(body, htmlBody), eventId.value, genTransactionId())
innerRoom.sendReply(messageEventContentFromParts(body, htmlBody), eventId.value, genTransactionId())
}
}

Expand Down Expand Up @@ -456,4 +458,11 @@ class RustMatrixRoom(
MediaUploadHandlerImpl(files, handle())
}
}

private fun messageEventContentFromParts(body: String, htmlBody: String?): RoomMessageEventContentWithoutRelation =
if(htmlBody != null) {
messageEventContentFromHtml(body, htmlBody)
} else {
messageEventContentFromMarkdown(body)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class FakeMatrixRoom(
private var sendPollResponseResult = Result.success(Unit)
private var endPollResult = Result.success(Unit)
private var progressCallbackValues = emptyList<Pair<Long, Long>>()
val editMessageCalls = mutableListOf<Pair<String, String>>()
val editMessageCalls = mutableListOf<Pair<String, String?>>()

var sendMediaCount = 0
private set
Expand Down Expand Up @@ -171,7 +171,7 @@ class FakeMatrixRoom(
userAvatarUrlResult
}

override suspend fun sendMessage(body: String, htmlBody: String) = simulateLongTask {
override suspend fun sendMessage(body: String, htmlBody: String?) = simulateLongTask {
Result.success(Unit)
}

Expand Down Expand Up @@ -200,15 +200,15 @@ class FakeMatrixRoom(
return cancelSendResult
}

override suspend fun editMessage(originalEventId: EventId?, transactionId: TransactionId?, body: String, htmlBody: String): Result<Unit> {
override suspend fun editMessage(originalEventId: EventId?, transactionId: TransactionId?, body: String, htmlBody: String?): Result<Unit> {
editMessageCalls += body to htmlBody
return Result.success(Unit)
}

var replyMessageParameter: Pair<String, String>? = null
var replyMessageParameter: Pair<String, String?>? = null
private set

override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String): Result<Unit> {
override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?): Result<Unit> {
replyMessageParameter = body to htmlBody
return Result.success(Unit)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@
package io.element.android.libraries.textcomposer

data class Message(
val html: String,
val html: String?,
val markdown: String,
)
Loading