[Rich text editor] Add feature flag for rich text editor (#1289)
This commit is contained in:
parent
f136a308b2
commit
d5b01ac0b4
16 changed files with 84 additions and 25 deletions
1
changelog.d/1289.feature
Normal file
1
changelog.d/1289.feature
Normal file
|
|
@ -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.
|
||||||
|
|
@ -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.SnackbarDispatcher
|
||||||
import io.element.android.libraries.designsystem.utils.SnackbarMessage
|
import io.element.android.libraries.designsystem.utils.SnackbarMessage
|
||||||
import io.element.android.libraries.designsystem.utils.collectSnackbarMessageAsState
|
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.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
||||||
|
|
@ -95,6 +97,7 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
private val dispatchers: CoroutineDispatchers,
|
private val dispatchers: CoroutineDispatchers,
|
||||||
private val clipboardHelper: ClipboardHelper,
|
private val clipboardHelper: ClipboardHelper,
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
|
private val featureFlagService: FeatureFlagService,
|
||||||
@Assisted private val navigator: MessagesNavigator,
|
@Assisted private val navigator: MessagesNavigator,
|
||||||
) : Presenter<MessagesState> {
|
) : Presenter<MessagesState> {
|
||||||
|
|
||||||
|
|
@ -143,6 +146,11 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
timelineState.eventSink(TimelineEvents.SetHighlightedEvent(composerState.mode.relatedEventId))
|
timelineState.eventSink(TimelineEvents.SetHighlightedEvent(composerState.mode.relatedEventId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var enableTextFormatting by remember { mutableStateOf(true) }
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
enableTextFormatting = featureFlagService.isFeatureEnabled(FeatureFlags.RichTextEditor)
|
||||||
|
}
|
||||||
|
|
||||||
fun handleEvents(event: MessagesEvents) {
|
fun handleEvents(event: MessagesEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is MessagesEvents.HandleAction -> {
|
is MessagesEvents.HandleAction -> {
|
||||||
|
|
@ -178,6 +186,7 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
snackbarMessage = snackbarMessage,
|
snackbarMessage = snackbarMessage,
|
||||||
showReinvitePrompt = showReinvitePrompt,
|
showReinvitePrompt = showReinvitePrompt,
|
||||||
inviteProgress = inviteProgress.value,
|
inviteProgress = inviteProgress.value,
|
||||||
|
enableTextFormatting = enableTextFormatting,
|
||||||
eventSink = { handleEvents(it) }
|
eventSink = { handleEvents(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -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(
|
val composerMode = MessageComposerMode.Edit(
|
||||||
targetEvent.eventId,
|
targetEvent.eventId,
|
||||||
(targetEvent.content as? TimelineItemTextBasedContent)?.let {
|
(targetEvent.content as? TimelineItemTextBasedContent)?.let {
|
||||||
it.htmlBody ?: it.body
|
if (featureFlagService.isFeatureEnabled(FeatureFlags.RichTextEditor)) {
|
||||||
|
it.htmlBody ?: it.body
|
||||||
|
} else {
|
||||||
|
it.body
|
||||||
|
}
|
||||||
}.orEmpty(),
|
}.orEmpty(),
|
||||||
targetEvent.transactionId,
|
targetEvent.transactionId,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -45,5 +45,6 @@ data class MessagesState(
|
||||||
val snackbarMessage: SnackbarMessage?,
|
val snackbarMessage: SnackbarMessage?,
|
||||||
val inviteProgress: Async<Unit>,
|
val inviteProgress: Async<Unit>,
|
||||||
val showReinvitePrompt: Boolean,
|
val showReinvitePrompt: Boolean,
|
||||||
|
val enableTextFormatting: Boolean,
|
||||||
val eventSink: (MessagesEvents) -> Unit
|
val eventSink: (MessagesEvents) -> Unit
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -82,5 +82,6 @@ fun aMessagesState() = MessagesState(
|
||||||
snackbarMessage = null,
|
snackbarMessage = null,
|
||||||
inviteProgress = Async.Uninitialized,
|
inviteProgress = Async.Uninitialized,
|
||||||
showReinvitePrompt = false,
|
showReinvitePrompt = false,
|
||||||
|
enableTextFormatting = true,
|
||||||
eventSink = {}
|
eventSink = {}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -304,6 +304,7 @@ private fun MessagesViewContent(
|
||||||
state = state.composerState,
|
state = state.composerState,
|
||||||
onSendLocationClicked = onSendLocationClicked,
|
onSendLocationClicked = onSendLocationClicked,
|
||||||
onCreatePollClicked = onCreatePollClicked,
|
onCreatePollClicked = onCreatePollClicked,
|
||||||
|
enableTextFormatting = state.enableTextFormatting,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.wrapContentHeight(Alignment.Bottom)
|
.wrapContentHeight(Alignment.Bottom)
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ internal fun AttachmentsBottomSheet(
|
||||||
state: MessageComposerState,
|
state: MessageComposerState,
|
||||||
onSendLocationClicked: () -> Unit,
|
onSendLocationClicked: () -> Unit,
|
||||||
onCreatePollClicked: () -> Unit,
|
onCreatePollClicked: () -> Unit,
|
||||||
|
enableTextFormatting: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val localView = LocalView.current
|
val localView = LocalView.current
|
||||||
|
|
@ -87,6 +88,7 @@ internal fun AttachmentsBottomSheet(
|
||||||
) {
|
) {
|
||||||
AttachmentSourcePickerMenu(
|
AttachmentSourcePickerMenu(
|
||||||
state = state,
|
state = state,
|
||||||
|
enableTextFormatting = enableTextFormatting,
|
||||||
onSendLocationClicked = onSendLocationClicked,
|
onSendLocationClicked = onSendLocationClicked,
|
||||||
onCreatePollClicked = onCreatePollClicked,
|
onCreatePollClicked = onCreatePollClicked,
|
||||||
)
|
)
|
||||||
|
|
@ -100,6 +102,7 @@ internal fun AttachmentSourcePickerMenu(
|
||||||
state: MessageComposerState,
|
state: MessageComposerState,
|
||||||
onSendLocationClicked: () -> Unit,
|
onSendLocationClicked: () -> Unit,
|
||||||
onCreatePollClicked: () -> Unit,
|
onCreatePollClicked: () -> Unit,
|
||||||
|
enableTextFormatting: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -146,11 +149,13 @@ internal fun AttachmentSourcePickerMenu(
|
||||||
text = { Text(stringResource(R.string.screen_room_attachment_source_poll)) },
|
text = { Text(stringResource(R.string.screen_room_attachment_source_poll)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ListItem(
|
if (enableTextFormatting) {
|
||||||
modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.ToggleTextFormatting(enabled = true)) },
|
ListItem(
|
||||||
icon = { Icon(Icons.Default.FormatColorText, null) },
|
modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.ToggleTextFormatting(enabled = true)) },
|
||||||
text = { Text(stringResource(R.string.screen_room_attachment_text_formatting)) },
|
icon = { Icon(Icons.Default.FormatColorText, null) },
|
||||||
)
|
text = { Text(stringResource(R.string.screen_room_attachment_text_formatting)) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,5 +168,6 @@ internal fun AttachmentSourcePickerMenuPreview() = ElementPreview {
|
||||||
),
|
),
|
||||||
onSendLocationClicked = {},
|
onSendLocationClicked = {},
|
||||||
onCreatePollClicked = {},
|
onCreatePollClicked = {},
|
||||||
|
enableTextFormatting = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ fun MessageComposerView(
|
||||||
state: MessageComposerState,
|
state: MessageComposerState,
|
||||||
onSendLocationClicked: () -> Unit,
|
onSendLocationClicked: () -> Unit,
|
||||||
onCreatePollClicked: () -> Unit,
|
onCreatePollClicked: () -> Unit,
|
||||||
|
enableTextFormatting: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
fun onFullscreenToggle() {
|
fun onFullscreenToggle() {
|
||||||
|
|
@ -62,6 +63,7 @@ fun MessageComposerView(
|
||||||
state = state,
|
state = state,
|
||||||
onSendLocationClicked = onSendLocationClicked,
|
onSendLocationClicked = onSendLocationClicked,
|
||||||
onCreatePollClicked = onCreatePollClicked,
|
onCreatePollClicked = onCreatePollClicked,
|
||||||
|
enableTextFormatting = enableTextFormatting,
|
||||||
)
|
)
|
||||||
|
|
||||||
TextComposer(
|
TextComposer(
|
||||||
|
|
@ -74,6 +76,7 @@ fun MessageComposerView(
|
||||||
onResetComposerMode = ::onCloseSpecialMode,
|
onResetComposerMode = ::onCloseSpecialMode,
|
||||||
onAddAttachment = ::onAddAttachment,
|
onAddAttachment = ::onAddAttachment,
|
||||||
onDismissTextFormatting = ::onDismissTextFormatting,
|
onDismissTextFormatting = ::onDismissTextFormatting,
|
||||||
|
enableTextFormatting = enableTextFormatting,
|
||||||
onError = ::onError,
|
onError = ::onError,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -95,5 +98,6 @@ private fun ContentToPreview(state: MessageComposerState) {
|
||||||
state = state,
|
state = state,
|
||||||
onSendLocationClicked = {},
|
onSendLocationClicked = {},
|
||||||
onCreatePollClicked = {},
|
onCreatePollClicked = {},
|
||||||
|
enableTextFormatting = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -628,6 +628,7 @@ class MessagesPresenterTest {
|
||||||
val customReactionPresenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider())
|
val customReactionPresenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider())
|
||||||
val reactionSummaryPresenter = ReactionSummaryPresenter(room = matrixRoom)
|
val reactionSummaryPresenter = ReactionSummaryPresenter(room = matrixRoom)
|
||||||
val retrySendMenuPresenter = RetrySendMenuPresenter(room = matrixRoom)
|
val retrySendMenuPresenter = RetrySendMenuPresenter(room = matrixRoom)
|
||||||
|
val featureFlagsService = FakeFeatureFlagService(mapOf(FeatureFlags.RichTextEditor.key to true))
|
||||||
return MessagesPresenter(
|
return MessagesPresenter(
|
||||||
room = matrixRoom,
|
room = matrixRoom,
|
||||||
composerPresenter = messageComposerPresenter,
|
composerPresenter = messageComposerPresenter,
|
||||||
|
|
@ -642,6 +643,7 @@ class MessagesPresenterTest {
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
clipboardHelper = clipboardHelper,
|
clipboardHelper = clipboardHelper,
|
||||||
analyticsService = analyticsService,
|
analyticsService = analyticsService,
|
||||||
|
featureFlagService = featureFlagsService,
|
||||||
dispatchers = coroutineDispatchers,
|
dispatchers = coroutineDispatchers,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ dependencyanalysis = "1.21.0"
|
||||||
stem = "2.3.0"
|
stem = "2.3.0"
|
||||||
sqldelight = "1.5.5"
|
sqldelight = "1.5.5"
|
||||||
telephoto = "0.6.0"
|
telephoto = "0.6.0"
|
||||||
wysiwyg = "2.9.0"
|
wysiwyg = "2.10.0"
|
||||||
|
|
||||||
# DI
|
# DI
|
||||||
dagger = "2.48"
|
dagger = "2.48"
|
||||||
|
|
|
||||||
|
|
@ -44,4 +44,9 @@ enum class FeatureFlags(
|
||||||
// Do not forget to edit StaticFeatureFlagProvider when enabling the feature.
|
// Do not forget to edit StaticFeatureFlagProvider when enabling the feature.
|
||||||
defaultValue = false,
|
defaultValue = false,
|
||||||
),
|
),
|
||||||
|
RichTextEditor(
|
||||||
|
key = "feature.richtexteditor",
|
||||||
|
title = "Enable rich text editor",
|
||||||
|
defaultValue = true,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ class StaticFeatureFlagProvider @Inject constructor() :
|
||||||
FeatureFlags.LocationSharing -> true
|
FeatureFlags.LocationSharing -> true
|
||||||
FeatureFlags.Polls -> true
|
FeatureFlags.Polls -> true
|
||||||
FeatureFlags.NotificationSettings -> false
|
FeatureFlags.NotificationSettings -> false
|
||||||
|
FeatureFlags.RichTextEditor -> true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|
|
||||||
|
|
@ -79,11 +79,11 @@ interface MatrixRoom : Closeable {
|
||||||
|
|
||||||
suspend fun userAvatarUrl(userId: UserId): Result<String?>
|
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>
|
suspend fun redactEvent(eventId: EventId, reason: String? = null): Result<Unit>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,10 +63,12 @@ import org.matrix.rustcomponents.sdk.RequiredState
|
||||||
import org.matrix.rustcomponents.sdk.Room
|
import org.matrix.rustcomponents.sdk.Room
|
||||||
import org.matrix.rustcomponents.sdk.RoomListItem
|
import org.matrix.rustcomponents.sdk.RoomListItem
|
||||||
import org.matrix.rustcomponents.sdk.RoomMember
|
import org.matrix.rustcomponents.sdk.RoomMember
|
||||||
|
import org.matrix.rustcomponents.sdk.RoomMessageEventContentWithoutRelation
|
||||||
import org.matrix.rustcomponents.sdk.RoomSubscription
|
import org.matrix.rustcomponents.sdk.RoomSubscription
|
||||||
import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle
|
import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle
|
||||||
import org.matrix.rustcomponents.sdk.genTransactionId
|
import org.matrix.rustcomponents.sdk.genTransactionId
|
||||||
import org.matrix.rustcomponents.sdk.messageEventContentFromHtml
|
import org.matrix.rustcomponents.sdk.messageEventContentFromHtml
|
||||||
|
import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
|
@ -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()
|
val transactionId = genTransactionId()
|
||||||
messageEventContentFromHtml(body, htmlBody).use { content ->
|
messageEventContentFromParts(body, htmlBody).use { content ->
|
||||||
runCatching {
|
runCatching {
|
||||||
innerRoom.send(content, transactionId)
|
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) {
|
withContext(roomDispatcher) {
|
||||||
if (originalEventId != null) {
|
if (originalEventId != null) {
|
||||||
runCatching {
|
runCatching {
|
||||||
innerRoom.edit(messageEventContentFromHtml(body, htmlBody), originalEventId.value, transactionId?.value)
|
innerRoom.edit(messageEventContentFromParts(body, htmlBody), originalEventId.value, transactionId?.value)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
runCatching {
|
runCatching {
|
||||||
transactionId?.let { cancelSend(it) }
|
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 {
|
runCatching {
|
||||||
innerRoom.sendReply(messageEventContentFromHtml(body, htmlBody), eventId.value, genTransactionId())
|
innerRoom.sendReply(messageEventContentFromParts(body, htmlBody), eventId.value, genTransactionId())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -456,4 +458,11 @@ class RustMatrixRoom(
|
||||||
MediaUploadHandlerImpl(files, handle())
|
MediaUploadHandlerImpl(files, handle())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun messageEventContentFromParts(body: String, htmlBody: String?): RoomMessageEventContentWithoutRelation =
|
||||||
|
if(htmlBody != null) {
|
||||||
|
messageEventContentFromHtml(body, htmlBody)
|
||||||
|
} else {
|
||||||
|
messageEventContentFromMarkdown(body)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ class FakeMatrixRoom(
|
||||||
private var sendPollResponseResult = Result.success(Unit)
|
private var sendPollResponseResult = Result.success(Unit)
|
||||||
private var endPollResult = Result.success(Unit)
|
private var endPollResult = Result.success(Unit)
|
||||||
private var progressCallbackValues = emptyList<Pair<Long, Long>>()
|
private var progressCallbackValues = emptyList<Pair<Long, Long>>()
|
||||||
val editMessageCalls = mutableListOf<Pair<String, String>>()
|
val editMessageCalls = mutableListOf<Pair<String, String?>>()
|
||||||
|
|
||||||
var sendMediaCount = 0
|
var sendMediaCount = 0
|
||||||
private set
|
private set
|
||||||
|
|
@ -171,7 +171,7 @@ class FakeMatrixRoom(
|
||||||
userAvatarUrlResult
|
userAvatarUrlResult
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun sendMessage(body: String, htmlBody: String) = simulateLongTask {
|
override suspend fun sendMessage(body: String, htmlBody: String?) = simulateLongTask {
|
||||||
Result.success(Unit)
|
Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -200,15 +200,15 @@ class FakeMatrixRoom(
|
||||||
return cancelSendResult
|
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
|
editMessageCalls += body to htmlBody
|
||||||
return Result.success(Unit)
|
return Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
var replyMessageParameter: Pair<String, String>? = null
|
var replyMessageParameter: Pair<String, String?>? = null
|
||||||
private set
|
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
|
replyMessageParameter = body to htmlBody
|
||||||
return Result.success(Unit)
|
return Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,6 @@
|
||||||
package io.element.android.libraries.textcomposer
|
package io.element.android.libraries.textcomposer
|
||||||
|
|
||||||
data class Message(
|
data class Message(
|
||||||
val html: String,
|
val html: String?,
|
||||||
val markdown: String,
|
val markdown: String,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@ fun TextComposer(
|
||||||
state: RichTextEditorState,
|
state: RichTextEditorState,
|
||||||
composerMode: MessageComposerMode,
|
composerMode: MessageComposerMode,
|
||||||
canSendMessage: Boolean,
|
canSendMessage: Boolean,
|
||||||
|
enableTextFormatting: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
showTextFormatting: Boolean = false,
|
showTextFormatting: Boolean = false,
|
||||||
onRequestFocus: () -> Unit = {},
|
onRequestFocus: () -> Unit = {},
|
||||||
|
|
@ -101,7 +102,8 @@ fun TextComposer(
|
||||||
onError: (Throwable) -> Unit = {},
|
onError: (Throwable) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val onSendClicked = {
|
val onSendClicked = {
|
||||||
onSendMessage(Message(html = state.messageHtml, markdown = state.messageMarkdown))
|
val html = if (enableTextFormatting) state.messageHtml else null
|
||||||
|
onSendMessage(Message(html = html, markdown = state.messageMarkdown))
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -600,6 +602,7 @@ internal fun TextComposerSimplePreview() = ElementPreview {
|
||||||
onSendMessage = {},
|
onSendMessage = {},
|
||||||
composerMode = MessageComposerMode.Normal(""),
|
composerMode = MessageComposerMode.Normal(""),
|
||||||
onResetComposerMode = {},
|
onResetComposerMode = {},
|
||||||
|
enableTextFormatting = true,
|
||||||
)
|
)
|
||||||
TextComposer(
|
TextComposer(
|
||||||
RichTextEditorState("A message", fake = true).apply { requestFocus() },
|
RichTextEditorState("A message", fake = true).apply { requestFocus() },
|
||||||
|
|
@ -607,6 +610,7 @@ internal fun TextComposerSimplePreview() = ElementPreview {
|
||||||
onSendMessage = {},
|
onSendMessage = {},
|
||||||
composerMode = MessageComposerMode.Normal(""),
|
composerMode = MessageComposerMode.Normal(""),
|
||||||
onResetComposerMode = {},
|
onResetComposerMode = {},
|
||||||
|
enableTextFormatting = true,
|
||||||
)
|
)
|
||||||
TextComposer(
|
TextComposer(
|
||||||
RichTextEditorState(
|
RichTextEditorState(
|
||||||
|
|
@ -619,6 +623,7 @@ internal fun TextComposerSimplePreview() = ElementPreview {
|
||||||
onSendMessage = {},
|
onSendMessage = {},
|
||||||
composerMode = MessageComposerMode.Normal(""),
|
composerMode = MessageComposerMode.Normal(""),
|
||||||
onResetComposerMode = {},
|
onResetComposerMode = {},
|
||||||
|
enableTextFormatting = true,
|
||||||
)
|
)
|
||||||
TextComposer(
|
TextComposer(
|
||||||
RichTextEditorState("A message without focus", fake = true),
|
RichTextEditorState("A message without focus", fake = true),
|
||||||
|
|
@ -626,6 +631,7 @@ internal fun TextComposerSimplePreview() = ElementPreview {
|
||||||
onSendMessage = {},
|
onSendMessage = {},
|
||||||
composerMode = MessageComposerMode.Normal(""),
|
composerMode = MessageComposerMode.Normal(""),
|
||||||
onResetComposerMode = {},
|
onResetComposerMode = {},
|
||||||
|
enableTextFormatting = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -639,18 +645,21 @@ internal fun TextComposerFormattingPreview() = ElementPreview {
|
||||||
canSendMessage = false,
|
canSendMessage = false,
|
||||||
showTextFormatting = true,
|
showTextFormatting = true,
|
||||||
composerMode = MessageComposerMode.Normal(""),
|
composerMode = MessageComposerMode.Normal(""),
|
||||||
|
enableTextFormatting = true,
|
||||||
)
|
)
|
||||||
TextComposer(
|
TextComposer(
|
||||||
RichTextEditorState("A message", fake = true),
|
RichTextEditorState("A message", fake = true),
|
||||||
canSendMessage = true,
|
canSendMessage = true,
|
||||||
showTextFormatting = true,
|
showTextFormatting = true,
|
||||||
composerMode = MessageComposerMode.Normal(""),
|
composerMode = MessageComposerMode.Normal(""),
|
||||||
|
enableTextFormatting = true,
|
||||||
)
|
)
|
||||||
TextComposer(
|
TextComposer(
|
||||||
RichTextEditorState("A message\nWith several lines\nTo preview larger textfields and long lines with overflow", fake = true),
|
RichTextEditorState("A message\nWith several lines\nTo preview larger textfields and long lines with overflow", fake = true),
|
||||||
canSendMessage = true,
|
canSendMessage = true,
|
||||||
showTextFormatting = true,
|
showTextFormatting = true,
|
||||||
composerMode = MessageComposerMode.Normal(""),
|
composerMode = MessageComposerMode.Normal(""),
|
||||||
|
enableTextFormatting = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -664,6 +673,7 @@ internal fun TextComposerEditPreview() = ElementPreview {
|
||||||
onSendMessage = {},
|
onSendMessage = {},
|
||||||
composerMode = MessageComposerMode.Edit(EventId("$1234"), "Some text", TransactionId("1234")),
|
composerMode = MessageComposerMode.Edit(EventId("$1234"), "Some text", TransactionId("1234")),
|
||||||
onResetComposerMode = {},
|
onResetComposerMode = {},
|
||||||
|
enableTextFormatting = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -684,6 +694,7 @@ internal fun TextComposerReplyPreview() = ElementPreview {
|
||||||
"To preview larger textfields and long lines with overflow"
|
"To preview larger textfields and long lines with overflow"
|
||||||
),
|
),
|
||||||
onResetComposerMode = {},
|
onResetComposerMode = {},
|
||||||
|
enableTextFormatting = true,
|
||||||
)
|
)
|
||||||
TextComposer(
|
TextComposer(
|
||||||
RichTextEditorState("A message", fake = true),
|
RichTextEditorState("A message", fake = true),
|
||||||
|
|
@ -701,6 +712,7 @@ internal fun TextComposerReplyPreview() = ElementPreview {
|
||||||
defaultContent = "image.jpg"
|
defaultContent = "image.jpg"
|
||||||
),
|
),
|
||||||
onResetComposerMode = {},
|
onResetComposerMode = {},
|
||||||
|
enableTextFormatting = true,
|
||||||
)
|
)
|
||||||
TextComposer(
|
TextComposer(
|
||||||
RichTextEditorState("A message", fake = true),
|
RichTextEditorState("A message", fake = true),
|
||||||
|
|
@ -718,6 +730,7 @@ internal fun TextComposerReplyPreview() = ElementPreview {
|
||||||
defaultContent = "video.mp4"
|
defaultContent = "video.mp4"
|
||||||
),
|
),
|
||||||
onResetComposerMode = {},
|
onResetComposerMode = {},
|
||||||
|
enableTextFormatting = true,
|
||||||
)
|
)
|
||||||
TextComposer(
|
TextComposer(
|
||||||
RichTextEditorState("A message", fake = true),
|
RichTextEditorState("A message", fake = true),
|
||||||
|
|
@ -735,6 +748,7 @@ internal fun TextComposerReplyPreview() = ElementPreview {
|
||||||
defaultContent = "logs.txt"
|
defaultContent = "logs.txt"
|
||||||
),
|
),
|
||||||
onResetComposerMode = {},
|
onResetComposerMode = {},
|
||||||
|
enableTextFormatting = true,
|
||||||
)
|
)
|
||||||
TextComposer(
|
TextComposer(
|
||||||
RichTextEditorState("A message", fake = true).apply { requestFocus() },
|
RichTextEditorState("A message", fake = true).apply { requestFocus() },
|
||||||
|
|
@ -752,6 +766,7 @@ internal fun TextComposerReplyPreview() = ElementPreview {
|
||||||
defaultContent = "Shared location"
|
defaultContent = "Shared location"
|
||||||
),
|
),
|
||||||
onResetComposerMode = {},
|
onResetComposerMode = {},
|
||||||
|
enableTextFormatting = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue