diff --git a/atox/src/main/kotlin/ui/chat/ChatAdapter.kt b/atox/src/main/kotlin/ui/chat/ChatAdapter.kt index 7d153e6e..891e9d7c 100644 --- a/atox/src/main/kotlin/ui/chat/ChatAdapter.kt +++ b/atox/src/main/kotlin/ui/chat/ChatAdapter.kt @@ -1,7 +1,12 @@ package ltd.evilcorp.atox.ui.chat import android.content.res.Resources +import android.graphics.Typeface +import android.text.Spannable import android.text.format.Formatter +import android.text.style.RelativeSizeSpan +import android.text.style.StyleSpan +import android.text.style.TypefaceSpan import android.util.Log import android.view.Gravity import android.view.LayoutInflater @@ -15,6 +20,7 @@ import android.widget.ListView import android.widget.ProgressBar import android.widget.RelativeLayout import android.widget.TextView +import androidx.core.text.toSpannable import com.squareup.picasso.Picasso import java.net.URLConnection import java.text.DateFormat @@ -31,6 +37,27 @@ import ltd.evilcorp.core.vo.isStarted private const val TAG = "ChatAdapter" +private fun applyStyle( + view: TextView, + regex: Regex, + typefaceStyle: Int, + fontFamily: String = "", + size: Float = 1f, +) { + val spannable = view.text.toSpannable() + for (match in regex.findAll(spannable)) { + val start = match.range.first + val end = match.range.last + 1 + spannable.setSpan(RelativeSizeSpan(size), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + spannable.setSpan(StyleSpan(typefaceStyle), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + if (fontFamily.isNotEmpty()) { + spannable.setSpan(TypefaceSpan(fontFamily), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + } + + view.text = spannable +} + private fun FileTransfer.isImage() = try { URLConnection.guessContentTypeFromName(fileName).startsWith("image/") } catch (e: Exception) { @@ -151,6 +178,11 @@ class ChatAdapter( } } + applyStyle(vh.message, Regex("(? { diff --git a/atox/src/main/kotlin/ui/chat/ChatFragment.kt b/atox/src/main/kotlin/ui/chat/ChatFragment.kt index 1fc58fbb..a4f320ad 100644 --- a/atox/src/main/kotlin/ui/chat/ChatFragment.kt +++ b/atox/src/main/kotlin/ui/chat/ChatFragment.kt @@ -5,19 +5,27 @@ import android.content.ActivityNotFoundException import android.content.ClipData import android.content.ClipboardManager import android.content.Intent +import android.graphics.Typeface import android.net.Uri import android.os.Bundle +import android.text.Spannable +import android.text.style.ForegroundColorSpan +import android.text.style.RelativeSizeSpan +import android.text.style.StyleSpan +import android.text.style.TypefaceSpan import android.view.ContextMenu import android.view.MenuItem import android.view.MotionEvent import android.view.View import android.widget.AdapterView +import android.widget.EditText import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.core.content.FileProvider import androidx.core.content.getSystemService import androidx.core.content.res.ResourcesCompat import androidx.core.os.bundleOf +import androidx.core.text.getSpans import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsAnimationCompat import androidx.core.view.WindowInsetsCompat @@ -50,6 +58,39 @@ import ltd.evilcorp.domain.tox.PublicKey const val CONTACT_PUBLIC_KEY = "publicKey" private const val MAX_CONFIRM_DELETE_STRING_LENGTH = 20 +private inline fun clearStyle(e: Spannable) { + for (span in e.getSpans()) { + e.removeSpan(span) + } +} + +private fun clearStyles(view: EditText) { + val spannable = view.text + clearStyle(spannable) + clearStyle(spannable) + clearStyle(spannable) + clearStyle(spannable) +} + +private fun applyStyle( + view: EditText, + regex: Regex, + typefaceStyle: Int, + fontFamily: String = "", + size: Float = 1f, +) { + val spannable = view.text + for (match in regex.findAll(spannable)) { + val start = match.range.first + val end = match.range.last + 1 + spannable.setSpan(RelativeSizeSpan(size), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + spannable.setSpan(StyleSpan(typefaceStyle), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + if (fontFamily.isNotEmpty()) { + spannable.setSpan(TypefaceSpan(fontFamily), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + } +} + class ChatFragment : BaseFragment(FragmentChatBinding::inflate) { private val viewModel: ChatViewModel by viewModels { vmFactory } @@ -265,6 +306,11 @@ class ChatFragment : BaseFragment(FragmentChatBinding::infl outgoingMessage.doAfterTextChanged { viewModel.setTyping(outgoingMessage.text.isNotEmpty()) updateActions() + clearStyles(outgoingMessage) + applyStyle(outgoingMessage, Regex("(?