Merge branch 'dla/feature/connect_sdk_to_global_notifications_ui' of https://github.com/vector-im/element-x-android into dla/feature/connect_sdk_to_global_notifications_ui
This commit is contained in:
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.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
|
||||
@@ -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> {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
fun handleEvents(event: MessagesEvents) {
|
||||
when (event) {
|
||||
is MessagesEvents.HandleAction -> {
|
||||
@@ -178,6 +186,7 @@ class MessagesPresenter @AssistedInject constructor(
|
||||
snackbarMessage = snackbarMessage,
|
||||
showReinvitePrompt = showReinvitePrompt,
|
||||
inviteProgress = inviteProgress.value,
|
||||
enableTextFormatting = enableTextFormatting,
|
||||
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(
|
||||
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,
|
||||
)
|
||||
|
||||
@@ -45,5 +45,6 @@ data class MessagesState(
|
||||
val snackbarMessage: SnackbarMessage?,
|
||||
val inviteProgress: Async<Unit>,
|
||||
val showReinvitePrompt: Boolean,
|
||||
val enableTextFormatting: Boolean,
|
||||
val eventSink: (MessagesEvents) -> Unit
|
||||
)
|
||||
|
||||
@@ -82,5 +82,6 @@ fun aMessagesState() = MessagesState(
|
||||
snackbarMessage = null,
|
||||
inviteProgress = Async.Uninitialized,
|
||||
showReinvitePrompt = false,
|
||||
enableTextFormatting = true,
|
||||
eventSink = {}
|
||||
)
|
||||
|
||||
@@ -304,6 +304,7 @@ private fun MessagesViewContent(
|
||||
state = state.composerState,
|
||||
onSendLocationClicked = onSendLocationClicked,
|
||||
onCreatePollClicked = onCreatePollClicked,
|
||||
enableTextFormatting = state.enableTextFormatting,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight(Alignment.Bottom)
|
||||
|
||||
@@ -55,6 +55,7 @@ internal fun AttachmentsBottomSheet(
|
||||
state: MessageComposerState,
|
||||
onSendLocationClicked: () -> Unit,
|
||||
onCreatePollClicked: () -> Unit,
|
||||
enableTextFormatting: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val localView = LocalView.current
|
||||
@@ -87,6 +88,7 @@ internal fun AttachmentsBottomSheet(
|
||||
) {
|
||||
AttachmentSourcePickerMenu(
|
||||
state = state,
|
||||
enableTextFormatting = enableTextFormatting,
|
||||
onSendLocationClicked = onSendLocationClicked,
|
||||
onCreatePollClicked = onCreatePollClicked,
|
||||
)
|
||||
@@ -100,6 +102,7 @@ internal fun AttachmentSourcePickerMenu(
|
||||
state: MessageComposerState,
|
||||
onSendLocationClicked: () -> Unit,
|
||||
onCreatePollClicked: () -> Unit,
|
||||
enableTextFormatting: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
@@ -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)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,5 +168,6 @@ internal fun AttachmentSourcePickerMenuPreview() = ElementPreview {
|
||||
),
|
||||
onSendLocationClicked = {},
|
||||
onCreatePollClicked = {},
|
||||
enableTextFormatting = true,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -148,14 +148,6 @@ class MessageComposerPresenter @Inject constructor(
|
||||
)
|
||||
is MessageComposerEvents.SetMode -> {
|
||||
messageComposerContext.composerMode = event.composerMode
|
||||
analyticsService.capture(
|
||||
Composer(
|
||||
inThread = messageComposerContext.composerMode.inThread,
|
||||
isEditing = messageComposerContext.composerMode.isEditing,
|
||||
isReply = messageComposerContext.composerMode.isReply,
|
||||
messageType = Composer.MessageType.Text,
|
||||
)
|
||||
)
|
||||
}
|
||||
MessageComposerEvents.AddAttachment -> localCoroutineScope.launch {
|
||||
showAttachmentSourcePicker = true
|
||||
@@ -238,6 +230,14 @@ class MessageComposerPresenter @Inject constructor(
|
||||
message.html,
|
||||
)
|
||||
}
|
||||
analyticsService.capture(
|
||||
Composer(
|
||||
inThread = capturedMode.inThread,
|
||||
isEditing = capturedMode.isEditing,
|
||||
isReply = capturedMode.isReply,
|
||||
messageType = Composer.MessageType.Text, // Set proper type when we'll be sending other types of messages.
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.sendAttachment(
|
||||
|
||||
@@ -31,6 +31,7 @@ fun MessageComposerView(
|
||||
state: MessageComposerState,
|
||||
onSendLocationClicked: () -> Unit,
|
||||
onCreatePollClicked: () -> Unit,
|
||||
enableTextFormatting: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
fun onFullscreenToggle() {
|
||||
@@ -62,6 +63,7 @@ fun MessageComposerView(
|
||||
state = state,
|
||||
onSendLocationClicked = onSendLocationClicked,
|
||||
onCreatePollClicked = onCreatePollClicked,
|
||||
enableTextFormatting = enableTextFormatting,
|
||||
)
|
||||
|
||||
TextComposer(
|
||||
@@ -74,6 +76,7 @@ fun MessageComposerView(
|
||||
onResetComposerMode = ::onCloseSpecialMode,
|
||||
onAddAttachment = ::onAddAttachment,
|
||||
onDismissTextFormatting = ::onDismissTextFormatting,
|
||||
enableTextFormatting = enableTextFormatting,
|
||||
onError = ::onError,
|
||||
)
|
||||
}
|
||||
@@ -95,5 +98,6 @@ private fun ContentToPreview(state: MessageComposerState) {
|
||||
state = state,
|
||||
onSendLocationClicked = {},
|
||||
onCreatePollClicked = {},
|
||||
enableTextFormatting = true,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -642,6 +643,7 @@ class MessagesPresenterTest {
|
||||
navigator = navigator,
|
||||
clipboardHelper = clipboardHelper,
|
||||
analyticsService = analyticsService,
|
||||
featureFlagService = featureFlagsService,
|
||||
dispatchers = coroutineDispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.ReceiveTurbine
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import im.vector.app.features.analytics.plan.Composer
|
||||
import io.element.android.features.messages.impl.messagecomposer.AttachmentsState
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerContextImpl
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
|
||||
@@ -57,6 +58,7 @@ import io.element.android.libraries.textcomposer.Message
|
||||
import io.element.android.libraries.textcomposer.MessageComposerMode
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.waitForPredicate
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@@ -208,6 +210,15 @@ class MessageComposerPresenterTest {
|
||||
val messageSentState = awaitItem()
|
||||
assertThat(messageSentState.richTextEditorState.messageHtml).isEqualTo("")
|
||||
assertThat(messageSentState.canSendMessage).isFalse()
|
||||
waitForPredicate { analyticsService.capturedEvents.size == 1 }
|
||||
assertThat(analyticsService.capturedEvents).containsExactly(
|
||||
Composer(
|
||||
inThread = false,
|
||||
isEditing = false,
|
||||
isReply = false,
|
||||
messageType = Composer.MessageType.Text,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +251,14 @@ class MessageComposerPresenterTest {
|
||||
assertThat(messageSentState.richTextEditorState.messageHtml).isEqualTo("")
|
||||
assertThat(messageSentState.canSendMessage).isFalse()
|
||||
assertThat(fakeMatrixRoom.editMessageCalls.first()).isEqualTo(ANOTHER_MESSAGE to ANOTHER_MESSAGE)
|
||||
assertThat(analyticsService.capturedEvents).containsExactly(
|
||||
Composer(
|
||||
inThread = false,
|
||||
isEditing = true,
|
||||
isReply = false,
|
||||
messageType = Composer.MessageType.Text,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,6 +291,14 @@ class MessageComposerPresenterTest {
|
||||
assertThat(messageSentState.richTextEditorState.messageHtml).isEqualTo("")
|
||||
assertThat(messageSentState.canSendMessage).isFalse()
|
||||
assertThat(fakeMatrixRoom.editMessageCalls.first()).isEqualTo(ANOTHER_MESSAGE to ANOTHER_MESSAGE)
|
||||
assertThat(analyticsService.capturedEvents).containsExactly(
|
||||
Composer(
|
||||
inThread = false,
|
||||
isEditing = true,
|
||||
isReply = false,
|
||||
messageType = Composer.MessageType.Text,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,6 +331,14 @@ class MessageComposerPresenterTest {
|
||||
assertThat(messageSentState.richTextEditorState.messageHtml).isEqualTo("")
|
||||
assertThat(messageSentState.canSendMessage).isFalse()
|
||||
assertThat(fakeMatrixRoom.replyMessageParameter).isEqualTo(A_REPLY to A_REPLY)
|
||||
assertThat(analyticsService.capturedEvents).containsExactly(
|
||||
Composer(
|
||||
inThread = false,
|
||||
isEditing = false,
|
||||
isReply = true,
|
||||
messageType = Composer.MessageType.Text,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.IconToggleButton
|
||||
import io.element.android.libraries.designsystem.theme.components.LinearProgressIndicator
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.progressIndicatorTrackColor
|
||||
import io.element.android.libraries.designsystem.toEnabledColor
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
import io.element.android.libraries.ui.strings.CommonPlurals
|
||||
@@ -111,6 +112,7 @@ fun PollAnswerView(
|
||||
answerItem.isSelected -> 1f
|
||||
else -> 0f
|
||||
},
|
||||
trackColor = ElementTheme.colors.progressIndicatorTrackColor,
|
||||
strokeCap = StrokeCap.Round,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -149,7 +149,7 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" }
|
||||
appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" }
|
||||
molecule-runtime = { module = "app.cash.molecule:molecule-runtime", version.ref = "molecule" }
|
||||
timber = "com.jakewharton.timber:timber:5.0.1"
|
||||
matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.50"
|
||||
matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.51"
|
||||
matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" }
|
||||
matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" }
|
||||
sqldelight-driver-android = { module = "com.squareup.sqldelight:android-driver", version.ref = "sqldelight" }
|
||||
|
||||
@@ -65,6 +65,16 @@ val SemanticColors.messageFromOtherBackground
|
||||
Color(0xFF26282D)
|
||||
}
|
||||
|
||||
// This color is not present in Semantic color, so put hard-coded value for now
|
||||
val SemanticColors.progressIndicatorTrackColor
|
||||
get() = if (isLight) {
|
||||
// We want LightDesignTokens.colorAlphaGray500
|
||||
Color(0x33052448)
|
||||
} else {
|
||||
// We want DarkDesignTokens.colorAlphaGray500
|
||||
Color(0x25F4F7FA)
|
||||
}
|
||||
|
||||
// Temporary color, which is not in the token right now
|
||||
val SemanticColors.temporaryColorBgSpecial
|
||||
get() = if (isLight) Color(0xFFE4E8F0) else Color(0xFF3A4048)
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ class StaticFeatureFlagProvider @Inject constructor() :
|
||||
FeatureFlags.LocationSharing -> true
|
||||
FeatureFlags.Polls -> true
|
||||
FeatureFlags.NotificationSettings -> false
|
||||
FeatureFlags.RichTextEditor -> true
|
||||
}
|
||||
} else {
|
||||
false
|
||||
|
||||
@@ -85,11 +85,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>
|
||||
|
||||
|
||||
@@ -26,6 +26,9 @@ val oidcConfiguration: OidcConfiguration = OidcConfiguration(
|
||||
logoUri = "https://element.io/mobile-icon.png",
|
||||
tosUri = "https://element.io/acceptable-use-policy-terms",
|
||||
policyUri = "https://element.io/privacy",
|
||||
contacts = listOf(
|
||||
"support@element.io",
|
||||
),
|
||||
/**
|
||||
* Some homeservers/auth issuers don't support dynamic client registration, and have to be registered manually
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,6 @@
|
||||
package io.element.android.libraries.textcomposer
|
||||
|
||||
data class Message(
|
||||
val html: String,
|
||||
val html: String?,
|
||||
val markdown: String,
|
||||
)
|
||||
|
||||
@@ -91,6 +91,7 @@ fun TextComposer(
|
||||
state: RichTextEditorState,
|
||||
composerMode: MessageComposerMode,
|
||||
canSendMessage: Boolean,
|
||||
enableTextFormatting: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
showTextFormatting: Boolean = false,
|
||||
onRequestFocus: () -> Unit = {},
|
||||
@@ -101,7 +102,8 @@ fun TextComposer(
|
||||
onError: (Throwable) -> Unit = {},
|
||||
) {
|
||||
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(
|
||||
@@ -600,6 +602,7 @@ internal fun TextComposerSimplePreview() = ElementPreview {
|
||||
onSendMessage = {},
|
||||
composerMode = MessageComposerMode.Normal(""),
|
||||
onResetComposerMode = {},
|
||||
enableTextFormatting = true,
|
||||
)
|
||||
TextComposer(
|
||||
RichTextEditorState("A message", fake = true).apply { requestFocus() },
|
||||
@@ -607,6 +610,7 @@ internal fun TextComposerSimplePreview() = ElementPreview {
|
||||
onSendMessage = {},
|
||||
composerMode = MessageComposerMode.Normal(""),
|
||||
onResetComposerMode = {},
|
||||
enableTextFormatting = true,
|
||||
)
|
||||
TextComposer(
|
||||
RichTextEditorState(
|
||||
@@ -619,6 +623,7 @@ internal fun TextComposerSimplePreview() = ElementPreview {
|
||||
onSendMessage = {},
|
||||
composerMode = MessageComposerMode.Normal(""),
|
||||
onResetComposerMode = {},
|
||||
enableTextFormatting = true,
|
||||
)
|
||||
TextComposer(
|
||||
RichTextEditorState("A message without focus", fake = true),
|
||||
@@ -626,6 +631,7 @@ internal fun TextComposerSimplePreview() = ElementPreview {
|
||||
onSendMessage = {},
|
||||
composerMode = MessageComposerMode.Normal(""),
|
||||
onResetComposerMode = {},
|
||||
enableTextFormatting = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -639,18 +645,21 @@ internal fun TextComposerFormattingPreview() = ElementPreview {
|
||||
canSendMessage = false,
|
||||
showTextFormatting = true,
|
||||
composerMode = MessageComposerMode.Normal(""),
|
||||
enableTextFormatting = true,
|
||||
)
|
||||
TextComposer(
|
||||
RichTextEditorState("A message", fake = true),
|
||||
canSendMessage = true,
|
||||
showTextFormatting = true,
|
||||
composerMode = MessageComposerMode.Normal(""),
|
||||
enableTextFormatting = true,
|
||||
)
|
||||
TextComposer(
|
||||
RichTextEditorState("A message\nWith several lines\nTo preview larger textfields and long lines with overflow", fake = true),
|
||||
canSendMessage = true,
|
||||
showTextFormatting = true,
|
||||
composerMode = MessageComposerMode.Normal(""),
|
||||
enableTextFormatting = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -664,6 +673,7 @@ internal fun TextComposerEditPreview() = ElementPreview {
|
||||
onSendMessage = {},
|
||||
composerMode = MessageComposerMode.Edit(EventId("$1234"), "Some text", TransactionId("1234")),
|
||||
onResetComposerMode = {},
|
||||
enableTextFormatting = true,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -684,6 +694,7 @@ internal fun TextComposerReplyPreview() = ElementPreview {
|
||||
"To preview larger textfields and long lines with overflow"
|
||||
),
|
||||
onResetComposerMode = {},
|
||||
enableTextFormatting = true,
|
||||
)
|
||||
TextComposer(
|
||||
RichTextEditorState("A message", fake = true),
|
||||
@@ -701,6 +712,7 @@ internal fun TextComposerReplyPreview() = ElementPreview {
|
||||
defaultContent = "image.jpg"
|
||||
),
|
||||
onResetComposerMode = {},
|
||||
enableTextFormatting = true,
|
||||
)
|
||||
TextComposer(
|
||||
RichTextEditorState("A message", fake = true),
|
||||
@@ -718,6 +730,7 @@ internal fun TextComposerReplyPreview() = ElementPreview {
|
||||
defaultContent = "video.mp4"
|
||||
),
|
||||
onResetComposerMode = {},
|
||||
enableTextFormatting = true,
|
||||
)
|
||||
TextComposer(
|
||||
RichTextEditorState("A message", fake = true),
|
||||
@@ -735,6 +748,7 @@ internal fun TextComposerReplyPreview() = ElementPreview {
|
||||
defaultContent = "logs.txt"
|
||||
),
|
||||
onResetComposerMode = {},
|
||||
enableTextFormatting = true,
|
||||
)
|
||||
TextComposer(
|
||||
RichTextEditorState("A message", fake = true).apply { requestFocus() },
|
||||
@@ -752,6 +766,7 @@ internal fun TextComposerReplyPreview() = ElementPreview {
|
||||
defaultContent = "Shared location"
|
||||
),
|
||||
onResetComposerMode = {},
|
||||
enableTextFormatting = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user