Commit 88711265 authored by Leonardo Aramaki's avatar Leonardo Aramaki

Modularize all suggestions autocompletion code

parent 93d43d3b
......@@ -93,6 +93,7 @@ dependencies {
implementation project(':draw')
implementation project(':util')
implementation project(':core')
implementation project(':suggestions')
implementation libraries.kotlin
implementation libraries.coroutines
......
......@@ -7,9 +7,9 @@ import android.widget.TextView
import chat.rocket.android.R
import chat.rocket.android.chatroom.adapter.CommandSuggestionsAdapter.CommandSuggestionsViewHolder
import chat.rocket.android.chatroom.uimodel.suggestion.CommandSuggestionUiModel
import chat.rocket.android.widget.autocompletion.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.ui.BaseSuggestionViewHolder
import chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter
import chat.rocket.android.suggestions.model.SuggestionModel
import chat.rocket.android.suggestions.ui.BaseSuggestionViewHolder
import chat.rocket.android.suggestions.ui.SuggestionsAdapter
class CommandSuggestionsAdapter : SuggestionsAdapter<CommandSuggestionsViewHolder>(token = "/",
constraint = CONSTRAINT_BOUND_TO_START, threshold = RESULT_COUNT_UNLIMITED) {
......
......@@ -11,9 +11,9 @@ import chat.rocket.android.R
import chat.rocket.android.chatroom.adapter.PeopleSuggestionsAdapter.PeopleSuggestionViewHolder
import chat.rocket.android.chatroom.uimodel.suggestion.PeopleSuggestionUiModel
import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.widget.autocompletion.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.ui.BaseSuggestionViewHolder
import chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter
import chat.rocket.android.suggestions.model.SuggestionModel
import chat.rocket.android.suggestions.ui.BaseSuggestionViewHolder
import chat.rocket.android.suggestions.ui.SuggestionsAdapter
import com.facebook.drawee.view.SimpleDraweeView
class PeopleSuggestionsAdapter(context: Context) : SuggestionsAdapter<PeopleSuggestionViewHolder>("@") {
......
......@@ -7,9 +7,9 @@ import android.widget.TextView
import chat.rocket.android.R
import chat.rocket.android.chatroom.adapter.RoomSuggestionsAdapter.RoomSuggestionsViewHolder
import chat.rocket.android.chatroom.uimodel.suggestion.ChatRoomSuggestionUiModel
import chat.rocket.android.widget.autocompletion.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.ui.BaseSuggestionViewHolder
import chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter
import chat.rocket.android.suggestions.model.SuggestionModel
import chat.rocket.android.suggestions.ui.BaseSuggestionViewHolder
import chat.rocket.android.suggestions.ui.SuggestionsAdapter
class RoomSuggestionsAdapter : SuggestionsAdapter<RoomSuggestionsViewHolder>("#") {
......
package chat.rocket.android.chatroom.uimodel.suggestion
import chat.rocket.android.widget.autocompletion.model.SuggestionModel
import chat.rocket.android.suggestions.model.SuggestionModel
class ChatRoomSuggestionUiModel(text: String,
val fullName: String,
......
package chat.rocket.android.chatroom.uimodel.suggestion
import chat.rocket.android.widget.autocompletion.model.SuggestionModel
import chat.rocket.android.suggestions.model.SuggestionModel
class CommandSuggestionUiModel(text: String,
val description: String,
......
package chat.rocket.android.chatroom.uimodel.suggestion
import chat.rocket.android.widget.autocompletion.model.SuggestionModel
import chat.rocket.android.suggestions.model.SuggestionModel
import chat.rocket.common.model.UserStatus
class PeopleSuggestionUiModel(val imageUri: String?,
......
......@@ -74,7 +74,7 @@
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<chat.rocket.android.widget.autocompletion.ui.SuggestionsView
<chat.rocket.android.suggestions.ui.SuggestionsView
android:id="@+id/suggestions_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
......
......@@ -41,10 +41,6 @@
<dimen name="padding_mention">4dp</dimen>
<dimen name="radius_mention">6dp</dimen>
<!-- Autocomplete Popup -->
<dimen name="popup_max_height">150dp</dimen>
<dimen name="suggestions_box_max_height">250dp</dimen>
<dimen name="viewer_toolbar_padding">16dp</dimen>
<dimen name="viewer_toolbar_title">16sp</dimen>
......
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.0.0'
apply from: rootProject.file('dependencies.gradle')
repositories {
......
include ':app', ':player', ':emoji', ':draw', ':util', ':core' //, ':wear'
\ No newline at end of file
include ':app', ':player', ':emoji', ':draw', ':util', ':core', ':suggestions' //, ':wear'
\ No newline at end of file
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion versions.compileSdk
buildToolsVersion versions.buildTools
defaultConfig {
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
versionCode 1
versionName "1.0.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation libraries.kotlin
implementation libraries.recyclerview
implementation libraries.appCompat
implementation libraries.material
}
androidExtensions {
experimental = true
}
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
package yampsample.leonardoaramaki.github.com.suggestions;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("yampsample.leonardoaramaki.github.com.suggestions.test", appContext.getPackageName());
}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="chat.rocket.android.suggestions" />
package chat.rocket.android.widget.autocompletion.model
package chat.rocket.android.suggestions.model
abstract class SuggestionModel(val text: String, // This is the text key for searches, must be unique.
val searchList: List<String> = emptyList(), // Where to search for matches.
......@@ -15,4 +15,4 @@ abstract class SuggestionModel(val text: String, // This is the text key for sea
override fun hashCode(): Int {
return text.hashCode()
}
}
\ No newline at end of file
}
package chat.rocket.android.widget.autocompletion.repository
package chat.rocket.android.suggestions.repository
interface LocalSuggestionProvider {
fun find(prefix: String)
......
package chat.rocket.android.widget.autocompletion.strategy
package chat.rocket.android.suggestions.strategy
import chat.rocket.android.widget.autocompletion.model.SuggestionModel
import chat.rocket.android.suggestions.model.SuggestionModel
interface CompletionStrategy {
fun getItem(prefix: String, position: Int): SuggestionModel
......@@ -8,4 +8,4 @@ interface CompletionStrategy {
fun addAll(list: List<SuggestionModel>)
fun addPinned(list: List<SuggestionModel>)
fun size(): Int
}
\ No newline at end of file
}
package chat.rocket.android.widget.autocompletion.strategy.regex
package chat.rocket.android.suggestions.strategy.regex
import chat.rocket.android.widget.autocompletion.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.strategy.CompletionStrategy
import chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter.Companion.RESULT_COUNT_UNLIMITED
import chat.rocket.android.suggestions.model.SuggestionModel
import chat.rocket.android.suggestions.strategy.CompletionStrategy
import chat.rocket.android.suggestions.ui.SuggestionsAdapter.Companion.RESULT_COUNT_UNLIMITED
import java.util.concurrent.CopyOnWriteArrayList
internal class StringMatchingCompletionStrategy(private val threshold: Int = RESULT_COUNT_UNLIMITED) : CompletionStrategy {
......@@ -46,4 +46,4 @@ internal class StringMatchingCompletionStrategy(private val threshold: Int = RES
override fun size(): Int {
return list.size
}
}
\ No newline at end of file
}
package chat.rocket.android.widget.autocompletion.strategy.trie
package chat.rocket.android.suggestions.strategy.trie
import chat.rocket.android.widget.autocompletion.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.strategy.CompletionStrategy
import chat.rocket.android.widget.autocompletion.strategy.trie.data.Trie
import chat.rocket.android.suggestions.model.SuggestionModel
import chat.rocket.android.suggestions.strategy.CompletionStrategy
import chat.rocket.android.suggestions.strategy.trie.data.Trie
class TrieCompletionStrategy : CompletionStrategy {
private val items = mutableListOf<SuggestionModel>()
......@@ -28,8 +28,7 @@ class TrieCompletionStrategy : CompletionStrategy {
}
override fun addPinned(list: List<SuggestionModel>) {
}
override fun size() = items.size
}
\ No newline at end of file
}
package chat.rocket.android.widget.autocompletion.strategy.trie.data
package chat.rocket.android.suggestions.strategy.trie.data
import chat.rocket.android.widget.autocompletion.model.SuggestionModel
import chat.rocket.android.suggestions.model.SuggestionModel
internal class Trie {
private val root = TrieNode(' ')
......@@ -67,4 +67,4 @@ internal class Trie {
}
fun getCount() = count
}
\ No newline at end of file
}
package chat.rocket.android.widget.autocompletion.strategy.trie.data
package chat.rocket.android.suggestions.strategy.trie.data
import chat.rocket.android.widget.autocompletion.model.SuggestionModel
import chat.rocket.android.suggestions.model.SuggestionModel
internal class TrieNode(internal var data: Char,
internal var parent: TrieNode? = null,
......@@ -44,4 +44,4 @@ internal class TrieNode(internal var data: Char,
}
override fun toString(): String = if (parent == null) "" else "${parent.toString()}$data"
}
\ No newline at end of file
}
package chat.rocket.android.widget.autocompletion.ui
package chat.rocket.android.suggestions.ui
import androidx.recyclerview.widget.RecyclerView
import android.view.View
import chat.rocket.android.widget.autocompletion.model.SuggestionModel
import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.suggestions.model.SuggestionModel
abstract class BaseSuggestionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
abstract fun bind(item: SuggestionModel, itemClickListener: SuggestionsAdapter.ItemClickListener?)
}
\ No newline at end of file
}
package chat.rocket.android.widget.autocompletion.ui
package chat.rocket.android.suggestions.ui
import android.content.Context
import android.util.AttributeSet
import android.util.DisplayMetrics
import android.view.WindowManager
import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.R
import chat.rocket.android.suggestions.R
internal class PopupRecyclerView : RecyclerView {
private var displayWidth: Int = 0
......@@ -38,4 +38,4 @@ internal class PopupRecyclerView : RecyclerView {
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
super.onLayout(changed, l + 40, t, r - 40, b)
}
}
\ No newline at end of file
}
package chat.rocket.android.widget.autocompletion.ui
package chat.rocket.android.suggestions.ui
import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.widget.autocompletion.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.strategy.CompletionStrategy
import chat.rocket.android.widget.autocompletion.strategy.regex.StringMatchingCompletionStrategy
import chat.rocket.android.suggestions.model.SuggestionModel
import chat.rocket.android.suggestions.strategy.CompletionStrategy
import chat.rocket.android.suggestions.strategy.regex.StringMatchingCompletionStrategy
import java.lang.reflect.Type
import kotlin.properties.Delegates
......@@ -32,10 +32,10 @@ abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>(
// The strategy used for suggesting completions.
private val strategy: CompletionStrategy = StringMatchingCompletionStrategy(resultsThreshold)
// Current input term to look up for suggestions.
private var currentTerm: String by Delegates.observable("", { _, _, newTerm ->
private var currentTerm: String by Delegates.observable("") { _, _, newTerm ->
val items = strategy.autocompleteItems(newTerm)
notifyDataSetChanged()
})
}
init {
setHasStableIds(true)
......
package chat.rocket.android.widget.autocompletion.ui
package chat.rocket.android.suggestions.ui
import android.content.Context
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.Drawable
import androidx.annotation.DrawableRes
import androidx.transition.Slide
import androidx.transition.TransitionManager
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.text.Editable
import android.text.InputType
import android.text.TextWatcher
import android.transition.Slide
import android.transition.TransitionManager
import android.util.AttributeSet
import android.view.Gravity
import android.view.View
import android.widget.EditText
import android.widget.FrameLayout
import chat.rocket.android.R
import chat.rocket.android.widget.autocompletion.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter.Companion.CONSTRAINT_BOUND_TO_START
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.suggestions.R
import chat.rocket.android.suggestions.model.SuggestionModel
import chat.rocket.android.suggestions.ui.SuggestionsAdapter.Companion.CONSTRAINT_BOUND_TO_START
import java.lang.ref.WeakReference
import java.util.concurrent.atomic.AtomicInteger
......@@ -103,7 +103,8 @@ class SuggestionsView : FrameLayout, TextWatcher {
val prefixEndIndex = this.editor?.get()?.selectionStart ?: NO_STATE_INDEX
if (prefixEndIndex == NO_STATE_INDEX || prefixEndIndex < completionOffset.get()) return
val prefix = s.subSequence(completionOffset.get(), this.editor?.get()?.selectionStart ?: completionOffset.get()).toString()
val prefix = s.subSequence(completionOffset.get(), this.editor?.get()?.selectionStart
?: completionOffset.get()).toString()
recyclerView.adapter?.let {
it as SuggestionsAdapter
// we need to look up only after the '@'
......@@ -156,7 +157,7 @@ class SuggestionsView : FrameLayout, TextWatcher {
if (list.isNotEmpty()) {
val adapter = adapter(token)
localProvidersByToken.getOrPut(token, { hashMapOf() })
.put(adapter.term(), list)
.put(adapter.term(), list)
if (completionOffset.get() > NO_STATE_INDEX && adapter.itemCount == 0) expand()
adapter.addItems(list)
}
......@@ -192,7 +193,8 @@ class SuggestionsView : FrameLayout, TextWatcher {
}
private fun adapter(token: String): SuggestionsAdapter<*> {
return adaptersByToken[token] ?: throw IllegalStateException("no adapter binds to token \"$token\"")
return adaptersByToken[token]
?: throw IllegalStateException("no adapter binds to token \"$token\"")
}
private fun cancelSuggestions(haltCompletion: Boolean) {
......
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="popup_max_height">150dp</dimen>
<dimen name="suggestions_box_max_height">250dp</dimen>
</resources>
\ No newline at end of file
<resources>
<string name="app_name">suggestions</string>
</resources>
package yampsample.leonardoaramaki.github.com.suggestions;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment