correctly count emojis when composing a post (#4152)
Thx to @evant for the help closes #4140
This commit is contained in:
parent
4f38678be7
commit
ee3760fcc9
4 changed files with 46 additions and 24 deletions
|
|
@ -25,6 +25,7 @@ import android.content.pm.PackageManager
|
|||
import android.graphics.Bitmap
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffColorFilter
|
||||
import android.icu.text.BreakIterator
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
|
|
@ -1406,7 +1407,7 @@ class ComposeActivity :
|
|||
*/
|
||||
@JvmStatic
|
||||
fun statusLength(body: Spanned, contentWarning: Spanned?, urlLength: Int): Int {
|
||||
var length = body.length - body.getSpans(0, body.length, URLSpan::class.java)
|
||||
var length = body.toString().perceivedCharacterLength() - body.getSpans(0, body.length, URLSpan::class.java)
|
||||
.fold(0) { acc, span ->
|
||||
// Accumulate a count of characters to be *ignored* in the final length
|
||||
acc + when (span) {
|
||||
|
|
@ -1419,15 +1420,25 @@ class ComposeActivity :
|
|||
}
|
||||
else -> {
|
||||
// Expected to be negative if the URL length < maxUrlLength
|
||||
span.url.length - urlLength
|
||||
span.url.perceivedCharacterLength() - urlLength
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Content warning text is treated as is, URLs or mentions there are not special
|
||||
contentWarning?.let { length += it.length }
|
||||
|
||||
contentWarning?.let { length += it.toString().perceivedCharacterLength() }
|
||||
return length
|
||||
}
|
||||
|
||||
// String.length would count emojis as multiple characters but Mastodon counts them as 1, so we need this workaround
|
||||
private fun String.perceivedCharacterLength(): Int {
|
||||
val breakIterator = BreakIterator.getCharacterInstance()
|
||||
breakIterator.setText(this)
|
||||
var count = 0
|
||||
while (breakIterator.next() != BreakIterator.DONE) {
|
||||
count++
|
||||
}
|
||||
return count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package com.keylesspalace.tusky.components.compose.ComposeActivity
|
||||
package com.keylesspalace.tusky.components.compose
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Looper.getMainLooper
|
||||
|
|
@ -24,8 +24,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
|||
import at.connyduck.calladapter.networkresult.NetworkResult
|
||||
import com.google.gson.Gson
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.components.compose.ComposeActivity
|
||||
import com.keylesspalace.tusky.components.compose.ComposeViewModel
|
||||
import com.keylesspalace.tusky.components.instanceinfo.InstanceInfoRepository
|
||||
import com.keylesspalace.tusky.db.AccountEntity
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
|
|
@ -252,7 +250,21 @@ class ComposeActivityTest {
|
|||
fun whenTextContainsNoUrl_everyCharacterIsCounted() {
|
||||
val content = "This is test content please ignore thx "
|
||||
insertSomeTextInContent(content)
|
||||
assertEquals(activity.calculateTextLength(), content.length)
|
||||
assertEquals(content.length, activity.calculateTextLength())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenTextContainsEmoji_emojisAreCountedAsOneCharacter() {
|
||||
val content = "Test 😜"
|
||||
insertSomeTextInContent(content)
|
||||
assertEquals(6, activity.calculateTextLength())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenTextContainsUrlWithEmoji_ellipsizedUrlIsCountedCorrectly() {
|
||||
val content = "https://🤪.com"
|
||||
insertSomeTextInContent(content)
|
||||
assertEquals(InstanceInfoRepository.DEFAULT_CHARACTERS_RESERVED_PER_URL, activity.calculateTextLength())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -260,7 +272,7 @@ class ComposeActivityTest {
|
|||
val url = "https://www.google.dk/search?biw=1920&bih=990&tbm=isch&sa=1&ei=bmDrWuOoKMv6kwWOkIaoDQ&q=indiana+jones+i+hate+snakes+animated&oq=indiana+jones+i+hate+snakes+animated&gs_l=psy-ab.3...54174.55443.0.55553.9.7.0.0.0.0.255.333.1j0j1.2.0....0...1c.1.64.psy-ab..7.0.0....0.40G-kcDkC6A#imgdii=PSp15hQjN1JqvM:&imgrc=H0hyE2JW5wrpBM:"
|
||||
val additionalContent = "Check out this @image #search result: "
|
||||
insertSomeTextInContent(additionalContent + url)
|
||||
assertEquals(activity.calculateTextLength(), additionalContent.length + InstanceInfoRepository.DEFAULT_CHARACTERS_RESERVED_PER_URL)
|
||||
assertEquals(additionalContent.length + InstanceInfoRepository.DEFAULT_CHARACTERS_RESERVED_PER_URL, activity.calculateTextLength())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -269,7 +281,7 @@ class ComposeActivityTest {
|
|||
val url = "https://www.google.dk/search?biw=1920&bih=990&tbm=isch&sa=1&ei=bmDrWuOoKMv6kwWOkIaoDQ&q=indiana+jones+i+hate+snakes+animated&oq=indiana+jones+i+hate+snakes+animated&gs_l=psy-ab.3...54174.55443.0.55553.9.7.0.0.0.0.255.333.1j0j1.2.0....0...1c.1.64.psy-ab..7.0.0....0.40G-kcDkC6A#imgdii=PSp15hQjN1JqvM:&imgrc=H0hyE2JW5wrpBM:"
|
||||
val additionalContent = " Check out this @image #search result: "
|
||||
insertSomeTextInContent(shortUrl + additionalContent + url)
|
||||
assertEquals(activity.calculateTextLength(), additionalContent.length + (InstanceInfoRepository.DEFAULT_CHARACTERS_RESERVED_PER_URL * 2))
|
||||
assertEquals(additionalContent.length + (InstanceInfoRepository.DEFAULT_CHARACTERS_RESERVED_PER_URL * 2), activity.calculateTextLength())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -277,7 +289,7 @@ class ComposeActivityTest {
|
|||
val url = "https://www.google.dk/search?biw=1920&bih=990&tbm=isch&sa=1&ei=bmDrWuOoKMv6kwWOkIaoDQ&q=indiana+jones+i+hate+snakes+animated&oq=indiana+jones+i+hate+snakes+animated&gs_l=psy-ab.3...54174.55443.0.55553.9.7.0.0.0.0.255.333.1j0j1.2.0....0...1c.1.64.psy-ab..7.0.0....0.40G-kcDkC6A#imgdii=PSp15hQjN1JqvM:&imgrc=H0hyE2JW5wrpBM:"
|
||||
val additionalContent = " Check out this @image #search result: "
|
||||
insertSomeTextInContent(url + additionalContent + url)
|
||||
assertEquals(activity.calculateTextLength(), additionalContent.length + (InstanceInfoRepository.DEFAULT_CHARACTERS_RESERVED_PER_URL * 2))
|
||||
assertEquals(additionalContent.length + (InstanceInfoRepository.DEFAULT_CHARACTERS_RESERVED_PER_URL * 2), activity.calculateTextLength())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -289,7 +301,7 @@ class ComposeActivityTest {
|
|||
setupActivity()
|
||||
shadowOf(getMainLooper()).idle()
|
||||
insertSomeTextInContent(additionalContent + url)
|
||||
assertEquals(activity.calculateTextLength(), additionalContent.length + customUrlLength)
|
||||
assertEquals(additionalContent.length + customUrlLength, activity.calculateTextLength())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -301,7 +313,7 @@ class ComposeActivityTest {
|
|||
setupActivity()
|
||||
shadowOf(getMainLooper()).idle()
|
||||
insertSomeTextInContent(additionalContent + url)
|
||||
assertEquals(activity.calculateTextLength(), additionalContent.length + customUrlLength)
|
||||
assertEquals(additionalContent.length + customUrlLength, activity.calculateTextLength())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -314,7 +326,7 @@ class ComposeActivityTest {
|
|||
setupActivity()
|
||||
shadowOf(getMainLooper()).idle()
|
||||
insertSomeTextInContent(shortUrl + additionalContent + url)
|
||||
assertEquals(activity.calculateTextLength(), additionalContent.length + (customUrlLength * 2))
|
||||
assertEquals(additionalContent.length + (customUrlLength * 2), activity.calculateTextLength())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -327,7 +339,7 @@ class ComposeActivityTest {
|
|||
setupActivity()
|
||||
shadowOf(getMainLooper()).idle()
|
||||
insertSomeTextInContent(shortUrl + additionalContent + url)
|
||||
assertEquals(activity.calculateTextLength(), additionalContent.length + (customUrlLength * 2))
|
||||
assertEquals(additionalContent.length + (customUrlLength * 2), activity.calculateTextLength())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -339,7 +351,7 @@ class ComposeActivityTest {
|
|||
setupActivity()
|
||||
shadowOf(getMainLooper()).idle()
|
||||
insertSomeTextInContent(url + additionalContent + url)
|
||||
assertEquals(activity.calculateTextLength(), additionalContent.length + (customUrlLength * 2))
|
||||
assertEquals(additionalContent.length + (customUrlLength * 2), activity.calculateTextLength())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -351,7 +363,7 @@ class ComposeActivityTest {
|
|||
setupActivity()
|
||||
shadowOf(getMainLooper()).idle()
|
||||
insertSomeTextInContent(url + additionalContent + url)
|
||||
assertEquals(activity.calculateTextLength(), additionalContent.length + (customUrlLength * 2))
|
||||
assertEquals(additionalContent.length + (customUrlLength * 2), activity.calculateTextLength())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -13,9 +13,8 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.components.compose.ComposeTokenizer
|
||||
package com.keylesspalace.tusky.components.compose
|
||||
|
||||
import com.keylesspalace.tusky.components.compose.ComposeTokenizer
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
|
@ -15,29 +15,29 @@
|
|||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package com.keylesspalace.tusky.components.compose.ComposeActivity
|
||||
package com.keylesspalace.tusky.components.compose
|
||||
|
||||
import com.keylesspalace.tusky.SpanUtilsTest
|
||||
import com.keylesspalace.tusky.components.compose.ComposeActivity
|
||||
import com.keylesspalace.tusky.util.highlightSpans
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner
|
||||
|
||||
@RunWith(Parameterized::class)
|
||||
@RunWith(ParameterizedRobolectricTestRunner::class)
|
||||
class StatusLengthTest(
|
||||
private val text: String,
|
||||
private val expectedLength: Int
|
||||
) {
|
||||
companion object {
|
||||
@Parameterized.Parameters(name = "{0}")
|
||||
@ParameterizedRobolectricTestRunner.Parameters(name = "{0}")
|
||||
@JvmStatic
|
||||
fun data(): Iterable<Any> {
|
||||
return listOf(
|
||||
arrayOf("", 0),
|
||||
arrayOf(" ", 1),
|
||||
arrayOf("123", 3),
|
||||
arrayOf("🫣", 1),
|
||||
// "@user@server" should be treated as "@user"
|
||||
arrayOf("123 @example@example.org", 12),
|
||||
// URLs under 23 chars are treated as 23 chars
|
||||
Loading…
Add table
Add a link
Reference in a new issue