Commit 202c69db authored by Leonardo Aramaki's avatar Leonardo Aramaki

Merge branch 'develop-2.x' into perf-and-fixes

parents 64a8d351 2e7a0037
package chat.rocket.android.chatroom.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import chat.rocket.android.R
import chat.rocket.android.chatroom.adapter.CommandSuggestionsAdapter.CommandSuggestionsViewHolder
import chat.rocket.android.chatroom.viewmodel.suggestion.CommandSuggestionViewModel
import chat.rocket.android.widget.autocompletion.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.ui.BaseSuggestionViewHolder
import chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter
class CommandSuggestionsAdapter : SuggestionsAdapter<CommandSuggestionsViewHolder>(token = "/",
constraint = CONSTRAINT_BOUND_TO_START, threshold = RESULT_COUNT_UNLIMITED) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommandSuggestionsViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.suggestion_command_item, parent,
false)
return CommandSuggestionsViewHolder(view)
}
class CommandSuggestionsViewHolder(view: View) : BaseSuggestionViewHolder(view) {
override fun bind(item: SuggestionModel, itemClickListener: SuggestionsAdapter.ItemClickListener?) {
item as CommandSuggestionViewModel
with(itemView) {
val nameTextView = itemView.findViewById<TextView>(R.id.text_command_name)
val descriptionTextView = itemView.findViewById<TextView>(R.id.text_command_description)
nameTextView.text = "/${item.text}"
val res = context.resources
val id = res.getIdentifier(item.description, "string", context.packageName)
val description = if (id > 0) res.getString(id) else ""
descriptionTextView.text = description.toLowerCase()
setOnClickListener {
itemClickListener?.onClick(item)
}
}
}
}
}
\ No newline at end of file
......@@ -9,12 +9,11 @@ import android.widget.ImageView
import android.widget.TextView
import chat.rocket.android.R
import chat.rocket.android.chatroom.adapter.PeopleSuggestionsAdapter.PeopleSuggestionViewHolder
import chat.rocket.android.chatroom.viewmodel.PeopleViewModel
import chat.rocket.android.chatroom.viewmodel.suggestion.PeopleSuggestionViewModel
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.common.model.UserStatus
import com.facebook.drawee.view.SimpleDraweeView
class PeopleSuggestionsAdapter(context: Context) : SuggestionsAdapter<PeopleSuggestionViewHolder>("@") {
......@@ -23,14 +22,14 @@ class PeopleSuggestionsAdapter(context: Context) : SuggestionsAdapter<PeopleSugg
val allDescription = context.getString(R.string.suggest_all_description)
val hereDescription = context.getString(R.string.suggest_here_description)
val pinnedList = listOf(
PeopleViewModel(imageUri = null,
PeopleSuggestionViewModel(imageUri = null,
text = "all",
username = "all",
name = allDescription,
status = null,
pinned = false,
searchList = listOf("all")),
PeopleViewModel(imageUri = null,
PeopleSuggestionViewModel(imageUri = null,
text = "here",
username = "here",
name = hereDescription,
......@@ -50,7 +49,7 @@ class PeopleSuggestionsAdapter(context: Context) : SuggestionsAdapter<PeopleSugg
class PeopleSuggestionViewHolder(view: View) : BaseSuggestionViewHolder(view) {
override fun bind(item: SuggestionModel, itemClickListener: SuggestionsAdapter.ItemClickListener?) {
item as PeopleViewModel
item as PeopleSuggestionViewModel
with(itemView) {
val username = itemView.findViewById<TextView>(R.id.text_username)
val name = itemView.findViewById<TextView>(R.id.text_name)
......
......@@ -6,7 +6,7 @@ import android.view.ViewGroup
import android.widget.TextView
import chat.rocket.android.R
import chat.rocket.android.chatroom.adapter.RoomSuggestionsAdapter.RoomSuggestionsViewHolder
import chat.rocket.android.chatroom.viewmodel.ChatRoomViewModel
import chat.rocket.android.chatroom.viewmodel.suggestion.ChatRoomSuggestionViewModel
import chat.rocket.android.widget.autocompletion.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.ui.BaseSuggestionViewHolder
import chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter
......@@ -22,7 +22,7 @@ class RoomSuggestionsAdapter : SuggestionsAdapter<RoomSuggestionsViewHolder>("#"
class RoomSuggestionsViewHolder(view: View) : BaseSuggestionViewHolder(view) {
override fun bind(item: SuggestionModel, itemClickListener: SuggestionsAdapter.ItemClickListener?) {
item as ChatRoomViewModel
item as ChatRoomSuggestionViewModel
with(itemView) {
val fullname = itemView.findViewById<TextView>(R.id.text_fullname)
val name = itemView.findViewById<TextView>(R.id.text_name)
......
......@@ -2,8 +2,9 @@ package chat.rocket.android.chatroom.presentation
import android.net.Uri
import chat.rocket.android.chatroom.viewmodel.BaseViewModel
import chat.rocket.android.chatroom.viewmodel.ChatRoomViewModel
import chat.rocket.android.chatroom.viewmodel.PeopleViewModel
import chat.rocket.android.chatroom.viewmodel.suggestion.ChatRoomSuggestionViewModel
import chat.rocket.android.chatroom.viewmodel.suggestion.CommandSuggestionViewModel
import chat.rocket.android.chatroom.viewmodel.suggestion.PeopleSuggestionViewModel
import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
import chat.rocket.core.internal.realtime.State
......@@ -102,12 +103,19 @@ interface ChatRoomView : LoadingView, MessageView {
fun showInvalidFileSize(fileSize: Int, maxFileSize: Int)
fun showConnectionState(state: State)
fun populateMembers(members: List<PeopleViewModel>)
fun populateRooms(chatRooms: List<ChatRoomViewModel>)
fun populatePeopleSuggestions(members: List<PeopleSuggestionViewModel>)
fun populateRoomSuggestions(chatRooms: List<ChatRoomSuggestionViewModel>)
/**
* This user has joined the chat callback.
*/
fun onJoined()
fun showReactionsPopup(messageId: String)
/**
* Show list of commands.
*
* @param commands The list of available commands.
*/
fun populateCommandSuggestions(commands: List<CommandSuggestionViewModel>)
}
\ No newline at end of file
......@@ -15,16 +15,14 @@ import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.*
import chat.rocket.android.R
import chat.rocket.android.chatroom.adapter.ChatRoomAdapter
import chat.rocket.android.chatroom.adapter.PEOPLE
import chat.rocket.android.chatroom.adapter.PeopleSuggestionsAdapter
import chat.rocket.android.chatroom.adapter.RoomSuggestionsAdapter
import chat.rocket.android.chatroom.adapter.*
import chat.rocket.android.chatroom.presentation.ChatRoomPresenter
import chat.rocket.android.chatroom.presentation.ChatRoomView
import chat.rocket.android.chatroom.viewmodel.BaseViewModel
import chat.rocket.android.chatroom.viewmodel.ChatRoomViewModel
import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import chat.rocket.android.chatroom.viewmodel.PeopleViewModel
import chat.rocket.android.chatroom.viewmodel.suggestion.ChatRoomSuggestionViewModel
import chat.rocket.android.chatroom.viewmodel.suggestion.CommandSuggestionViewModel
import chat.rocket.android.chatroom.viewmodel.suggestion.PeopleSuggestionViewModel
import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.MessageParser
......@@ -218,7 +216,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
override fun sendMessage(text: String) {
if (!text.isBlank()) {
presenter.sendMessage(chatRoomId, text, editingMessageId)
if (!text.startsWith("/")) {
presenter.sendMessage(chatRoomId, text, editingMessageId)
} else {
presenter.runCommand(text, chatRoomId)
}
}
}
......@@ -287,14 +289,18 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
override fun populateMembers(members: List<PeopleViewModel>) {
override fun populatePeopleSuggestions(members: List<PeopleSuggestionViewModel>) {
suggestions_view.addItems("@", members)
}
override fun populateRooms(chatRooms: List<ChatRoomViewModel>) {
override fun populateRoomSuggestions(chatRooms: List<ChatRoomSuggestionViewModel>) {
suggestions_view.addItems("#", chatRooms)
}
override fun populateCommandSuggestions(commands: List<CommandSuggestionViewModel>) {
suggestions_view.addItems("/", commands)
}
override fun copyToClipboard(message: String) {
activity?.apply {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
......@@ -492,9 +498,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
private fun setupSuggestionsView() {
suggestions_view.anchor(text_message)
.bindTokenAdapter(PeopleSuggestionsAdapter(context!!))
.bindTokenAdapter(RoomSuggestionsAdapter())
suggestions_view.anchorTo(text_message)
.setMaximumHeight(resources.getDimensionPixelSize(R.dimen.suggestions_box_max_height))
.addTokenAdapter(PeopleSuggestionsAdapter(context!!))
.addTokenAdapter(CommandSuggestionsAdapter())
.addTokenAdapter(RoomSuggestionsAdapter())
.addSuggestionProviderAction("@") { query ->
if (query.isNotEmpty()) {
presenter.spotlight(query, PEOPLE, true)
......@@ -505,6 +513,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
presenter.loadChatRooms()
}
}
.addSuggestionProviderAction("/") { _ ->
presenter.loadCommands()
}
presenter.loadCommands()
}
private fun openEmojiKeyboardPopup() {
......
package chat.rocket.android.chatroom.viewmodel
import chat.rocket.android.widget.autocompletion.model.SuggestionModel
class ChatRoomViewModel(text: String,
val fullName: String,
val name: String,
searchList: List<String>) : SuggestionModel(text, searchList, false) {
}
\ No newline at end of file
package chat.rocket.android.chatroom.viewmodel
import chat.rocket.android.widget.autocompletion.model.SuggestionModel
import chat.rocket.common.model.UserStatus
class PeopleViewModel(val imageUri: String?,
text: String,
val username: String,
val name: String,
val status: UserStatus?,
pinned: Boolean = false,
searchList: List<String>) : SuggestionModel(text, searchList, pinned) {
override fun toString(): String {
return "PeopleViewModel(imageUri='$imageUri', username='$username', name='$name', status=$status, pinned=$pinned)"
}
}
\ No newline at end of file
package chat.rocket.android.chatroom.viewmodel.suggestion
import chat.rocket.android.widget.autocompletion.model.SuggestionModel
class ChatRoomSuggestionViewModel(text: String,
val fullName: String,
val name: String,
searchList: List<String>) : SuggestionModel(text, searchList, false) {
}
\ No newline at end of file
package chat.rocket.android.chatroom.viewmodel.suggestion
import chat.rocket.android.widget.autocompletion.model.SuggestionModel
class CommandSuggestionViewModel(text: String,
val description: String,
searchList: List<String>) : SuggestionModel(text, searchList)
\ No newline at end of file
package chat.rocket.android.chatroom.viewmodel.suggestion
import chat.rocket.android.widget.autocompletion.model.SuggestionModel
import chat.rocket.common.model.UserStatus
class PeopleSuggestionViewModel(val imageUri: String?,
text: String,
val username: String,
val name: String,
val status: UserStatus?,
pinned: Boolean = false,
searchList: List<String>) : SuggestionModel(text, searchList, pinned) {
override fun toString(): String {
return "PeopleSuggestionViewModel(imageUri='$imageUri', username='$username', name='$name', status=$status, pinned=$pinned)"
}
}
\ No newline at end of file
......@@ -2,12 +2,17 @@ package chat.rocket.android.widget.autocompletion.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 java.util.concurrent.CopyOnWriteArrayList
internal class StringMatchingCompletionStrategy : CompletionStrategy {
internal class StringMatchingCompletionStrategy(private val threshold: Int = RESULT_COUNT_UNLIMITED) : CompletionStrategy {
private val list = CopyOnWriteArrayList<SuggestionModel>()
private val pinnedList = mutableListOf<SuggestionModel>()
init {
check(threshold >= RESULT_COUNT_UNLIMITED)
}
override fun autocompleteItems(prefix: String): List<SuggestionModel> {
val partialResult = list.filter {
it.searchList.forEach { word ->
......@@ -17,9 +22,13 @@ internal class StringMatchingCompletionStrategy : CompletionStrategy {
}
false
}.sortedByDescending { it.pinned }
val result = partialResult.take(5).toMutableList()
result.addAll(pinnedList)
return result.toList()
return if (threshold == RESULT_COUNT_UNLIMITED)
partialResult.toList()
else {
val result = partialResult.take(threshold).toMutableList()
result.addAll(pinnedList)
result.toList()
}
}
override fun addAll(list: List<SuggestionModel>) {
......
......@@ -7,14 +7,33 @@ import chat.rocket.android.widget.autocompletion.strategy.regex.StringMatchingCo
import java.lang.reflect.Type
import kotlin.properties.Delegates
abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>(val token: String) : RecyclerView.Adapter<VH>() {
private val strategy: CompletionStrategy = StringMatchingCompletionStrategy()
abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>(
val token: String,
val constraint: Int = CONSTRAINT_UNBOUND,
threshold: Int = MAX_RESULT_COUNT) : RecyclerView.Adapter<VH>() {
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
}
private var itemType: Type? = null
private var itemClickListener: ItemClickListener? = null
// Called to gather results when no results have previously matched.
private var providerExternal: ((query: String) -> Unit)? = null
private var pinnedSuggestions: List<SuggestionModel>? = null
private var prefix: String by Delegates.observable("", { _, _, _ ->
strategy.autocompleteItems(prefix)
// Maximum number of results/suggestions to display.
private var resultsThreshold: Int = if (threshold > 0) threshold else RESULT_COUNT_UNLIMITED
// 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 ->
val items = strategy.autocompleteItems(newTerm)
notifyDataSetChanged()
})
......@@ -30,10 +49,10 @@ abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>(val token: Stri
holder.bind(getItem(position), itemClickListener)
}
override fun getItemCount() = strategy.autocompleteItems(prefix).size
override fun getItemCount() = strategy.autocompleteItems(currentTerm).size
private fun getItem(position: Int): SuggestionModel {
return strategy.autocompleteItems(prefix)[position]
return strategy.autocompleteItems(currentTerm)[position]
}
/**
......@@ -45,15 +64,15 @@ abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>(val token: Stri
this.strategy.addPinned(suggestions)
}
fun autocomplete(prefix: String) {
this.prefix = prefix.toLowerCase().trim()
fun autocomplete(newTerm: String) {
this.currentTerm = newTerm.toLowerCase().trim()
}
fun addItems(list: List<SuggestionModel>) {
strategy.addAll(list)
// Since we've just added new items we should check for possible new completion suggestions.
strategy.autocompleteItems(prefix)
notifyItemRangeChanged(0, 5)
strategy.autocompleteItems(currentTerm)
notifyDataSetChanged()
}
fun setOnClickListener(clickListener: ItemClickListener) {
......@@ -62,11 +81,24 @@ abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>(val token: Stri
fun hasItemClickListener() = itemClickListener != null
fun prefix() = prefix
/**
* Return the current searched term.
*/
fun term() = this.currentTerm
/**
* Set the maximum number of results to show.
*
* @param threshold The maximum number of suggestions to display.
*/
fun setResultsThreshold(threshold: Int) {
check(threshold > 0)
resultsThreshold = threshold
}
fun cancel() {
strategy.addAll(emptyList())
strategy.autocompleteItems(prefix)
strategy.autocompleteItems(currentTerm)
notifyDataSetChanged()
}
......
......@@ -21,12 +21,11 @@ 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 java.lang.ref.WeakReference
import java.util.concurrent.atomic.AtomicInteger
/**
* This is a special index that means we're not at an autocompleting state.
*/
// This is a special index that means we're not at an autocompleting state.
private const val NO_STATE_INDEX = 0
class SuggestionsView : FrameLayout, TextWatcher {
......@@ -37,6 +36,7 @@ class SuggestionsView : FrameLayout, TextWatcher {
private val localProvidersByToken = hashMapOf<String, HashMap<String, List<SuggestionModel>>>()
private var editor: WeakReference<EditText>? = null
private var completionOffset = AtomicInteger(NO_STATE_INDEX)
private var maxHeight: Int = 0
companion object {
private val SLIDE_TRANSITION = Slide(Gravity.BOTTOM).setDuration(200)
......@@ -80,6 +80,10 @@ class SuggestionsView : FrameLayout, TextWatcher {
val new = s.subSequence(start, start + count).toString()
if (adaptersByToken.containsKey(new)) {
val constraint = adapter(new).constraint
if (constraint == CONSTRAINT_BOUND_TO_START && start != 0) {
return
}
swapAdapter(getAdapterForToken(new)!!)
completionOffset.compareAndSet(NO_STATE_INDEX, start + 1)
this.editor?.let {
......@@ -115,9 +119,18 @@ class SuggestionsView : FrameLayout, TextWatcher {
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
if (maxHeight > 0) {
val hSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST)
super.onMeasure(widthMeasureSpec, hSpec)
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
}
private fun swapAdapter(adapter: SuggestionsAdapter<*>): SuggestionsView {
recyclerView.adapter = adapter
// Don't override if user set an item click listener already/
// Don't override if user has set an item click listener already
if (!adapter.hasItemClickListener()) {
setOnItemClickListener(adapter) {
// set default item click behavior
......@@ -126,16 +139,16 @@ class SuggestionsView : FrameLayout, TextWatcher {
return this
}
fun getAdapterForToken(token: String): SuggestionsAdapter<*>? = adaptersByToken.get(token)
private fun getAdapterForToken(token: String): SuggestionsAdapter<*>? = adaptersByToken.get(token)
fun anchor(editText: EditText): SuggestionsView {
fun anchorTo(editText: EditText): SuggestionsView {
editText.removeTextChangedListener(this)
editText.addTextChangedListener(this)
editor = WeakReference(editText)
return this
}
fun bindTokenAdapter(adapter: SuggestionsAdapter<*>): SuggestionsView {
fun addTokenAdapter(adapter: SuggestionsAdapter<*>): SuggestionsView {
adaptersByToken.getOrPut(adapter.token, { adapter })
return this
}
......@@ -144,13 +157,20 @@ class SuggestionsView : FrameLayout, TextWatcher {
if (list.isNotEmpty()) {
val adapter = adapter(token)
localProvidersByToken.getOrPut(token, { hashMapOf() })
.put(adapter.prefix(), list)
.put(adapter.term(), list)
if (completionOffset.get() > NO_STATE_INDEX && adapter.itemCount == 0) expand()
adapter.addItems(list)
}
return this
}
fun setMaximumHeight(height: Int): SuggestionsView {
check(height > 0)
this.maxHeight = height
requestLayout()
return this
}
fun setOnItemClickListener(tokenAdapter: SuggestionsAdapter<*>,
clickListener: (item: SuggestionModel) -> Unit): SuggestionsView {
tokenAdapter.setOnClickListener(object : SuggestionsAdapter.ItemClickListener {
......@@ -165,6 +185,9 @@ class SuggestionsView : FrameLayout, TextWatcher {
}
fun addSuggestionProviderAction(token: String, provider: (query: String) -> Unit): SuggestionsView {
if (adaptersByToken[token] == null) {
throw IllegalStateException("token \"$token\" suggestion provider added without adapter")
}
externalProvidersByToken.getOrPut(token, { provider })
return this
}
......
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginTop="4dp"
android:background="@color/suggestion_background_color"
android:orientation="horizontal"
android:paddingTop="2dp">
<TextView
android:id="@+id/text_command_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/black"
android:textSize="14sp"
tools:text="/leave" />
<TextView
android:id="@+id/text_command_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="16dp"
android:layout_toRightOf="@id/text_command_name"
android:ellipsize="end"
android:gravity="start"
android:maxLines="1"
android:textColor="@color/gray_material"
android:textSize="14sp"
tools:text="Leave a channel" />
</RelativeLayout>
\ No newline at end of file
......@@ -121,4 +121,28 @@
<!--Suggestions-->
<string name="suggest_all_description">Notifica todos nesta sala</string>
<string name="suggest_here_description">Notifica usuários ativos nesta sala</string>
<!-- Slash Commands -->
<string name="Slash_Gimme_Description">Exibir ༼ つ ◕_◕ ༽つ antes de sua mensagem</string>
<string name="Slash_LennyFace_Description">Exibir ( ͡° ͜ʖ ͡°) depois de sua mensagem</string>
<string name="Slash_Shrug_Description">Exibir ¯\_(ツ)_/¯ depois de sua mensagem</string>
<string name="Slash_Tableflip_Description">Exibir (╯°□°)╯︵ ┻━┻</string>
<string name="Slash_TableUnflip_Description">Exibir ┬─┬ ノ( ゜-゜ノ)</string>
<string name="Create_A_New_Channel">Criar um novo canal</string>
<string name="Show_the_keyboard_shortcut_list">Show the keyboard shortcut list</string>
<string name="Invite_user_to_join_channel_all_from"> do [#canal] para entrar neste</string>
<string name="Invite_user_to_join_channel_all_to">Convidar todos os usuários deste canal para entrar no [#canal]</string>
<string name="Archive">Arquivar</string>
<string name="Remove_someone_from_room">Remover alguém do canal</string>
<string name="Leave_the_current_channel">Sair do canal atual</string>
<string name="Displays_action_text">Exibir texto de ação</string>
<string name="Direct_message_someone">Enviar DM para alguém</string>
<string name="Mute_someone_in_room">Mutar alguém</string>
<string name="Unmute_someone_in_room">Desmutar alguém na sala</string>
<string name="Invite_user_to_join_channel">Convidar algum usuário para entrar neste canal</string>
<string name="Unarchive">Desarquivar</string>
<string name="Join_the_given_channel">Entrar no canal especificado</string>
<string name="Guggy_Command_Description">Gera um gif baseado no texto dado</string>
<string name="Slash_Topic_Description">Definir tópico</string>
</resources>
\ No newline at end of file
......@@ -34,5 +34,6 @@
<!-- Autocomplete Popup -->
<dimen name="popup_max_height">150dp</dimen>
<dimen name="suggestions_box_max_height">250dp</dimen>
</resources>
\ No newline at end of file
......@@ -123,4 +123,27 @@
<string name="suggest_all_description">Notify all in this room</string>
<string name="suggest_here_description">Notify active users in this room</string>
<!-- Slash Commands -->
<string name="Slash_Gimme_Description">Displays ༼ つ ◕_◕ ༽つ before your message</string>
<string name="Slash_LennyFace_Description">Displays ( ͡° ͜ʖ ͡°) after your message</string>
<string name="Slash_Shrug_Description">Displays ¯\_(ツ)_/¯ after your message</string>
<string name="Slash_Tableflip_Description">Displays (╯°□°)╯︵ ┻━┻</string>
<string name="Slash_TableUnflip_Description">Displays ┬─┬ ノ( ゜-゜ノ)</string>
<string name="Create_A_New_Channel">Create a new channel</string>
<string name="Show_the_keyboard_shortcut_list">Show the keyboard shortcut list</string>
<string name="Invite_user_to_join_channel_all_from">Invite all users from [#channel] to join this channel</string>
<string name="Invite_user_to_join_channel_all_to">Invite all users from this channel to join [#channel]</string>
<string name="Archive">Archive</string>
<string name="Remove_someone_from_room">Remove someone from the room</string>
<string name="Leave_the_current_channel">Leave the current channel</string>
<string name="Displays_action_text">Displays action text</string>
<string name="Direct_message_someone">Direct message someone</string>
<string name="Mute_someone_in_room">Mute someone in the room</string>
<string name="Unmute_someone_in_room">Unmute someone in the room</string>
<string name="Invite_user_to_join_channel">Invite one user to join this channel</string>
<string name="Unarchive">Unarchive</string>
<string name="Join_the_given_channel">Join the given channel</string>
<string name="Guggy_Command_Description">Generates a gif based upon the provided text</string>
<string name="Slash_Topic_Description">Set topic</string>
</resources>
\ 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