From 3f1cc466edce088c7f785b7a971af40d4a1822c0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 9 Apr 2019 09:58:07 +0200 Subject: [PATCH] Autocomplete : handle click --- .../core/epoxy/VectorEpoxyModel.kt | 3 + .../EpoxyAutocompletePresenter.kt | 13 +---- ...Policy.kt => CommandAutocompletePolicy.kt} | 2 +- .../home/room/detail/RoomDetailFragment.kt | 55 +++++++++++++++---- .../features/html/PillImageSpan.kt | 1 - 5 files changed, 48 insertions(+), 26 deletions(-) rename vector/src/main/java/im/vector/riotredesign/features/autocomplete/command/{CommandPolicy.kt => CommandAutocompletePolicy.kt} (96%) diff --git a/vector/src/main/java/im/vector/riotredesign/core/epoxy/VectorEpoxyModel.kt b/vector/src/main/java/im/vector/riotredesign/core/epoxy/VectorEpoxyModel.kt index bb7bb10c..16c2401d 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/epoxy/VectorEpoxyModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/epoxy/VectorEpoxyModel.kt @@ -19,6 +19,9 @@ package im.vector.riotredesign.core.epoxy import com.airbnb.epoxy.EpoxyModelWithHolder import com.airbnb.epoxy.VisibilityState +/** + * EpoxyModelWithHolder which can listen to visibility state change + */ abstract class VectorEpoxyModel : EpoxyModelWithHolder() { private var onModelVisibilityStateChangedListener: OnVisibilityStateChangedListener? = null diff --git a/vector/src/main/java/im/vector/riotredesign/features/autocomplete/EpoxyAutocompletePresenter.kt b/vector/src/main/java/im/vector/riotredesign/features/autocomplete/EpoxyAutocompletePresenter.kt index cf5818d1..552b0eb6 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/autocomplete/EpoxyAutocompletePresenter.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/autocomplete/EpoxyAutocompletePresenter.kt @@ -22,7 +22,6 @@ import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.airbnb.epoxy.EpoxyController import com.airbnb.epoxy.EpoxyRecyclerView -import com.otaliastudios.autocomplete.AutocompleteCallback import com.otaliastudios.autocomplete.AutocompletePresenter import im.vector.riotredesign.core.listener.Listener @@ -60,25 +59,15 @@ abstract class EpoxyAutocompletePresenter(context: Context) : AutocompletePre } abstract fun providesController(): EpoxyController - /** - * Dispatch click event to [AutocompleteCallback]. - * Should be called when items are clicked. - * - * @param item the clicked item. - */ - protected fun dispatchClick(item: T) { - clicks?.click(item) - } protected fun dispatchLayoutChange() { observer?.onChanged() } override fun onEvent(t: T) { - dispatchClick(t) + clicks?.click(t) } - private class Observer internal constructor(private val root: DataSetObserver) : RecyclerView.AdapterDataObserver() { override fun onChanged() { diff --git a/vector/src/main/java/im/vector/riotredesign/features/autocomplete/command/CommandPolicy.kt b/vector/src/main/java/im/vector/riotredesign/features/autocomplete/command/CommandAutocompletePolicy.kt similarity index 96% rename from vector/src/main/java/im/vector/riotredesign/features/autocomplete/command/CommandPolicy.kt rename to vector/src/main/java/im/vector/riotredesign/features/autocomplete/command/CommandAutocompletePolicy.kt index 6cf6dfa3..74ee50aa 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/autocomplete/command/CommandPolicy.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/autocomplete/command/CommandAutocompletePolicy.kt @@ -19,7 +19,7 @@ package im.vector.riotredesign.features.autocomplete.command import android.text.Spannable import com.otaliastudios.autocomplete.AutocompletePolicy -class CommandPolicy : AutocompletePolicy { +class CommandAutocompletePolicy : AutocompletePolicy { override fun getQuery(text: Spannable): CharSequence { if (text.length > 0) { return text.substring(1, text.length) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index d7873e11..82c2d7d5 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -21,6 +21,7 @@ import android.graphics.drawable.ColorDrawable import android.os.Bundle import android.os.Parcelable import android.text.Editable +import android.text.Spannable import android.view.View import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -29,14 +30,16 @@ import com.airbnb.mvrx.fragmentViewModel import com.otaliastudios.autocomplete.Autocomplete import com.otaliastudios.autocomplete.AutocompleteCallback import com.otaliastudios.autocomplete.CharPolicy +import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.user.model.User import im.vector.riotredesign.R import im.vector.riotredesign.core.epoxy.LayoutManagerStateRestorer +import im.vector.riotredesign.core.glide.GlideApp import im.vector.riotredesign.core.platform.ToolbarConfigurable import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandPresenter -import im.vector.riotredesign.features.autocomplete.command.CommandPolicy +import im.vector.riotredesign.features.autocomplete.command.CommandAutocompletePolicy import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserPresenter import im.vector.riotredesign.features.command.Command import im.vector.riotredesign.features.home.AvatarRenderer @@ -47,6 +50,7 @@ import im.vector.riotredesign.features.home.room.detail.composer.TextComposerVie import im.vector.riotredesign.features.home.room.detail.composer.TextComposerViewState import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController import im.vector.riotredesign.features.home.room.detail.timeline.helper.EndlessRecyclerViewScrollListener +import im.vector.riotredesign.features.html.PillImageSpan import im.vector.riotredesign.features.media.MediaContentRenderer import im.vector.riotredesign.features.media.MediaViewerActivity import kotlinx.android.parcel.Parcelize @@ -75,6 +79,12 @@ class RoomDetailFragment : VectorBaseFragment(), TimelineEventController.Callbac } } + private val session by inject() + // TODO Inject? + private val glideRequests by lazy { + GlideApp.with(this) + } + private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel() private val textComposerViewModel: TextComposerViewModel by fragmentViewModel() private val timelineEventController: TimelineEventController by inject { parametersOf(this) } @@ -136,16 +146,16 @@ class RoomDetailFragment : VectorBaseFragment(), TimelineEventController.Callbac val elevation = 6f val backgroundDrawable = ColorDrawable(Color.WHITE) Autocomplete.on(composerEditText) - .with(CommandPolicy()) + .with(CommandAutocompletePolicy()) .with(autocompleteCommandPresenter) .with(elevation) .with(backgroundDrawable) .with(object : AutocompleteCallback { - override fun onPopupItemClicked(editable: Editable?, item: Command?): Boolean { - editable?.clear() + override fun onPopupItemClicked(editable: Editable, item: Command): Boolean { + editable.clear() editable - ?.append(item?.command) - ?.append(" ") + .append(item.command) + .append(" ") return true } @@ -156,15 +166,37 @@ class RoomDetailFragment : VectorBaseFragment(), TimelineEventController.Callbac autocompleteUserPresenter.callback = this Autocomplete.on(composerEditText) - .with(CharPolicy('@', false)) + .with(CharPolicy('@', true)) .with(autocompleteUserPresenter) .with(elevation) .with(backgroundDrawable) .with(object : AutocompleteCallback { - override fun onPopupItemClicked(editable: Editable?, item: User?): Boolean { - // TODO - editable?.append(item?.displayName) - ?.append(" ") + override fun onPopupItemClicked(editable: Editable, item: User): Boolean { + // Detect last '@' and remove it + var startIndex = editable.lastIndexOf("@") + if (startIndex == -1) { + startIndex = 0 + } + + // Detect next word separator + var endIndex = editable.indexOf(" ", startIndex) + if (endIndex == -1) { + endIndex = editable.length + } + + // Replace the word by its completion + val displayName = item.displayName ?: item.userId + + // with a trailing space + editable.replace(startIndex, endIndex, "$displayName ") + + // Add the span + val user = session.getUser(item.userId) + // FIXME avatar is not displayed + val span = PillImageSpan(glideRequests, context!!, item.userId, user) + + editable.setSpan(span, startIndex, startIndex + displayName.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + return true } @@ -224,5 +256,4 @@ class RoomDetailFragment : VectorBaseFragment(), TimelineEventController.Callbac override fun onQueryUsers(query: CharSequence?) { textComposerViewModel.process(TextComposerActions.QueryUsers(query)) } - } diff --git a/vector/src/main/java/im/vector/riotredesign/features/html/PillImageSpan.kt b/vector/src/main/java/im/vector/riotredesign/features/html/PillImageSpan.kt index a5189494..5ab18531 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/html/PillImageSpan.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/html/PillImageSpan.kt @@ -36,7 +36,6 @@ import java.lang.ref.WeakReference * This span is able to replace a text by a [ChipDrawable] * It's needed to call [bind] method to start requesting avatar, otherwise only the placeholder icon will be displayed if not already cached. */ - class PillImageSpan(private val glideRequests: GlideRequests, private val context: Context, private val userId: String,