Add support for link preview descriptions.
This commit is contained in:
parent
a3438c4f8d
commit
c78e098cb4
19 changed files with 151 additions and 58 deletions
|
@ -119,6 +119,10 @@ public class ConversationItemThumbnail extends FrameLayout {
|
||||||
outliner.setRadii(topLeft, topRight, bottomRight, bottomLeft);
|
outliner.setRadii(topLeft, topRight, bottomRight, bottomLeft);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setMinimumThumbnailWidth(int width) {
|
||||||
|
thumbnail.setMinimumThumbnailWidth(width);
|
||||||
|
}
|
||||||
|
|
||||||
public void setBorderless(boolean borderless) {
|
public void setBorderless(boolean borderless) {
|
||||||
this.borderless = borderless;
|
this.borderless = borderless;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,11 +21,12 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.mms.ImageSlide;
|
import org.thoughtcrime.securesms.mms.ImageSlide;
|
||||||
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
import okhttp3.HttpUrl;
|
import okhttp3.HttpUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The view shown in the compose box that represents the state of the link preview.
|
* The view shown in the compose box or conversation that represents the state of the link preview.
|
||||||
*/
|
*/
|
||||||
public class LinkPreviewView extends FrameLayout {
|
public class LinkPreviewView extends FrameLayout {
|
||||||
|
|
||||||
|
@ -35,6 +36,7 @@ public class LinkPreviewView extends FrameLayout {
|
||||||
private ViewGroup container;
|
private ViewGroup container;
|
||||||
private OutlinedThumbnailView thumbnail;
|
private OutlinedThumbnailView thumbnail;
|
||||||
private TextView title;
|
private TextView title;
|
||||||
|
private TextView description;
|
||||||
private TextView site;
|
private TextView site;
|
||||||
private View divider;
|
private View divider;
|
||||||
private View closeButton;
|
private View closeButton;
|
||||||
|
@ -63,6 +65,7 @@ public class LinkPreviewView extends FrameLayout {
|
||||||
container = findViewById(R.id.linkpreview_container);
|
container = findViewById(R.id.linkpreview_container);
|
||||||
thumbnail = findViewById(R.id.linkpreview_thumbnail);
|
thumbnail = findViewById(R.id.linkpreview_thumbnail);
|
||||||
title = findViewById(R.id.linkpreview_title);
|
title = findViewById(R.id.linkpreview_title);
|
||||||
|
description = findViewById(R.id.linkpreview_description);
|
||||||
site = findViewById(R.id.linkpreview_site);
|
site = findViewById(R.id.linkpreview_site);
|
||||||
divider = findViewById(R.id.linkpreview_divider);
|
divider = findViewById(R.id.linkpreview_divider);
|
||||||
spinner = findViewById(R.id.linkpreview_progress_wheel);
|
spinner = findViewById(R.id.linkpreview_progress_wheel);
|
||||||
|
@ -85,6 +88,8 @@ public class LinkPreviewView extends FrameLayout {
|
||||||
container.setPadding(0, 0, 0, 0);
|
container.setPadding(0, 0, 0, 0);
|
||||||
divider.setVisibility(VISIBLE);
|
divider.setVisibility(VISIBLE);
|
||||||
closeButton.setVisibility(VISIBLE);
|
closeButton.setVisibility(VISIBLE);
|
||||||
|
title.setMaxLines(2);
|
||||||
|
description.setMaxLines(2);
|
||||||
|
|
||||||
closeButton.setOnClickListener(v -> {
|
closeButton.setOnClickListener(v -> {
|
||||||
if (closeClickedListener != null) {
|
if (closeClickedListener != null) {
|
||||||
|
@ -108,6 +113,7 @@ public class LinkPreviewView extends FrameLayout {
|
||||||
public void setLoading() {
|
public void setLoading() {
|
||||||
title.setVisibility(GONE);
|
title.setVisibility(GONE);
|
||||||
site.setVisibility(GONE);
|
site.setVisibility(GONE);
|
||||||
|
description.setVisibility(GONE);
|
||||||
thumbnail.setVisibility(GONE);
|
thumbnail.setVisibility(GONE);
|
||||||
spinner.setVisibility(VISIBLE);
|
spinner.setVisibility(VISIBLE);
|
||||||
noPreview.setVisibility(INVISIBLE);
|
noPreview.setVisibility(INVISIBLE);
|
||||||
|
@ -123,17 +129,33 @@ public class LinkPreviewView extends FrameLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLinkPreview(@NonNull GlideRequests glideRequests, @NonNull LinkPreview linkPreview, boolean showThumbnail) {
|
public void setLinkPreview(@NonNull GlideRequests glideRequests, @NonNull LinkPreview linkPreview, boolean showThumbnail) {
|
||||||
title.setVisibility(VISIBLE);
|
|
||||||
site.setVisibility(VISIBLE);
|
|
||||||
thumbnail.setVisibility(VISIBLE);
|
|
||||||
spinner.setVisibility(GONE);
|
spinner.setVisibility(GONE);
|
||||||
noPreview.setVisibility(GONE);
|
noPreview.setVisibility(GONE);
|
||||||
|
|
||||||
title.setText(linkPreview.getTitle());
|
if (!Util.isEmpty(linkPreview.getTitle())) {
|
||||||
|
title.setText(linkPreview.getTitle());
|
||||||
|
title.setVisibility(VISIBLE);
|
||||||
|
} else {
|
||||||
|
title.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
|
||||||
HttpUrl url = HttpUrl.parse(linkPreview.getUrl());
|
if (!Util.isEmpty(linkPreview.getDescription())) {
|
||||||
if (url != null) {
|
description.setText(linkPreview.getDescription());
|
||||||
site.setText(url.topPrivateDomain());
|
description.setVisibility(VISIBLE);
|
||||||
|
} else {
|
||||||
|
description.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Util.isEmpty(linkPreview.getUrl())) {
|
||||||
|
HttpUrl url = HttpUrl.parse(linkPreview.getUrl());
|
||||||
|
if (url != null) {
|
||||||
|
site.setText(url.topPrivateDomain());
|
||||||
|
site.setVisibility(VISIBLE);
|
||||||
|
} else {
|
||||||
|
site.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
site.setVisibility(GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showThumbnail && linkPreview.getThumbnail().isPresent()) {
|
if (showThumbnail && linkPreview.getThumbnail().isPresent()) {
|
||||||
|
|
|
@ -141,6 +141,11 @@ public class ThumbnailView extends FrameLayout {
|
||||||
captionIcon.setScaleY(captionIconScale);
|
captionIcon.setScaleY(captionIconScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setMinimumThumbnailWidth(int width) {
|
||||||
|
bounds[MIN_WIDTH] = width;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("SuspiciousNameCombination")
|
@SuppressWarnings("SuspiciousNameCombination")
|
||||||
private void fillTargetDimensions(int[] targetDimens, int[] dimens, int[] bounds) {
|
private void fillTargetDimensions(int[] targetDimens, int[] dimens, int[] bounds) {
|
||||||
int dimensFilledCount = getNonZeroCount(dimens);
|
int dimensFilledCount = getNonZeroCount(dimens);
|
||||||
|
|
|
@ -116,6 +116,7 @@ import org.thoughtcrime.securesms.util.LongClickMovementMethod;
|
||||||
import org.thoughtcrime.securesms.util.SearchUtil;
|
import org.thoughtcrime.securesms.util.SearchUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.thoughtcrime.securesms.util.VibrateUtil;
|
import org.thoughtcrime.securesms.util.VibrateUtil;
|
||||||
import org.thoughtcrime.securesms.util.UrlClickHandler;
|
import org.thoughtcrime.securesms.util.UrlClickHandler;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
@ -569,10 +570,17 @@ public class ConversationItem extends LinearLayout implements BindableConversati
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasBigImageLinkPreview(MessageRecord messageRecord) {
|
private boolean hasBigImageLinkPreview(MessageRecord messageRecord) {
|
||||||
if (!hasLinkPreview(messageRecord)) return false;
|
if (!hasLinkPreview(messageRecord)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
LinkPreview linkPreview = ((MmsMessageRecord) messageRecord).getLinkPreviews().get(0);
|
LinkPreview linkPreview = ((MmsMessageRecord) messageRecord).getLinkPreviews().get(0);
|
||||||
int minWidth = getResources().getDimensionPixelSize(R.dimen.media_bubble_min_width);
|
|
||||||
|
if (linkPreview.getThumbnail().isPresent() && !Util.isEmpty(linkPreview.getDescription())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int minWidth = getResources().getDimensionPixelSize(R.dimen.media_bubble_min_width_solo);
|
||||||
|
|
||||||
return linkPreview.getThumbnail().isPresent() &&
|
return linkPreview.getThumbnail().isPresent() &&
|
||||||
linkPreview.getThumbnail().get().getWidth() >= minWidth &&
|
linkPreview.getThumbnail().get().getWidth() >= minWidth &&
|
||||||
|
@ -681,6 +689,7 @@ public class ConversationItem extends LinearLayout implements BindableConversati
|
||||||
|
|
||||||
if (hasBigImageLinkPreview(messageRecord)) {
|
if (hasBigImageLinkPreview(messageRecord)) {
|
||||||
mediaThumbnailStub.get().setVisibility(VISIBLE);
|
mediaThumbnailStub.get().setVisibility(VISIBLE);
|
||||||
|
mediaThumbnailStub.get().setMinimumThumbnailWidth(readDimen(R.dimen.media_bubble_min_width_with_content));
|
||||||
mediaThumbnailStub.get().setImageResource(glideRequests, Collections.singletonList(new ImageSlide(context, linkPreview.getThumbnail().get())), showControls, false);
|
mediaThumbnailStub.get().setImageResource(glideRequests, Collections.singletonList(new ImageSlide(context, linkPreview.getThumbnail().get())), showControls, false);
|
||||||
mediaThumbnailStub.get().setThumbnailClickListener(new LinkPreviewThumbnailClickListener());
|
mediaThumbnailStub.get().setThumbnailClickListener(new LinkPreviewThumbnailClickListener());
|
||||||
mediaThumbnailStub.get().setDownloadClickListener(downloadClickListener);
|
mediaThumbnailStub.get().setDownloadClickListener(downloadClickListener);
|
||||||
|
@ -778,10 +787,12 @@ public class ConversationItem extends LinearLayout implements BindableConversati
|
||||||
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||||
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
|
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
|
||||||
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
|
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
|
||||||
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
||||||
if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE);
|
if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE);
|
||||||
|
|
||||||
List<Slide> thumbnailSlides = ((MmsMessageRecord) messageRecord).getSlideDeck().getThumbnailSlides();
|
List<Slide> thumbnailSlides = ((MmsMessageRecord) messageRecord).getSlideDeck().getThumbnailSlides();
|
||||||
|
mediaThumbnailStub.get().setMinimumThumbnailWidth(readDimen(isCaptionlessMms(messageRecord) ? R.dimen.media_bubble_min_width_solo
|
||||||
|
: R.dimen.media_bubble_min_width_with_content));
|
||||||
mediaThumbnailStub.get().setImageResource(glideRequests,
|
mediaThumbnailStub.get().setImageResource(glideRequests,
|
||||||
thumbnailSlides,
|
thumbnailSlides,
|
||||||
showControls,
|
showControls,
|
||||||
|
|
|
@ -1118,7 +1118,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||||
if (preview.getAttachmentId() != null) {
|
if (preview.getAttachmentId() != null) {
|
||||||
DatabaseAttachment attachment = attachmentIdMap.get(preview.getAttachmentId());
|
DatabaseAttachment attachment = attachmentIdMap.get(preview.getAttachmentId());
|
||||||
if (attachment != null) {
|
if (attachment != null) {
|
||||||
previews.add(new LinkPreview(preview.getUrl(), preview.getTitle(), attachment));
|
previews.add(new LinkPreview(preview.getUrl(), preview.getTitle(), preview.getDescription(), attachment));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
previews.add(preview);
|
previews.add(preview);
|
||||||
|
@ -1526,7 +1526,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||||
attachmentId = insertedAttachmentIds.get(preview.getThumbnail().get());
|
attachmentId = insertedAttachmentIds.get(preview.getThumbnail().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
LinkPreview updatedPreview = new LinkPreview(preview.getUrl(), preview.getTitle(), attachmentId);
|
LinkPreview updatedPreview = new LinkPreview(preview.getUrl(), preview.getTitle(), preview.getDescription(), attachmentId);
|
||||||
linkPreviewJson.put(new JSONObject(updatedPreview.serialize()));
|
linkPreviewJson.put(new JSONObject(updatedPreview.serialize()));
|
||||||
} catch (JSONException | IOException e) {
|
} catch (JSONException | IOException e) {
|
||||||
Log.w(TAG, "Failed to serialize shared contact. Skipping it.", e);
|
Log.w(TAG, "Failed to serialize shared contact. Skipping it.", e);
|
||||||
|
|
|
@ -1693,15 +1693,16 @@ public final class PushProcessMessageJob extends BaseJob {
|
||||||
Optional<Attachment> thumbnail = PointerAttachment.forPointer(preview.getImage());
|
Optional<Attachment> thumbnail = PointerAttachment.forPointer(preview.getImage());
|
||||||
Optional<String> url = Optional.fromNullable(preview.getUrl());
|
Optional<String> url = Optional.fromNullable(preview.getUrl());
|
||||||
Optional<String> title = Optional.fromNullable(preview.getTitle());
|
Optional<String> title = Optional.fromNullable(preview.getTitle());
|
||||||
boolean hasContent = !TextUtils.isEmpty(title.or("")) || thumbnail.isPresent();
|
Optional<String> description = Optional.fromNullable(preview.getDescription());
|
||||||
|
boolean hasTitle = !TextUtils.isEmpty(title.or(""));
|
||||||
boolean presentInBody = url.isPresent() && Stream.of(LinkPreviewUtil.findValidPreviewUrls(message)).map(Link::getUrl).collect(Collectors.toSet()).contains(url.get());
|
boolean presentInBody = url.isPresent() && Stream.of(LinkPreviewUtil.findValidPreviewUrls(message)).map(Link::getUrl).collect(Collectors.toSet()).contains(url.get());
|
||||||
boolean validDomain = url.isPresent() && LinkPreviewUtil.isValidPreviewUrl(url.get());
|
boolean validDomain = url.isPresent() && LinkPreviewUtil.isValidPreviewUrl(url.get());
|
||||||
|
|
||||||
if (hasContent && presentInBody && validDomain) {
|
if (hasTitle && presentInBody && validDomain) {
|
||||||
LinkPreview linkPreview = new LinkPreview(url.get(), title.or(""), thumbnail);
|
LinkPreview linkPreview = new LinkPreview(url.get(), title.or(""), description.or(""), thumbnail);
|
||||||
linkPreviews.add(linkPreview);
|
linkPreviews.add(linkPreview);
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, String.format("Discarding an invalid link preview. hasContent: %b presentInBody: %b validDomain: %b", hasContent, presentInBody, validDomain));
|
Log.w(TAG, String.format("Discarding an invalid link preview. hasTitle: %b presentInBody: %b validDomain: %b", hasTitle, presentInBody, validDomain));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -315,7 +315,7 @@ public abstract class PushSendJob extends SendJob {
|
||||||
List<Preview> getPreviewsFor(OutgoingMediaMessage mediaMessage) {
|
List<Preview> getPreviewsFor(OutgoingMediaMessage mediaMessage) {
|
||||||
return Stream.of(mediaMessage.getLinkPreviews()).map(lp -> {
|
return Stream.of(mediaMessage.getLinkPreviews()).map(lp -> {
|
||||||
SignalServiceAttachment attachment = lp.getThumbnail().isPresent() ? getAttachmentPointerFor(lp.getThumbnail().get()) : null;
|
SignalServiceAttachment attachment = lp.getThumbnail().isPresent() ? getAttachmentPointerFor(lp.getThumbnail().get()) : null;
|
||||||
return new Preview(lp.getUrl(), lp.getTitle(), Optional.fromNullable(attachment));
|
return new Preview(lp.getUrl(), lp.getTitle(), lp.getDescription(), Optional.fromNullable(attachment));
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,45 +22,56 @@ public class LinkPreview {
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private final String title;
|
private final String title;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private final String description;
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private final AttachmentId attachmentId;
|
private final AttachmentId attachmentId;
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private final Optional<Attachment> thumbnail;
|
private final Optional<Attachment> thumbnail;
|
||||||
|
|
||||||
public LinkPreview(@NonNull String url, @NonNull String title, @NonNull DatabaseAttachment thumbnail) {
|
public LinkPreview(@NonNull String url, @NonNull String title, @NonNull String description, @NonNull DatabaseAttachment thumbnail) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
|
this.description = description;
|
||||||
this.thumbnail = Optional.of(thumbnail);
|
this.thumbnail = Optional.of(thumbnail);
|
||||||
this.attachmentId = thumbnail.getAttachmentId();
|
this.attachmentId = thumbnail.getAttachmentId();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LinkPreview(@NonNull String url, @NonNull String title, @NonNull Optional<Attachment> thumbnail) {
|
public LinkPreview(@NonNull String url, @NonNull String title, @NonNull String description, @NonNull Optional<Attachment> thumbnail) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
|
this.description = description;
|
||||||
this.thumbnail = thumbnail;
|
this.thumbnail = thumbnail;
|
||||||
this.attachmentId = null;
|
this.attachmentId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LinkPreview(@JsonProperty("url") @NonNull String url,
|
public LinkPreview(@JsonProperty("url") @NonNull String url,
|
||||||
@JsonProperty("title") @NonNull String title,
|
@JsonProperty("title") @NonNull String title,
|
||||||
|
@JsonProperty("description") @Nullable String description,
|
||||||
@JsonProperty("attachmentId") @Nullable AttachmentId attachmentId)
|
@JsonProperty("attachmentId") @Nullable AttachmentId attachmentId)
|
||||||
{
|
{
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
|
this.description = Optional.fromNullable(description).or("");
|
||||||
this.attachmentId = attachmentId;
|
this.attachmentId = attachmentId;
|
||||||
this.thumbnail = Optional.absent();
|
this.thumbnail = Optional.absent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUrl() {
|
public @NonNull String getUrl() {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTitle() {
|
public @NonNull String getTitle() {
|
||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<Attachment> getThumbnail() {
|
public @NonNull String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull Optional<Attachment> getThumbnail() {
|
||||||
return thumbnail;
|
return thumbnail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,11 +79,11 @@ public class LinkPreview {
|
||||||
return attachmentId;
|
return attachmentId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String serialize() throws IOException {
|
public @NonNull String serialize() throws IOException {
|
||||||
return JsonUtils.toJson(this);
|
return JsonUtils.toJson(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LinkPreview deserialize(@NonNull String serialized) throws IOException {
|
public static @NonNull LinkPreview deserialize(@NonNull String serialized) throws IOException {
|
||||||
return JsonUtils.fromJson(serialized, LinkPreview.class);
|
return JsonUtils.fromJson(serialized, LinkPreview.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,7 +105,7 @@ public class LinkPreviewRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!metadata.getImageUrl().isPresent()) {
|
if (!metadata.getImageUrl().isPresent()) {
|
||||||
callback.onSuccess(new LinkPreview(url, metadata.getTitle().get(), Optional.absent()));
|
callback.onSuccess(new LinkPreview(url, metadata.getTitle().or(""), metadata.getDescription().or(""), Optional.absent()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ public class LinkPreviewRepository {
|
||||||
if (!metadata.getTitle().isPresent() && !attachment.isPresent()) {
|
if (!metadata.getTitle().isPresent() && !attachment.isPresent()) {
|
||||||
callback.onError(Error.PREVIEW_NOT_AVAILABLE);
|
callback.onError(Error.PREVIEW_NOT_AVAILABLE);
|
||||||
} else {
|
} else {
|
||||||
callback.onSuccess(new LinkPreview(url, metadata.getTitle().or(""), attachment));
|
callback.onSuccess(new LinkPreview(url, metadata.getTitle().or(""), metadata.getDescription().or(""), attachment));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -147,17 +147,18 @@ public class LinkPreviewRepository {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String body = OkHttpUtil.readAsString(response.body(), FAILSAFE_MAX_TEXT_SIZE);
|
String body = OkHttpUtil.readAsString(response.body(), FAILSAFE_MAX_TEXT_SIZE);
|
||||||
OpenGraph openGraph = LinkPreviewUtil.parseOpenGraphFields(body);
|
OpenGraph openGraph = LinkPreviewUtil.parseOpenGraphFields(body);
|
||||||
Optional<String> title = openGraph.getTitle();
|
Optional<String> title = openGraph.getTitle();
|
||||||
Optional<String> imageUrl = openGraph.getImageUrl();
|
Optional<String> description = openGraph.getDescription();
|
||||||
|
Optional<String> imageUrl = openGraph.getImageUrl();
|
||||||
|
|
||||||
if (imageUrl.isPresent() && !LinkPreviewUtil.isValidPreviewUrl(imageUrl.get())) {
|
if (imageUrl.isPresent() && !LinkPreviewUtil.isValidPreviewUrl(imageUrl.get())) {
|
||||||
Log.i(TAG, "Image URL was invalid or for a non-whitelisted domain. Skipping.");
|
Log.i(TAG, "Image URL was invalid or for a non-whitelisted domain. Skipping.");
|
||||||
imageUrl = Optional.absent();
|
imageUrl = Optional.absent();
|
||||||
}
|
}
|
||||||
|
|
||||||
callback.accept(new Metadata(title, imageUrl));
|
callback.accept(new Metadata(title, description, imageUrl));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -225,7 +226,7 @@ public class LinkPreviewRepository {
|
||||||
|
|
||||||
Optional<Attachment> thumbnail = bitmapToAttachment(bitmap, Bitmap.CompressFormat.WEBP, MediaUtil.IMAGE_WEBP);
|
Optional<Attachment> thumbnail = bitmapToAttachment(bitmap, Bitmap.CompressFormat.WEBP, MediaUtil.IMAGE_WEBP);
|
||||||
|
|
||||||
callback.onSuccess(new LinkPreview(packUrl, title, thumbnail));
|
callback.onSuccess(new LinkPreview(packUrl, title, "", thumbnail));
|
||||||
} else {
|
} else {
|
||||||
callback.onError(Error.PREVIEW_NOT_AVAILABLE);
|
callback.onError(Error.PREVIEW_NOT_AVAILABLE);
|
||||||
}
|
}
|
||||||
|
@ -268,7 +269,7 @@ public class LinkPreviewRepository {
|
||||||
thumbnail = bitmapToAttachment(bitmap, Bitmap.CompressFormat.WEBP, MediaUtil.IMAGE_WEBP);
|
thumbnail = bitmapToAttachment(bitmap, Bitmap.CompressFormat.WEBP, MediaUtil.IMAGE_WEBP);
|
||||||
}
|
}
|
||||||
|
|
||||||
callback.onSuccess(new LinkPreview(groupUrl, title, thumbnail));
|
callback.onSuccess(new LinkPreview(groupUrl, title, "", thumbnail));
|
||||||
} else {
|
} else {
|
||||||
Log.i(TAG, "Group is not locally available for preview generation, fetching from server");
|
Log.i(TAG, "Group is not locally available for preview generation, fetching from server");
|
||||||
|
|
||||||
|
@ -284,7 +285,7 @@ public class LinkPreviewRepository {
|
||||||
if (bitmap != null) bitmap.recycle();
|
if (bitmap != null) bitmap.recycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
callback.onSuccess(new LinkPreview(groupUrl, joinInfo.getTitle(), thumbnail));
|
callback.onSuccess(new LinkPreview(groupUrl, joinInfo.getTitle(), "", thumbnail));
|
||||||
}
|
}
|
||||||
} catch (ExecutionException | InterruptedException | IOException | VerificationFailedException e) {
|
} catch (ExecutionException | InterruptedException | IOException | VerificationFailedException e) {
|
||||||
Log.w(TAG, "Failed to fetch group link preview.", e);
|
Log.w(TAG, "Failed to fetch group link preview.", e);
|
||||||
|
@ -337,21 +338,27 @@ public class LinkPreviewRepository {
|
||||||
|
|
||||||
private static class Metadata {
|
private static class Metadata {
|
||||||
private final Optional<String> title;
|
private final Optional<String> title;
|
||||||
|
private final Optional<String> description;
|
||||||
private final Optional<String> imageUrl;
|
private final Optional<String> imageUrl;
|
||||||
|
|
||||||
Metadata(Optional<String> title, Optional<String> imageUrl) {
|
Metadata(Optional<String> title, Optional<String> description, Optional<String> imageUrl) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.imageUrl = imageUrl;
|
this.description = description;
|
||||||
|
this.imageUrl = imageUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Metadata empty() {
|
static Metadata empty() {
|
||||||
return new Metadata(Optional.absent(), Optional.absent());
|
return new Metadata(Optional.absent(), Optional.absent(), Optional.absent());
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<String> getTitle() {
|
Optional<String> getTitle() {
|
||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Optional<String> getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
Optional<String> getImageUrl() {
|
Optional<String> getImageUrl() {
|
||||||
return imageUrl;
|
return imageUrl;
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,8 +154,9 @@ public final class LinkPreviewUtil {
|
||||||
private final @Nullable String htmlTitle;
|
private final @Nullable String htmlTitle;
|
||||||
private final @Nullable String faviconUrl;
|
private final @Nullable String faviconUrl;
|
||||||
|
|
||||||
private static final String KEY_TITLE = "title";
|
private static final String KEY_TITLE = "title";
|
||||||
private static final String KEY_IMAGE_URL = "image";
|
private static final String KEY_DESCRIPTION_URL = "description";
|
||||||
|
private static final String KEY_IMAGE_URL = "image";
|
||||||
|
|
||||||
public OpenGraph(@NonNull Map<String, String> values, @Nullable String htmlTitle, @Nullable String faviconUrl) {
|
public OpenGraph(@NonNull Map<String, String> values, @Nullable String htmlTitle, @Nullable String faviconUrl) {
|
||||||
this.values = values;
|
this.values = values;
|
||||||
|
@ -170,6 +171,10 @@ public final class LinkPreviewUtil {
|
||||||
public @NonNull Optional<String> getImageUrl() {
|
public @NonNull Optional<String> getImageUrl() {
|
||||||
return OptionalUtil.absentIfEmpty(Util.getFirstNonEmpty(values.get(KEY_IMAGE_URL), faviconUrl));
|
return OptionalUtil.absentIfEmpty(Util.getFirstNonEmpty(values.get(KEY_IMAGE_URL), faviconUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @NonNull Optional<String> getDescription() {
|
||||||
|
return OptionalUtil.absentIfEmpty(values.get(KEY_DESCRIPTION_URL));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface HtmlDecoder {
|
public interface HtmlDecoder {
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
android:contentDescription="@string/conversation_item__mms_image_description"
|
android:contentDescription="@string/conversation_item__mms_image_description"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:elevation="8dp"
|
android:elevation="8dp"
|
||||||
app:conversationThumbnail_minWidth="@dimen/media_bubble_min_width"
|
app:conversationThumbnail_minWidth="@dimen/media_bubble_min_width_solo"
|
||||||
app:conversationThumbnail_maxWidth="@dimen/media_bubble_max_width"
|
app:conversationThumbnail_maxWidth="@dimen/media_bubble_max_width"
|
||||||
app:conversationThumbnail_minHeight="@dimen/media_bubble_min_height"
|
app:conversationThumbnail_minHeight="@dimen/media_bubble_min_height"
|
||||||
app:conversationThumbnail_maxHeight="@dimen/media_bubble_max_height"
|
app:conversationThumbnail_maxHeight="@dimen/media_bubble_max_height"
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
android:contentDescription="@string/conversation_item__mms_image_description"
|
android:contentDescription="@string/conversation_item__mms_image_description"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:elevation="8dp"
|
android:elevation="8dp"
|
||||||
app:conversationThumbnail_minWidth="@dimen/media_bubble_min_width"
|
app:conversationThumbnail_minWidth="@dimen/media_bubble_min_width_solo"
|
||||||
app:conversationThumbnail_maxWidth="@dimen/media_bubble_max_width"
|
app:conversationThumbnail_maxWidth="@dimen/media_bubble_max_width"
|
||||||
app:conversationThumbnail_minHeight="@dimen/media_bubble_min_height"
|
app:conversationThumbnail_minHeight="@dimen/media_bubble_min_height"
|
||||||
app:conversationThumbnail_maxHeight="@dimen/media_bubble_max_height"
|
app:conversationThumbnail_maxHeight="@dimen/media_bubble_max_height"
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
android:id="@+id/linkpreview_thumbnail"
|
android:id="@+id/linkpreview_thumbnail"
|
||||||
android:layout_width="72dp"
|
android:layout_width="72dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
|
android:maxHeight="72dp"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/linkpreview_divider"
|
app:layout_constraintBottom_toTopOf="@+id/linkpreview_divider"
|
||||||
|
@ -26,11 +27,11 @@
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||||
android:id="@+id/linkpreview_title"
|
android:id="@+id/linkpreview_title"
|
||||||
style="@style/Signal.Text.Body"
|
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
|
style="@style/Signal.Text.Preview"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:fontFamily="sans-serif-medium"
|
android:fontFamily="sans-serif-medium"
|
||||||
android:maxLines="2"
|
android:maxLines="2"
|
||||||
|
@ -39,7 +40,23 @@
|
||||||
app:layout_constraintHorizontal_bias="0.0"
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
app:layout_constraintStart_toEndOf="@+id/linkpreview_thumbnail"
|
app:layout_constraintStart_toEndOf="@+id/linkpreview_thumbnail"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:text="Wall Crawler Strikes Again!" />
|
tools:text="J. Jonah Jameson on Twitter" />
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||||
|
android:id="@+id/linkpreview_description"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
style="@style/Signal.Text.Preview"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="15"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/linkpreview_close"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/linkpreview_thumbnail"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/linkpreview_title"
|
||||||
|
tools:text="Wall crawler strikes again!" />
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||||
android:id="@+id/linkpreview_site"
|
android:id="@+id/linkpreview_site"
|
||||||
|
@ -50,7 +67,7 @@
|
||||||
android:layout_marginTop="2dp"
|
android:layout_marginTop="2dp"
|
||||||
android:textColor="?linkpreview_secondary_text_color"
|
android:textColor="?linkpreview_secondary_text_color"
|
||||||
app:layout_constraintStart_toEndOf="@+id/linkpreview_thumbnail"
|
app:layout_constraintStart_toEndOf="@+id/linkpreview_thumbnail"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/linkpreview_title"
|
app:layout_constraintTop_toBottomOf="@+id/linkpreview_description"
|
||||||
tools:text="dailybugle.com" />
|
tools:text="dailybugle.com" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
android:layout_height="@dimen/media_bubble_sticker_dimens"
|
android:layout_height="@dimen/media_bubble_sticker_dimens"
|
||||||
app:thumbnail_radius="0dp"
|
app:thumbnail_radius="0dp"
|
||||||
app:thumbnail_fit="fit_center"
|
app:thumbnail_fit="fit_center"
|
||||||
app:minWidth="@dimen/media_bubble_min_width"
|
app:minWidth="@dimen/media_bubble_min_width_solo"
|
||||||
app:maxWidth="@dimen/media_bubble_max_width"
|
app:maxWidth="@dimen/media_bubble_max_width"
|
||||||
app:minHeight="@dimen/media_bubble_min_height"
|
app:minHeight="@dimen/media_bubble_min_height"
|
||||||
app:maxHeight="@dimen/media_bubble_max_height" />
|
app:maxHeight="@dimen/media_bubble_max_height" />
|
||||||
|
|
|
@ -41,7 +41,8 @@
|
||||||
<dimen name="media_bubble_remove_button_size">24dp</dimen>
|
<dimen name="media_bubble_remove_button_size">24dp</dimen>
|
||||||
<dimen name="media_bubble_edit_button_size">24dp</dimen>
|
<dimen name="media_bubble_edit_button_size">24dp</dimen>
|
||||||
<dimen name="media_bubble_default_dimens">210dp</dimen>
|
<dimen name="media_bubble_default_dimens">210dp</dimen>
|
||||||
<dimen name="media_bubble_min_width">150dp</dimen>
|
<dimen name="media_bubble_min_width_solo">150dp</dimen>
|
||||||
|
<dimen name="media_bubble_min_width_with_content">240dp</dimen>
|
||||||
<dimen name="media_bubble_max_width">240dp</dimen>
|
<dimen name="media_bubble_max_width">240dp</dimen>
|
||||||
<dimen name="media_bubble_min_height">100dp</dimen>
|
<dimen name="media_bubble_min_height">100dp</dimen>
|
||||||
<dimen name="media_bubble_max_height">320dp</dimen>
|
<dimen name="media_bubble_max_height">320dp</dimen>
|
||||||
|
|
|
@ -628,6 +628,7 @@ public class SignalServiceMessageSender {
|
||||||
for (SignalServiceDataMessage.Preview preview : message.getPreviews().get()) {
|
for (SignalServiceDataMessage.Preview preview : message.getPreviews().get()) {
|
||||||
DataMessage.Preview.Builder previewBuilder = DataMessage.Preview.newBuilder();
|
DataMessage.Preview.Builder previewBuilder = DataMessage.Preview.newBuilder();
|
||||||
previewBuilder.setTitle(preview.getTitle());
|
previewBuilder.setTitle(preview.getTitle());
|
||||||
|
previewBuilder.setDescription(preview.getDescription());
|
||||||
previewBuilder.setUrl(preview.getUrl());
|
previewBuilder.setUrl(preview.getUrl());
|
||||||
|
|
||||||
if (preview.getImage().isPresent()) {
|
if (preview.getImage().isPresent()) {
|
||||||
|
|
|
@ -685,8 +685,9 @@ public final class SignalServiceContent {
|
||||||
}
|
}
|
||||||
|
|
||||||
results.add(new SignalServiceDataMessage.Preview(preview.getUrl(),
|
results.add(new SignalServiceDataMessage.Preview(preview.getUrl(),
|
||||||
preview.getTitle(),
|
preview.getTitle(),
|
||||||
Optional.fromNullable(attachment)));
|
preview.getDescription(),
|
||||||
|
Optional.fromNullable(attachment)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
|
|
|
@ -412,12 +412,14 @@ public class SignalServiceDataMessage {
|
||||||
public static class Preview {
|
public static class Preview {
|
||||||
private final String url;
|
private final String url;
|
||||||
private final String title;
|
private final String title;
|
||||||
|
private final String description;
|
||||||
private final Optional<SignalServiceAttachment> image;
|
private final Optional<SignalServiceAttachment> image;
|
||||||
|
|
||||||
public Preview(String url, String title, Optional<SignalServiceAttachment> image) {
|
public Preview(String url, String title, String description, Optional<SignalServiceAttachment> image) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.image = image;
|
this.description = description;
|
||||||
|
this.image = image;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUrl() {
|
public String getUrl() {
|
||||||
|
@ -428,6 +430,10 @@ public class SignalServiceDataMessage {
|
||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
public Optional<SignalServiceAttachment> getImage() {
|
public Optional<SignalServiceAttachment> getImage() {
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
|
@ -203,9 +203,10 @@ message DataMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
message Preview {
|
message Preview {
|
||||||
optional string url = 1;
|
optional string url = 1;
|
||||||
optional string title = 2;
|
optional string title = 2;
|
||||||
optional AttachmentPointer image = 3;
|
optional AttachmentPointer image = 3;
|
||||||
|
optional string description = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Sticker {
|
message Sticker {
|
||||||
|
|
Loading…
Add table
Reference in a new issue