Unverified Commit 91763a21 authored by Filipe de Lima Brito's avatar Filipe de Lima Brito Committed by GitHub

Merge branch 'develop' into fix-random-crash-emoji-keyboard

parents a74e410c d4ced11a
...@@ -93,6 +93,7 @@ dependencies { ...@@ -93,6 +93,7 @@ dependencies {
implementation project(':draw') implementation project(':draw')
implementation project(':util') implementation project(':util')
implementation project(':core') implementation project(':core')
implementation project(':suggestions')
implementation libraries.kotlin implementation libraries.kotlin
implementation libraries.coroutines implementation libraries.coroutines
......
...@@ -65,7 +65,9 @@ abstract class BaseViewHolder<T : BaseUiModel<*>>( ...@@ -65,7 +65,9 @@ abstract class BaseViewHolder<T : BaseUiModel<*>>(
val manager = FlexboxLayoutManager(context, FlexDirection.ROW) val manager = FlexboxLayoutManager(context, FlexDirection.ROW)
recyclerView.layoutManager = manager recyclerView.layoutManager = manager
recyclerView.adapter = adapter recyclerView.adapter = adapter
adapter.addReactions(it.reactions.filterNot { it.unicode.startsWith(":") }) adapter.addReactions(it.reactions.filterNot { reactionUiModel ->
reactionUiModel.unicode.startsWith(":") && reactionUiModel.url.isNullOrEmpty()
})
} }
} }
} }
......
...@@ -7,9 +7,9 @@ import android.widget.TextView ...@@ -7,9 +7,9 @@ import android.widget.TextView
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatroom.adapter.CommandSuggestionsAdapter.CommandSuggestionsViewHolder import chat.rocket.android.chatroom.adapter.CommandSuggestionsAdapter.CommandSuggestionsViewHolder
import chat.rocket.android.chatroom.uimodel.suggestion.CommandSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.CommandSuggestionUiModel
import chat.rocket.android.widget.autocompletion.model.SuggestionModel import chat.rocket.android.suggestions.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.ui.BaseSuggestionViewHolder import chat.rocket.android.suggestions.ui.BaseSuggestionViewHolder
import chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter import chat.rocket.android.suggestions.ui.SuggestionsAdapter
class CommandSuggestionsAdapter : SuggestionsAdapter<CommandSuggestionsViewHolder>(token = "/", class CommandSuggestionsAdapter : SuggestionsAdapter<CommandSuggestionsViewHolder>(token = "/",
constraint = CONSTRAINT_BOUND_TO_START, threshold = RESULT_COUNT_UNLIMITED) { constraint = CONSTRAINT_BOUND_TO_START, threshold = RESULT_COUNT_UNLIMITED) {
......
...@@ -5,6 +5,7 @@ import android.view.View ...@@ -5,6 +5,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatroom.uimodel.ReactionUiModel import chat.rocket.android.chatroom.uimodel.ReactionUiModel
...@@ -13,15 +14,13 @@ import chat.rocket.android.emoji.Emoji ...@@ -13,15 +14,13 @@ import chat.rocket.android.emoji.Emoji
import chat.rocket.android.emoji.EmojiKeyboardListener import chat.rocket.android.emoji.EmojiKeyboardListener
import chat.rocket.android.emoji.EmojiPickerPopup import chat.rocket.android.emoji.EmojiPickerPopup
import chat.rocket.android.emoji.EmojiReactionListener import chat.rocket.android.emoji.EmojiReactionListener
import chat.rocket.android.emoji.internal.GlideApp
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import kotlinx.android.synthetic.main.item_reaction.view.*
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject import javax.inject.Inject
class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() { class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
companion object {
private const val REACTION_VIEW_TYPE = 0
private const val ADD_REACTION_VIEW_TYPE = 1
}
private val reactions = CopyOnWriteArrayList<ReactionUiModel>() private val reactions = CopyOnWriteArrayList<ReactionUiModel>()
var listener: EmojiReactionListener? = null var listener: EmojiReactionListener? = null
...@@ -74,9 +73,11 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() ...@@ -74,9 +73,11 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>()
fun contains(reactionShortname: String) = fun contains(reactionShortname: String) =
reactions.firstOrNull { it.shortname == reactionShortname } != null reactions.firstOrNull { it.shortname == reactionShortname } != null
class SingleReactionViewHolder(view: View, class SingleReactionViewHolder(
private val listener: EmojiReactionListener?) view: View,
: RecyclerView.ViewHolder(view), View.OnClickListener { private val listener: EmojiReactionListener?
) : RecyclerView.ViewHolder(view), View.OnClickListener {
@Inject @Inject
lateinit var localRepository: LocalRepository lateinit var localRepository: LocalRepository
@Volatile @Volatile
...@@ -95,23 +96,33 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() ...@@ -95,23 +96,33 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>()
clickHandled = false clickHandled = false
this.reaction = reaction this.reaction = reaction
with(itemView) { with(itemView) {
val emojiTextView = findViewById<TextView>(R.id.text_emoji) if (reaction.url.isNullOrEmpty()) {
val countTextView = findViewById<TextView>(R.id.text_count) text_emoji.text = reaction.unicode
emojiTextView.text = reaction.unicode view_flipper_reaction.displayedChild = 0
countTextView.text = reaction.count.toString() } else {
view_flipper_reaction.displayedChild = 1
val glideRequest = if (reaction.url!!.endsWith("gif", true)) {
GlideApp.with(context).asGif()
} else {
GlideApp.with(context).asBitmap()
}
glideRequest.load(reaction.url).into(image_emoji)
}
text_count.text = reaction.count.toString()
val myself = localRepository.get(LocalRepository.CURRENT_USERNAME_KEY) val myself = localRepository.get(LocalRepository.CURRENT_USERNAME_KEY)
if (reaction.usernames.contains(myself)) { if (reaction.usernames.contains(myself)) {
val context = itemView.context val context = itemView.context
val resources = context.resources text_count.setTextColor(ContextCompat.getColor(context, R.color.colorAccent))
countTextView.setTextColor(resources.getColor(R.color.colorAccent))
} }
emojiTextView.setOnClickListener(this@SingleReactionViewHolder) view_flipper_reaction.setOnClickListener(this@SingleReactionViewHolder)
countTextView.setOnClickListener(this@SingleReactionViewHolder) text_count.setOnClickListener(this@SingleReactionViewHolder)
} }
} }
override fun onClick(v: View?) { override fun onClick(v: View) {
synchronized(this) { synchronized(this) {
if (!clickHandled) { if (!clickHandled) {
clickHandled = true clickHandled = true
...@@ -121,8 +132,11 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() ...@@ -121,8 +132,11 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>()
} }
} }
class AddReactionViewHolder(view: View, class AddReactionViewHolder(
private val listener: EmojiReactionListener?) : RecyclerView.ViewHolder(view) { view: View,
private val listener: EmojiReactionListener?
) : RecyclerView.ViewHolder(view) {
fun bind(messageId: String) { fun bind(messageId: String) {
itemView as ImageView itemView as ImageView
itemView.setOnClickListener { itemView.setOnClickListener {
...@@ -136,4 +150,9 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() ...@@ -136,4 +150,9 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>()
} }
} }
} }
}
\ No newline at end of file companion object {
private const val REACTION_VIEW_TYPE = 0
private const val ADD_REACTION_VIEW_TYPE = 1
}
}
...@@ -11,9 +11,9 @@ import chat.rocket.android.R ...@@ -11,9 +11,9 @@ import chat.rocket.android.R
import chat.rocket.android.chatroom.adapter.PeopleSuggestionsAdapter.PeopleSuggestionViewHolder import chat.rocket.android.chatroom.adapter.PeopleSuggestionsAdapter.PeopleSuggestionViewHolder
import chat.rocket.android.chatroom.uimodel.suggestion.PeopleSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.PeopleSuggestionUiModel
import chat.rocket.android.util.extensions.setVisible import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.widget.autocompletion.model.SuggestionModel import chat.rocket.android.suggestions.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.ui.BaseSuggestionViewHolder import chat.rocket.android.suggestions.ui.BaseSuggestionViewHolder
import chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter import chat.rocket.android.suggestions.ui.SuggestionsAdapter
import com.facebook.drawee.view.SimpleDraweeView import com.facebook.drawee.view.SimpleDraweeView
class PeopleSuggestionsAdapter(context: Context) : SuggestionsAdapter<PeopleSuggestionViewHolder>("@") { class PeopleSuggestionsAdapter(context: Context) : SuggestionsAdapter<PeopleSuggestionViewHolder>("@") {
......
...@@ -7,9 +7,9 @@ import android.widget.TextView ...@@ -7,9 +7,9 @@ import android.widget.TextView
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatroom.adapter.RoomSuggestionsAdapter.RoomSuggestionsViewHolder import chat.rocket.android.chatroom.adapter.RoomSuggestionsAdapter.RoomSuggestionsViewHolder
import chat.rocket.android.chatroom.uimodel.suggestion.ChatRoomSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.ChatRoomSuggestionUiModel
import chat.rocket.android.widget.autocompletion.model.SuggestionModel import chat.rocket.android.suggestions.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.ui.BaseSuggestionViewHolder import chat.rocket.android.suggestions.ui.BaseSuggestionViewHolder
import chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter import chat.rocket.android.suggestions.ui.SuggestionsAdapter
class RoomSuggestionsAdapter : SuggestionsAdapter<RoomSuggestionsViewHolder>("#") { class RoomSuggestionsAdapter : SuggestionsAdapter<RoomSuggestionsViewHolder>("#") {
......
...@@ -5,5 +5,6 @@ data class ReactionUiModel( ...@@ -5,5 +5,6 @@ data class ReactionUiModel(
val shortname: String, val shortname: String,
val unicode: CharSequence, val unicode: CharSequence,
val count: Int, val count: Int,
val usernames: List<String> = emptyList() val usernames: List<String> = emptyList(),
var url: String? = null
) )
\ No newline at end of file
...@@ -18,6 +18,7 @@ import chat.rocket.android.chatroom.domain.MessageReply ...@@ -18,6 +18,7 @@ import chat.rocket.android.chatroom.domain.MessageReply
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.db.DatabaseManager import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.emoji.EmojiParser import chat.rocket.android.emoji.EmojiParser
import chat.rocket.android.emoji.EmojiRepository
import chat.rocket.android.helper.MessageHelper import chat.rocket.android.helper.MessageHelper
import chat.rocket.android.helper.MessageParser import chat.rocket.android.helper.MessageParser
import chat.rocket.android.helper.UserHelper import chat.rocket.android.helper.UserHelper
...@@ -504,15 +505,18 @@ class UiModelMapper @Inject constructor( ...@@ -504,15 +505,18 @@ class UiModelMapper @Inject constructor(
private fun getReactions(message: Message): List<ReactionUiModel> { private fun getReactions(message: Message): List<ReactionUiModel> {
val reactions = message.reactions?.let { val reactions = message.reactions?.let {
val list = mutableListOf<ReactionUiModel>() val list = mutableListOf<ReactionUiModel>()
val customEmojis = EmojiRepository.getCustomEmojis()
it.getShortNames().forEach { shortname -> it.getShortNames().forEach { shortname ->
val usernames = it.getUsernames(shortname) ?: emptyList() val usernames = it.getUsernames(shortname) ?: emptyList()
val count = usernames.size val count = usernames.size
val custom = customEmojis.firstOrNull { emoji -> emoji.shortname == shortname }
list.add( list.add(
ReactionUiModel(messageId = message.id, ReactionUiModel(messageId = message.id,
shortname = shortname, shortname = shortname,
unicode = EmojiParser.parse(context, shortname), unicode = EmojiParser.parse(context, shortname),
count = count, count = count,
usernames = usernames) usernames = usernames,
url = custom?.url)
) )
} }
list list
......
package chat.rocket.android.chatroom.uimodel.suggestion 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, class ChatRoomSuggestionUiModel(text: String,
val fullName: String, val fullName: String,
......
package chat.rocket.android.chatroom.uimodel.suggestion 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, class CommandSuggestionUiModel(text: String,
val description: String, val description: String,
......
package chat.rocket.android.chatroom.uimodel.suggestion 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 import chat.rocket.common.model.UserStatus
class PeopleSuggestionUiModel(val imageUri: String?, class PeopleSuggestionUiModel(val imageUri: String?,
......
...@@ -5,5 +5,5 @@ ...@@ -5,5 +5,5 @@
android:width="24dp" android:width="24dp"
android:height="24dp" /> android:height="24dp" />
<solid android:color="#efeeee" /> <solid android:color="#efeeee" />
<corners android:radius="4dp"/> <corners android:radius="4dp" />
</shape> </shape>
\ No newline at end of file
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" /> tools:visibility="visible" />
<chat.rocket.android.widget.autocompletion.ui.SuggestionsView <chat.rocket.android.suggestions.ui.SuggestionsView
android:id="@+id/suggestions_view" android:id="@+id/suggestions_view"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="2dp" android:layout_marginEnd="2dp"
android:layout_marginRight="2dp" android:background="@drawable/rounded_background">
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:descendantFocusability="beforeDescendants"
android:background="@drawable/rounded_background"
android:orientation="horizontal">
<TextView <ViewFlipper
android:id="@+id/text_emoji" android:id="@+id/view_flipper_reaction"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="end" app:layout_constraintBottom_toBottomOf="parent"
android:maxLines="1" app:layout_constraintEnd_toStartOf="@+id/text_count"
android:paddingLeft="4dp" app:layout_constraintStart_toStartOf="parent"
android:paddingStart="4dp" app:layout_constraintTop_toTopOf="parent">
android:textColor="#868585"
android:textSize="16sp" <TextView
tools:text=":)" /> android:id="@+id/text_emoji"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:paddingStart="4dp"
android:paddingLeft="4dp"
android:textColor="#868585"
android:textSize="16sp"
tools:text=":)" />
<ImageView
android:id="@+id/image_emoji"
android:layout_width="@dimen/custom_emoji_small"
android:layout_height="@dimen/custom_emoji_small"
android:layout_gravity="center"
tools:src="@tools:sample/avatars" />
</ViewFlipper>
<TextView <TextView
android:id="@+id/text_count" android:id="@+id/text_count"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:paddingBottom="4dp"
android:paddingEnd="4dp"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:paddingStart="4dp" android:paddingStart="4dp"
android:paddingLeft="4dp"
android:paddingTop="4dp" android:paddingTop="4dp"
android:paddingEnd="4dp"
android:paddingRight="4dp"
android:paddingBottom="4dp"
android:textColor="#868585" android:textColor="#868585"
android:textSize="16sp" android:textSize="16sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/view_flipper_reaction"
app:layout_constraintTop_toTopOf="parent"
tools:text="12" /> tools:text="12" />
</LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>
...@@ -41,10 +41,6 @@ ...@@ -41,10 +41,6 @@
<dimen name="padding_mention">4dp</dimen> <dimen name="padding_mention">4dp</dimen>
<dimen name="radius_mention">6dp</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_padding">16dp</dimen>
<dimen name="viewer_toolbar_title">16sp</dimen> <dimen name="viewer_toolbar_title">16sp</dimen>
......
...@@ -10,7 +10,7 @@ buildscript { ...@@ -10,7 +10,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.2.0-rc02' classpath 'com.android.tools.build:gradle:3.2.0-rc03'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}" classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}"
classpath 'com.google.gms:google-services:4.0.2' classpath 'com.google.gms:google-services:4.0.2'
......
...@@ -210,7 +210,7 @@ object EmojiRepository { ...@@ -210,7 +210,7 @@ object EmojiRepository {
} }
} }
internal fun getCustomEmojis(): List<Emoji> = customEmojis fun getCustomEmojis(): List<Emoji> = customEmojis
/** /**
* Get all recently used emojis ordered by usage count. * Get all recently used emojis ordered by usage count.
......
include ':app', ':player', ':emoji', ':draw', ':util', ':core' //, ':wear' include ':app', ':player', ':emoji', ':draw', ':util', ':core', ':suggestions' //, ':wear'
\ No newline at end of file \ 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. 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. 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 ...@@ -15,4 +15,4 @@ abstract class SuggestionModel(val text: String, // This is the text key for sea
override fun hashCode(): Int { override fun hashCode(): Int {
return text.hashCode() return text.hashCode()
} }
} }
\ No newline at end of file
package chat.rocket.android.widget.autocompletion.repository package chat.rocket.android.suggestions.repository
interface LocalSuggestionProvider { interface LocalSuggestionProvider {
fun find(prefix: String) 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 { interface CompletionStrategy {
fun getItem(prefix: String, position: Int): SuggestionModel fun getItem(prefix: String, position: Int): SuggestionModel
...@@ -8,4 +8,4 @@ interface CompletionStrategy { ...@@ -8,4 +8,4 @@ interface CompletionStrategy {
fun addAll(list: List<SuggestionModel>) fun addAll(list: List<SuggestionModel>)
fun addPinned(list: List<SuggestionModel>) fun addPinned(list: List<SuggestionModel>)
fun size(): Int 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.suggestions.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.strategy.CompletionStrategy import chat.rocket.android.suggestions.strategy.CompletionStrategy
import chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter.Companion.RESULT_COUNT_UNLIMITED import chat.rocket.android.suggestions.ui.SuggestionsAdapter.Companion.RESULT_COUNT_UNLIMITED
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
internal class StringMatchingCompletionStrategy(private val threshold: Int = RESULT_COUNT_UNLIMITED) : CompletionStrategy { internal class StringMatchingCompletionStrategy(private val threshold: Int = RESULT_COUNT_UNLIMITED) : CompletionStrategy {
...@@ -46,4 +46,4 @@ internal class StringMatchingCompletionStrategy(private val threshold: Int = RES ...@@ -46,4 +46,4 @@ internal class StringMatchingCompletionStrategy(private val threshold: Int = RES
override fun size(): Int { override fun size(): Int {
return list.size 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.suggestions.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.strategy.CompletionStrategy import chat.rocket.android.suggestions.strategy.CompletionStrategy
import chat.rocket.android.widget.autocompletion.strategy.trie.data.Trie import chat.rocket.android.suggestions.strategy.trie.data.Trie
class TrieCompletionStrategy : CompletionStrategy { class TrieCompletionStrategy : CompletionStrategy {
private val items = mutableListOf<SuggestionModel>() private val items = mutableListOf<SuggestionModel>()
...@@ -28,8 +28,7 @@ class TrieCompletionStrategy : CompletionStrategy { ...@@ -28,8 +28,7 @@ class TrieCompletionStrategy : CompletionStrategy {
} }
override fun addPinned(list: List<SuggestionModel>) { override fun addPinned(list: List<SuggestionModel>) {
} }
override fun size() = items.size 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 { internal class Trie {
private val root = TrieNode(' ') private val root = TrieNode(' ')
...@@ -67,4 +67,4 @@ internal class Trie { ...@@ -67,4 +67,4 @@ internal class Trie {
} }
fun getCount() = count 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 class TrieNode(internal var data: Char,
internal var parent: TrieNode? = null, internal var parent: TrieNode? = null,
...@@ -44,4 +44,4 @@ internal class TrieNode(internal var data: Char, ...@@ -44,4 +44,4 @@ internal class TrieNode(internal var data: Char,
} }
override fun toString(): String = if (parent == null) "" else "${parent.toString()}$data" 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 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 class BaseSuggestionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
abstract fun bind(item: SuggestionModel, itemClickListener: SuggestionsAdapter.ItemClickListener?) 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.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.WindowManager import android.view.WindowManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.R import chat.rocket.android.suggestions.R
internal class PopupRecyclerView : RecyclerView { internal class PopupRecyclerView : RecyclerView {
private var displayWidth: Int = 0 private var displayWidth: Int = 0
...@@ -38,4 +38,4 @@ internal class PopupRecyclerView : RecyclerView { ...@@ -38,4 +38,4 @@ internal class PopupRecyclerView : RecyclerView {
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
super.onLayout(changed, l + 40, t, r - 40, b) 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 androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.widget.autocompletion.model.SuggestionModel import chat.rocket.android.suggestions.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.strategy.CompletionStrategy import chat.rocket.android.suggestions.strategy.CompletionStrategy
import chat.rocket.android.widget.autocompletion.strategy.regex.StringMatchingCompletionStrategy import chat.rocket.android.suggestions.strategy.regex.StringMatchingCompletionStrategy
import java.lang.reflect.Type import java.lang.reflect.Type
import kotlin.properties.Delegates import kotlin.properties.Delegates
...@@ -32,10 +32,10 @@ abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>( ...@@ -32,10 +32,10 @@ abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>(
// The strategy used for suggesting completions. // The strategy used for suggesting completions.
private val strategy: CompletionStrategy = StringMatchingCompletionStrategy(resultsThreshold) private val strategy: CompletionStrategy = StringMatchingCompletionStrategy(resultsThreshold)
// Current input term to look up for suggestions. // 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) val items = strategy.autocompleteItems(newTerm)
notifyDataSetChanged() notifyDataSetChanged()
}) }
init { init {
setHasStableIds(true) setHasStableIds(true)
......
package chat.rocket.android.widget.autocompletion.ui package chat.rocket.android.suggestions.ui
import android.content.Context import android.content.Context
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Rect import android.graphics.Rect
import android.graphics.drawable.Drawable 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.Editable
import android.text.InputType import android.text.InputType
import android.text.TextWatcher import android.text.TextWatcher
import android.transition.Slide
import android.transition.TransitionManager
import android.util.AttributeSet import android.util.AttributeSet
import android.view.Gravity import android.view.Gravity
import android.view.View import android.view.View
import android.widget.EditText import android.widget.EditText
import android.widget.FrameLayout import android.widget.FrameLayout
import chat.rocket.android.R import androidx.annotation.DrawableRes
import chat.rocket.android.widget.autocompletion.model.SuggestionModel import androidx.core.content.ContextCompat
import chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter.Companion.CONSTRAINT_BOUND_TO_START 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.lang.ref.WeakReference
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
...@@ -103,7 +103,8 @@ class SuggestionsView : FrameLayout, TextWatcher { ...@@ -103,7 +103,8 @@ class SuggestionsView : FrameLayout, TextWatcher {
val prefixEndIndex = this.editor?.get()?.selectionStart ?: NO_STATE_INDEX val prefixEndIndex = this.editor?.get()?.selectionStart ?: NO_STATE_INDEX
if (prefixEndIndex == NO_STATE_INDEX || prefixEndIndex < completionOffset.get()) return 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 { recyclerView.adapter?.let {
it as SuggestionsAdapter it as SuggestionsAdapter
// we need to look up only after the '@' // we need to look up only after the '@'
...@@ -156,7 +157,7 @@ class SuggestionsView : FrameLayout, TextWatcher { ...@@ -156,7 +157,7 @@ class SuggestionsView : FrameLayout, TextWatcher {
if (list.isNotEmpty()) { if (list.isNotEmpty()) {
val adapter = adapter(token) val adapter = adapter(token)
localProvidersByToken.getOrPut(token, { hashMapOf() }) localProvidersByToken.getOrPut(token, { hashMapOf() })
.put(adapter.term(), list) .put(adapter.term(), list)
if (completionOffset.get() > NO_STATE_INDEX && adapter.itemCount == 0) expand() if (completionOffset.get() > NO_STATE_INDEX && adapter.itemCount == 0) expand()
adapter.addItems(list) adapter.addItems(list)
} }
...@@ -192,7 +193,8 @@ class SuggestionsView : FrameLayout, TextWatcher { ...@@ -192,7 +193,8 @@ class SuggestionsView : FrameLayout, TextWatcher {
} }
private fun adapter(token: String): SuggestionsAdapter<*> { 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) { private fun cancelSuggestions(haltCompletion: Boolean) {
......
...@@ -8,4 +8,4 @@ ...@@ -8,4 +8,4 @@
<size android:height="2dp" /> <size android:height="2dp" />
</shape> </shape>
\ No newline at end of file
<?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