i have 1 edittext maxLength = 30, but i can only type 6 character emoji dog => 1 emoji dog = 6 regular character. So please help me type 30 emoji dog. Thanks everyone.
[enter image description here][1]
When someone types an emoji, you can call .length on that emoji, then increase your max character count by that amount. (You would have to remember your original character count and use that on your UI if you wanted to hide the magic).
i.e. when someone types a "dog" then you increase your max count from 30 to 35. (1 has been used and a dog usually counts for 6)
Ref:
https://twitter.com/riggaroo/status/1148278279713017858
https://developer.android.com/reference/java/text/BreakIterator
https://lemire.me/blog/2018/06/15/emojis-java-and-strings/
https://developer.android.com/reference/kotlin/androidx/emoji/text/EmojiCompat
editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
oldTextString = charSequence.toString()
}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) {
var newTextString = editable.toString()
if (!oldTextString.equals(newTextString)) {
if (Character.codePointCount(
newTextString,
0,
newTextString.length
) > maxCharactersAllowed
) {
newTextString = oldTextString
}
editText.setText(newTextString)
editText.setSelection(newTextString.length)
}
}
})
Related
I have an edittext. I've added a text watcher to edittext. I listen text changes. İf the word user typing starts with #, I show user suggestions (like when you type # and twitter show you suggestions)
If text starts with a normal letter everything works fine.
For example:
hello #random_user how are you
#this also works because there is an empty space before '#'
This examples works.
However if text starts with special characters Text Watcher shows incorrect values
For example:
#hello_user
#someHashtag
text watcher return false value. I'm using onTextChanged method to track text
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//Text in edittext is '#user' but I get:
//start = 0, before = 0 and count = 1;
//edittext.getSelectionStart() also returns 1 but cursor is at the end of the line.
//edittext.getText().toString().length() also returns 1 but #user is 5 length.
}
How can I solve this?
Edit: edittext.postdesc.getText().toString() only returns first char. For example if my text is '#user', getText method only returns #
You can try this workaround:
class TextWatcherExtended(
private val onTextChanged: (text: CharSequence?, start: Int, before: Int, count: Int) -> Unit
) : TextWatcher {
private var before = 0
override fun afterTextChanged(s: Editable?) {
}
override fun beforeTextChanged(text: CharSequence?, start: Int, count: Int, after: Int) {
this.before = text?.length ?: 0
}
override fun onTextChanged(text: CharSequence?, start: Int, before: Int, count: Int) {
onTextChanged.invoke(text, start, this.before, count)
}
}
I want to compare two String objects coming out of two different lists and no matter if i use
equals(), contentEquals() or ==, it is always false.
Has it something to do with how the strings of the first list are put into it?
edit: it's getting weirder look at the log outcome in this picture:
DictWord.dictWords.forEach {
Log.i("testen", "it is: $it and equals 'black'? - ${it.equals("black")}")
Log.i("testen", "it is: $it and equals $newWord - ${it.equals(newWord)}")
Log.i("testen", "it is: $it and equals $newWord - ${it.contentEquals("black")}")
Log.i("testen", "it is: $it and == $newWord - ${it == newWord}")
Log.i("black", "it is: 'black' and equals $newWord - ${"black" == newWord}")
... subStrainsAdapter.addHeaderAndSubmitList(null)
var textList = mutableListOf<String>()
var movingText = ""
thoughtContent.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable) {}
override fun beforeTextChanged(
s: CharSequence,
start: Int, count: Int, after: Int) {}
override fun onTextChanged(
s: CharSequence,
start: Int, before: Int, count: Int)
{movingText += s.last()}
})
//SUBSTRAIN INPUT - goes to onSubStrainEditEnd above when ENTER hit
thoughtContent.setOnKeyListener(object : View.OnKeyListener {
#RequiresApi(Build.VERSION_CODES.Q)
override fun onKey(v: View?, key: Int, event: KeyEvent): Boolean {
return if (event.action == KeyEvent.ACTION_DOWN && key == KeyEvent.KEYCODE_SPACE) {
textList.add(movingText)
movingText = ""
false } else false
}})
Output log for the above code:
edit
if(b == false) {
thoughtsViewModel.editThought(thoughtContent.text.toString(), thoughtItem.id)
val testList = thoughtContent.text.toString().split(" ")
textList.forEach {
(Log.i("testen", "it is $it"))
if(DictWord.dictWords.keys.contains(it)) {Log.i("testen", "TRIGGGEERRRED and its $it")}
}
testList.forEach {
(Log.i("testen", "it is $it"))
if(DictWord.dictWords.keys.contains(it)) {Log.i("testen", "test list TRIGGGEERRRED and its $it")}
}
newWord is not trimmed it seems. From your log, it has an extra space before it.
These two lines in the log correspond to this code respectively:
Log.i("testen", "it is: $it and equals 'black'? - ${it.equals("black")}")
Log.i("testen", "it is: $it and equals $newWord - ${it.equals(newWord)}")
You can see that you didn't add two spaces in the second line but still your black has an extra space before
You should either correct your list or do newWord.trim() which will remove all leading and trailing whitespaces
Otherwise you should always use String.equals(otherString: String) or s1 == s2 (they are the same in kotlin)
Your example is badly formatted and I believe missing some code, but as #snachmsm pointed out, contentEquals is a method on arrays in Kotlin. You should use equals() for Strings instead (though == is the preferred way in Kotlin)
I am filtering most characters and numbers out in this use case. When I type several 'filtered' characters, let's say 555, the source of each following filter event still has those 5's in it, even though they have been filtered out. This means that after typing 555, and having nothing appear in the EditText, I have to backspace 3 times before I actually start backspacing what is in the EditText. Not only that, but my 'invalid input' toast fires on every backspace, because my source still has the 5's in it.
So, if I type abc123abc, my field shows abcabc, but logging my source shows abc123abc and I throw invalid toasts all over the place.
The superclass for the filter has nothing except a protected method to show the toast, and is used on the filters that work, as well.
InputFilter
class TextInputFilter constructor(
private val letters: Boolean,
private val numbers: Boolean,
private val whitespace: Boolean,
private val extraCharacters: Array<Char>,
context: Context?
) : ToastInputFilter(context) {
override fun filter(source: CharSequence, start: Int, end: Int, dest: Spanned?, dstart: Int, dend: Int): CharSequence? {
var valid = true
val builder = StringBuilder()
source.forEach { c ->
if (c.isValid()) {
builder.append(c)
} else {
valid = false
}
}
return if (valid) {
null
} else {
showInputToast(R.string.textInputInvalid)
if (source is Spanned) {
val spannable = SpannableString(builder)
TextUtils.copySpansFrom(source, start, builder.length, null, spannable, 0)
spannable
} else {
builder
}
}
}
private fun Char.isValid(): Boolean {
return when {
isLetter() -> letters
isDigit() -> numbers
isWhitespace() -> whitespace
else -> this in extraCharacters
}
}
}
Instantiation with args
titleEditText.filters = arrayOf(
TextInputFilter(letters = true, numbers = false, whitespace = true, extraCharacters = chars, context = context)
)
The extra chars that I'm allowing
chars = safeGetString(R.string.alphaExtraChars).toCharArray().toTypedArray()
<string name="alphaExtraChars">\'.-</string>
I've tried everything I can think of, and I have other filters that work fine on number input, because the source does not come in as Spannable, and only deals with the new piece of input, not the entire field.
Edit after using start|end arguments, this happens:
A -> Log: A | Display: A
b -> Log: Ab | Display: Ab
c -> Log: Abc | Display: Abc
1 -> Log: Abc1 | Display: Abc + TOAST
2 -> Log: Abc12 | Display: Abc + TOAST
3 -> Log: Abc123 | Display: Abc + TOAST
Backspace -> Log: Ab | Display: Ab
Good.
But, when I keep typing valid characters after invalid characters:
A -> Log: A | Display: A
1 -> Log: A1 | Display: A + TOAST
a -> Log: A1a | Display: Aa + TOAST
1 -> Log: A1a1 | Display: Aa + TOAST
a -> Log: A1a1a | Display: Aaa + TOAST
Backspace -> Log: A1a1 | Display: Aa + TOAST
Backspace -> Log: A | Display: A
I suggest this to concisely take the source delimiters into account:
override fun filter(source: CharSequence, start: Int, end: Int, dest: Spanned?, dstart: Int, dend: Int): CharSequence? {
val builder = StringBuilder()
for (c in source.subSequence(start, end) {
if (c.isValid()) builder.append(c)
}
return if (builder.length == end - start) {
null
} else {
showInputToast(R.string.textInputInvalid)
//...
}
}
You might consider simplifying the whole thing to reject any incoming block of text if it contains any invalid characters. Presumably, you will get either one new character at a time, or the user is pasting a block of text. Do you really want to filter pieces of a block of text that contains invalid characters? In many cases, this would be unexpected behavior.
So if it's acceptable behavior to do this, your filter simplifies down to:
override fun filter(source: CharSequence, start: Int, end: Int, dest: Spanned?, dstart: Int, dend: Int): CharSequence? {
for (i in start until end) {
if (!source.charAt(i).isValid()) {
showInputToast(R.string.textInputInvalid)
return ""
}
}
return null
}
I have EditText with content: #<m id="36c03920-f411-4919-a175-a5b1eb616592">Full name</m>, how are you doing?. I would like to hide tag with id from the user and keep only #Full name, how are you doing? when displaying text. Still getText should return full content.
I found ReplacementSpan useful to this approach. At first step, tried replacing only </m> but text is drawn twice in two lines. First line starts with # and second one starts with <. Also cannot insert new text. getSize is called multiple times and draw is called twice.
Is my approach correct? Should I try to found different solution and store ids in separate collection and do post processing on getText()?
Code:
inner class RemoveTagSpan : ReplacementSpan() {
private val regexEndTag = Regex("</m>")
override fun draw(canvas: Canvas, text: CharSequence?, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {
text?.replace(regexEndTag, "")?.let { canvas.drawText(it, start, it.length - 1, x, y.toFloat(), paint) }
}
override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fm: FontMetricsInt?): Int {
return text?.replace(regexEndTag, "")?.let { paint.measureText(it).roundToInt() } ?: 0
}
}
Not sure if this will give you exactly what you want, but generally displaying markup language in a TextView or EditText(?) can be done in this way:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
textView.setText(Html.fromHtml("#<m id="36c03920-f411-4919-a175-a5b1eb616592">Full name</m>, how are you doing?", Html.FROM_HTML_MODE_COMPACT));
} else {
textView.setText(Html.fromHtml("#<m id="36c03920-f411-4919-a175-a5b1eb616592">Full name</m>, how are you doing?"));
So I'm using a text watcher to show the user suggestions for tags.
Currently I am using a dummy list of tags. When the user starts typing, the code should create a another list of tags that is just the objects of the first list filtered based on whether they contain the character sequence the user is currently typing.
It works great for the first character, but as the user keeps typing the list doesn't change anymore.
This is my code:
val tags = listOf(
"John Smith",
"Kate Eckhart",
"Emily Sun",
"Frodo Baggins",
"Yanay Zabary",
"Sze Lok Ho",
"Jesse Albright",
"Shayna something",
"Makena Lawrence"
)
questionTagsInput.addTextChangedListener(object : TextWatcher {
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
val userInput = s.toString()
if (userInput == "") {
tagSuggestionRecycler.visibility = View.GONE
tagsFiltredAdapter.clear()
} else {
val relevantTags: List<String> = tags.filter { it.contains(userInput) }
for (t in relevantTags) {
tagSuggestionRecycler.visibility = View.VISIBLE
tagsFiltredAdapter.add(SingleTagSuggestion(t))
}
}
}
override fun afterTextChanged(s: Editable?) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
})
You Need to clear the tagsFiltredAdapter in each call of onTextChanged.
Currently, when typing the first character, your filtered list contains the right results, and when typing more characters, the list's size should decrease to match the new characters. Instead, you just adding items to the list.