mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 12:05:22 +00:00
Highlight @You mentions (#985)
* Highlight @You mentions * fix: resolve merge conflicts * Setting the proper design rules for mentions * New RoundedBackgroundSpan, applied to "you" mentions The rounded background highlighter can take padding, so there is no need to add those extra spaces at the start and end. * Better mention highlight logic Some mention highlight should only format the text and not apply any styling. Also making sure we cater for all cases properly * Updated the text color logic based on design rules * Fine tuning the color rules * Removing usage of Resources.getSystem() Only making the db call if there actually is a mention * Moving color definition outside the loop to avoid repetitions --------- Co-authored-by: charles <charles@oxen.io> Co-authored-by: 0x330a <92654767+0x330a@users.noreply.github.com> Co-authored-by: ThomasSession <thomas.r@getsession.org>
This commit is contained in:
parent
1d80bb0ba9
commit
a260717d42
@ -1958,7 +1958,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
val messageIterator = sortedMessages.iterator()
|
val messageIterator = sortedMessages.iterator()
|
||||||
while (messageIterator.hasNext()) {
|
while (messageIterator.hasNext()) {
|
||||||
val message = messageIterator.next()
|
val message = messageIterator.next()
|
||||||
val body = MentionUtilities.highlightMentions(message.body, viewModel.threadId, this)
|
val body = MentionUtilities.highlightMentions(
|
||||||
|
text = message.body,
|
||||||
|
formatOnly = true, // no styling here, only text formatting
|
||||||
|
threadID = viewModel.threadId,
|
||||||
|
context = this
|
||||||
|
)
|
||||||
|
|
||||||
if (TextUtils.isEmpty(body)) { continue }
|
if (TextUtils.isEmpty(body)) { continue }
|
||||||
if (messageSize > 1) {
|
if (messageSize > 1) {
|
||||||
val formattedTimestamp = DateUtils.getDisplayFormattedTimeSpanString(this, Locale.getDefault(), message.timestamp)
|
val formattedTimestamp = DateUtils.getDisplayFormattedTimeSpanString(this, Locale.getDefault(), message.timestamp)
|
||||||
|
@ -80,7 +80,15 @@ class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
|
|||||||
binding.quoteViewAuthorTextView.text = authorDisplayName
|
binding.quoteViewAuthorTextView.text = authorDisplayName
|
||||||
binding.quoteViewAuthorTextView.setTextColor(getTextColor(isOutgoingMessage))
|
binding.quoteViewAuthorTextView.setTextColor(getTextColor(isOutgoingMessage))
|
||||||
// Body
|
// Body
|
||||||
binding.quoteViewBodyTextView.text = if (isOpenGroupInvitation) resources.getString(R.string.open_group_invitation_view__open_group_invitation) else MentionUtilities.highlightMentions((body ?: "").toSpannable(), threadID, context)
|
binding.quoteViewBodyTextView.text = if (isOpenGroupInvitation)
|
||||||
|
resources.getString(R.string.open_group_invitation_view__open_group_invitation)
|
||||||
|
else MentionUtilities.highlightMentions(
|
||||||
|
text = (body ?: "").toSpannable(),
|
||||||
|
isOutgoingMessage = isOutgoingMessage,
|
||||||
|
isQuote = true,
|
||||||
|
threadID = threadID,
|
||||||
|
context = context
|
||||||
|
)
|
||||||
binding.quoteViewBodyTextView.setTextColor(getTextColor(isOutgoingMessage))
|
binding.quoteViewBodyTextView.setTextColor(getTextColor(isOutgoingMessage))
|
||||||
// Accent line / attachment preview
|
// Accent line / attachment preview
|
||||||
val hasAttachments = (attachments != null && attachments.asAttachments().isNotEmpty()) && !isOriginalMissing
|
val hasAttachments = (attachments != null && attachments.asAttachments().isNotEmpty()) && !isOriginalMissing
|
||||||
|
@ -282,7 +282,12 @@ class VisibleMessageContentView : ConstraintLayout {
|
|||||||
fun getBodySpans(context: Context, message: MessageRecord, searchQuery: String?): Spannable {
|
fun getBodySpans(context: Context, message: MessageRecord, searchQuery: String?): Spannable {
|
||||||
var body = message.body.toSpannable()
|
var body = message.body.toSpannable()
|
||||||
|
|
||||||
body = MentionUtilities.highlightMentions(body, message.isOutgoing, message.threadId, context)
|
body = MentionUtilities.highlightMentions(
|
||||||
|
text = body,
|
||||||
|
isOutgoingMessage = message.isOutgoing,
|
||||||
|
threadID = message.threadId,
|
||||||
|
context = context
|
||||||
|
)
|
||||||
body = SearchUtil.getHighlightedSpan(Locale.getDefault(),
|
body = SearchUtil.getHighlightedSpan(Locale.getDefault(),
|
||||||
{ BackgroundColorSpan(Color.WHITE) }, body, searchQuery)
|
{ BackgroundColorSpan(Color.WHITE) }, body, searchQuery)
|
||||||
body = SearchUtil.getHighlightedSpan(Locale.getDefault(),
|
body = SearchUtil.getHighlightedSpan(Locale.getDefault(),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package org.thoughtcrime.securesms.conversation.v2.utilities
|
package org.thoughtcrime.securesms.conversation.v2.utilities
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
@ -9,43 +9,60 @@ import android.text.style.BackgroundColorSpan
|
|||||||
import android.text.style.ForegroundColorSpan
|
import android.text.style.ForegroundColorSpan
|
||||||
import android.text.style.StyleSpan
|
import android.text.style.StyleSpan
|
||||||
import android.util.Range
|
import android.util.Range
|
||||||
import androidx.appcompat.widget.ThemeUtils
|
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import nl.komponents.kovenant.combine.Tuple2
|
import nl.komponents.kovenant.combine.Tuple2
|
||||||
import org.session.libsession.messaging.contacts.Contact
|
import org.session.libsession.messaging.contacts.Contact
|
||||||
|
import org.session.libsession.messaging.open_groups.OpenGroup
|
||||||
import org.session.libsession.messaging.utilities.SodiumUtilities
|
import org.session.libsession.messaging.utilities.SodiumUtilities
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsession.utilities.ThemeUtil
|
import org.session.libsession.utilities.ThemeUtil
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsession.utilities.getColorFromAttr
|
||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||||
import org.thoughtcrime.securesms.util.UiModeUtilities
|
import org.thoughtcrime.securesms.util.RoundedBackgroundSpan
|
||||||
import org.thoughtcrime.securesms.util.getAccentColor
|
import org.thoughtcrime.securesms.util.getAccentColor
|
||||||
import org.thoughtcrime.securesms.util.getColorResourceIdFromAttr
|
import org.thoughtcrime.securesms.util.toPx
|
||||||
import org.thoughtcrime.securesms.util.getMessageTextColourAttr
|
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
object MentionUtilities {
|
object MentionUtilities {
|
||||||
|
|
||||||
@JvmStatic
|
private val pattern by lazy { Pattern.compile("@[0-9a-fA-F]*") }
|
||||||
fun highlightMentions(text: CharSequence, threadID: Long, context: Context): String {
|
|
||||||
return highlightMentions(text, false, threadID, context).toString() // isOutgoingMessage is irrelevant
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlights mentions in a given text.
|
||||||
|
*
|
||||||
|
* @param text The text to highlight mentions in.
|
||||||
|
* @param isOutgoingMessage Whether the message is outgoing.
|
||||||
|
* @param isQuote Whether the message is a quote.
|
||||||
|
* @param formatOnly Whether to only format the mentions. If true we only format the text itself,
|
||||||
|
* for example resolving an accountID to a username. If false we also apply styling, like colors and background.
|
||||||
|
* @param threadID The ID of the thread the message belongs to.
|
||||||
|
* @param context The context to use.
|
||||||
|
* @return A SpannableString with highlighted mentions.
|
||||||
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun highlightMentions(text: CharSequence, isOutgoingMessage: Boolean, threadID: Long, context: Context): SpannableString {
|
fun highlightMentions(
|
||||||
|
text: CharSequence,
|
||||||
|
isOutgoingMessage: Boolean = false,
|
||||||
|
isQuote: Boolean = false,
|
||||||
|
formatOnly: Boolean = false,
|
||||||
|
threadID: Long,
|
||||||
|
context: Context
|
||||||
|
): SpannableString {
|
||||||
@Suppress("NAME_SHADOWING") var text = text
|
@Suppress("NAME_SHADOWING") var text = text
|
||||||
val pattern = Pattern.compile("@[0-9a-fA-F]*")
|
|
||||||
var matcher = pattern.matcher(text)
|
var matcher = pattern.matcher(text)
|
||||||
val mentions = mutableListOf<Tuple2<Range<Int>, String>>()
|
val mentions = mutableListOf<Tuple2<Range<Int>, String>>()
|
||||||
var startIndex = 0
|
var startIndex = 0
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
||||||
val openGroup = DatabaseComponent.get(context).storage().getOpenGroup(threadID)
|
val openGroup by lazy { DatabaseComponent.get(context).storage().getOpenGroup(threadID) }
|
||||||
|
|
||||||
|
// format the mention text
|
||||||
if (matcher.find(startIndex)) {
|
if (matcher.find(startIndex)) {
|
||||||
while (true) {
|
while (true) {
|
||||||
val publicKey = text.subSequence(matcher.start() + 1, matcher.end()).toString() // +1 to get rid of the @
|
val publicKey = text.subSequence(matcher.start() + 1, matcher.end()).toString() // +1 to get rid of the @
|
||||||
val isUserBlindedPublicKey = openGroup?.let { SodiumUtilities.sessionId(userPublicKey, publicKey, it.publicKey) } ?: false
|
val isYou = isYou(publicKey, userPublicKey, openGroup)
|
||||||
val userDisplayName: String? = if (publicKey.equals(userPublicKey, ignoreCase = true) || isUserBlindedPublicKey) {
|
val userDisplayName: String? = if (isYou) {
|
||||||
context.getString(R.string.MessageRecord_you)
|
context.getString(R.string.MessageRecord_you)
|
||||||
} else {
|
} else {
|
||||||
val contact = DatabaseComponent.get(context).sessionContactDatabase().getContactWithSessionID(publicKey)
|
val contact = DatabaseComponent.get(context).sessionContactDatabase().getContactWithSessionID(publicKey)
|
||||||
@ -53,7 +70,8 @@ object MentionUtilities {
|
|||||||
contact?.displayName(context)
|
contact?.displayName(context)
|
||||||
}
|
}
|
||||||
if (userDisplayName != null) {
|
if (userDisplayName != null) {
|
||||||
text = text.subSequence(0, matcher.start()).toString() + "@" + userDisplayName + text.subSequence(matcher.end(), text.length)
|
val mention = "@$userDisplayName"
|
||||||
|
text = text.subSequence(0, matcher.start()).toString() + mention + text.subSequence(matcher.end(), text.length)
|
||||||
val endIndex = matcher.start() + 1 + userDisplayName.length
|
val endIndex = matcher.start() + 1 + userDisplayName.length
|
||||||
startIndex = endIndex
|
startIndex = endIndex
|
||||||
mentions.add(Tuple2(Range.create(matcher.start(), endIndex), publicKey))
|
mentions.add(Tuple2(Range.create(matcher.start(), endIndex), publicKey))
|
||||||
@ -66,37 +84,83 @@ object MentionUtilities {
|
|||||||
}
|
}
|
||||||
val result = SpannableString(text)
|
val result = SpannableString(text)
|
||||||
|
|
||||||
var mentionTextColour: Int? = null
|
// apply styling if required
|
||||||
// In dark themes..
|
// Normal text color: black in dark mode and primary text color for light mode
|
||||||
if (ThemeUtil.isDarkTheme(context)) {
|
val mainTextColor by lazy {
|
||||||
// ..we use the standard outgoing message colour for outgoing messages..
|
if (ThemeUtil.isDarkTheme(context)) context.getColor(R.color.black)
|
||||||
if (isOutgoingMessage) {
|
else context.getColorFromAttr(android.R.attr.textColorPrimary)
|
||||||
val mentionTextColourAttributeId = getMessageTextColourAttr(true)
|
|
||||||
val mentionTextColourResourceId = getColorResourceIdFromAttr(context, mentionTextColourAttributeId)
|
|
||||||
mentionTextColour = ResourcesCompat.getColor(context.resources, mentionTextColourResourceId, context.theme)
|
|
||||||
}
|
|
||||||
else // ..but we use the accent colour for incoming messages (i.e., someone mentioning us)..
|
|
||||||
{
|
|
||||||
mentionTextColour = context.getAccentColor()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else // ..while in light themes we always just use the incoming or outgoing message text colour for mentions.
|
|
||||||
{
|
|
||||||
val mentionTextColourAttributeId = getMessageTextColourAttr(isOutgoingMessage)
|
|
||||||
val mentionTextColourResourceId = getColorResourceIdFromAttr(context, mentionTextColourAttributeId)
|
|
||||||
mentionTextColour = ResourcesCompat.getColor(context.resources, mentionTextColourResourceId, context.theme)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Highlighted text color: primary/accent in dark mode and primary text color for light mode
|
||||||
|
val highlightedTextColor by lazy {
|
||||||
|
if (ThemeUtil.isDarkTheme(context)) context.getAccentColor()
|
||||||
|
else context.getColorFromAttr(android.R.attr.textColorPrimary)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!formatOnly) {
|
||||||
for (mention in mentions) {
|
for (mention in mentions) {
|
||||||
result.setSpan(ForegroundColorSpan(mentionTextColour), mention.first.lower, mention.first.upper, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
val backgroundColor: Int?
|
||||||
result.setSpan(StyleSpan(Typeface.BOLD), mention.first.lower, mention.first.upper, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
val foregroundColor: Int?
|
||||||
|
|
||||||
// If we're using a light theme then we change the background colour of the mention to be the accent colour
|
// quotes
|
||||||
if (ThemeUtil.isLightTheme(context)) {
|
if(isQuote) {
|
||||||
val backgroundColour = context.getAccentColor();
|
backgroundColor = null
|
||||||
result.setSpan(BackgroundColorSpan(backgroundColour), mention.first.lower, mention.first.upper, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
// the text color has different rule depending if the message is incoming or outgoing
|
||||||
|
foregroundColor = if(isOutgoingMessage) null else highlightedTextColor
|
||||||
|
}
|
||||||
|
// incoming message mentioning you
|
||||||
|
else if (isYou(mention.second, userPublicKey, openGroup)) {
|
||||||
|
backgroundColor = context.getAccentColor()
|
||||||
|
foregroundColor = mainTextColor
|
||||||
|
}
|
||||||
|
// outgoing message
|
||||||
|
else if (isOutgoingMessage) {
|
||||||
|
backgroundColor = null
|
||||||
|
foregroundColor = mainTextColor
|
||||||
|
}
|
||||||
|
// incoming messages mentioning someone else
|
||||||
|
else {
|
||||||
|
backgroundColor = null
|
||||||
|
// accent color for dark themes and primary text for light
|
||||||
|
foregroundColor = highlightedTextColor
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply the background, if any
|
||||||
|
backgroundColor?.let { background ->
|
||||||
|
result.setSpan(
|
||||||
|
RoundedBackgroundSpan(
|
||||||
|
context = context,
|
||||||
|
textColor = mainTextColor,
|
||||||
|
backgroundColor = background
|
||||||
|
),
|
||||||
|
mention.first.lower, mention.first.upper, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply the foreground, if any
|
||||||
|
foregroundColor?.let {
|
||||||
|
result.setSpan(
|
||||||
|
ForegroundColorSpan(it),
|
||||||
|
mention.first.lower,
|
||||||
|
mention.first.upper,
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply bold on the mention
|
||||||
|
result.setSpan(
|
||||||
|
StyleSpan(Typeface.BOLD),
|
||||||
|
mention.first.lower,
|
||||||
|
mention.first.upper,
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isYou(mentionedPublicKey: String, userPublicKey: String, openGroup: OpenGroup?): Boolean {
|
||||||
|
val isUserBlindedPublicKey = openGroup?.let { SodiumUtilities.sessionId(userPublicKey, mentionedPublicKey, it.publicKey) } ?: false
|
||||||
|
return mentionedPublicKey.equals(userPublicKey, ignoreCase = true) || isUserBlindedPublicKey
|
||||||
|
}
|
||||||
}
|
}
|
@ -103,7 +103,12 @@ class ConversationView : LinearLayout {
|
|||||||
R.drawable.ic_notifications_mentions
|
R.drawable.ic_notifications_mentions
|
||||||
}
|
}
|
||||||
binding.muteIndicatorImageView.setImageResource(drawableRes)
|
binding.muteIndicatorImageView.setImageResource(drawableRes)
|
||||||
binding.snippetTextView.text = highlightMentions(thread.getSnippet(), thread.threadId, context)
|
binding.snippetTextView.text = highlightMentions(
|
||||||
|
text = thread.getSnippet(),
|
||||||
|
formatOnly = true, // no styling here, only text formatting
|
||||||
|
threadID = thread.threadId,
|
||||||
|
context = context
|
||||||
|
)
|
||||||
binding.snippetTextView.typeface = if (unreadCount > 0 && !thread.isRead) Typeface.DEFAULT_BOLD else Typeface.DEFAULT
|
binding.snippetTextView.typeface = if (unreadCount > 0 && !thread.isRead) Typeface.DEFAULT_BOLD else Typeface.DEFAULT
|
||||||
binding.snippetTextView.visibility = if (isTyping) View.GONE else View.VISIBLE
|
binding.snippetTextView.visibility = if (isTyping) View.GONE else View.VISIBLE
|
||||||
if (isTyping) {
|
if (isTyping) {
|
||||||
|
@ -39,7 +39,13 @@ class MessageRequestView : LinearLayout {
|
|||||||
binding.displayNameTextView.text = senderDisplayName
|
binding.displayNameTextView.text = senderDisplayName
|
||||||
binding.timestampTextView.text = DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), thread.date)
|
binding.timestampTextView.text = DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), thread.date)
|
||||||
val rawSnippet = thread.getDisplayBody(context)
|
val rawSnippet = thread.getDisplayBody(context)
|
||||||
val snippet = highlightMentions(rawSnippet, thread.threadId, context)
|
val snippet = highlightMentions(
|
||||||
|
text = rawSnippet,
|
||||||
|
formatOnly = true, // no styling here, only text formatting
|
||||||
|
threadID = thread.threadId,
|
||||||
|
context = context
|
||||||
|
)
|
||||||
|
|
||||||
binding.snippetTextView.text = snippet
|
binding.snippetTextView.text = snippet
|
||||||
|
|
||||||
post {
|
post {
|
||||||
|
@ -444,13 +444,30 @@ public class DefaultMessageNotifier implements MessageNotifier {
|
|||||||
while(iterator.hasPrevious()) {
|
while(iterator.hasPrevious()) {
|
||||||
NotificationItem item = iterator.previous();
|
NotificationItem item = iterator.previous();
|
||||||
builder.addMessageBody(item.getIndividualRecipient(), item.getRecipient(),
|
builder.addMessageBody(item.getIndividualRecipient(), item.getRecipient(),
|
||||||
MentionUtilities.highlightMentions(item.getText(), item.getThreadId(), context));
|
MentionUtilities.highlightMentions(
|
||||||
|
item.getText() != null ? item.getText() : "",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true, // no styling here, only text formatting
|
||||||
|
item.getThreadId(),
|
||||||
|
context
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (signal) {
|
if (signal) {
|
||||||
builder.setAlarms(notificationState.getRingtone(context), notificationState.getVibrate());
|
builder.setAlarms(notificationState.getRingtone(context), notificationState.getVibrate());
|
||||||
|
CharSequence text = notifications.get(0).getText();
|
||||||
builder.setTicker(notifications.get(0).getIndividualRecipient(),
|
builder.setTicker(notifications.get(0).getIndividualRecipient(),
|
||||||
MentionUtilities.highlightMentions(notifications.get(0).getText(), notifications.get(0).getThreadId(), context));
|
MentionUtilities.highlightMentions(
|
||||||
|
text != null ? text : "",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true, // no styling here, only text formatting
|
||||||
|
notifications.get(0).getThreadId(),
|
||||||
|
context
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.putStringExtra(LATEST_MESSAGE_ID_TAG, messageIdTag);
|
builder.putStringExtra(LATEST_MESSAGE_ID_TAG, messageIdTag);
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
package org.thoughtcrime.securesms.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.RectF
|
||||||
|
import android.text.style.ReplacementSpan
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Span that draws text with a rounded background.
|
||||||
|
*
|
||||||
|
* @param textColor - The color of the text.
|
||||||
|
* @param backgroundColor - The color of the background.
|
||||||
|
* @param cornerRadius - The corner radius of the background in pixels. Defaults to 8dp.
|
||||||
|
* @param paddingHorizontal - The horizontal padding of the text in pixels. Defaults to 3dp.
|
||||||
|
* @param paddingVertical - The vertical padding of the text in pixels. Defaults to 3dp.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
class RoundedBackgroundSpan(
|
||||||
|
context: Context,
|
||||||
|
private val textColor: Int,
|
||||||
|
private val backgroundColor: Int,
|
||||||
|
private val cornerRadius: Float = toPx(8, context.resources).toFloat(), // setting some Session defaults
|
||||||
|
private val paddingHorizontal: Float = toPx(3, context.resources).toFloat(),
|
||||||
|
private val paddingVertical: Float = toPx(3, context.resources).toFloat()
|
||||||
|
) : ReplacementSpan() {
|
||||||
|
|
||||||
|
override fun draw(
|
||||||
|
canvas: Canvas, text: CharSequence, start: Int, end: Int,
|
||||||
|
x: Float, top: Int, y: Int, bottom: Int, paint: Paint
|
||||||
|
) {
|
||||||
|
// the top needs to take into account the font and the required vertical padding
|
||||||
|
val newTop = y + paint.fontMetrics.ascent - paddingVertical
|
||||||
|
val newBottom = y + paint.fontMetrics.descent + paddingVertical
|
||||||
|
val rect = RectF(
|
||||||
|
x,
|
||||||
|
newTop,
|
||||||
|
x + measureText(paint, text, start, end) + 2 * paddingHorizontal,
|
||||||
|
newBottom
|
||||||
|
)
|
||||||
|
paint.color = backgroundColor
|
||||||
|
|
||||||
|
canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint)
|
||||||
|
paint.color = textColor
|
||||||
|
canvas.drawText(text, start, end, x + paddingHorizontal, y.toFloat(), paint)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSize(
|
||||||
|
paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt?
|
||||||
|
): Int {
|
||||||
|
return (paint.measureText(text, start, end) + 2 * paddingHorizontal).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun measureText(
|
||||||
|
paint: Paint, text: CharSequence, start: Int, end: Int
|
||||||
|
): Float {
|
||||||
|
return paint.measureText(text, start, end)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user