mirror of
https://github.com/element-hq/element-android
synced 2024-11-23 18:05:36 +03:00
Autocomplete : handle click
This commit is contained in:
parent
c64d6b6b28
commit
3f1cc466ed
5 changed files with 48 additions and 26 deletions
|
@ -19,6 +19,9 @@ package im.vector.riotredesign.core.epoxy
|
||||||
import com.airbnb.epoxy.EpoxyModelWithHolder
|
import com.airbnb.epoxy.EpoxyModelWithHolder
|
||||||
import com.airbnb.epoxy.VisibilityState
|
import com.airbnb.epoxy.VisibilityState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EpoxyModelWithHolder which can listen to visibility state change
|
||||||
|
*/
|
||||||
abstract class VectorEpoxyModel<H : VectorEpoxyHolder> : EpoxyModelWithHolder<H>() {
|
abstract class VectorEpoxyModel<H : VectorEpoxyHolder> : EpoxyModelWithHolder<H>() {
|
||||||
|
|
||||||
private var onModelVisibilityStateChangedListener: OnVisibilityStateChangedListener? = null
|
private var onModelVisibilityStateChangedListener: OnVisibilityStateChangedListener? = null
|
||||||
|
|
|
@ -22,7 +22,6 @@ import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.airbnb.epoxy.EpoxyController
|
import com.airbnb.epoxy.EpoxyController
|
||||||
import com.airbnb.epoxy.EpoxyRecyclerView
|
import com.airbnb.epoxy.EpoxyRecyclerView
|
||||||
import com.otaliastudios.autocomplete.AutocompleteCallback
|
|
||||||
import com.otaliastudios.autocomplete.AutocompletePresenter
|
import com.otaliastudios.autocomplete.AutocompletePresenter
|
||||||
import im.vector.riotredesign.core.listener.Listener
|
import im.vector.riotredesign.core.listener.Listener
|
||||||
|
|
||||||
|
@ -60,25 +59,15 @@ abstract class EpoxyAutocompletePresenter<T>(context: Context) : AutocompletePre
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun providesController(): EpoxyController
|
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() {
|
protected fun dispatchLayoutChange() {
|
||||||
observer?.onChanged()
|
observer?.onChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEvent(t: T) {
|
override fun onEvent(t: T) {
|
||||||
dispatchClick(t)
|
clicks?.click(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private class Observer internal constructor(private val root: DataSetObserver) : RecyclerView.AdapterDataObserver() {
|
private class Observer internal constructor(private val root: DataSetObserver) : RecyclerView.AdapterDataObserver() {
|
||||||
|
|
||||||
override fun onChanged() {
|
override fun onChanged() {
|
||||||
|
|
|
@ -19,7 +19,7 @@ package im.vector.riotredesign.features.autocomplete.command
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import com.otaliastudios.autocomplete.AutocompletePolicy
|
import com.otaliastudios.autocomplete.AutocompletePolicy
|
||||||
|
|
||||||
class CommandPolicy : AutocompletePolicy {
|
class CommandAutocompletePolicy : AutocompletePolicy {
|
||||||
override fun getQuery(text: Spannable): CharSequence {
|
override fun getQuery(text: Spannable): CharSequence {
|
||||||
if (text.length > 0) {
|
if (text.length > 0) {
|
||||||
return text.substring(1, text.length)
|
return text.substring(1, text.length)
|
|
@ -21,6 +21,7 @@ import android.graphics.drawable.ColorDrawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
|
import android.text.Spannable
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
@ -29,14 +30,16 @@ import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.otaliastudios.autocomplete.Autocomplete
|
import com.otaliastudios.autocomplete.Autocomplete
|
||||||
import com.otaliastudios.autocomplete.AutocompleteCallback
|
import com.otaliastudios.autocomplete.AutocompleteCallback
|
||||||
import com.otaliastudios.autocomplete.CharPolicy
|
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.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
import im.vector.riotredesign.core.epoxy.LayoutManagerStateRestorer
|
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.ToolbarConfigurable
|
||||||
import im.vector.riotredesign.core.platform.VectorBaseFragment
|
import im.vector.riotredesign.core.platform.VectorBaseFragment
|
||||||
import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandPresenter
|
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.autocomplete.user.AutocompleteUserPresenter
|
||||||
import im.vector.riotredesign.features.command.Command
|
import im.vector.riotredesign.features.command.Command
|
||||||
import im.vector.riotredesign.features.home.AvatarRenderer
|
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.composer.TextComposerViewState
|
||||||
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
|
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.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.MediaContentRenderer
|
||||||
import im.vector.riotredesign.features.media.MediaViewerActivity
|
import im.vector.riotredesign.features.media.MediaViewerActivity
|
||||||
import kotlinx.android.parcel.Parcelize
|
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 roomDetailViewModel: RoomDetailViewModel by fragmentViewModel()
|
||||||
private val textComposerViewModel: TextComposerViewModel by fragmentViewModel()
|
private val textComposerViewModel: TextComposerViewModel by fragmentViewModel()
|
||||||
private val timelineEventController: TimelineEventController by inject { parametersOf(this) }
|
private val timelineEventController: TimelineEventController by inject { parametersOf(this) }
|
||||||
|
@ -136,16 +146,16 @@ class RoomDetailFragment : VectorBaseFragment(), TimelineEventController.Callbac
|
||||||
val elevation = 6f
|
val elevation = 6f
|
||||||
val backgroundDrawable = ColorDrawable(Color.WHITE)
|
val backgroundDrawable = ColorDrawable(Color.WHITE)
|
||||||
Autocomplete.on<Command>(composerEditText)
|
Autocomplete.on<Command>(composerEditText)
|
||||||
.with(CommandPolicy())
|
.with(CommandAutocompletePolicy())
|
||||||
.with(autocompleteCommandPresenter)
|
.with(autocompleteCommandPresenter)
|
||||||
.with(elevation)
|
.with(elevation)
|
||||||
.with(backgroundDrawable)
|
.with(backgroundDrawable)
|
||||||
.with(object : AutocompleteCallback<Command> {
|
.with(object : AutocompleteCallback<Command> {
|
||||||
override fun onPopupItemClicked(editable: Editable?, item: Command?): Boolean {
|
override fun onPopupItemClicked(editable: Editable, item: Command): Boolean {
|
||||||
editable?.clear()
|
editable.clear()
|
||||||
editable
|
editable
|
||||||
?.append(item?.command)
|
.append(item.command)
|
||||||
?.append(" ")
|
.append(" ")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,15 +166,37 @@ class RoomDetailFragment : VectorBaseFragment(), TimelineEventController.Callbac
|
||||||
|
|
||||||
autocompleteUserPresenter.callback = this
|
autocompleteUserPresenter.callback = this
|
||||||
Autocomplete.on<User>(composerEditText)
|
Autocomplete.on<User>(composerEditText)
|
||||||
.with(CharPolicy('@', false))
|
.with(CharPolicy('@', true))
|
||||||
.with(autocompleteUserPresenter)
|
.with(autocompleteUserPresenter)
|
||||||
.with(elevation)
|
.with(elevation)
|
||||||
.with(backgroundDrawable)
|
.with(backgroundDrawable)
|
||||||
.with(object : AutocompleteCallback<User> {
|
.with(object : AutocompleteCallback<User> {
|
||||||
override fun onPopupItemClicked(editable: Editable?, item: User?): Boolean {
|
override fun onPopupItemClicked(editable: Editable, item: User): Boolean {
|
||||||
// TODO
|
// Detect last '@' and remove it
|
||||||
editable?.append(item?.displayName)
|
var startIndex = editable.lastIndexOf("@")
|
||||||
?.append(" ")
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,5 +256,4 @@ class RoomDetailFragment : VectorBaseFragment(), TimelineEventController.Callbac
|
||||||
override fun onQueryUsers(query: CharSequence?) {
|
override fun onQueryUsers(query: CharSequence?) {
|
||||||
textComposerViewModel.process(TextComposerActions.QueryUsers(query))
|
textComposerViewModel.process(TextComposerActions.QueryUsers(query))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,6 @@ import java.lang.ref.WeakReference
|
||||||
* This span is able to replace a text by a [ChipDrawable]
|
* 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.
|
* 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,
|
class PillImageSpan(private val glideRequests: GlideRequests,
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val userId: String,
|
private val userId: String,
|
||||||
|
|
Loading…
Reference in a new issue