forked from GitHub-Mirror/riotX-android
Autocomplete : handle click and better detection for / commands
This commit is contained in:
parent
6d3028c2d7
commit
c64d6b6b28
@ -27,7 +27,7 @@ import kotlin.reflect.KProperty
|
|||||||
* See [SampleKotlinModelWithHolder] for a usage example.
|
* See [SampleKotlinModelWithHolder] for a usage example.
|
||||||
*/
|
*/
|
||||||
abstract class VectorEpoxyHolder : EpoxyHolder() {
|
abstract class VectorEpoxyHolder : EpoxyHolder() {
|
||||||
private lateinit var view: View
|
lateinit var view: View
|
||||||
|
|
||||||
override fun bindView(itemView: View) {
|
override fun bindView(itemView: View) {
|
||||||
view = itemView
|
view = itemView
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.riotredesign.core.listener
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple generic listener interface
|
||||||
|
*/
|
||||||
|
interface Listener<T> {
|
||||||
|
|
||||||
|
fun onEvent(t: T)
|
||||||
|
}
|
@ -24,8 +24,9 @@ import com.airbnb.epoxy.EpoxyController
|
|||||||
import com.airbnb.epoxy.EpoxyRecyclerView
|
import com.airbnb.epoxy.EpoxyRecyclerView
|
||||||
import com.otaliastudios.autocomplete.AutocompleteCallback
|
import com.otaliastudios.autocomplete.AutocompleteCallback
|
||||||
import com.otaliastudios.autocomplete.AutocompletePresenter
|
import com.otaliastudios.autocomplete.AutocompletePresenter
|
||||||
|
import im.vector.riotredesign.core.listener.Listener
|
||||||
|
|
||||||
abstract class EpoxyViewPresenter<T>(context: Context) : AutocompletePresenter<T>(context) {
|
abstract class EpoxyAutocompletePresenter<T>(context: Context) : AutocompletePresenter<T>(context), Listener<T> {
|
||||||
|
|
||||||
private var recyclerView: EpoxyRecyclerView? = null
|
private var recyclerView: EpoxyRecyclerView? = null
|
||||||
private var clicks: AutocompletePresenter.ClickProvider<T>? = null
|
private var clicks: AutocompletePresenter.ClickProvider<T>? = null
|
||||||
@ -73,6 +74,10 @@ abstract class EpoxyViewPresenter<T>(context: Context) : AutocompletePresenter<T
|
|||||||
observer?.onChanged()
|
observer?.onChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onEvent(t: T) {
|
||||||
|
dispatchClick(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private class Observer internal constructor(private val root: DataSetObserver) : RecyclerView.AdapterDataObserver() {
|
private class Observer internal constructor(private val root: DataSetObserver) : RecyclerView.AdapterDataObserver() {
|
||||||
|
|
@ -17,21 +17,27 @@
|
|||||||
package im.vector.riotredesign.features.autocomplete.command
|
package im.vector.riotredesign.features.autocomplete.command
|
||||||
|
|
||||||
import com.airbnb.epoxy.TypedEpoxyController
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
|
import im.vector.riotredesign.core.listener.Listener
|
||||||
import im.vector.riotredesign.core.resources.StringProvider
|
import im.vector.riotredesign.core.resources.StringProvider
|
||||||
import im.vector.riotredesign.features.command.Command
|
import im.vector.riotredesign.features.command.Command
|
||||||
|
|
||||||
class AutocompleteCommandController(private val stringProvider: StringProvider) : TypedEpoxyController<List<Command>>() {
|
class AutocompleteCommandController(private val stringProvider: StringProvider) : TypedEpoxyController<List<Command>>() {
|
||||||
|
|
||||||
|
var listener: Listener<Command>? = null
|
||||||
|
|
||||||
override fun buildModels(data: List<Command>?) {
|
override fun buildModels(data: List<Command>?) {
|
||||||
if (data.isNullOrEmpty()) {
|
if (data.isNullOrEmpty()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data.forEach {
|
data.forEach { command ->
|
||||||
autocompleteCommandItem {
|
autocompleteCommandItem {
|
||||||
id(it.command)
|
id(command.command)
|
||||||
name(it.command)
|
name(command.command)
|
||||||
parameters(it.parameters)
|
parameters(command.parameters)
|
||||||
description(stringProvider.getString(it.description))
|
description(stringProvider.getString(command.description))
|
||||||
|
clickListener { _ ->
|
||||||
|
listener?.onEvent(command)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package im.vector.riotredesign.features.autocomplete.command
|
package im.vector.riotredesign.features.autocomplete.command
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
@ -32,8 +33,12 @@ abstract class AutocompleteCommandItem : VectorEpoxyModel<AutocompleteCommandIte
|
|||||||
var parameters: CharSequence? = null
|
var parameters: CharSequence? = null
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var description: CharSequence? = null
|
var description: CharSequence? = null
|
||||||
|
@EpoxyAttribute
|
||||||
|
var clickListener: View.OnClickListener? = null
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
|
holder.view.setOnClickListener(clickListener)
|
||||||
|
|
||||||
holder.nameView.text = name
|
holder.nameView.text = name
|
||||||
holder.parametersView.text = parameters
|
holder.parametersView.text = parameters
|
||||||
holder.descriptionView.text = description
|
holder.descriptionView.text = description
|
||||||
|
@ -18,12 +18,16 @@ package im.vector.riotredesign.features.autocomplete.command
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.airbnb.epoxy.EpoxyController
|
import com.airbnb.epoxy.EpoxyController
|
||||||
import im.vector.riotredesign.features.autocomplete.EpoxyViewPresenter
|
import im.vector.riotredesign.features.autocomplete.EpoxyAutocompletePresenter
|
||||||
import im.vector.riotredesign.features.command.Command
|
import im.vector.riotredesign.features.command.Command
|
||||||
|
|
||||||
class AutocompleteCommandPresenter(context: Context,
|
class AutocompleteCommandPresenter(context: Context,
|
||||||
private val controller: AutocompleteCommandController
|
private val controller: AutocompleteCommandController) :
|
||||||
) : EpoxyViewPresenter<Command>(context) {
|
EpoxyAutocompletePresenter<Command>(context) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
controller.listener = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun providesController(): EpoxyController {
|
override fun providesController(): EpoxyController {
|
||||||
return controller
|
return controller
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.riotredesign.features.autocomplete.command
|
||||||
|
|
||||||
|
import android.text.Spannable
|
||||||
|
import com.otaliastudios.autocomplete.AutocompletePolicy
|
||||||
|
|
||||||
|
class CommandPolicy : AutocompletePolicy {
|
||||||
|
override fun getQuery(text: Spannable): CharSequence {
|
||||||
|
if (text.length > 0) {
|
||||||
|
return text.substring(1, text.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should not happen
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDismiss(text: Spannable?) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only if text which starts with '/' and without space
|
||||||
|
override fun shouldShowPopup(text: Spannable?, cursorPos: Int): Boolean {
|
||||||
|
return text?.startsWith("/") == true
|
||||||
|
&& !text.contains(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shouldDismissPopup(text: Spannable?, cursorPos: Int): Boolean {
|
||||||
|
return !shouldShowPopup(text, cursorPos)
|
||||||
|
}
|
||||||
|
}
|
@ -18,18 +18,24 @@ package im.vector.riotredesign.features.autocomplete.user
|
|||||||
|
|
||||||
import com.airbnb.epoxy.TypedEpoxyController
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
import im.vector.riotredesign.core.listener.Listener
|
||||||
|
|
||||||
class AutocompleteUserController() : TypedEpoxyController<List<User>>() {
|
class AutocompleteUserController : TypedEpoxyController<List<User>>() {
|
||||||
|
|
||||||
|
var listener: Listener<User>? = null
|
||||||
|
|
||||||
override fun buildModels(data: List<User>?) {
|
override fun buildModels(data: List<User>?) {
|
||||||
if (data.isNullOrEmpty()) {
|
if (data.isNullOrEmpty()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data.forEach {
|
data.forEach { user ->
|
||||||
autocompleteUserItem {
|
autocompleteUserItem {
|
||||||
id(it.userId)
|
id(user.userId)
|
||||||
name(it.displayName)
|
name(user.displayName)
|
||||||
avatarUrl(it.avatarUrl)
|
avatarUrl(user.avatarUrl)
|
||||||
|
clickListener { _ ->
|
||||||
|
listener?.onEvent(user)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package im.vector.riotredesign.features.autocomplete.user
|
package im.vector.riotredesign.features.autocomplete.user
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
@ -32,8 +33,12 @@ abstract class AutocompleteUserItem : VectorEpoxyModel<AutocompleteUserItem.Hold
|
|||||||
var name: String? = null
|
var name: String? = null
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var avatarUrl: String? = null
|
var avatarUrl: String? = null
|
||||||
|
@EpoxyAttribute
|
||||||
|
var clickListener: View.OnClickListener? = null
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
|
holder.view.setOnClickListener(clickListener)
|
||||||
|
|
||||||
holder.nameView.text = name
|
holder.nameView.text = name
|
||||||
AvatarRenderer.render(avatarUrl, name, holder.avatarImageView)
|
AvatarRenderer.render(avatarUrl, name, holder.avatarImageView)
|
||||||
}
|
}
|
||||||
|
@ -21,14 +21,19 @@ import com.airbnb.epoxy.EpoxyController
|
|||||||
import com.airbnb.mvrx.Async
|
import com.airbnb.mvrx.Async
|
||||||
import com.airbnb.mvrx.Success
|
import com.airbnb.mvrx.Success
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
import im.vector.riotredesign.features.autocomplete.EpoxyViewPresenter
|
import im.vector.riotredesign.core.listener.Listener
|
||||||
|
import im.vector.riotredesign.features.autocomplete.EpoxyAutocompletePresenter
|
||||||
|
|
||||||
class AutocompleteUserPresenter(context: Context,
|
class AutocompleteUserPresenter(context: Context,
|
||||||
private val controller: AutocompleteUserController
|
private val controller: AutocompleteUserController
|
||||||
) : EpoxyViewPresenter<User>(context) {
|
) : EpoxyAutocompletePresenter<User>(context), Listener<User> {
|
||||||
|
|
||||||
var callback: Callback? = null
|
var callback: Callback? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
controller.listener = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun providesController(): EpoxyController {
|
override fun providesController(): EpoxyController {
|
||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
@ -20,12 +20,14 @@ import android.graphics.Color
|
|||||||
import android.graphics.drawable.ColorDrawable
|
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.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
|
||||||
import com.airbnb.epoxy.EpoxyVisibilityTracker
|
import com.airbnb.epoxy.EpoxyVisibilityTracker
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.otaliastudios.autocomplete.Autocomplete
|
import com.otaliastudios.autocomplete.Autocomplete
|
||||||
|
import com.otaliastudios.autocomplete.AutocompleteCallback
|
||||||
import com.otaliastudios.autocomplete.CharPolicy
|
import com.otaliastudios.autocomplete.CharPolicy
|
||||||
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
|
||||||
@ -34,6 +36,7 @@ import im.vector.riotredesign.core.epoxy.LayoutManagerStateRestorer
|
|||||||
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.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
|
||||||
@ -133,10 +136,22 @@ 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(CharPolicy('/', false))
|
.with(CommandPolicy())
|
||||||
.with(autocompleteCommandPresenter)
|
.with(autocompleteCommandPresenter)
|
||||||
.with(elevation)
|
.with(elevation)
|
||||||
.with(backgroundDrawable)
|
.with(backgroundDrawable)
|
||||||
|
.with(object : AutocompleteCallback<Command> {
|
||||||
|
override fun onPopupItemClicked(editable: Editable?, item: Command?): Boolean {
|
||||||
|
editable?.clear()
|
||||||
|
editable
|
||||||
|
?.append(item?.command)
|
||||||
|
?.append(" ")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPopupVisibilityChanged(shown: Boolean) {
|
||||||
|
}
|
||||||
|
})
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
autocompleteUserPresenter.callback = this
|
autocompleteUserPresenter.callback = this
|
||||||
@ -145,6 +160,17 @@ class RoomDetailFragment : VectorBaseFragment(), TimelineEventController.Callbac
|
|||||||
.with(autocompleteUserPresenter)
|
.with(autocompleteUserPresenter)
|
||||||
.with(elevation)
|
.with(elevation)
|
||||||
.with(backgroundDrawable)
|
.with(backgroundDrawable)
|
||||||
|
.with(object : AutocompleteCallback<User> {
|
||||||
|
override fun onPopupItemClicked(editable: Editable?, item: User?): Boolean {
|
||||||
|
// TODO
|
||||||
|
editable?.append(item?.displayName)
|
||||||
|
?.append(" ")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPopupVisibilityChanged(shown: Boolean) {
|
||||||
|
}
|
||||||
|
})
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
sendButton.setOnClickListener {
|
sendButton.setOnClickListener {
|
||||||
|
Loading…
Reference in New Issue
Block a user