Unverified Commit a8de7cfd authored by Lucio Maciel's avatar Lucio Maciel Committed by GitHub

Merge branch 'develop' into fix-markdown-mentions-and-emphasis

parents 663b28e8 8ddbd933
package chat.rocket.android.chatroom.adapter
import android.annotation.SuppressLint
import android.text.SpannableStringBuilder
import android.text.style.ImageSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import chat.rocket.android.R
import chat.rocket.android.chatroom.adapter.EmojiSuggestionsAdapter.EmojiSuggestionViewHolder
import chat.rocket.android.chatroom.uimodel.suggestion.EmojiSuggestionUiModel
import chat.rocket.android.emoji.EmojiParser
import chat.rocket.android.emoji.internal.isCustom
import chat.rocket.android.suggestions.model.SuggestionModel
import chat.rocket.android.suggestions.strategy.trie.TrieCompletionStrategy
import chat.rocket.android.suggestions.ui.BaseSuggestionViewHolder
import chat.rocket.android.suggestions.ui.SuggestionsAdapter
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.suggestion_emoji_item.view.*
class EmojiSuggestionsAdapter : SuggestionsAdapter<EmojiSuggestionViewHolder>(
token = ":",
completionStrategy = TrieCompletionStrategy()
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EmojiSuggestionViewHolder {
val inflater = LayoutInflater.from(parent.context)
val view = inflater.inflate(R.layout.suggestion_emoji_item, parent,false)
return EmojiSuggestionViewHolder(view)
}
class EmojiSuggestionViewHolder(view: View) : BaseSuggestionViewHolder(view) {
@SuppressLint("SetTextI18n")
override fun bind(item: SuggestionModel, itemClickListener: SuggestionsAdapter.ItemClickListener?) {
item as EmojiSuggestionUiModel
with(itemView) {
text_emoji_shortname.text = ":${item.text}"
if (item.emoji.isCustom()) {
view_flipper_emoji.displayedChild = 1
val sp = SpannableStringBuilder().append(item.emoji.shortname)
Glide.with(context).asDrawable().load(item.emoji.url).into(image_emoji)
} else {
text_emoji.text = EmojiParser.parse(context, item.emoji.unicode)
view_flipper_emoji.displayedChild = 0
}
setOnClickListener {
itemClickListener?.onClick(item)
}
}
}
}
}
package chat.rocket.android.chatroom.adapter package chat.rocket.android.chatroom.adapter
import android.animation.ValueAnimator
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.view.View import android.view.View
import android.view.animation.LinearInterpolator
import androidx.core.view.isVisible
import chat.rocket.android.R
import chat.rocket.android.chatroom.uimodel.MessageAttachmentUiModel import chat.rocket.android.chatroom.uimodel.MessageAttachmentUiModel
import chat.rocket.android.emoji.EmojiReactionListener import chat.rocket.android.emoji.EmojiReactionListener
import kotlinx.android.synthetic.main.item_message_attachment.view.* import kotlinx.android.synthetic.main.item_message_attachment.view.*
class MessageAttachmentViewHolder( class MessageAttachmentViewHolder(
itemView: View, itemView: View,
listener: ActionsListener, listener: ActionsListener,
reactionListener: EmojiReactionListener? = null reactionListener: EmojiReactionListener? = null
) : BaseViewHolder<MessageAttachmentUiModel>(itemView, listener, reactionListener) { ) : BaseViewHolder<MessageAttachmentUiModel>(itemView, listener, reactionListener) {
private var expanded = true
init { init {
with(itemView) { with(itemView) {
setupActionMenu(attachment_container) setupActionMenu(attachment_container)
...@@ -21,9 +27,77 @@ class MessageAttachmentViewHolder( ...@@ -21,9 +27,77 @@ class MessageAttachmentViewHolder(
override fun bindViews(data: MessageAttachmentUiModel) { override fun bindViews(data: MessageAttachmentUiModel) {
with(itemView) { with(itemView) {
val collapsedHeight = context.resources.getDimensionPixelSize(R.dimen.quote_collapsed_height)
val viewMore = context.getString(R.string.msg_view_more)
val viewLess = context.getString(R.string.msg_view_less)
text_message_time.text = data.time text_message_time.text = data.time
text_sender.text = data.senderName text_sender.text = data.senderName
text_content.text = data.content text_content.text = data.content
text_view_more.text = viewLess
text_content.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
override fun onLayoutChange(v: View, left: Int, top: Int, right: Int, bottom: Int,
oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) {
val textMeasuredHeight = bottom - top
if (collapsedHeight >= textMeasuredHeight) {
text_view_more.isVisible = false
text_content.removeOnLayoutChangeListener(this)
return
}
text_view_more.isVisible = true
val expandAnimation = ValueAnimator
.ofInt(collapsedHeight, textMeasuredHeight)
.setDuration(300)
expandAnimation.interpolator = LinearInterpolator()
val collapseAnimation = ValueAnimator
.ofInt(textMeasuredHeight, collapsedHeight)
.setDuration(300)
collapseAnimation.interpolator = LinearInterpolator()
val lp = text_content.layoutParams
expandAnimation.addUpdateListener {
val value = it.animatedValue as Int
lp.height = value
text_content.layoutParams = lp
expanded = if (value == textMeasuredHeight) {
text_view_more.text = viewLess
true
} else {
text_view_more.text = viewMore
false
}
}
collapseAnimation.addUpdateListener {
val value = it.animatedValue as Int
lp.height = value
text_content.layoutParams = lp
expanded = if (value == textMeasuredHeight) {
text_view_more.text = viewLess
true
} else {
text_view_more.text = viewMore
false
}
}
text_view_more.setOnClickListener {
if (expandAnimation.isRunning) return@setOnClickListener
if (expanded) {
collapseAnimation.start()
} else {
expandAnimation.start()
}
}
text_content.removeOnLayoutChangeListener(this)
}
})
} }
} }
} }
\ No newline at end of file
...@@ -13,9 +13,7 @@ class MessageReplyViewHolder( ...@@ -13,9 +13,7 @@ class MessageReplyViewHolder(
) : BaseViewHolder<MessageReplyUiModel>(itemView, listener, reactionListener) { ) : BaseViewHolder<MessageReplyUiModel>(itemView, listener, reactionListener) {
init { init {
with(itemView) { setupActionMenu(itemView)
setupActionMenu(itemView)
}
} }
override fun bindViews(data: MessageReplyUiModel) { override fun bindViews(data: MessageReplyUiModel) {
......
...@@ -13,10 +13,12 @@ import chat.rocket.android.chatroom.uimodel.RoomUiModel ...@@ -13,10 +13,12 @@ import chat.rocket.android.chatroom.uimodel.RoomUiModel
import chat.rocket.android.chatroom.uimodel.UiModelMapper import chat.rocket.android.chatroom.uimodel.UiModelMapper
import chat.rocket.android.chatroom.uimodel.suggestion.ChatRoomSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.ChatRoomSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.CommandSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.CommandSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.EmojiSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.PeopleSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.PeopleSuggestionUiModel
import chat.rocket.android.core.behaviours.showMessage import chat.rocket.android.core.behaviours.showMessage
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.db.DatabaseManager import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.emoji.EmojiRepository
import chat.rocket.android.helper.MessageHelper import chat.rocket.android.helper.MessageHelper
import chat.rocket.android.helper.UserHelper import chat.rocket.android.helper.UserHelper
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
...@@ -34,7 +36,6 @@ import chat.rocket.android.server.domain.useRealName ...@@ -34,7 +36,6 @@ import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infraestructure.state import chat.rocket.android.server.infraestructure.state
import chat.rocket.android.util.extension.compressImageAndGetByteArray import chat.rocket.android.util.extension.compressImageAndGetByteArray
import chat.rocket.android.util.extension.compressImageAndGetInputStream
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.retryIO import chat.rocket.android.util.retryIO
...@@ -80,9 +81,7 @@ import kotlinx.coroutines.experimental.launch ...@@ -80,9 +81,7 @@ import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.experimental.withContext
import org.threeten.bp.Instant import org.threeten.bp.Instant
import timber.log.Timber import timber.log.Timber
import java.io.InputStream
import java.util.* import java.util.*
import java.util.zip.DeflaterInputStream
import javax.inject.Inject import javax.inject.Inject
class ChatRoomPresenter @Inject constructor( class ChatRoomPresenter @Inject constructor(
...@@ -1009,6 +1008,20 @@ class ChatRoomPresenter @Inject constructor( ...@@ -1009,6 +1008,20 @@ class ChatRoomPresenter @Inject constructor(
} }
} }
fun loadEmojis() {
launchUI(strategy) {
val emojiSuggestionUiModels = EmojiRepository.getAll().map {
EmojiSuggestionUiModel(
text = it.shortname.replaceFirst(":", ""),
pinned = false,
emoji = it,
searchList = listOf(it.shortname)
)
}
view.populateEmojiSuggestions(emojis = emojiSuggestionUiModels)
}
}
fun runCommand(text: String, roomId: String) { fun runCommand(text: String, roomId: String) {
launchUI(strategy) { launchUI(strategy) {
try { try {
......
...@@ -3,6 +3,7 @@ package chat.rocket.android.chatroom.presentation ...@@ -3,6 +3,7 @@ package chat.rocket.android.chatroom.presentation
import chat.rocket.android.chatroom.uimodel.BaseUiModel import chat.rocket.android.chatroom.uimodel.BaseUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.ChatRoomSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.ChatRoomSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.CommandSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.CommandSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.EmojiSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.PeopleSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.PeopleSuggestionUiModel
import chat.rocket.android.core.behaviours.LoadingView import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView import chat.rocket.android.core.behaviours.MessageView
...@@ -127,6 +128,9 @@ interface ChatRoomView : LoadingView, MessageView { ...@@ -127,6 +128,9 @@ interface ChatRoomView : LoadingView, MessageView {
fun populatePeopleSuggestions(members: List<PeopleSuggestionUiModel>) fun populatePeopleSuggestions(members: List<PeopleSuggestionUiModel>)
fun populateRoomSuggestions(chatRooms: List<ChatRoomSuggestionUiModel>) fun populateRoomSuggestions(chatRooms: List<ChatRoomSuggestionUiModel>)
fun populateEmojiSuggestions(emojis: List<EmojiSuggestionUiModel>)
/** /**
* This user has joined the chat callback. * This user has joined the chat callback.
* *
......
...@@ -35,6 +35,7 @@ import chat.rocket.android.analytics.AnalyticsManager ...@@ -35,6 +35,7 @@ import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.ScreenViewEvent import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.chatroom.adapter.ChatRoomAdapter import chat.rocket.android.chatroom.adapter.ChatRoomAdapter
import chat.rocket.android.chatroom.adapter.CommandSuggestionsAdapter import chat.rocket.android.chatroom.adapter.CommandSuggestionsAdapter
import chat.rocket.android.chatroom.adapter.EmojiSuggestionsAdapter
import chat.rocket.android.chatroom.adapter.PEOPLE import chat.rocket.android.chatroom.adapter.PEOPLE
import chat.rocket.android.chatroom.adapter.PeopleSuggestionsAdapter import chat.rocket.android.chatroom.adapter.PeopleSuggestionsAdapter
import chat.rocket.android.chatroom.adapter.RoomSuggestionsAdapter import chat.rocket.android.chatroom.adapter.RoomSuggestionsAdapter
...@@ -45,6 +46,7 @@ import chat.rocket.android.chatroom.uimodel.BaseUiModel ...@@ -45,6 +46,7 @@ import chat.rocket.android.chatroom.uimodel.BaseUiModel
import chat.rocket.android.chatroom.uimodel.MessageUiModel import chat.rocket.android.chatroom.uimodel.MessageUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.ChatRoomSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.ChatRoomSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.CommandSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.CommandSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.EmojiSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.PeopleSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.PeopleSuggestionUiModel
import chat.rocket.android.draw.main.ui.DRAWING_BYTE_ARRAY_EXTRA_DATA import chat.rocket.android.draw.main.ui.DRAWING_BYTE_ARRAY_EXTRA_DATA
import chat.rocket.android.draw.main.ui.DrawingActivity import chat.rocket.android.draw.main.ui.DrawingActivity
...@@ -556,11 +558,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -556,11 +558,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
} }
} }
override fun showReplyingAction( override fun showReplyingAction(username: String, replyMarkdown: String, quotedMessage: String) {
username: String,
replyMarkdown: String,
quotedMessage: String
) {
ui { ui {
citation = replyMarkdown citation = replyMarkdown
actionSnackbar.title = username actionSnackbar.title = username
...@@ -610,6 +608,12 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -610,6 +608,12 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
} }
} }
override fun populateEmojiSuggestions(emojis: List<EmojiSuggestionUiModel>) {
ui {
suggestions_view.addItems(":", emojis)
}
}
override fun copyToClipboard(message: String) { override fun copyToClipboard(message: String) {
ui { ui {
val clipboard = it.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clipboard = it.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
...@@ -769,10 +773,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -769,10 +773,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
button_show_attachment_options.alpha = 1f button_show_attachment_options.alpha = 1f
button_show_attachment_options.isVisible = true button_show_attachment_options.isVisible = true
activity?.supportFragmentManager?.addOnBackStackChangedListener {
println("attach")
}
activity?.supportFragmentManager?.registerFragmentLifecycleCallbacks( activity?.supportFragmentManager?.registerFragmentLifecycleCallbacks(
object : FragmentManager.FragmentLifecycleCallbacks() { object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentAttached( override fun onFragmentAttached(
...@@ -837,16 +837,16 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -837,16 +837,16 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}, 400) }, 400)
} }
button_add_reaction.setOnClickListener { view -> button_add_reaction.setOnClickListener { _ ->
openEmojiKeyboardPopup() openEmojiKeyboardPopup()
} }
button_drawing.setOnClickListener { button_drawing.setOnClickListener {
activity?.let { activity?.let { fragmentActivity ->
if (!ImageHelper.canWriteToExternalStorage(it)) { if (!ImageHelper.canWriteToExternalStorage(fragmentActivity)) {
ImageHelper.checkWritingPermission(it) ImageHelper.checkWritingPermission(fragmentActivity)
} else { } else {
val intent = Intent(it, DrawingActivity::class.java) val intent = Intent(fragmentActivity, DrawingActivity::class.java)
startActivityForResult(intent, REQUEST_CODE_FOR_DRAW) startActivityForResult(intent, REQUEST_CODE_FOR_DRAW)
} }
} }
...@@ -877,6 +877,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -877,6 +877,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
.addTokenAdapter(PeopleSuggestionsAdapter(context!!)) .addTokenAdapter(PeopleSuggestionsAdapter(context!!))
.addTokenAdapter(CommandSuggestionsAdapter()) .addTokenAdapter(CommandSuggestionsAdapter())
.addTokenAdapter(RoomSuggestionsAdapter()) .addTokenAdapter(RoomSuggestionsAdapter())
.addTokenAdapter(EmojiSuggestionsAdapter())
.addSuggestionProviderAction("@") { query -> .addSuggestionProviderAction("@") { query ->
if (query.isNotEmpty()) { if (query.isNotEmpty()) {
presenter.spotlight(query, PEOPLE, true) presenter.spotlight(query, PEOPLE, true)
...@@ -887,10 +888,14 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -887,10 +888,14 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
presenter.loadChatRooms() presenter.loadChatRooms()
} }
} }
.addSuggestionProviderAction("/") { _ -> .addSuggestionProviderAction("/") {
presenter.loadCommands() presenter.loadCommands()
} }
.addSuggestionProviderAction(":") {
presenter.loadEmojis()
}
presenter.loadEmojis()
presenter.loadCommands() presenter.loadCommands()
} }
......
package chat.rocket.android.chatroom.uimodel.suggestion
import chat.rocket.android.emoji.Emoji
import chat.rocket.android.suggestions.model.SuggestionModel
import chat.rocket.common.model.UserStatus
class EmojiSuggestionUiModel(
text: String,
pinned: Boolean = false,
val emoji: Emoji,
searchList: List<String>
) : SuggestionModel(text, searchList, pinned) {
override fun toString(): String {
return "EmojiSuggestionUiModel(text='$text', searchList='$searchList', pinned=$pinned)"
}
}
...@@ -5,13 +5,14 @@ ...@@ -5,13 +5,14 @@
android:id="@+id/attachment_container" android:id="@+id/attachment_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:paddingBottom="@dimen/message_item_top_and_bottom_padding"
android:paddingEnd="@dimen/screen_edge_left_and_right_padding"
android:paddingStart="@dimen/screen_edge_left_and_right_padding" android:paddingStart="@dimen/screen_edge_left_and_right_padding"
android:paddingTop="@dimen/message_item_top_and_bottom_padding"> android:paddingTop="@dimen/message_item_top_and_bottom_padding"
android:paddingEnd="@dimen/screen_edge_left_and_right_padding"
android:paddingBottom="@dimen/message_item_top_and_bottom_padding">
<View <View
android:id="@+id/quote_bar" android:id="@+id/quote_bar"
...@@ -19,7 +20,7 @@ ...@@ -19,7 +20,7 @@
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginStart="56dp" android:layout_marginStart="56dp"
android:background="@drawable/quote_vertical_gray_bar" android:background="@drawable/quote_vertical_gray_bar"
app:layout_constraintBottom_toTopOf="@+id/recycler_view_reactions" app:layout_constraintBottom_toTopOf="@+id/text_view_more"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
...@@ -28,11 +29,11 @@ ...@@ -28,11 +29,11 @@
style="@style/Sender.Name.TextView" style="@style/Sender.Name.TextView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textColor="@color/colorPrimary" android:textColor="@color/colorPrimary"
tools:text="Ronald Perkins"
app:layout_constraintStart_toEndOf="@+id/quote_bar" app:layout_constraintStart_toEndOf="@+id/quote_bar"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
android:layout_marginStart="8dp" /> tools:text="Ronald Perkins" />
<TextView <TextView
android:id="@+id/text_message_time" android:id="@+id/text_message_time"
...@@ -40,28 +41,39 @@ ...@@ -40,28 +41,39 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
tools:text="11:45 PM" app:layout_constraintBottom_toBottomOf="@+id/text_sender"
app:layout_constraintStart_toEndOf="@+id/text_sender" app:layout_constraintStart_toEndOf="@+id/text_sender"
app:layout_constraintTop_toTopOf="@+id/text_sender" app:layout_constraintTop_toTopOf="@+id/text_sender"
app:layout_constraintBottom_toBottomOf="@+id/text_sender"/> tools:text="11:45 PM" />
<TextView <TextView
android:id="@+id/text_content" android:id="@+id/text_content"
style="@style/Message.Quote.TextView" style="@style/Message.Quote.TextView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="end" app:layout_constraintBottom_toTopOf="@id/text_view_more"
android:singleLine="true"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/text_sender" app:layout_constraintStart_toStartOf="@+id/text_sender"
app:layout_constraintTop_toBottomOf="@+id/text_sender" app:layout_constraintTop_toBottomOf="@+id/text_sender"
tools:text="This is a multiline chat message from Bertie that will take more than just one line of text. I have sure that everything is amazing!" /> tools:text="This is a multiline chat message from Bertie that will take more than just one line of text. I have sure that everything is amazing!" />
<TextView
android:id="@+id/text_view_more"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="end"
android:textColor="@color/darkGray"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="@+id/text_content"
app:layout_constraintStart_toStartOf="@+id/quote_bar"
app:layout_constraintTop_toBottomOf="@+id/text_content"
tools:text="Visualizar mais" />
<include <include
layout="@layout/layout_reactions" layout="@layout/layout_reactions"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="@+id/quote_bar" app:layout_constraintStart_toStartOf="@+id/quote_bar"
app:layout_constraintTop_toBottomOf="@+id/text_content" /> app:layout_constraintTop_toBottomOf="@+id/text_view_more" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="2dp"
android:layout_marginEnd="2dp"
android:layout_marginBottom="2dp"
android:background="@color/suggestion_background_color">
<ViewFlipper
android:id="@+id/view_flipper_emoji"
android:layout_width="24dp"
android:layout_height="24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/text_emoji"
android:layout_width="24dp"
android:layout_height="24dp"
android:textColor="@android:color/black"
tools:text=":)" />
<ImageView
android:id="@+id/image_emoji"
android:layout_width="24dp"
android:layout_height="24dp"
tools:src="@tools:sample/avatars" />
</ViewFlipper>
<TextView
android:id="@+id/text_emoji_shortname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:maxLines="1"
android:textColor="@color/colorBlack"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/view_flipper_emoji"
app:layout_constraintTop_toTopOf="parent"
tools:text=":grinning:" />
</androidx.constraintlayout.widget.ConstraintLayout>
...@@ -142,6 +142,10 @@ ...@@ -142,6 +142,10 @@
<string name="msg_message_copied">Nachricht kopiert</string> <string name="msg_message_copied">Nachricht kopiert</string>
<string name="msg_delete_message">Lösche Nachricht</string> <string name="msg_delete_message">Lösche Nachricht</string>
<string name="msg_delete_description">Sind Sie sicher, dass Sie diese Nachricht löschen wollen?</string> <string name="msg_delete_description">Sind Sie sicher, dass Sie diese Nachricht löschen wollen?</string>
<!-- TODO - Add proper translation -->
<string name="msg_view_more">view more</string>
<!-- TODO - Add proper translation -->
<string name="msg_view_less">view less</string>
<!-- Preferences messages --> <!-- Preferences messages -->
<string name="msg_analytics_tracking">Analytics tracking</string> <!-- TODO Add translation --> <string name="msg_analytics_tracking">Analytics tracking</string> <!-- TODO Add translation -->
......
...@@ -139,6 +139,10 @@ ...@@ -139,6 +139,10 @@
<string name="msg_member_already_added">Ya has seleccionado este usuario</string> <string name="msg_member_already_added">Ya has seleccionado este usuario</string>
<string name="msg_member_not_found">Miembro no encontrado</string> <string name="msg_member_not_found">Miembro no encontrado</string>
<string name="msg_channel_created_successfully">Canal creado con éxito</string> <string name="msg_channel_created_successfully">Canal creado con éxito</string>
<!-- TODO - Add proper translation -->
<string name="msg_view_more">view more</string>
<!-- TODO - Add proper translation -->
<string name="msg_view_less">view less</string>
<!-- Preferences messages --> <!-- Preferences messages -->
<string name="msg_analytics_tracking">Analytics tracking</string> <!-- TODO Add translation --> <string name="msg_analytics_tracking">Analytics tracking</string> <!-- TODO Add translation -->
......
...@@ -131,6 +131,10 @@ ...@@ -131,6 +131,10 @@
<string name="msg_send">envoyer</string> <string name="msg_send">envoyer</string>
<string name="msg_delete_message">Supprimer Message</string> <string name="msg_delete_message">Supprimer Message</string>
<string name="msg_delete_description">Êtes-vous sûr de vouloir supprimer ce message</string> <string name="msg_delete_description">Êtes-vous sûr de vouloir supprimer ce message</string>
<!-- TODO - Add proper translation -->
<string name="msg_view_more">view more</string>
<!-- TODO - Add proper translation -->
<string name="msg_view_less">view less</string>
<!-- Create channel messages --> <!-- Create channel messages -->
<string name="msg_private_channel">Privé</string> <string name="msg_private_channel">Privé</string>
......
...@@ -145,6 +145,10 @@ ...@@ -145,6 +145,10 @@
<string name="msg_member_already_added">आपने पहले से ही इस यूजर को चुन चुके है।</string> <string name="msg_member_already_added">आपने पहले से ही इस यूजर को चुन चुके है।</string>
<string name="msg_member_not_found">सदस्य नहीं मिला</string> <string name="msg_member_not_found">सदस्य नहीं मिला</string>
<string name="msg_channel_created_successfully">चैनल सफलतापूर्वक बनाया गया</string> <string name="msg_channel_created_successfully">चैनल सफलतापूर्वक बनाया गया</string>
<!-- TODO - Add proper translation -->
<string name="msg_view_more">view more</string>
<!-- TODO - Add proper translation -->
<string name="msg_view_less">view less</string>
<!-- Preferences messages --> <!-- Preferences messages -->
<string name="msg_analytics_tracking">एनालिटिक्स ट्रैकिंग</string> <string name="msg_analytics_tracking">एनालिटिक्स ट्रैकिंग</string>
......
...@@ -132,6 +132,10 @@ ...@@ -132,6 +132,10 @@
<string name="msg_file_description">ファイルの説明</string> <string name="msg_file_description">ファイルの説明</string>
<string name="msg_send">送信</string> <string name="msg_send">送信</string>
<string name="msg_sent_attachment">添付ファイルを送信しました</string> <string name="msg_sent_attachment">添付ファイルを送信しました</string>
<!-- TODO - Add proper translation -->
<string name="msg_view_more">view more</string>
<!-- TODO - Add proper translation -->
<string name="msg_view_less">view less</string>
<!-- Create channel messages --> <!-- Create channel messages -->
<string name="msg_private_channel">プライベート</string> <string name="msg_private_channel">プライベート</string>
......
...@@ -130,9 +130,10 @@ ...@@ -130,9 +130,10 @@
<string name="msg_upload_file">Subir arquivo</string> <string name="msg_upload_file">Subir arquivo</string>
<string name="msg_file_description">Descrição do arquivo</string> <string name="msg_file_description">Descrição do arquivo</string>
<string name="msg_send">Enviar</string> <string name="msg_send">Enviar</string>
// TODO: Add proper translation. <string name="msg_delete_message">Remove mensagem</string>
<string name="msg_delete_message">Delete Message</string> <string name="msg_delete_description">Tem certeza que quer apagar esta mensagem?</string>
<string name="msg_delete_description">Are you sure you want to delete this message</string> <string name="msg_view_more">visualizar mais</string>
<string name="msg_view_less">visualizar menos</string>
<!-- Create channel messages --> <!-- Create channel messages -->
<string name="msg_private_channel">Privado</string> <string name="msg_private_channel">Privado</string>
......
...@@ -130,6 +130,10 @@ ...@@ -130,6 +130,10 @@
<string name="msg_delete_description">Вы уверены, что хотите удалить это сообщение?</string> <string name="msg_delete_description">Вы уверены, что хотите удалить это сообщение?</string>
<string name="msg_channel_name">Название канала</string> <string name="msg_channel_name">Название канала</string>
<string name="msg_search">Поиск</string> <string name="msg_search">Поиск</string>
<!-- TODO - Add proper translation -->
<string name="msg_view_more">view more</string>
<!-- TODO - Add proper translation -->
<string name="msg_view_less">view less</string>
<!-- Create channel messages --> <!-- Create channel messages -->
<string name="msg_private_channel">Приватный</string> <string name="msg_private_channel">Приватный</string>
......
...@@ -129,6 +129,10 @@ ...@@ -129,6 +129,10 @@
<string name="msg_send">Надіслати</string> <string name="msg_send">Надіслати</string>
<string name="msg_delete_message">Видалити повідомлення</string> <string name="msg_delete_message">Видалити повідомлення</string>
<string name="msg_delete_description">Ви впевнені, що хочете видалити це повідомлення?</string> <string name="msg_delete_description">Ви впевнені, що хочете видалити це повідомлення?</string>
<!-- TODO - Add proper translation -->
<string name="msg_view_more">view more</string>
<!-- TODO - Add proper translation -->
<string name="msg_view_less">view less</string>
<!-- Create channel messages --> <!-- Create channel messages -->
<string name="msg_private_channel">Приватний</string> <string name="msg_private_channel">Приватний</string>
......
...@@ -44,4 +44,6 @@ ...@@ -44,4 +44,6 @@
<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>
<dimen name="quote_collapsed_height">32dp</dimen>
</resources> </resources>
\ No newline at end of file
...@@ -159,6 +159,8 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin ...@@ -159,6 +159,8 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<string name="msg_message_copied">Message copied</string> <string name="msg_message_copied">Message copied</string>
<string name="msg_delete_message">Delete Message</string> <string name="msg_delete_message">Delete Message</string>
<string name="msg_delete_description">Are you sure you want to delete this message</string> <string name="msg_delete_description">Are you sure you want to delete this message</string>
<string name="msg_view_more">view more</string>
<string name="msg_view_less">view less</string>
<!-- Preferences messages --> <!-- Preferences messages -->
<string name="msg_analytics_tracking">Analytics tracking</string> <string name="msg_analytics_tracking">Analytics tracking</string>
......
...@@ -146,7 +146,7 @@ object EmojiRepository { ...@@ -146,7 +146,7 @@ object EmojiRepository {
* *
* @return All emojis for all categories. * @return All emojis for all categories.
*/ */
internal suspend fun getAll(): List<Emoji> = withContext(CommonPool) { suspend fun getAll(): List<Emoji> = withContext(CommonPool) {
return@withContext db.emojiDao().loadAllEmojis() return@withContext db.emojiDao().loadAllEmojis()
} }
......
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());
}
}
package chat.rocket.android.suggestions.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 searchList: List<String> = emptyList(), // Where to search for matches. val text: String, // This is the text key for searches, must be unique.
val pinned: Boolean = false /* If pinned item will have priority to show */) { val searchList: List<String> = emptyList(), // Where to search for matches.
val pinned: Boolean = false /* If pinned item will have priority to show */
) {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (other !is SuggestionModel) return false if (other !is SuggestionModel) return false
......
...@@ -18,7 +18,9 @@ class TrieCompletionStrategy : CompletionStrategy { ...@@ -18,7 +18,9 @@ class TrieCompletionStrategy : CompletionStrategy {
return item return item
} }
override fun autocompleteItems(prefix: String) = trie.autocompleteItems(prefix) override fun autocompleteItems(prefix: String): List<SuggestionModel> {
return trie.autocompleteItems(prefix)
}
override fun addAll(list: List<SuggestionModel>) { override fun addAll(list: List<SuggestionModel>) {
items.addAll(list) items.addAll(list)
......
...@@ -34,10 +34,7 @@ internal class Trie { ...@@ -34,10 +34,7 @@ internal class Trie {
val sanitizedWord = word.trim().toLowerCase() val sanitizedWord = word.trim().toLowerCase()
var current = root var current = root
sanitizedWord.forEach { ch -> sanitizedWord.forEach { ch ->
val child = current.getChild(ch) val child = current.getChild(ch) ?: return false
if (child == null) {
return false
}
current = child current = child
} }
if (current.isLeaf) { if (current.isLeaf) {
...@@ -63,7 +60,7 @@ internal class Trie { ...@@ -63,7 +60,7 @@ internal class Trie {
lastNode = lastNode?.getChild(ch) lastNode = lastNode?.getChild(ch)
if (lastNode == null) return emptyList() if (lastNode == null) return emptyList()
} }
return lastNode!!.getItems() return lastNode!!.getItems().take(5).toList()
} }
fun getCount() = count fun getCount() = count
......
package chat.rocket.android.suggestions.strategy.trie.data package chat.rocket.android.suggestions.strategy.trie.data
import chat.rocket.android.suggestions.model.SuggestionModel import chat.rocket.android.suggestions.model.SuggestionModel
import kotlin.coroutines.experimental.buildSequence
internal class TrieNode(
internal var data: Char,
internal var parent: TrieNode? = null,
internal var isLeaf: Boolean = false,
internal var item: SuggestionModel? = null
) {
internal class TrieNode(internal var data: Char,
internal var parent: TrieNode? = null,
internal var isLeaf: Boolean = false,
internal var item: SuggestionModel? = null) {
val children = hashMapOf<Char, TrieNode>() val children = hashMapOf<Char, TrieNode>()
fun getChild(c: Char): TrieNode? { fun getChild(c: Char): TrieNode? {
...@@ -28,19 +32,17 @@ internal class TrieNode(internal var data: Char, ...@@ -28,19 +32,17 @@ internal class TrieNode(internal var data: Char,
return list return list
} }
class X : SuggestionModel("") fun getItems(): Sequence<SuggestionModel> = buildSequence {
fun getItems(): List<SuggestionModel> {
val list = arrayListOf<SuggestionModel>()
if (isLeaf) { if (isLeaf) {
list.add(item!!) yield(item!!)
} }
children.forEach { node -> children.forEach { node ->
node.value.let { node.value.let {
list.addAll(it.getItems()) yieldAll(it.getItems())
} }
} }
return list
} }
override fun toString(): String = if (parent == null) "" else "${parent.toString()}$data" override fun toString(): String = if (parent == null) "" else "${parent.toString()}$data"
......
...@@ -10,17 +10,9 @@ import kotlin.properties.Delegates ...@@ -10,17 +10,9 @@ import kotlin.properties.Delegates
abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>( abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>(
val token: String, val token: String,
val constraint: Int = CONSTRAINT_UNBOUND, val constraint: Int = CONSTRAINT_UNBOUND,
threshold: Int = MAX_RESULT_COUNT) : RecyclerView.Adapter<VH>() { completionStrategy: CompletionStrategy? = null,
companion object { threshold: Int = MAX_RESULT_COUNT
// Any number of results. ) : RecyclerView.Adapter<VH>() {
const val RESULT_COUNT_UNLIMITED = -1
// Trigger suggestions only if on the line start.
const val CONSTRAINT_BOUND_TO_START = 0
// Trigger suggestions from anywhere.
const val CONSTRAINT_UNBOUND = 1
// Maximum number of results to display by default.
private const val MAX_RESULT_COUNT = 5
}
private var itemType: Type? = null private var itemType: Type? = null
private var itemClickListener: ItemClickListener? = null private var itemClickListener: ItemClickListener? = null
...@@ -30,7 +22,7 @@ abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>( ...@@ -30,7 +22,7 @@ abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>(
// Maximum number of results/suggestions to display. // Maximum number of results/suggestions to display.
private var resultsThreshold: Int = if (threshold > 0) threshold else RESULT_COUNT_UNLIMITED private var resultsThreshold: Int = if (threshold > 0) threshold else RESULT_COUNT_UNLIMITED
// The strategy used for suggesting completions. // The strategy used for suggesting completions.
private val strategy: CompletionStrategy = StringMatchingCompletionStrategy(resultsThreshold) private val strategy: CompletionStrategy = 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)
...@@ -105,4 +97,15 @@ abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>( ...@@ -105,4 +97,15 @@ abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>(
interface ItemClickListener { interface ItemClickListener {
fun onClick(item: SuggestionModel) fun onClick(item: SuggestionModel)
} }
}
\ No newline at end of file companion object {
// Any number of results.
const val RESULT_COUNT_UNLIMITED = -1
// Trigger suggestions only if on the line start.
const val CONSTRAINT_BOUND_TO_START = 0
// Trigger suggestions from anywhere.
const val CONSTRAINT_UNBOUND = 1
// Maximum number of results to display by default.
private const val MAX_RESULT_COUNT = 5
}
}
...@@ -149,15 +149,14 @@ class SuggestionsView : FrameLayout, TextWatcher { ...@@ -149,15 +149,14 @@ class SuggestionsView : FrameLayout, TextWatcher {
} }
fun addTokenAdapter(adapter: SuggestionsAdapter<*>): SuggestionsView { fun addTokenAdapter(adapter: SuggestionsAdapter<*>): SuggestionsView {
adaptersByToken.getOrPut(adapter.token, { adapter }) adaptersByToken.getOrPut(adapter.token) { adapter }
return this return this
} }
fun addItems(token: String, list: List<SuggestionModel>): SuggestionsView { fun addItems(token: String, list: List<SuggestionModel>): SuggestionsView {
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)
} }
......
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