Fix split second spoiler reveal when quoting a message with a spoiler.
This commit is contained in:
parent
131f9c4bc9
commit
f2846efd2c
9 changed files with 34 additions and 51 deletions
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.components.emoji;
|
|||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Paint.FontMetricsInt;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
@ -55,6 +56,10 @@ public class EmojiSpan extends AnimatingImageSpan {
|
|||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
|
||||
if (paint.getColor() == Color.TRANSPARENT) {
|
||||
return;
|
||||
}
|
||||
|
||||
int height = bottom - top;
|
||||
int centeringMargin = (height - size) / 2;
|
||||
int adjustedMargin = (int) (centeringMargin * SHIFT_FACTOR);
|
||||
|
|
|
@ -3,8 +3,6 @@ package org.thoughtcrime.securesms.components.emoji;
|
|||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Region;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.text.Annotation;
|
||||
|
@ -70,7 +68,6 @@ public class EmojiTextView extends AppCompatTextView {
|
|||
private TextDirectionHeuristic textDirection;
|
||||
private boolean isJumbomoji;
|
||||
private boolean forceJumboEmoji;
|
||||
private boolean isInOnDraw;
|
||||
|
||||
private MentionRendererDelegate mentionRendererDelegate;
|
||||
private final SpoilerRendererDelegate spoilerRendererDelegate;
|
||||
|
@ -116,16 +113,11 @@ public class EmojiTextView extends AppCompatTextView {
|
|||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
isInOnDraw = true;
|
||||
|
||||
boolean hasSpannedText = getText() instanceof Spanned;
|
||||
boolean hasLayout = getLayout() != null;
|
||||
|
||||
if (hasSpannedText && hasLayout) {
|
||||
Path textClipPath = drawSpecialRenderers(canvas, mentionRendererDelegate, spoilerRendererDelegate);
|
||||
canvas.translate(getTotalPaddingLeft(), getTotalPaddingTop());
|
||||
canvas.clipPath(textClipPath, Region.Op.DIFFERENCE);
|
||||
canvas.translate(-getTotalPaddingLeft(), -getTotalPaddingTop());
|
||||
drawSpecialRenderers(canvas, mentionRendererDelegate, spoilerRendererDelegate);
|
||||
}
|
||||
|
||||
super.onDraw(canvas);
|
||||
|
@ -133,18 +125,16 @@ public class EmojiTextView extends AppCompatTextView {
|
|||
if (hasSpannedText && !hasLayout && getLayout() != null) {
|
||||
drawSpecialRenderers(canvas, null, spoilerRendererDelegate);
|
||||
}
|
||||
|
||||
isInOnDraw = false;
|
||||
}
|
||||
|
||||
private Path drawSpecialRenderers(@NonNull Canvas canvas, @Nullable MentionRendererDelegate mentionDelegate, @NonNull SpoilerRendererDelegate spoilerDelegate) {
|
||||
private void drawSpecialRenderers(@NonNull Canvas canvas, @Nullable MentionRendererDelegate mentionDelegate, @NonNull SpoilerRendererDelegate spoilerDelegate) {
|
||||
int checkpoint = canvas.save();
|
||||
canvas.translate(getTotalPaddingLeft(), getTotalPaddingTop());
|
||||
try {
|
||||
if (mentionDelegate != null) {
|
||||
mentionDelegate.draw(canvas, (Spanned) getText(), getLayout());
|
||||
}
|
||||
return spoilerDelegate.draw(canvas, (Spanned) getText(), getLayout());
|
||||
spoilerDelegate.draw(canvas, (Spanned) getText(), getLayout());
|
||||
} finally {
|
||||
canvas.restoreToCount(checkpoint);
|
||||
}
|
||||
|
|
|
@ -2,8 +2,6 @@ package org.thoughtcrime.securesms.components.emoji
|
|||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Path
|
||||
import android.graphics.Region
|
||||
import android.text.Spanned
|
||||
import android.text.TextUtils
|
||||
import android.util.AttributeSet
|
||||
|
@ -29,23 +27,16 @@ open class SimpleEmojiTextView @JvmOverloads constructor(
|
|||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
var textClipPath: Path? = null
|
||||
if (text is Spanned && layout != null) {
|
||||
val checkpoint = canvas.save()
|
||||
canvas.translate(totalPaddingLeft.toFloat(), totalPaddingTop.toFloat())
|
||||
try {
|
||||
textClipPath = spoilerRendererDelegate.draw(canvas, (text as Spanned), layout)
|
||||
spoilerRendererDelegate.draw(canvas, (text as Spanned), layout)
|
||||
} finally {
|
||||
canvas.restoreToCount(checkpoint)
|
||||
}
|
||||
}
|
||||
|
||||
if (textClipPath != null) {
|
||||
canvas.translate(totalPaddingLeft.toFloat(), totalPaddingTop.toFloat())
|
||||
canvas.clipPath(textClipPath, Region.Op.DIFFERENCE)
|
||||
canvas.translate(-totalPaddingLeft.toFloat(), -totalPaddingTop.toFloat())
|
||||
}
|
||||
|
||||
super.onDraw(canvas)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
package org.thoughtcrime.securesms.components.spoiler
|
||||
|
||||
import android.graphics.Color
|
||||
import android.text.Annotation
|
||||
import android.text.Selection
|
||||
import android.text.Spannable
|
||||
import android.text.Spanned
|
||||
import android.text.TextPaint
|
||||
import android.text.style.ClickableSpan
|
||||
import android.text.style.MetricAffectingSpan
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
|
||||
|
@ -55,11 +56,11 @@ object SpoilerAnnotation {
|
|||
revealedSpoilers.clear()
|
||||
}
|
||||
|
||||
class SpoilerClickableSpan(private val spoiler: Annotation) : ClickableSpan() {
|
||||
class SpoilerClickableSpan(private val spoiler: Annotation) : MetricAffectingSpan() {
|
||||
val spoilerRevealed
|
||||
get() = revealedSpoilers.contains(spoiler.value)
|
||||
|
||||
override fun onClick(widget: View) {
|
||||
fun onClick(widget: View) {
|
||||
revealedSpoilers.add(spoiler.value)
|
||||
|
||||
if (widget is TextView) {
|
||||
|
@ -70,6 +71,12 @@ object SpoilerAnnotation {
|
|||
}
|
||||
}
|
||||
|
||||
override fun updateDrawState(ds: TextPaint) = Unit
|
||||
override fun updateDrawState(ds: TextPaint) {
|
||||
if (!spoilerRevealed) {
|
||||
ds.color = Color.TRANSPARENT
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateMeasureState(textPaint: TextPaint) = Unit
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.components.spoiler
|
|||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Path
|
||||
import android.text.Layout
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.Px
|
||||
|
@ -28,8 +27,7 @@ class SpoilerRenderer(
|
|||
startLine: Int,
|
||||
endLine: Int,
|
||||
startOffset: Int,
|
||||
endOffset: Int,
|
||||
textClipPath: Path
|
||||
endOffset: Int
|
||||
) {
|
||||
if (startLine == endLine) {
|
||||
val lineTop = lineTopCache.get(startLine, layout) { getLineTop(layout, startLine) }
|
||||
|
@ -40,7 +38,6 @@ class SpoilerRenderer(
|
|||
if (renderForComposing) {
|
||||
canvas.drawComposeBackground(left, lineTop, right, lineBottom)
|
||||
} else {
|
||||
textClipPath.addRect(left, lineTop, right, lineBottom)
|
||||
spoilerDrawable.setBounds(left, lineTop, right, lineBottom)
|
||||
spoilerDrawable.draw(canvas)
|
||||
}
|
||||
|
@ -53,7 +50,7 @@ class SpoilerRenderer(
|
|||
val lineEndOffset: Float = if (paragraphDirection == Layout.DIR_RIGHT_TO_LEFT) layout.getLineLeft(startLine) else layout.getLineRight(startLine)
|
||||
var lineBottom = lineBottomCache.get(startLine, layout) { getLineBottom(layout, startLine) }
|
||||
var lineTop = lineTopCache.get(startLine, layout) { getLineTop(layout, startLine) }
|
||||
drawPartialLine(canvas, startOffset, lineTop, lineEndOffset.toInt(), lineBottom, textClipPath)
|
||||
drawPartialLine(canvas, startOffset, lineTop, lineEndOffset.toInt(), lineBottom)
|
||||
|
||||
for (line in startLine + 1 until endLine) {
|
||||
val left: Int = layout.getLineLeft(line).toInt()
|
||||
|
@ -65,7 +62,6 @@ class SpoilerRenderer(
|
|||
if (renderForComposing) {
|
||||
canvas.drawComposeBackground(left, lineTop, right, lineBottom)
|
||||
} else {
|
||||
textClipPath.addRect(left, lineTop, right, lineBottom)
|
||||
spoilerDrawable.setBounds(left, lineTop, right, lineBottom)
|
||||
spoilerDrawable.draw(canvas)
|
||||
}
|
||||
|
@ -74,20 +70,18 @@ class SpoilerRenderer(
|
|||
val lineStartOffset: Float = if (paragraphDirection == Layout.DIR_RIGHT_TO_LEFT) layout.getLineRight(startLine) else layout.getLineLeft(startLine)
|
||||
lineBottom = lineBottomCache.get(endLine, layout) { getLineBottom(layout, endLine) }
|
||||
lineTop = lineTopCache.get(endLine, layout) { getLineTop(layout, endLine) }
|
||||
drawPartialLine(canvas, lineStartOffset.toInt(), lineTop, endOffset, lineBottom, textClipPath)
|
||||
drawPartialLine(canvas, lineStartOffset.toInt(), lineTop, endOffset, lineBottom)
|
||||
}
|
||||
|
||||
private fun drawPartialLine(canvas: Canvas, start: Int, top: Int, end: Int, bottom: Int, textClipPath: Path) {
|
||||
private fun drawPartialLine(canvas: Canvas, start: Int, top: Int, end: Int, bottom: Int) {
|
||||
if (renderForComposing) {
|
||||
canvas.drawComposeBackground(start, top, end, bottom)
|
||||
return
|
||||
}
|
||||
|
||||
if (start > end) {
|
||||
textClipPath.addRect(end, top, start, bottom)
|
||||
spoilerDrawable.setBounds(end, top, start, bottom)
|
||||
} else {
|
||||
textClipPath.addRect(start, top, end, bottom)
|
||||
spoilerDrawable.setBounds(start, top, end, bottom)
|
||||
}
|
||||
spoilerDrawable.draw(canvas)
|
||||
|
@ -116,8 +110,4 @@ class SpoilerRenderer(
|
|||
paint
|
||||
)
|
||||
}
|
||||
|
||||
private fun Path.addRect(left: Int, top: Int, end: Int, bottom: Int) {
|
||||
addRect(left.toFloat() - padding, top.toFloat() - padding, end.toFloat() + padding, bottom.toFloat() + padding, Path.Direction.CCW)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.components.spoiler
|
|||
|
||||
import android.animation.ValueAnimator
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Path
|
||||
import android.text.Annotation
|
||||
import android.text.Layout
|
||||
import android.text.Spanned
|
||||
|
@ -39,8 +38,6 @@ class SpoilerRendererDelegate @JvmOverloads constructor(private val view: TextVi
|
|||
repeatMode = ValueAnimator.REVERSE
|
||||
}
|
||||
|
||||
private val textClipPath: Path = Path()
|
||||
|
||||
init {
|
||||
textColor = view.textColors.defaultColor
|
||||
spoilerDrawable = SpoilerDrawable(textColor)
|
||||
|
@ -65,11 +62,10 @@ class SpoilerRendererDelegate @JvmOverloads constructor(private val view: TextVi
|
|||
}
|
||||
}
|
||||
|
||||
fun draw(canvas: Canvas, text: Spanned, layout: Layout): Path {
|
||||
fun draw(canvas: Canvas, text: Spanned, layout: Layout) {
|
||||
var hasSpoilersToRender = false
|
||||
val annotations: Map<Annotation, SpoilerClickableSpan?> = cachedAnnotations.getFromCache(text) { SpoilerAnnotation.getSpoilerAndClickAnnotations(text) }
|
||||
|
||||
textClipPath.reset()
|
||||
for ((annotation, clickSpan) in annotations.entries) {
|
||||
if (clickSpan?.spoilerRevealed == true) {
|
||||
continue
|
||||
|
@ -92,7 +88,7 @@ class SpoilerRendererDelegate @JvmOverloads constructor(private val view: TextVi
|
|||
)
|
||||
}
|
||||
|
||||
renderer.draw(canvas, layout, measurements.startLine, measurements.endLine, measurements.startOffset, measurements.endOffset, textClipPath)
|
||||
renderer.draw(canvas, layout, measurements.startLine, measurements.endLine, measurements.startOffset, measurements.endOffset)
|
||||
hasSpoilersToRender = true
|
||||
}
|
||||
|
||||
|
@ -104,8 +100,6 @@ class SpoilerRendererDelegate @JvmOverloads constructor(private val view: TextVi
|
|||
} else {
|
||||
stopAnimating()
|
||||
}
|
||||
|
||||
return textClipPath
|
||||
}
|
||||
|
||||
private fun stopAnimating() {
|
||||
|
|
|
@ -1512,6 +1512,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
|||
int end = messageBody.getSpanEnd(urlSpan);
|
||||
URLSpan span = new InterceptableLongClickCopyLinkSpan(urlSpan.getURL(), urlClickHandler);
|
||||
messageBody.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
messageBody.removeSpan(urlSpan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.text.TextPaint;
|
||||
import android.text.style.URLSpan;
|
||||
import android.view.View;
|
||||
|
@ -41,6 +42,10 @@ public class LongClickCopySpan extends URLSpan {
|
|||
|
||||
@Override
|
||||
public void updateDrawState(@NonNull TextPaint ds) {
|
||||
if (ds.getColor() == Color.TRANSPARENT) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.updateDrawState(ds);
|
||||
if (textColor != null) {
|
||||
ds.setColor(textColor);
|
||||
|
|
|
@ -22,8 +22,8 @@ class ConversationItemTest_linkifyUrlLinks(private val input: String, private va
|
|||
ConversationItem.linkifyUrlLinks(spannableStringBuilder, true, UrlHandler)
|
||||
|
||||
val spans = spannableStringBuilder.getSpans(0, expectedUrl.length, URLSpan::class.java)
|
||||
assertEquals(2, spans.size)
|
||||
assertEquals(expectedUrl, spans.get(0).url)
|
||||
assertEquals(1, spans.size)
|
||||
assertEquals(expectedUrl, spans[0].url)
|
||||
}
|
||||
|
||||
private object UrlHandler : UrlClickHandler {
|
||||
|
|
Loading…
Add table
Reference in a new issue