Autocomplete : handle click

This commit is contained in:
Benoit Marty 2019-04-09 09:58:07 +02:00
parent c64d6b6b28
commit 3f1cc466ed
5 changed files with 48 additions and 26 deletions

View file

@ -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<H : VectorEpoxyHolder> : EpoxyModelWithHolder<H>() {
private var onModelVisibilityStateChangedListener: OnVisibilityStateChangedListener? = null

View file

@ -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<T>(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() {

View file

@ -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)

View file

@ -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<Session>()
// 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<Command>(composerEditText)
.with(CommandPolicy())
.with(CommandAutocompletePolicy())
.with(autocompleteCommandPresenter)
.with(elevation)
.with(backgroundDrawable)
.with(object : AutocompleteCallback<Command> {
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<User>(composerEditText)
.with(CharPolicy('@', false))
.with(CharPolicy('@', true))
.with(autocompleteUserPresenter)
.with(elevation)
.with(backgroundDrawable)
.with(object : AutocompleteCallback<User> {
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))
}
}

View file

@ -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,