mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-26 19:56:02 +01:00
Add text formatting send and receive support for conversations.
This commit is contained in:
committed by
Greyson Parrelli
parent
aa2075c78f
commit
cc490f4b73
@@ -101,6 +101,7 @@ import org.whispersystems.signalservice.internal.push.SendGroupMessageResponse;
|
||||
import org.whispersystems.signalservice.internal.push.SendMessageResponse;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.AttachmentPointer;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.BodyRange;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.CallMessage;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage;
|
||||
@@ -845,6 +846,10 @@ public class SignalServiceMessageSender {
|
||||
builder.setTextAttachment(createTextAttachment(message.getTextAttachment().get()));
|
||||
}
|
||||
|
||||
if (message.getBodyRanges().isPresent()) {
|
||||
builder.addAllBodyRanges(message.getBodyRanges().get());
|
||||
}
|
||||
|
||||
builder.setAllowsReplies(message.getAllowsReplies().orElse(true));
|
||||
|
||||
return container.setStoryMessage(builder).build();
|
||||
@@ -929,15 +934,20 @@ public class SignalServiceMessageSender {
|
||||
List<SignalServiceDataMessage.Mention> mentions = message.getQuote().get().getMentions();
|
||||
if (mentions != null && !mentions.isEmpty()) {
|
||||
for (SignalServiceDataMessage.Mention mention : mentions) {
|
||||
quoteBuilder.addBodyRanges(DataMessage.BodyRange.newBuilder()
|
||||
.setStart(mention.getStart())
|
||||
.setLength(mention.getLength())
|
||||
.setMentionUuid(mention.getServiceId().toString()));
|
||||
quoteBuilder.addBodyRanges(BodyRange.newBuilder()
|
||||
.setStart(mention.getStart())
|
||||
.setLength(mention.getLength())
|
||||
.setMentionUuid(mention.getServiceId().toString()));
|
||||
}
|
||||
|
||||
builder.setRequiredProtocolVersion(Math.max(DataMessage.ProtocolVersion.MENTIONS_VALUE, builder.getRequiredProtocolVersion()));
|
||||
}
|
||||
|
||||
List<BodyRange> bodyRanges = message.getQuote().get().getBodyRanges();
|
||||
if (bodyRanges != null) {
|
||||
quoteBuilder.addAllBodyRanges(bodyRanges);
|
||||
}
|
||||
|
||||
List<SignalServiceDataMessage.Quote.QuotedAttachment> attachments = message.getQuote().get().getAttachments();
|
||||
if (attachments != null) {
|
||||
for (SignalServiceDataMessage.Quote.QuotedAttachment attachment : attachments) {
|
||||
@@ -972,10 +982,10 @@ public class SignalServiceMessageSender {
|
||||
|
||||
if (message.getMentions().isPresent()) {
|
||||
for (SignalServiceDataMessage.Mention mention : message.getMentions().get()) {
|
||||
builder.addBodyRanges(DataMessage.BodyRange.newBuilder()
|
||||
.setStart(mention.getStart())
|
||||
.setLength(mention.getLength())
|
||||
.setMentionUuid(mention.getServiceId().toString()));
|
||||
builder.addBodyRanges(BodyRange.newBuilder()
|
||||
.setStart(mention.getStart())
|
||||
.setLength(mention.getLength())
|
||||
.setMentionUuid(mention.getServiceId().toString()));
|
||||
}
|
||||
builder.setRequiredProtocolVersion(Math.max(DataMessage.ProtocolVersion.MENTIONS_VALUE, builder.getRequiredProtocolVersion()));
|
||||
}
|
||||
@@ -1060,6 +1070,10 @@ public class SignalServiceMessageSender {
|
||||
.setReceiptCredentialPresentation(ByteString.copyFrom(giftBadge.getReceiptCredentialPresentation().serialize())));
|
||||
}
|
||||
|
||||
if (message.getBodyRanges().isPresent()) {
|
||||
builder.addAllBodyRanges(message.getBodyRanges().get());
|
||||
}
|
||||
|
||||
builder.setTimestamp(message.getTimestamp());
|
||||
|
||||
return enforceMaxContentSize(container.setDataMessage(builder).build());
|
||||
|
||||
@@ -680,9 +680,9 @@ import javax.annotation.Nullable;
|
||||
Optional<SignalServiceGroupV2> groupContext = Optional.ofNullable(groupInfoV2);
|
||||
|
||||
List<SignalServiceAttachment> attachments = new LinkedList<>();
|
||||
boolean endSession = ((content.getFlags() & SignalServiceProtos.DataMessage.Flags.END_SESSION_VALUE ) != 0);
|
||||
boolean endSession = ((content.getFlags() & SignalServiceProtos.DataMessage.Flags.END_SESSION_VALUE) != 0);
|
||||
boolean expirationUpdate = ((content.getFlags() & SignalServiceProtos.DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE) != 0);
|
||||
boolean profileKeyUpdate = ((content.getFlags() & SignalServiceProtos.DataMessage.Flags.PROFILE_KEY_UPDATE_VALUE ) != 0);
|
||||
boolean profileKeyUpdate = ((content.getFlags() & SignalServiceProtos.DataMessage.Flags.PROFILE_KEY_UPDATE_VALUE) != 0);
|
||||
boolean isGroupV2 = groupInfoV2 != null;
|
||||
SignalServiceDataMessage.Quote quote = createQuote(content, isGroupV2);
|
||||
List<SharedContact> sharedContacts = createSharedContacts(content);
|
||||
@@ -694,6 +694,7 @@ import javax.annotation.Nullable;
|
||||
SignalServiceDataMessage.GroupCallUpdate groupCallUpdate = createGroupCallUpdate(content);
|
||||
SignalServiceDataMessage.StoryContext storyContext = createStoryContext(content);
|
||||
SignalServiceDataMessage.GiftBadge giftBadge = createGiftBadge(content);
|
||||
List<SignalServiceProtos.BodyRange> bodyRanges = createBodyRanges(content.getBodyRangesList(), content.getBody());
|
||||
|
||||
if (content.getRequiredProtocolVersion() > SignalServiceProtos.DataMessage.ProtocolVersion.CURRENT_VALUE) {
|
||||
throw new UnsupportedDataMessageProtocolVersionException(SignalServiceProtos.DataMessage.ProtocolVersion.CURRENT_VALUE,
|
||||
@@ -745,6 +746,7 @@ import javax.annotation.Nullable;
|
||||
.withPayment(payment)
|
||||
.withStoryContext(storyContext)
|
||||
.withGiftBadge(giftBadge)
|
||||
.withBodyRanges(bodyRanges)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -1092,12 +1094,14 @@ import javax.annotation.Nullable;
|
||||
return SignalServiceStoryMessage.forFileAttachment(profileKey,
|
||||
createGroupV2Info(content),
|
||||
createAttachmentPointer(content.getFileAttachment()),
|
||||
content.getAllowsReplies());
|
||||
content.getAllowsReplies(),
|
||||
content.getBodyRangesList());
|
||||
} else {
|
||||
return SignalServiceStoryMessage.forTextAttachment(profileKey,
|
||||
createGroupV2Info(content),
|
||||
createTextAttachment(content.getTextAttachment()),
|
||||
content.getAllowsReplies());
|
||||
content.getAllowsReplies(),
|
||||
content.getBodyRangesList());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1121,7 +1125,8 @@ import javax.annotation.Nullable;
|
||||
content.getQuote().getText(),
|
||||
attachments,
|
||||
createMentions(content.getQuote().getBodyRangesList(), content.getQuote().getText(), isGroupV2),
|
||||
SignalServiceDataMessage.Quote.Type.fromProto(content.getQuote().getType()));
|
||||
SignalServiceDataMessage.Quote.Type.fromProto(content.getQuote().getType()),
|
||||
createBodyRanges(content.getQuote().getBodyRangesList(), content.getQuote().getText()));
|
||||
} else {
|
||||
Log.w(TAG, "Quote was missing an author! Returning null.");
|
||||
return null;
|
||||
@@ -1154,7 +1159,7 @@ import javax.annotation.Nullable;
|
||||
Optional.ofNullable(attachment));
|
||||
}
|
||||
|
||||
private static @Nullable List<SignalServiceDataMessage.Mention> createMentions(List<SignalServiceProtos.DataMessage.BodyRange> bodyRanges, String body, boolean isGroupV2)
|
||||
private static @Nullable List<SignalServiceDataMessage.Mention> createMentions(List<SignalServiceProtos.BodyRange> bodyRanges, String body, boolean isGroupV2)
|
||||
throws InvalidMessageStructureException
|
||||
{
|
||||
if (bodyRanges == null || bodyRanges.isEmpty() || body == null) {
|
||||
@@ -1163,7 +1168,7 @@ import javax.annotation.Nullable;
|
||||
|
||||
List<SignalServiceDataMessage.Mention> mentions = new LinkedList<>();
|
||||
|
||||
for (SignalServiceProtos.DataMessage.BodyRange bodyRange : bodyRanges) {
|
||||
for (SignalServiceProtos.BodyRange bodyRange : bodyRanges) {
|
||||
if (bodyRange.hasMentionUuid()) {
|
||||
try {
|
||||
mentions.add(new SignalServiceDataMessage.Mention(ServiceId.parseOrThrow(bodyRange.getMentionUuid()), bodyRange.getStart(), bodyRange.getLength()));
|
||||
@@ -1180,6 +1185,22 @@ import javax.annotation.Nullable;
|
||||
return mentions;
|
||||
}
|
||||
|
||||
private static @Nullable List<SignalServiceProtos.BodyRange> createBodyRanges(List<SignalServiceProtos.BodyRange> bodyRanges, String body) {
|
||||
if (bodyRanges == null || bodyRanges.isEmpty() || body == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<SignalServiceProtos.BodyRange> ranges = new LinkedList<>();
|
||||
|
||||
for (SignalServiceProtos.BodyRange bodyRange : bodyRanges) {
|
||||
if (bodyRange.hasStyle()) {
|
||||
ranges.add(bodyRange);
|
||||
}
|
||||
}
|
||||
|
||||
return ranges;
|
||||
}
|
||||
|
||||
private static @Nullable SignalServiceDataMessage.Sticker createSticker(SignalServiceProtos.DataMessage content) throws InvalidMessageStructureException {
|
||||
if (!content.hasSticker() ||
|
||||
!content.getSticker().hasPackId() ||
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.whispersystems.signalservice.api.messages.shared.SharedContact
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.api.util.OptionalUtil.asOptional
|
||||
import org.whispersystems.signalservice.api.util.OptionalUtil.emptyIfStringEmpty
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.BodyRange
|
||||
import java.util.LinkedList
|
||||
import java.util.Optional
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage.Payment as PaymentProto
|
||||
@@ -47,7 +48,8 @@ class SignalServiceDataMessage private constructor(
|
||||
val groupCallUpdate: Optional<GroupCallUpdate>,
|
||||
val payment: Optional<Payment>,
|
||||
val storyContext: Optional<StoryContext>,
|
||||
val giftBadge: Optional<GiftBadge>
|
||||
val giftBadge: Optional<GiftBadge>,
|
||||
val bodyRanges: Optional<List<BodyRange>>
|
||||
) {
|
||||
val isActivatePaymentsRequest: Boolean = payment.map { it.isActivationRequest }.orElse(false)
|
||||
val isPaymentsActivated: Boolean = payment.map { it.isActivation }.orElse(false)
|
||||
@@ -92,6 +94,7 @@ class SignalServiceDataMessage private constructor(
|
||||
private var payment: Payment? = null
|
||||
private var storyContext: StoryContext? = null
|
||||
private var giftBadge: GiftBadge? = null
|
||||
private var bodyRanges: MutableList<BodyRange> = LinkedList<BodyRange>()
|
||||
|
||||
fun withTimestamp(timestamp: Long): Builder {
|
||||
this.timestamp = timestamp
|
||||
@@ -210,6 +213,11 @@ class SignalServiceDataMessage private constructor(
|
||||
return this
|
||||
}
|
||||
|
||||
fun withBodyRanges(bodyRanges: List<BodyRange>?): Builder {
|
||||
bodyRanges?.let { this.bodyRanges.addAll(bodyRanges) }
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): SignalServiceDataMessage {
|
||||
if (timestamp == 0L) {
|
||||
timestamp = System.currentTimeMillis()
|
||||
@@ -236,7 +244,8 @@ class SignalServiceDataMessage private constructor(
|
||||
groupCallUpdate = groupCallUpdate.asOptional(),
|
||||
payment = payment.asOptional(),
|
||||
storyContext = storyContext.asOptional(),
|
||||
giftBadge = giftBadge.asOptional()
|
||||
giftBadge = giftBadge.asOptional(),
|
||||
bodyRanges = bodyRanges.asOptional()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -247,7 +256,8 @@ class SignalServiceDataMessage private constructor(
|
||||
val text: String,
|
||||
val attachments: List<QuotedAttachment>?,
|
||||
val mentions: List<Mention>?,
|
||||
val type: Type
|
||||
val type: Type,
|
||||
val bodyRanges: List<BodyRange>?
|
||||
) {
|
||||
enum class Type(val protoType: QuoteProto.Type) {
|
||||
NORMAL(QuoteProto.Type.NORMAL),
|
||||
|
||||
@@ -1,39 +1,50 @@
|
||||
package org.whispersystems.signalservice.api.messages;
|
||||
|
||||
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class SignalServiceStoryMessage {
|
||||
private final Optional<byte[]> profileKey;
|
||||
private final Optional<SignalServiceGroupV2> groupContext;
|
||||
private final Optional<SignalServiceAttachment> fileAttachment;
|
||||
private final Optional<SignalServiceTextAttachment> textAttachment;
|
||||
private final Optional<Boolean> allowsReplies;
|
||||
private final Optional<byte[]> profileKey;
|
||||
private final Optional<SignalServiceGroupV2> groupContext;
|
||||
private final Optional<SignalServiceAttachment> fileAttachment;
|
||||
private final Optional<SignalServiceTextAttachment> textAttachment;
|
||||
private final Optional<Boolean> allowsReplies;
|
||||
private final Optional<List<SignalServiceProtos.BodyRange>> bodyRanges;
|
||||
|
||||
private SignalServiceStoryMessage(byte[] profileKey,
|
||||
SignalServiceGroupV2 groupContext,
|
||||
SignalServiceAttachment fileAttachment,
|
||||
SignalServiceTextAttachment textAttachment,
|
||||
boolean allowsReplies) {
|
||||
boolean allowsReplies,
|
||||
List<SignalServiceProtos.BodyRange> bodyRanges)
|
||||
{
|
||||
this.profileKey = Optional.ofNullable(profileKey);
|
||||
this.groupContext = Optional.ofNullable(groupContext);
|
||||
this.fileAttachment = Optional.ofNullable(fileAttachment);
|
||||
this.textAttachment = Optional.ofNullable(textAttachment);
|
||||
this.allowsReplies = Optional.of(allowsReplies);
|
||||
this.bodyRanges = Optional.ofNullable(bodyRanges);
|
||||
}
|
||||
|
||||
public static SignalServiceStoryMessage forFileAttachment(byte[] profileKey,
|
||||
SignalServiceGroupV2 groupContext,
|
||||
SignalServiceAttachment fileAttachment,
|
||||
boolean allowsReplies) {
|
||||
return new SignalServiceStoryMessage(profileKey, groupContext, fileAttachment, null, allowsReplies);
|
||||
boolean allowsReplies,
|
||||
List<SignalServiceProtos.BodyRange> bodyRanges)
|
||||
{
|
||||
return new SignalServiceStoryMessage(profileKey, groupContext, fileAttachment, null, allowsReplies, bodyRanges);
|
||||
}
|
||||
|
||||
public static SignalServiceStoryMessage forTextAttachment(byte[] profileKey,
|
||||
SignalServiceGroupV2 groupContext,
|
||||
SignalServiceTextAttachment textAttachment,
|
||||
boolean allowsReplies) {
|
||||
return new SignalServiceStoryMessage(profileKey, groupContext, null, textAttachment, allowsReplies);
|
||||
boolean allowsReplies,
|
||||
List<SignalServiceProtos.BodyRange> bodyRanges)
|
||||
{
|
||||
return new SignalServiceStoryMessage(profileKey, groupContext, null, textAttachment, allowsReplies, bodyRanges);
|
||||
}
|
||||
|
||||
public Optional<byte[]> getProfileKey() {
|
||||
@@ -55,4 +66,8 @@ public class SignalServiceStoryMessage {
|
||||
public Optional<Boolean> getAllowsReplies() {
|
||||
return allowsReplies;
|
||||
}
|
||||
|
||||
public Optional<List<SignalServiceProtos.BodyRange>> getBodyRanges() {
|
||||
return bodyRanges;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +125,25 @@ message CallMessage {
|
||||
optional Opaque opaque = 10;
|
||||
}
|
||||
|
||||
message BodyRange {
|
||||
enum Style {
|
||||
NONE = 0;
|
||||
BOLD = 1;
|
||||
ITALIC = 2;
|
||||
SPOILER = 3;
|
||||
STRIKETHROUGH = 4;
|
||||
MONOSPACE = 5;
|
||||
}
|
||||
|
||||
optional uint32 start = 1;
|
||||
optional uint32 length = 2;
|
||||
|
||||
oneof associatedValue {
|
||||
string mentionUuid = 3;
|
||||
Style style = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message DataMessage {
|
||||
enum Flags {
|
||||
END_SESSION = 1;
|
||||
@@ -132,15 +151,6 @@ message DataMessage {
|
||||
PROFILE_KEY_UPDATE = 4;
|
||||
}
|
||||
|
||||
message BodyRange {
|
||||
optional uint32 start = 1;
|
||||
optional uint32 length = 2;
|
||||
|
||||
oneof associatedValue {
|
||||
string mentionUuid = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message Quote {
|
||||
enum Type {
|
||||
NORMAL = 0;
|
||||
@@ -382,6 +392,7 @@ message StoryMessage {
|
||||
TextAttachment textAttachment = 4;
|
||||
}
|
||||
optional bool allowsReplies = 5;
|
||||
repeated BodyRange bodyRanges = 6;
|
||||
}
|
||||
|
||||
message Preview {
|
||||
|
||||
Reference in New Issue
Block a user