Add text formatting send and receive support for conversations.

This commit is contained in:
Cody Henthorne
2023-01-25 10:31:36 -05:00
committed by Greyson Parrelli
parent aa2075c78f
commit cc490f4b73
73 changed files with 1664 additions and 516 deletions

View File

@@ -1,5 +1,7 @@
package org.signal.core.util;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import androidx.annotation.NonNull;
@@ -326,4 +328,52 @@ public final class StringUtil {
public static String forceLtr(@NonNull CharSequence text) {
return "\u202a" + text + "\u202c";
}
public static @NonNull CharSequence replace(@NonNull CharSequence text, char toReplace, String replacement) {
SpannableStringBuilder updatedText = null;
for (int i = text.length() - 1; i >= 0; i--) {
if (text.charAt(i) == toReplace) {
if (updatedText == null) {
updatedText = SpannableStringBuilder.valueOf(text);
}
updatedText.replace(i, i + 1, replacement);
}
}
if (updatedText != null) {
return updatedText;
} else {
return text;
}
}
public static boolean startsWith(@NonNull CharSequence text, @NonNull CharSequence substring) {
if (substring.length() > text.length()) {
return false;
}
for (int i = 0; i < substring.length(); i++) {
if (text.charAt(i) != substring.charAt(i)) {
return false;
}
}
return true;
}
public static boolean endsWith(@NonNull CharSequence text, @NonNull CharSequence substring) {
if (substring.length() > text.length()) {
return false;
}
int textIndex = text.length() - 1;
for (int substringIndex = substring.length() - 1; substringIndex >= 0; substringIndex--, textIndex--) {
if (text.charAt(textIndex) != substring.charAt(substringIndex)) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,54 @@
@file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
package org.signal.core.util
import android.app.Application
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.ParameterizedRobolectricTestRunner
import org.robolectric.ParameterizedRobolectricTestRunner.Parameter
import org.robolectric.ParameterizedRobolectricTestRunner.Parameters
import org.robolectric.annotation.Config
import java.lang.Boolean as JavaBoolean
@Suppress("ClassName")
@RunWith(value = ParameterizedRobolectricTestRunner::class)
@Config(manifest = Config.NONE, application = Application::class)
class StringUtilTest_endsWith {
@Parameter(0)
lateinit var text: CharSequence
@Parameter(1)
lateinit var substring: CharSequence
@Parameter(2)
lateinit var expected: JavaBoolean
companion object {
@JvmStatic
@Parameters
fun data(): Collection<Array<Any>> {
return listOf(
arrayOf("Text", "xt", true),
arrayOf("Text", "", true),
arrayOf("Text", "XT", false),
arrayOf("Text…", "xt…", true),
arrayOf("", "Te", false),
arrayOf("Text", "Text", true),
arrayOf("Text", "2Text", false),
arrayOf("\uD83D\uDC64Text", "Te", false),
arrayOf("Text text text\uD83D\uDC64", "\uD83D\uDC64", true),
arrayOf("Text\uD83D\uDC64Text", "\uD83D\uDC64Text", true)
)
}
}
@Test
fun replace() {
val result = StringUtil.endsWith(text, substring)
assertEquals(expected, result)
}
}

View File

@@ -0,0 +1,51 @@
package org.signal.core.util
import android.app.Application
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.ParameterizedRobolectricTestRunner
import org.robolectric.ParameterizedRobolectricTestRunner.Parameter
import org.robolectric.ParameterizedRobolectricTestRunner.Parameters
import org.robolectric.annotation.Config
@Suppress("ClassName")
@RunWith(value = ParameterizedRobolectricTestRunner::class)
@Config(manifest = Config.NONE, application = Application::class)
class StringUtilTest_replace {
@Parameter(0)
lateinit var text: CharSequence
@Parameter(1)
lateinit var charToReplace: Character
@Parameter(2)
lateinit var replacement: String
@Parameter(3)
lateinit var expected: CharSequence
companion object {
@JvmStatic
@Parameters
fun data(): Collection<Array<Any>> {
return listOf(
arrayOf("Replace\nme", '\n', " ", "Replace me"),
arrayOf("Replace me", '\n', " ", "Replace me"),
arrayOf("\nReplace me", '\n', " ", " Replace me"),
arrayOf("Replace me\n", '\n', " ", "Replace me "),
arrayOf("Replace\n\nme", '\n', " ", "Replace me"),
arrayOf("Replace\nme\n", '\n', " ", "Replace me "),
arrayOf("\n\nReplace\n\nme\n", '\n', " ", " Replace me ")
)
}
}
@Test
fun replace() {
val result = StringUtil.replace(text, charToReplace.charValue(), replacement)
assertEquals(expected.toString(), result.toString())
}
}

View File

@@ -0,0 +1,54 @@
@file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
package org.signal.core.util
import android.app.Application
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.ParameterizedRobolectricTestRunner
import org.robolectric.ParameterizedRobolectricTestRunner.Parameter
import org.robolectric.ParameterizedRobolectricTestRunner.Parameters
import org.robolectric.annotation.Config
import java.lang.Boolean as JavaBoolean
@Suppress("ClassName")
@RunWith(value = ParameterizedRobolectricTestRunner::class)
@Config(manifest = Config.NONE, application = Application::class)
class StringUtilTest_startsWith {
@Parameter(0)
lateinit var text: CharSequence
@Parameter(1)
lateinit var substring: CharSequence
@Parameter(2)
lateinit var expected: JavaBoolean
companion object {
@JvmStatic
@Parameters
fun data(): Collection<Array<Any>> {
return listOf(
arrayOf("Text", "Te", true),
arrayOf("Text", "", true),
arrayOf("Text", "te", false),
arrayOf("…Text", "…Te", true),
arrayOf("", "Te", false),
arrayOf("Text", "Text", true),
arrayOf("Text", "Text2", false),
arrayOf("\uD83D\uDC64Text", "Te", false),
arrayOf("Text text text\uD83D\uDC64", "\uD83D\uDC64", false),
arrayOf("\uD83D\uDC64Text", "\uD83D\uDC64Te", true)
)
}
}
@Test
fun replace() {
val result = StringUtil.startsWith(text, substring)
assertEquals(expected, result)
}
}