From e2d6c6e15d1f668f7f28312196c970c1d2a97ec7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 29 Dec 2023 15:06:21 +0100 Subject: [PATCH] Add support for sending rich content from the keyboard. --- .../android/wysiwyg/compose/RichTextEditor.kt | 6 ++++ .../element/android/wysiwyg/EditorEditText.kt | 13 +++++++++ .../internal/utils/UriContentListener.kt | 29 +++++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 platforms/android/library/src/main/java/io/element/android/wysiwyg/internal/utils/UriContentListener.kt diff --git a/platforms/android/library-compose/src/main/java/io/element/android/wysiwyg/compose/RichTextEditor.kt b/platforms/android/library-compose/src/main/java/io/element/android/wysiwyg/compose/RichTextEditor.kt index bf52d1599..913827ba2 100644 --- a/platforms/android/library-compose/src/main/java/io/element/android/wysiwyg/compose/RichTextEditor.kt +++ b/platforms/android/library-compose/src/main/java/io/element/android/wysiwyg/compose/RichTextEditor.kt @@ -1,5 +1,6 @@ package io.element.android.wysiwyg.compose +import android.net.Uri import android.view.View import androidx.appcompat.widget.AppCompatEditText import androidx.compose.runtime.Composable @@ -41,6 +42,7 @@ import timber.log.Timber * @param resolveRoomMentionDisplay A function to resolve the [TextDisplay] of an `@room` mention. * @param onTyping Called when the user starts or stops typing in the editor. * @param onError Called when an internal error occurs + * @param onRichContentSelected Called when user uses the keyboard to send a rich content */ @Composable fun RichTextEditor( @@ -53,6 +55,7 @@ fun RichTextEditor( resolveRoomMentionDisplay: () -> TextDisplay = RichTextEditorDefaults.RoomMentionDisplay, onTyping: (Boolean) -> Unit = {}, onError: (Throwable) -> Unit = {}, + onRichContentSelected: ((Uri) -> Unit)? = null, ) { val isPreview = LocalInspectionMode.current @@ -69,6 +72,7 @@ fun RichTextEditor( resolveMentionDisplay = resolveMentionDisplay, resolveRoomMentionDisplay = resolveRoomMentionDisplay, onTyping = onTyping, + onRichContentSelected = onRichContentSelected, ) } } @@ -84,6 +88,7 @@ private fun RealEditor( resolveMentionDisplay: (text: String, url: String) -> TextDisplay, resolveRoomMentionDisplay: () -> TextDisplay, onTyping: (Boolean) -> Unit, + onRichContentSelected: ((Uri) -> Unit)?, ) { val context = LocalContext.current val coroutineScope = rememberCoroutineScope() @@ -154,6 +159,7 @@ private fun RealEditor( setHtml(state.internalHtml) setSelection(state.selection.first, state.selection.second) + setOnRichContentSelected(onRichContentSelected) // Only start listening for text changes after the initial state has been restored if (registerStateUpdates) { coroutineScope.launch(context = Dispatchers.Main) { diff --git a/platforms/android/library/src/main/java/io/element/android/wysiwyg/EditorEditText.kt b/platforms/android/library/src/main/java/io/element/android/wysiwyg/EditorEditText.kt index 8b08a5fba..cf57a521c 100644 --- a/platforms/android/library/src/main/java/io/element/android/wysiwyg/EditorEditText.kt +++ b/platforms/android/library/src/main/java/io/element/android/wysiwyg/EditorEditText.kt @@ -5,6 +5,7 @@ import android.content.ClipboardManager import android.content.Context import android.content.Context.CLIPBOARD_SERVICE import android.graphics.Canvas +import android.net.Uri import android.os.Build import android.os.Parcelable import android.text.Selection @@ -18,10 +19,12 @@ import android.widget.EditText import androidx.annotation.VisibleForTesting import androidx.appcompat.widget.AppCompatEditText import androidx.core.graphics.withTranslation +import androidx.core.view.ViewCompat import androidx.lifecycle.* import io.element.android.wysiwyg.display.MentionDisplayHandler import io.element.android.wysiwyg.inputhandlers.InterceptInputConnection import io.element.android.wysiwyg.internal.display.MemoizingMentionDisplayHandler +import io.element.android.wysiwyg.internal.utils.UriContentListener import io.element.android.wysiwyg.internal.view.EditorEditTextAttributeReader import io.element.android.wysiwyg.internal.view.viewModel import io.element.android.wysiwyg.internal.viewmodel.EditorInputAction @@ -276,6 +279,16 @@ class EditorEditText : AppCompatEditText { htmlConverter = createHtmlConverter(styleConfig, mentionDisplayHandler) } + fun setOnRichContentSelected(onRichContentSelected: ((Uri) -> Unit)?) { + if (onRichContentSelected != null) { + ViewCompat.setOnReceiveContentListener( + this, + arrayOf("image/*"), + UriContentListener { onRichContentSelected(it) } + ) + } + } + private fun addHardwareKeyInterceptor() { // This seems to be the only way to prevent EditText from automatically handling key strokes setOnKeyListener { _, keyCode, event -> diff --git a/platforms/android/library/src/main/java/io/element/android/wysiwyg/internal/utils/UriContentListener.kt b/platforms/android/library/src/main/java/io/element/android/wysiwyg/internal/utils/UriContentListener.kt new file mode 100644 index 000000000..ad8bc69b1 --- /dev/null +++ b/platforms/android/library/src/main/java/io/element/android/wysiwyg/internal/utils/UriContentListener.kt @@ -0,0 +1,29 @@ +package io.element.android.wysiwyg.internal.utils + +import android.content.ClipData +import android.net.Uri +import android.view.View +import androidx.core.view.ContentInfoCompat +import androidx.core.view.OnReceiveContentListener + +internal class UriContentListener( + private val onContent: (uri: Uri) -> Unit, +) : OnReceiveContentListener { + override fun onReceiveContent(view: View, payload: ContentInfoCompat): ContentInfoCompat? { + val split = payload.partition { item -> item.uri != null } + val uriContent = split.first + val remaining = split.second + + if (uriContent != null) { + val clip: ClipData = uriContent.clip + for (i in 0 until clip.itemCount) { + val uri = clip.getItemAt(i).uri + // ... app-specific logic to handle the URI ... + onContent(uri) + } + } + // Return anything that we didn't handle ourselves. This preserves the default platform + // behavior for text and anything else for which we are not implementing custom handling. + return remaining + } +}