diff --git a/vector/build.gradle b/vector/build.gradle index 8bb46c06..bb99d9ac 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -171,6 +171,8 @@ dependencies { implementation "ru.noties.markwon:core:$markwon_version" implementation "ru.noties.markwon:html:$markwon_version" + implementation 'com.otaliastudios:autocomplete:1.1.0' + // Butterknife implementation 'com.jakewharton:butterknife:10.1.0' kapt 'com.jakewharton:butterknife-compiler:10.1.0' diff --git a/vector/src/main/java/im/vector/riotredesign/features/autocomplete/EpoxyViewPresenter.kt b/vector/src/main/java/im/vector/riotredesign/features/autocomplete/EpoxyViewPresenter.kt new file mode 100644 index 00000000..f7c64c0a --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/autocomplete/EpoxyViewPresenter.kt @@ -0,0 +1,99 @@ +/* + * 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 + +import android.content.Context +import android.database.DataSetObserver +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 + +abstract class EpoxyViewPresenter(context: Context) : AutocompletePresenter(context) { + + private var recyclerView: EpoxyRecyclerView? = null + private var clicks: AutocompletePresenter.ClickProvider? = null + private var observer: Observer? = null + + override fun registerClickProvider(provider: AutocompletePresenter.ClickProvider) { + this.clicks = provider + } + + override fun registerDataSetObserver(observer: DataSetObserver) { + this.observer = Observer(observer) + } + + override fun getView(): ViewGroup? { + recyclerView = EpoxyRecyclerView(context).apply { + setController(providesController()) + observer?.let { + adapter?.registerAdapterDataObserver(it) + } + itemAnimator = null + } + return recyclerView + } + + override fun onViewShown() {} + + + override fun onViewHidden() { + recyclerView = null + observer = null + } + + 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() + } + + + private class Observer internal constructor(private val root: DataSetObserver) : RecyclerView.AdapterDataObserver() { + + override fun onChanged() { + root.onChanged() + } + + override fun onItemRangeChanged(positionStart: Int, itemCount: Int) { + root.onChanged() + } + + override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) { + root.onChanged() + } + + override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { + root.onChanged() + } + + override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { + root.onChanged() + } + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/autocomplete/command/AutocompleteCommandController.kt b/vector/src/main/java/im/vector/riotredesign/features/autocomplete/command/AutocompleteCommandController.kt new file mode 100644 index 00000000..dbd6a144 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/autocomplete/command/AutocompleteCommandController.kt @@ -0,0 +1,37 @@ +/* + * 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 com.airbnb.epoxy.TypedEpoxyController +import im.vector.riotredesign.core.resources.StringProvider + +class AutocompleteCommandController(private val stringProvider: StringProvider) : TypedEpoxyController>() { + + override fun buildModels(data: List?) { + if (data.isNullOrEmpty()) { + return + } + data.forEach { + autocompleteCommandItem { + id(it.command) + name(it.command) + parameters(it.parameters) + description(stringProvider.getString(it.description)) + } + } + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/autocomplete/command/AutocompleteCommandItem.kt b/vector/src/main/java/im/vector/riotredesign/features/autocomplete/command/AutocompleteCommandItem.kt new file mode 100644 index 00000000..fa1788ad --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/autocomplete/command/AutocompleteCommandItem.kt @@ -0,0 +1,48 @@ +/* + * 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.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.riotredesign.R +import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder +import im.vector.riotredesign.core.epoxy.VectorEpoxyModel + +@EpoxyModelClass(layout = R.layout.item_command_autocomplete) +abstract class AutocompleteCommandItem : VectorEpoxyModel() { + + @EpoxyAttribute + var name: CharSequence? = null + @EpoxyAttribute + var parameters: CharSequence? = null + @EpoxyAttribute + var description: CharSequence? = null + + override fun bind(holder: Holder) { + holder.nameView.text = name + holder.parametersView.text = parameters + holder.descriptionView.text = description + } + + class Holder : VectorEpoxyHolder() { + val nameView by bind(R.id.commandName) + val parametersView by bind(R.id.commandParameter) + val descriptionView by bind(R.id.commandDescription) + } + +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/autocomplete/command/AutocompleteCommandPresenter.kt b/vector/src/main/java/im/vector/riotredesign/features/autocomplete/command/AutocompleteCommandPresenter.kt new file mode 100644 index 00000000..d0b10eb6 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/autocomplete/command/AutocompleteCommandPresenter.kt @@ -0,0 +1,41 @@ +/* + * 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.content.Context +import com.airbnb.epoxy.EpoxyController +import im.vector.riotredesign.features.autocomplete.EpoxyViewPresenter + +class AutocompleteCommandPresenter(context: Context, + private val controller: AutocompleteCommandController +) : EpoxyViewPresenter(context) { + + override fun providesController(): EpoxyController { + return controller + } + + override fun onQuery(query: CharSequence?) { + val data = Command.values().filter { + if (query.isNullOrEmpty()) { + true + } else { + it.command.startsWith(query, 1, true) + } + } + controller.setData(data) + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/autocomplete/command/Command.kt b/vector/src/main/java/im/vector/riotredesign/features/autocomplete/command/Command.kt new file mode 100644 index 00000000..b3d95fd9 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/autocomplete/command/Command.kt @@ -0,0 +1,36 @@ +/* + * 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 androidx.annotation.StringRes +import im.vector.riotredesign.R + +enum class Command(val command: String, val parameters: String, @StringRes val description: Int) { + EMOTE("/me", "", R.string.command_description_emote), + BAN_USER("/ban", " [reason]", R.string.command_description_ban_user), + UNBAN_USER("/unban", "", R.string.command_description_unban_user), + SET_USER_POWER_LEVEL("/op", " []", R.string.command_description_op_user), + RESET_USER_POWER_LEVEL("/deop", "", R.string.command_description_deop_user), + INVITE("/invite", "", R.string.command_description_invite_user), + JOIN_ROOM("/join", "", R.string.command_description_join_room), + PART("/part", "", R.string.command_description_part_room), + TOPIC("/topic", "", R.string.command_description_topic), + KICK_USER("/kick", " [reason]", R.string.command_description_kick_user), + CHANGE_DISPLAY_NAME("/nick", "", R.string.command_description_nick), + MARKDOWN("/markdown", "", R.string.command_description_markdown), + CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token); +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt index a013a0dc..03679345 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt @@ -18,6 +18,8 @@ package im.vector.riotredesign.features.home import androidx.fragment.app.Fragment import im.vector.riotredesign.core.glide.GlideApp +import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandController +import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandPresenter import im.vector.riotredesign.features.home.group.GroupSummaryController import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController import im.vector.riotredesign.features.home.room.detail.timeline.factory.* @@ -75,6 +77,9 @@ class HomeModule { GroupSummaryController() } - + scope(ROOM_DETAIL_SCOPE) { (fragment: Fragment) -> + val commandController = AutocompleteCommandController(get()) + AutocompleteCommandPresenter(fragment.requireContext(), commandController) + } } } \ No newline at end of file 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 98e3164f..54930799 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 @@ -16,6 +16,8 @@ package im.vector.riotredesign.features.home.room.detail +import android.graphics.Color +import android.graphics.drawable.ColorDrawable import android.os.Bundle import android.os.Parcelable import android.view.View @@ -23,11 +25,15 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.airbnb.epoxy.EpoxyVisibilityTracker import com.airbnb.mvrx.fragmentViewModel +import com.otaliastudios.autocomplete.Autocomplete +import com.otaliastudios.autocomplete.CharPolicy import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.riotredesign.R import im.vector.riotredesign.core.epoxy.LayoutManagerStateRestorer 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.Command import im.vector.riotredesign.features.home.AvatarRenderer import im.vector.riotredesign.features.home.HomeModule import im.vector.riotredesign.features.home.HomePermalinkHandler @@ -63,6 +69,7 @@ class RoomDetailFragment : VectorBaseFragment(), TimelineEventController.Callbac private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel() private val timelineEventController: TimelineEventController by inject { parametersOf(this) } + private val autocompleteCommandPresenter: AutocompleteCommandPresenter by inject { parametersOf(this) } private val homePermalinkHandler: HomePermalinkHandler by inject() private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback @@ -74,7 +81,7 @@ class RoomDetailFragment : VectorBaseFragment(), TimelineEventController.Callbac bindScope(getOrCreateScope(HomeModule.ROOM_DETAIL_SCOPE)) setupRecyclerView() setupToolbar() - setupSendButton() + setupComposer() roomDetailViewModel.subscribe { renderState(it) } } @@ -114,7 +121,16 @@ class RoomDetailFragment : VectorBaseFragment(), TimelineEventController.Callbac timelineEventController.callback = this } - private fun setupSendButton() { + private fun setupComposer() { + val elevation = 6f + val backgroundDrawable = ColorDrawable(Color.WHITE) + Autocomplete.on(composerEditText) + .with(CharPolicy('/', false)) + .with(autocompleteCommandPresenter) + .with(elevation) + .with(backgroundDrawable) + .build() + sendButton.setOnClickListener { val textMessage = composerEditText.text.toString() if (textMessage.isNotBlank()) { diff --git a/vector/src/main/res/layout/item_command_autocomplete.xml b/vector/src/main/res/layout/item_command_autocomplete.xml new file mode 100644 index 00000000..3800443b --- /dev/null +++ b/vector/src/main/res/layout/item_command_autocomplete.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + \ No newline at end of file