Skip to content
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

[Rich text editor] Add ability to insert GIFs from keyboard #8185

Merged
merged 1 commit into from
Mar 2, 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/8185.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[Rich text editor] Add ability to insert GIFs from keyboard
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

package im.vector.app.features.home.room.detail.composer

import android.content.ClipData
import android.content.Context
import android.net.Uri
import android.os.Build
Expand All @@ -27,12 +26,12 @@ import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
import androidx.annotation.RequiresApi
import androidx.appcompat.widget.AppCompatEditText
import androidx.core.view.OnReceiveContentListener
import androidx.core.view.ViewCompat
import androidx.core.view.inputmethod.EditorInfoCompat
import androidx.core.view.inputmethod.InputConnectionCompat
import im.vector.app.core.extensions.ooi
import im.vector.app.core.platform.SimpleTextWatcher
import im.vector.app.features.home.room.detail.composer.images.UriContentListener
import im.vector.app.features.html.PillImageSpan
import timber.log.Timber

Expand All @@ -56,27 +55,11 @@ class ComposerEditText @JvmOverloads constructor(
EditorInfoCompat.setContentMimeTypes(editorInfo, mimeTypes)
ic = InputConnectionCompat.createWrapper(this, ic, editorInfo)

val onReceiveContentListener = OnReceiveContentListener { _, payload ->
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 ...
callback?.onRichContentSelected(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 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.
remaining
}

ViewCompat.setOnReceiveContentListener(this, mimeTypes, onReceiveContentListener)
ViewCompat.setOnReceiveContentListener(
this,
mimeTypes,
UriContentListener { callback?.onRichContentSelected(it) }
)

return ic
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.text.toSpannable
import androidx.core.view.ViewCompat
import androidx.core.view.isGone
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
Expand All @@ -47,6 +48,7 @@ import im.vector.app.core.extensions.showKeyboard
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.databinding.ComposerRichTextLayoutBinding
import im.vector.app.databinding.ViewRichTextMenuButtonBinding
import im.vector.app.features.home.room.detail.composer.images.UriContentListener
import io.element.android.wysiwyg.EditorEditText
import io.element.android.wysiwyg.inputhandlers.models.InlineFormat
import io.element.android.wysiwyg.inputhandlers.models.LinkAction
Expand Down Expand Up @@ -188,6 +190,16 @@ internal class RichTextComposerLayout @JvmOverloads constructor(
views.plainTextComposerEditText.addTextChangedListener(
TextChangeListener({ callback?.onTextChanged(it) }, { updateTextFieldBorder(isFullScreen) })
)
ViewCompat.setOnReceiveContentListener(
Copy link
Contributor

Choose a reason for hiding this comment

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

I was wondering if we should unregister all of these listeners but I suppose they will be destroyed with the view as they're owned by the view?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That was also my understanding that the OnReceiveContentListener was attached to the view.

I had a look at the docs but couldn't see any requirement to unregister the listener. And neither are we unregistering the same type of listener on the existing composer so I think it's safe.

views.richTextComposerEditText,
arrayOf("image/*"),
Copy link
Member

Choose a reason for hiding this comment

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

Why not using what was used previously in ComposerEditText?
ViewCompat.getOnReceiveContentMimeTypes(views.richTextComposerEditText) ?: arrayOf("image/*")

Copy link
Contributor Author

@jonnyandrew jonnyandrew Mar 2, 2023

Choose a reason for hiding this comment

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

I was a bit confused by this line actually. According to the docs getOnReceiveContentMimeTypes() returns null by default, and otherwise returns any mime types set previously using setOnReceiveContentListener().

So I thought it would be redundant in the rich text editor but didn't want to risk breaking anything by removing it from the old composer.

Copy link
Member

Choose a reason for hiding this comment

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

I see, thanks for your investigation.

UriContentListener { callback?.onRichContentSelected(it) }
)
ViewCompat.setOnReceiveContentListener(
views.plainTextComposerEditText,
arrayOf("image/*"),
UriContentListener { callback?.onRichContentSelected(it) }
)

disallowParentInterceptTouchEvent(views.richTextComposerEditText)
disallowParentInterceptTouchEvent(views.plainTextComposerEditText)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.app.features.home.room.detail.composer.images

import android.content.ClipData
import android.net.Uri
import android.view.View
import androidx.core.view.ContentInfoCompat
import androidx.core.view.OnReceiveContentListener

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
}
}