Add send/recv/render support for text stories.

This commit is contained in:
Alex Hart
2022-03-09 13:11:56 -04:00
committed by Cody Henthorne
parent 3a2e8b9b19
commit ff8d7fa6c2
40 changed files with 963 additions and 93 deletions

View File

@@ -52,6 +52,9 @@ import org.thoughtcrime.securesms.database.model.ReactionRecord;
import org.thoughtcrime.securesms.database.model.StickerRecord;
import org.thoughtcrime.securesms.database.model.StoryType;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.database.model.databaseprotos.ChatColor;
import org.thoughtcrime.securesms.database.model.databaseprotos.StoryTextPost;
import org.thoughtcrime.securesms.database.model.databaseprotos.StoryTextPostOrBuilder;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.BadGroupIdException;
import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
@@ -116,6 +119,7 @@ import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.stickers.StickerLocator;
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.stories.Stories;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.Hex;
@@ -139,6 +143,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
import org.whispersystems.signalservice.api.messages.SignalServicePreview;
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceTextAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
import org.whispersystems.signalservice.api.messages.calls.AnswerMessage;
import org.whispersystems.signalservice.api.messages.calls.BusyMessage;
@@ -165,10 +170,12 @@ import org.whispersystems.signalservice.api.messages.shared.SharedContact;
import org.whispersystems.signalservice.api.payments.Money;
import org.whispersystems.signalservice.api.push.DistributionId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.util.OptionalUtil;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -1333,9 +1340,9 @@ public final class MessageContentProcessor {
try {
final StoryType storyType;
if (message.getAllowsReplies().or(false)) {
storyType = StoryType.STORY_WITH_REPLIES;
storyType = StoryType.withReplies(message.getTextAttachment().isPresent());
} else {
storyType = StoryType.STORY_WITHOUT_REPLIES;
storyType = StoryType.withoutReplies(message.getTextAttachment().isPresent());
}
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(senderRecipient.getId(),
@@ -1349,12 +1356,15 @@ public final class MessageContentProcessor {
false,
false,
content.isNeedsReceipt(),
Optional.absent(),
message.getTextAttachment().transform(this::serializeTextAttachment),
Optional.fromNullable(GroupUtil.getGroupContextIfPresent(content)),
message.getFileAttachment().transform(Collections::singletonList),
Optional.absent(),
Optional.absent(),
Optional.absent(),
getLinkPreviews(OptionalUtil.flatMap(message.getTextAttachment(),
t -> t.getPreview().transform(Collections::singletonList)),
"",
true),
Optional.absent(),
Optional.absent(),
content.getServerUuid());
@@ -1382,6 +1392,64 @@ public final class MessageContentProcessor {
}
}
private @NonNull String serializeTextAttachment(@NonNull SignalServiceTextAttachment textAttachment) {
StoryTextPost.Builder builder = StoryTextPost.newBuilder();
if (textAttachment.getText().isPresent()) {
builder.setBody(textAttachment.getText().get());
}
if (textAttachment.getStyle().isPresent()) {
switch (textAttachment.getStyle().get()) {
case DEFAULT:
builder.setStyle(StoryTextPost.Style.DEFAULT);
break;
case REGULAR:
builder.setStyle(StoryTextPost.Style.REGULAR);
break;
case BOLD:
builder.setStyle(StoryTextPost.Style.BOLD);
break;
case SERIF:
builder.setStyle(StoryTextPost.Style.SERIF);
break;
case SCRIPT:
builder.setStyle(StoryTextPost.Style.SCRIPT);
break;
case CONDENSED:
builder.setStyle(StoryTextPost.Style.CONDENSED);
break;
}
}
if (textAttachment.getTextBackgroundColor().isPresent()) {
builder.setTextBackgroundColor(textAttachment.getTextBackgroundColor().get());
}
if (textAttachment.getTextForegroundColor().isPresent()) {
builder.setTextForegroundColor(textAttachment.getTextForegroundColor().get());
}
ChatColor.Builder chatColorBuilder = ChatColor.newBuilder();
if (textAttachment.getBackgroundColor().isPresent()) {
chatColorBuilder.setSingleColor(ChatColor.SingleColor.newBuilder().setColor(textAttachment.getBackgroundColor().get()));
} else if (textAttachment.getBackgroundGradient().isPresent()) {
SignalServiceTextAttachment.Gradient gradient = textAttachment.getBackgroundGradient().get();
ChatColor.LinearGradient.Builder linearGradientBuilder = ChatColor.LinearGradient.newBuilder();
linearGradientBuilder.setRotation(gradient.getAngle().or(0).floatValue());
linearGradientBuilder.addColors(gradient.getStartColor().get());
linearGradientBuilder.addColors(gradient.getEndColor().get());
linearGradientBuilder.addAllPositions(Arrays.asList(0f, 1f));
chatColorBuilder.setLinearGradient(linearGradientBuilder);
}
builder.setBackground(chatColorBuilder);
return Base64.encodeBytes(builder.build().toByteArray());
}
private void handleStoryReply(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message, @NonNull Recipient senderRecipient) throws StorageFailedException {
log(content.getTimestamp(), "Story reply.");
@@ -1473,7 +1541,7 @@ public final class MessageContentProcessor {
try {
Optional<QuoteModel> quote = getValidatedQuote(message.getQuote());
Optional<List<Contact>> sharedContacts = getContacts(message.getSharedContacts());
Optional<List<LinkPreview>> linkPreviews = getLinkPreviews(message.getPreviews(), message.getBody().or(""));
Optional<List<LinkPreview>> linkPreviews = getLinkPreviews(message.getPreviews(), message.getBody().or(""), false);
Optional<List<Mention>> mentions = getMentions(message.getMentions());
Optional<Attachment> sticker = getStickerAttachment(message.getSticker());
@@ -1569,7 +1637,7 @@ public final class MessageContentProcessor {
Optional<QuoteModel> quote = getValidatedQuote(message.getMessage().getQuote());
Optional<Attachment> sticker = getStickerAttachment(message.getMessage().getSticker());
Optional<List<Contact>> sharedContacts = getContacts(message.getMessage().getSharedContacts());
Optional<List<LinkPreview>> previews = getLinkPreviews(message.getMessage().getPreviews(), message.getMessage().getBody().or(""));
Optional<List<LinkPreview>> previews = getLinkPreviews(message.getMessage().getPreviews(), message.getMessage().getBody().or(""), false);
Optional<List<Mention>> mentions = getMentions(message.getMessage().getMentions());
boolean viewOnce = message.getMessage().isViewOnce();
List<Attachment> syncAttachments = viewOnce ? Collections.singletonList(new TombstoneAttachment(MediaUtil.VIEW_ONCE, false))
@@ -2314,7 +2382,7 @@ public final class MessageContentProcessor {
return Optional.of(contacts);
}
private Optional<List<LinkPreview>> getLinkPreviews(Optional<List<SignalServicePreview>> previews, @NonNull String message) {
private Optional<List<LinkPreview>> getLinkPreviews(Optional<List<SignalServicePreview>> previews, @NonNull String message, boolean isStoryEmbed) {
if (!previews.isPresent() || previews.get().isEmpty()) return Optional.absent();
List<LinkPreview> linkPreviews = new ArrayList<>(previews.get().size());
@@ -2329,7 +2397,7 @@ public final class MessageContentProcessor {
boolean presentInBody = url.isPresent() && urlsInMessage.containsUrl(url.get());
boolean validDomain = url.isPresent() && LinkPreviewUtil.isValidPreviewUrl(url.get());
if (hasTitle && presentInBody && validDomain) {
if (hasTitle && (presentInBody || isStoryEmbed) && validDomain) {
LinkPreview linkPreview = new LinkPreview(url.get(), title.or(""), description.or(""), preview.getDate(), thumbnail);
linkPreviews.add(linkPreview);
} else {

View File

@@ -0,0 +1,58 @@
package org.thoughtcrime.securesms.messages
import com.google.protobuf.InvalidProtocolBufferException
import org.thoughtcrime.securesms.database.model.databaseprotos.StoryTextPost
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
import org.thoughtcrime.securesms.util.Base64
import org.whispersystems.libsignal.util.guava.Optional
import org.whispersystems.signalservice.api.messages.SignalServicePreview
import org.whispersystems.signalservice.api.messages.SignalServiceTextAttachment
import kotlin.math.roundToInt
object StorySendUtil {
@JvmStatic
@Throws(InvalidProtocolBufferException::class)
fun deserializeBodyToStoryTextAttachment(message: OutgoingMediaMessage, getPreviewsFor: (OutgoingMediaMessage) -> List<SignalServicePreview>): SignalServiceTextAttachment {
val storyTextPost = StoryTextPost.parseFrom(Base64.decode(message.body))
val preview = if (message.linkPreviews.isEmpty()) {
Optional.absent()
} else {
Optional.of(getPreviewsFor(message)[0])
}
return if (storyTextPost.background.hasLinearGradient()) {
SignalServiceTextAttachment.forGradientBackground(
Optional.fromNullable(storyTextPost.body),
Optional.fromNullable(getStyle(storyTextPost.style)),
Optional.of(storyTextPost.textForegroundColor),
Optional.of(storyTextPost.textBackgroundColor),
preview,
SignalServiceTextAttachment.Gradient(
Optional.of(storyTextPost.background.linearGradient.getColors(0)),
Optional.of(storyTextPost.background.linearGradient.getColors(1)),
Optional.of(storyTextPost.background.linearGradient.rotation.roundToInt())
)
)
} else {
SignalServiceTextAttachment.forSolidBackground(
Optional.fromNullable(storyTextPost.body),
Optional.fromNullable(getStyle(storyTextPost.style)),
Optional.of(storyTextPost.textForegroundColor),
Optional.of(storyTextPost.textBackgroundColor),
preview,
storyTextPost.background.singleColor.color
)
}
}
private fun getStyle(style: StoryTextPost.Style): SignalServiceTextAttachment.Style {
return when (style) {
StoryTextPost.Style.REGULAR -> SignalServiceTextAttachment.Style.REGULAR
StoryTextPost.Style.BOLD -> SignalServiceTextAttachment.Style.BOLD
StoryTextPost.Style.SERIF -> SignalServiceTextAttachment.Style.SERIF
StoryTextPost.Style.SCRIPT -> SignalServiceTextAttachment.Style.SCRIPT
StoryTextPost.Style.CONDENSED -> SignalServiceTextAttachment.Style.CONDENSED
else -> SignalServiceTextAttachment.Style.DEFAULT
}
}
}