implement sticker visualization (#1823)

Sticker picker is not implemented, just receiving(from DC Desktop, bots or future DC versions) and forwarding stickers
(code adapted from Signal)
This commit is contained in:
Asiel Díaz Benítez 2021-03-10 04:55:50 -05:00 committed by GitHub
parent 614516ff98
commit beee60d6a7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 256 additions and 3 deletions

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="8dp" />
<solid android:color="@color/universal_overlay" />
</shape>

View file

@ -118,6 +118,12 @@
android:layout_height="wrap_content"
android:layout="@layout/conversation_item_received_thumbnail" />
<ViewStub
android:id="@+id/sticker_view_stub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout="@layout/conversation_item_received_sticker" />
<ViewStub
android:id="@+id/audio_view_stub"
android:layout="@layout/conversation_item_received_audio"
@ -183,6 +189,19 @@
android:alpha="0.7"
app:footer_text_color="?conversation_item_incoming_text_secondary_color"/>
<org.thoughtcrime.securesms.components.ConversationItemFooter
android:id="@+id/conversation_item_sticker_footer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
android:layout_marginRight="@dimen/message_bubble_horizontal_padding"
android:layout_marginBottom="@dimen/message_bubble_bottom_padding"
android:layout_gravity="end"
android:gravity="end"
android:clipChildren="false"
android:clipToPadding="false"
app:footer_text_color="@color/gray50"/>
</LinearLayout>
</RelativeLayout>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.components.BorderlessImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/image_view"
android:layout_width="@dimen/media_bubble_sticker_dimens"
android:layout_height="@dimen/media_bubble_sticker_dimens"
android:contentDescription="@string/image" />

View file

@ -96,6 +96,12 @@
android:layout_height="wrap_content"
android:layout="@layout/conversation_item_sent_thumbnail" />
<ViewStub
android:id="@+id/sticker_view_stub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout="@layout/conversation_item_received_sticker" />
<ViewStub
android:id="@+id/audio_view_stub"
android:layout="@layout/conversation_item_sent_audio"
@ -160,6 +166,19 @@
android:clipToPadding="false"
app:footer_text_color="?attr/conversation_item_outgoing_text_secondary_color"/>
<org.thoughtcrime.securesms.components.ConversationItemFooter
android:id="@+id/conversation_item_sticker_footer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
android:layout_marginRight="@dimen/message_bubble_horizontal_padding"
android:layout_marginBottom="@dimen/message_bubble_bottom_padding"
android:layout_gravity="end"
android:gravity="end"
android:clipChildren="false"
android:clipToPadding="false"
app:footer_text_color="@color/gray50"/>
</LinearLayout>
<!-- the following view is only left because it is used as a reference for positioning above.

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.components.BorderlessImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/image_view"
android:layout_width="@dimen/media_bubble_sticker_dimens"
android:layout_height="@dimen/media_bubble_sticker_dimens"
android:contentDescription="@string/image" />

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:parentTag="org.thoughtcrime.securesms.components.BorderlessImageView">
<View
android:id="@+id/sticker_missing_shade"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/sticker_missing_background"
android:visibility="gone"
tools:visibility="visible"/>
<org.thoughtcrime.securesms.components.ThumbnailView
android:id="@+id/sticker_thumbnail"
android:layout_width="@dimen/media_bubble_sticker_dimens"
android:layout_height="@dimen/media_bubble_sticker_dimens"
app:thumbnail_radius="0dp"
app:minWidth="@dimen/media_bubble_min_width"
app:maxWidth="@dimen/media_bubble_max_width"
app:minHeight="@dimen/media_bubble_min_height"
app:maxHeight="@dimen/media_bubble_max_height" />
</merge>

View file

@ -31,6 +31,8 @@
<dimen name="media_bubble_max_height">320dp</dimen>
<dimen name="below_bubble">3dp</dimen>
<dimen name="media_bubble_sticker_dimens">175dp</dimen>
<dimen name="gallery_thumbnail_radius">1dp</dimen>
<dimen name="media_keyboard_provider_icon_padding">5dp</dimen>

View file

@ -9,6 +9,7 @@ public class DcMsg {
public final static int DC_MSG_TEXT = 10;
public final static int DC_MSG_IMAGE = 20;
public final static int DC_MSG_GIF = 21;
public final static int DC_MSG_STICKER = 23;
public final static int DC_MSG_AUDIO = 40;
public final static int DC_MSG_VOICE = 41;
public final static int DC_MSG_VIDEO = 50;

View file

@ -81,6 +81,8 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
private static final int MESSAGE_TYPE_DOCUMENT_OUTGOING = 7;
private static final int MESSAGE_TYPE_DOCUMENT_INCOMING = 8;
private static final int MESSAGE_TYPE_VIDEOCHAT_INVITE = 9;
private static final int MESSAGE_TYPE_STICKER_INCOMING = 10;
private static final int MESSAGE_TYPE_STICKER_OUTGOING = 11;
private final Set<DcMsg> batchSelected = Collections.synchronizedSet(new HashSet<DcMsg>());
@ -275,10 +277,12 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
case MESSAGE_TYPE_AUDIO_OUTGOING:
case MESSAGE_TYPE_THUMBNAIL_OUTGOING:
case MESSAGE_TYPE_DOCUMENT_OUTGOING:
case MESSAGE_TYPE_STICKER_OUTGOING:
case MESSAGE_TYPE_OUTGOING: return R.layout.conversation_item_sent;
case MESSAGE_TYPE_AUDIO_INCOMING:
case MESSAGE_TYPE_THUMBNAIL_INCOMING:
case MESSAGE_TYPE_DOCUMENT_INCOMING:
case MESSAGE_TYPE_STICKER_INCOMING:
case MESSAGE_TYPE_INCOMING: return R.layout.conversation_item_received;
case MESSAGE_TYPE_INFO: return R.layout.conversation_item_update;
case MESSAGE_TYPE_VIDEOCHAT_INVITE:return R.layout.conversation_item_videochat;
@ -302,6 +306,9 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
else if (type==DcMsg.DC_MSG_IMAGE || type==DcMsg.DC_MSG_GIF || type==DcMsg.DC_MSG_VIDEO) {
return dcMsg.isOutgoing()? MESSAGE_TYPE_THUMBNAIL_OUTGOING : MESSAGE_TYPE_THUMBNAIL_INCOMING;
}
else if (type == DcMsg.DC_MSG_STICKER) {
return dcMsg.isOutgoing()? MESSAGE_TYPE_STICKER_OUTGOING : MESSAGE_TYPE_STICKER_INCOMING;
}
else if (type == DcMsg.DC_MSG_VIDEOCHAT_INVITATION) {
return MESSAGE_TYPE_VIDEOCHAT_INVITE;
}

View file

@ -47,6 +47,7 @@ import com.b44t.messenger.DcMsg;
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
import org.thoughtcrime.securesms.components.AudioView;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.BorderlessImageView;
import org.thoughtcrime.securesms.components.ConversationItemFooter;
import org.thoughtcrime.securesms.components.ConversationItemThumbnail;
import org.thoughtcrime.securesms.components.DocumentView;
@ -61,6 +62,7 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideClickListener;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.mms.StickerSlide;
import org.thoughtcrime.securesms.mms.VideoSlide;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
@ -104,6 +106,7 @@ public class ConversationItem extends LinearLayout
@Nullable private QuoteView quoteView;
private TextView bodyText;
private ConversationItemFooter footer;
private ConversationItemFooter stickerFooter;
private TextView groupSender;
private View groupSenderHolder;
private AvatarImageView contactPhoto;
@ -116,6 +119,7 @@ public class ConversationItem extends LinearLayout
private @NonNull Stub<ConversationItemThumbnail> mediaThumbnailStub;
private @NonNull Stub<AudioView> audioViewStub;
private @NonNull Stub<DocumentView> documentViewStub;
private Stub<BorderlessImageView> stickerStub;
private @Nullable EventListener eventListener;
private int measureCalls;
@ -151,6 +155,7 @@ public class ConversationItem extends LinearLayout
this.bodyText = findViewById(R.id.conversation_item_body);
this.footer = findViewById(R.id.conversation_item_footer);
this.stickerFooter = findViewById(R.id.conversation_item_sticker_footer);
this.groupSender = findViewById(R.id.group_message_sender);
this.contactPhoto = findViewById(R.id.contact_photo);
this.contactPhotoHolder = findViewById(R.id.contact_photo_container);
@ -158,6 +163,7 @@ public class ConversationItem extends LinearLayout
this.mediaThumbnailStub = new Stub<>(findViewById(R.id.image_view_stub));
this.audioViewStub = new Stub<>(findViewById(R.id.audio_view_stub));
this.documentViewStub = new Stub<>(findViewById(R.id.document_view_stub));
this.stickerStub = new Stub<>(findViewById(R.id.sticker_view_stub));
this.groupSenderHolder = findViewById(R.id.group_sender_holder);
this.quoteView = findViewById(R.id.quote_view);
this.container = findViewById(R.id.container);
@ -335,8 +341,15 @@ public class ConversationItem extends LinearLayout
return type==DcMsg.DC_MSG_GIF || type==DcMsg.DC_MSG_IMAGE || type==DcMsg.DC_MSG_VIDEO;
}
private boolean hasSticker(DcMsg dcMsg) {
return dcMsg.getType()==DcMsg.DC_MSG_STICKER;
}
private boolean hasOnlyThumbnail(DcMsg messageRecord) {
return hasThumbnail(messageRecord) && !hasAudio(messageRecord) && !hasDocument(messageRecord);
return hasThumbnail(messageRecord) &&
!hasAudio(messageRecord) &&
!hasDocument(messageRecord) &&
!hasSticker(messageRecord);
}
private boolean hasDocument(DcMsg dcMsg) {
@ -403,6 +416,7 @@ public class ConversationItem extends LinearLayout
audioViewStub.get().setVisibility(View.VISIBLE);
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
//noinspection ConstantConditions
if(dcChat.getId() == DcChat.DC_CHAT_ID_DEADDROP) { // no audio on dead drops
@ -431,6 +445,7 @@ public class ConversationItem extends LinearLayout
documentViewStub.get().setVisibility(View.VISIBLE);
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
//noinspection ConstantConditions
documentViewStub.get().setDocument(new DocumentSlide(context, messageRecord));
@ -445,6 +460,7 @@ public class ConversationItem extends LinearLayout
mediaThumbnailStub.get().setVisibility(View.VISIBLE);
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
Slide slide;
if (messageRecord.getType()==DcMsg.DC_MSG_VIDEO) {
@ -482,6 +498,25 @@ public class ConversationItem extends LinearLayout
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
footer.setVisibility(VISIBLE);
}
else if (hasSticker(messageRecord)) {
stickerStub.get().setVisibility(View.VISIBLE);
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
bodyBubble.setBackgroundColor(Color.TRANSPARENT);
stickerStub.get().setSlide(glideRequests, new StickerSlide(context, messageRecord));
//stickerStub.get().setThumbnailClickListener(passthroughClickListener);
stickerStub.get().setOnLongClickListener(passthroughClickListener);
stickerStub.get().setOnClickListener(passthroughClickListener);
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
footer.setVisibility(VISIBLE);
}
else {
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
@ -601,6 +636,7 @@ public class ConversationItem extends LinearLayout
ViewUtil.updateLayoutParams(footer, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
footer.setVisibility(GONE);
stickerFooter.setVisibility(GONE);
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().getFooter().setVisibility(GONE);
ConversationItemFooter activeFooter = getActiveFooter(current);
@ -609,7 +645,9 @@ public class ConversationItem extends LinearLayout
}
private ConversationItemFooter getActiveFooter(@NonNull DcMsg messageRecord) {
if (hasOnlyThumbnail(messageRecord) && TextUtils.isEmpty(messageRecord.getText())) {
if (hasSticker(messageRecord)) {
return stickerFooter;
} else if (hasOnlyThumbnail(messageRecord) && TextUtils.isEmpty(messageRecord.getText())) {
return mediaThumbnailStub.get().getFooter();
} else {
return footer;
@ -625,6 +663,13 @@ public class ConversationItem extends LinearLayout
}
private void setGroupMessageStatus() {
if (messageRecord.getType()==DcMsg.DC_MSG_STICKER) {
this.groupSender.setVisibility(GONE);
return;
} else {
this.groupSender.setVisibility(VISIBLE);
}
if (messageRecord.isForwarded()) {
if (groupThread && !messageRecord.isOutgoing() && dcContact !=null) {
this.groupSender.setText(context.getString(R.string.forwarded_by, messageRecord.getSenderName(dcContact, false)));

View file

@ -0,0 +1,69 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideClickListener;
public class BorderlessImageView extends FrameLayout {
private ThumbnailView image;
private View missingShade;
public BorderlessImageView(@NonNull Context context) {
super(context);
init();
}
public BorderlessImageView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
inflate(getContext(), R.layout.sticker_view, this);
this.image = findViewById(R.id.sticker_thumbnail);
this.missingShade = findViewById(R.id.sticker_missing_shade);
}
@Override
public void setFocusable(boolean focusable) {
image.setFocusable(focusable);
}
@Override
public void setClickable(boolean clickable) {
image.setClickable(clickable);
}
@Override
public void setOnLongClickListener(@Nullable OnLongClickListener l) {
image.setOnLongClickListener(l);
}
public void setSlide(@NonNull GlideRequests glideRequests, @NonNull Slide slide) {
boolean showControls = slide.asAttachment().getDataUri() == null;
if (slide.hasSticker()) {
image.setImageResource(glideRequests, slide);
} else {
image.setImageResource(glideRequests, slide, slide.asAttachment().getWidth(), slide.asAttachment().getHeight());
}
missingShade.setVisibility(showControls ? View.VISIBLE : View.GONE);
}
public void setThumbnailClickListener(@NonNull SlideClickListener listener) {
image.setThumbnailClickListener(listener);
}
}

View file

@ -177,7 +177,7 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
}
private void setQuoteAttachment(@NonNull GlideRequests glideRequests, @NonNull SlideDeck slideDeck) {
List<Slide> imageVideoSlides = Stream.of(slideDeck.getSlides()).filter(s -> s.hasImage() || s.hasVideo()).limit(1).toList();
List<Slide> imageVideoSlides = Stream.of(slideDeck.getSlides()).filter(s -> s.hasImage() || s.hasVideo() || s.hasSticker()).limit(1).toList();
List<Slide> audioSlides = Stream.of(slideDeck.getSlides()).filter(s -> s.hasAudio()).limit(1).toList();
List<Slide> documentSlides = Stream.of(attachments.getSlides()).filter(Slide::hasDocument).limit(1).toList();

View file

@ -284,6 +284,10 @@ public class ThumbnailView extends FrameLayout {
slide = null;
}
public void setScaleType(@NonNull ImageView.ScaleType scale) {
image.setScaleType(scale);
}
private class ThumbnailClickDispatcher implements View.OnClickListener {
@Override
public void onClick(View view) {

View file

@ -80,6 +80,8 @@ public abstract class Slide {
return false;
}
public boolean hasSticker() { return false; }
public boolean hasVideo() {
return false;
}

View file

@ -0,0 +1,33 @@
package org.thoughtcrime.securesms.mms;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.NonNull;
import com.b44t.messenger.DcMsg;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.attachments.DcAttachment;
public class StickerSlide extends Slide {
public static final int WIDTH = 512;
public static final int HEIGHT = 512;
public StickerSlide(@NonNull Context context, @NonNull DcMsg dcMsg) {
super(context, new DcAttachment(dcMsg));
}
public StickerSlide(@NonNull Context context, @NonNull Uri uri,
long size, @NonNull String contentType)
{
super(context, constructAttachmentFromUri(context, uri, contentType, size, WIDTH, HEIGHT, uri, null, false));
}
@Override
public boolean hasSticker() {
return true;
}
}

View file

@ -24,6 +24,7 @@ import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.ImageSlide;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.StickerSlide;
import org.thoughtcrime.securesms.mms.VideoSlide;
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
@ -52,6 +53,8 @@ public class MediaUtil {
slide = new GifSlide(context, dcMsg);
} else if (dcMsg.getType() == DcMsg.DC_MSG_IMAGE) {
slide = new ImageSlide(context, dcMsg);
} else if (dcMsg.getType() == DcMsg.DC_MSG_STICKER) {
slide = new StickerSlide(context, dcMsg);
} else if (dcMsg.getType() == DcMsg.DC_MSG_VIDEO) {
slide = new VideoSlide(context, dcMsg);
} else if (dcMsg.getType() == DcMsg.DC_MSG_AUDIO