mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-19 08:09:12 +01:00
Complete text formatting.
This commit is contained in:
committed by
Greyson Parrelli
parent
534c5c3c64
commit
a64bffd83a
@@ -15,8 +15,6 @@ import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.SelectionLimits;
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
|
||||
import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob;
|
||||
import org.thoughtcrime.securesms.jobs.RemoteConfigRefreshJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.messageprocessingalarm.MessageProcessReceiver;
|
||||
@@ -107,7 +105,6 @@ public final class FeatureFlags {
|
||||
private static final String TEXT_FORMATTING = "android.textFormatting";
|
||||
private static final String ANY_ADDRESS_PORTS_KILL_SWITCH = "android.calling.fieldTrial.anyAddressPortsKillSwitch";
|
||||
private static final String CALLS_TAB = "android.calls.tab.2";
|
||||
private static final String TEXT_FORMATTING_SPOILER_SEND = "android.textFormatting.spoilerSend";
|
||||
private static final String AD_HOC_CALLING = "android.calling.ad.hoc";
|
||||
private static final String EDIT_MESSAGE_RECEIVE = "android.editMessage.receive";
|
||||
private static final String EDIT_MESSAGE_SEND = "android.editMessage.send";
|
||||
@@ -170,7 +167,6 @@ public final class FeatureFlags {
|
||||
TEXT_FORMATTING,
|
||||
ANY_ADDRESS_PORTS_KILL_SWITCH,
|
||||
CALLS_TAB,
|
||||
TEXT_FORMATTING_SPOILER_SEND,
|
||||
EDIT_MESSAGE_RECEIVE,
|
||||
EDIT_MESSAGE_SEND
|
||||
);
|
||||
@@ -238,7 +234,6 @@ public final class FeatureFlags {
|
||||
PAYMENTS_REQUEST_ACTIVATE_FLOW,
|
||||
CDS_HARD_LIMIT,
|
||||
TEXT_FORMATTING,
|
||||
TEXT_FORMATTING_SPOILER_SEND,
|
||||
EDIT_MESSAGE_RECEIVE,
|
||||
EDIT_MESSAGE_SEND
|
||||
);
|
||||
@@ -587,13 +582,6 @@ public final class FeatureFlags {
|
||||
return getBoolean(TEXT_FORMATTING, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not we should show spoiler text formatting option.
|
||||
*/
|
||||
public static boolean textFormattingSpoilerSend() {
|
||||
return getBoolean(TEXT_FORMATTING_SPOILER_SEND, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable RingRTC field trial for "AnyAddressPortsKillSwitch"
|
||||
*/
|
||||
|
||||
@@ -15,6 +15,7 @@ import android.widget.TextView;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.spoiler.SpoilerAnnotation;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
@@ -53,8 +54,7 @@ public class LongClickMovementMethod extends LinkMovementMethod {
|
||||
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
|
||||
int action = event.getAction();
|
||||
|
||||
if (action == MotionEvent.ACTION_UP ||
|
||||
action == MotionEvent.ACTION_DOWN) {
|
||||
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
|
||||
int x = (int) event.getX();
|
||||
int y = (int) event.getY();
|
||||
|
||||
@@ -68,14 +68,31 @@ public class LongClickMovementMethod extends LinkMovementMethod {
|
||||
int line = layout.getLineForVertical(y);
|
||||
int off = layout.getOffsetForHorizontal(line, x);
|
||||
|
||||
LongClickCopySpan longClickCopySpan[] = buffer.getSpans(off, off, LongClickCopySpan.class);
|
||||
SpoilerAnnotation.SpoilerClickableSpan[] spoilerClickableSpans = buffer.getSpans(off, off, SpoilerAnnotation.SpoilerClickableSpan.class);
|
||||
if (spoilerClickableSpans.length != 0) {
|
||||
boolean spoilerRevealed = false;
|
||||
for (SpoilerAnnotation.SpoilerClickableSpan spoilerClickSpan : spoilerClickableSpans) {
|
||||
if (!spoilerClickSpan.getSpoilerRevealed() && action == MotionEvent.ACTION_DOWN) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!spoilerClickSpan.getSpoilerRevealed() && action == MotionEvent.ACTION_UP) {
|
||||
spoilerClickSpan.onClick(widget);
|
||||
spoilerRevealed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (spoilerRevealed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
LongClickCopySpan[] longClickCopySpan = buffer.getSpans(off, off, LongClickCopySpan.class);
|
||||
if (longClickCopySpan.length != 0) {
|
||||
LongClickCopySpan aSingleSpan = longClickCopySpan[0];
|
||||
if (action == MotionEvent.ACTION_DOWN) {
|
||||
Selection.setSelection(buffer, buffer.getSpanStart(aSingleSpan),
|
||||
buffer.getSpanEnd(aSingleSpan));
|
||||
aSingleSpan.setHighlighted(true,
|
||||
ContextCompat.getColor(widget.getContext(), R.color.touch_highlight));
|
||||
Selection.setSelection(buffer, buffer.getSpanStart(aSingleSpan), buffer.getSpanEnd(aSingleSpan));
|
||||
aSingleSpan.setHighlighted(true, ContextCompat.getColor(widget.getContext(), R.color.touch_highlight));
|
||||
} else {
|
||||
Selection.removeSelection(buffer);
|
||||
aSingleSpan.setHighlighted(false, Color.TRANSPARENT);
|
||||
@@ -89,8 +106,7 @@ public class LongClickMovementMethod extends LinkMovementMethod {
|
||||
}
|
||||
} else if (action == MotionEvent.ACTION_CANCEL) {
|
||||
// Remove Selections.
|
||||
LongClickCopySpan[] spans = buffer.getSpans(Selection.getSelectionStart(buffer),
|
||||
Selection.getSelectionEnd(buffer), LongClickCopySpan.class);
|
||||
LongClickCopySpan[] spans = buffer.getSpans(Selection.getSelectionStart(buffer), Selection.getSelectionEnd(buffer), LongClickCopySpan.class);
|
||||
for (LongClickCopySpan aSpan : spans) {
|
||||
aSpan.setHighlighted(false, Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
package org.thoughtcrime.securesms.util
|
||||
|
||||
import android.text.Annotation
|
||||
import android.text.Spannable
|
||||
import org.thoughtcrime.securesms.components.spoiler.SpoilerAnnotation
|
||||
|
||||
/**
|
||||
* Filters the results of [getSpans] to exclude spans covered by an unrevealed spoiler when drawing or
|
||||
* processing clicks. Since [getSpans] can also be called when making copies of spannables, we do not filter
|
||||
* the call unless we know we are drawing or getting click spannables.
|
||||
*/
|
||||
class SpoilerFilteringSpannable(private val spannable: Spannable, private val inOnDrawProvider: InOnDrawProvider) : Spannable by spannable {
|
||||
|
||||
override fun <T : Any> getSpans(start: Int, end: Int, type: Class<T>): Array<T> {
|
||||
val spans: Array<T> = spannable.getSpans(start, end, type)
|
||||
|
||||
if (spans.isEmpty() || !(inOnDrawProvider.isInOnDraw() || type == LongClickCopySpan::class.java)) {
|
||||
return spans
|
||||
}
|
||||
|
||||
if (spannable.getSpans(0, spannable.length, Annotation::class.java).none { SpoilerAnnotation.isSpoilerAnnotation(it) }) {
|
||||
return spans
|
||||
}
|
||||
|
||||
val spansToExclude = HashSet<Any>()
|
||||
val spoilers: Map<Annotation, SpoilerAnnotation.SpoilerClickableSpan?> = SpoilerAnnotation.getSpoilerAndClickAnnotations(spannable, start, end)
|
||||
val allOtherTheSpans: Map<T, Pair<Int, Int>> = spans
|
||||
.filterNot { SpoilerAnnotation.isSpoilerAnnotation(it) || it is SpoilerAnnotation.SpoilerClickableSpan }
|
||||
.associateWith { (spannable.getSpanStart(it) to spannable.getSpanEnd(it)) }
|
||||
|
||||
spoilers.forEach { (spoiler, click) ->
|
||||
if (click?.spoilerRevealed == true) {
|
||||
spansToExclude += spoiler
|
||||
spansToExclude += click
|
||||
} else {
|
||||
val spoilerStart = spannable.getSpanStart(spoiler)
|
||||
val spoilerEnd = spannable.getSpanEnd(spoiler)
|
||||
|
||||
for ((span, position) in allOtherTheSpans) {
|
||||
if (position.first in spoilerStart..spoilerEnd) {
|
||||
spansToExclude += span
|
||||
} else if (position.second in spoilerStart..spoilerEnd) {
|
||||
spansToExclude += span
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return spans.filter(spansToExclude)
|
||||
}
|
||||
|
||||
/**
|
||||
* Kotlin does not handle generic JVM arrays well so instead of using all the nice collection functions
|
||||
* we do a move desired objects down and overwrite undesired objects and then copy the array to trim
|
||||
* it to the correct length. For our use case, it's okay to modify the original array.
|
||||
*/
|
||||
private fun <T : Any> Array<T>.filter(set: Set<Any>): Array<T> {
|
||||
var index = 0
|
||||
for (i in this.indices) {
|
||||
this[index] = this[i]
|
||||
if (!set.contains(this[index])) {
|
||||
index++
|
||||
}
|
||||
}
|
||||
return copyOfRange(0, index)
|
||||
}
|
||||
|
||||
override fun toString(): String = spannable.toString()
|
||||
override fun hashCode(): Int = spannable.hashCode()
|
||||
override fun equals(other: Any?): Boolean = spannable == other
|
||||
|
||||
fun interface InOnDrawProvider {
|
||||
fun isInOnDraw(): Boolean
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user