Add support for sending borderless keyboard stickers.

This commit is contained in:
Greyson Parrelli 2020-07-08 08:54:47 -07:00
parent a9e30eefdc
commit c9d2cef58d
8 changed files with 121 additions and 16 deletions

View file

@ -7,6 +7,9 @@ import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
import com.bumptech.glide.load.resource.bitmap.CenterInside;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.Slide;
@ -53,7 +56,14 @@ public class BorderlessImageView extends FrameLayout {
public void setSlide(@NonNull GlideRequests glideRequests, @NonNull Slide slide) {
boolean showControls = slide.asAttachment().getDataUri() == null;
image.setImageResource(glideRequests, slide, showControls, false);
if (slide.hasSticker()) {
image.setFit(new CenterInside());
image.setImageResource(glideRequests, slide, showControls, false);
} else {
image.setFit(new CenterCrop());
image.setImageResource(glideRequests, slide, showControls, false, slide.asAttachment().getWidth(), slide.asAttachment().getHeight());
}
missingShade.setVisibility(showControls ? View.VISIBLE : View.GONE);
}

View file

@ -398,6 +398,10 @@ public class ThumbnailView extends FrameLayout {
getTransferControls().showProgressSpinner();
}
public void setFit(@NonNull BitmapTransformation fit) {
this.fit = fit;
}
protected void setRadius(int radius) {
this.radius = radius;
}

View file

@ -41,7 +41,6 @@ import android.provider.Browser;
import android.provider.ContactsContract;
import android.provider.Telephony;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.Gravity;
import android.view.KeyEvent;
@ -63,6 +62,7 @@ import android.widget.Toast;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SearchView;
@ -73,6 +73,7 @@ import androidx.core.graphics.drawable.IconCompat;
import androidx.lifecycle.ViewModelProviders;
import com.annimon.stream.Stream;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
@ -246,7 +247,10 @@ import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@ -1997,7 +2001,12 @@ public class ConversationActivity extends PassphraseRequiredActivity
openContactShareEditor(uri);
return new SettableFuture<>(false);
} else if (MediaType.IMAGE.equals(mediaType) || MediaType.GIF.equals(mediaType) || MediaType.VIDEO.equals(mediaType)) {
Media media = new Media(uri, MediaUtil.getMimeType(this, uri), 0, width, height, 0, 0, borderless, Optional.absent(), Optional.absent(), Optional.absent());
String mimeType = MediaUtil.getMimeType(this, uri);
if (mimeType == null) {
mimeType = mediaType.toFallbackMimeType();
}
Media media = new Media(uri, mimeType, 0, width, height, 0, 0, borderless, Optional.absent(), Optional.absent(), Optional.absent());
startActivityForResult(MediaSendActivity.buildEditorIntent(ConversationActivity.this, Collections.singletonList(media), recipient.get(), composeText.getTextTrimmed(), sendButton.getSelectedTransport()), MEDIA_SENDER);
return new SettableFuture<>(false);
} else {
@ -2363,7 +2372,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
}
private ListenableFuture<Void> sendMediaMessage(final boolean forceSms,
String body,
@NonNull String body,
SlideDeck slideDeck,
QuoteModel quote,
List<Contact> contacts,
@ -2651,10 +2660,10 @@ public class ConversationActivity extends PassphraseRequiredActivity
@Override
public void onMediaSelected(@NonNull Uri uri, String contentType) {
if (!TextUtils.isEmpty(contentType) && contentType.trim().equals("image/gif")) {
setMedia(uri, MediaType.GIF);
} else if (MediaUtil.isImageType(contentType)) {
setMedia(uri, MediaType.IMAGE);
if (MediaUtil.isGif(contentType) || MediaUtil.isImageType(contentType)) {
SimpleTask.run(getLifecycle(),
() -> getKeyboardImageDetails(uri),
details -> sendKeyboardImage(uri, contentType, details));
} else if (MediaUtil.isVideoType(contentType)) {
setMedia(uri, MediaType.VIDEO);
} else if (MediaUtil.isAudioType(contentType)) {
@ -3066,6 +3075,55 @@ public class ConversationActivity extends PassphraseRequiredActivity
}
}
@WorkerThread
private @Nullable KeyboardImageDetails getKeyboardImageDetails(@NonNull Uri uri) {
try {
Bitmap bitmap = glideRequests.asBitmap()
.load(uri)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.submit()
.get(1000, TimeUnit.MILLISECONDS);
int topLeft = bitmap.getPixel(0, 0);
return new KeyboardImageDetails(bitmap.getWidth(), bitmap.getHeight(), Color.alpha(topLeft) < 255);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
return null;
}
}
private void sendKeyboardImage(@NonNull Uri uri, @NonNull String contentType, @Nullable KeyboardImageDetails details) {
if (details == null || !details.hasTransparency) {
setMedia(uri, Objects.requireNonNull(MediaType.from(contentType)));
return;
}
long expiresIn = recipient.get().getExpireMessages() * 1000L;
int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().or(-1);
boolean initiating = threadId == -1;
QuoteModel quote = inputPanel.getQuote().orNull();
SlideDeck slideDeck = new SlideDeck();
if (MediaUtil.isGif(contentType)) {
slideDeck.addSlide(new GifSlide(this, uri, 0, details.width, details.height, details.hasTransparency, null));
} else if (MediaUtil.isImageType(contentType)) {
slideDeck.addSlide(new ImageSlide(this, uri, contentType, 0, details.width, details.height, details.hasTransparency, null, null));
} else {
throw new AssertionError("Only images are supported!");
}
sendMediaMessage(isSmsForced(),
"",
slideDeck,
quote,
Collections.emptyList(),
Collections.emptyList(),
expiresIn,
false,
subscriptionId,
initiating,
true);
}
private class UnverifiedDismissedListener implements UnverifiedBannerView.DismissListener {
@Override
public void onDismissed(final List<IdentityRecord> unverifiedIdentities) {
@ -3155,4 +3213,16 @@ public class ConversationActivity extends PassphraseRequiredActivity
messageRequestBottomView.setRecipient(recipient);
}
private static class KeyboardImageDetails {
private final int width;
private final int height;
private final boolean hasTransparency;
private KeyboardImageDetails(int width, int height, boolean hasTransparency) {
this.width = width;
this.height = height;
this.hasTransparency = hasTransparency;
}
}
}

View file

@ -700,6 +700,7 @@ public class ConversationItem extends LinearLayout implements BindableConversati
} else {
//noinspection ConstantConditions
stickerStub.get().setSlide(glideRequests, ((MmsMessageRecord) messageRecord).getSlideDeck().getThumbnailSlide());
stickerStub.get().setThumbnailClickListener((v, slide) -> performClick());
}
stickerStub.get().setDownloadClickListener(downloadClickListener);

View file

@ -522,7 +522,19 @@ public class AttachmentManager {
}
public enum MediaType {
IMAGE, GIF, AUDIO, VIDEO, DOCUMENT, VCARD;
IMAGE(MediaUtil.IMAGE_JPEG),
GIF(MediaUtil.IMAGE_GIF),
AUDIO(MediaUtil.AUDIO_AAC),
VIDEO(MediaUtil.VIDEO_MP4),
DOCUMENT(MediaUtil.UNKNOWN),
VCARD(MediaUtil.VCARD);
private final String fallbackMimeType;
MediaType(String fallbackMimeType) {
this.fallbackMimeType = fallbackMimeType;
}
public @NonNull Slide createSlide(@NonNull Context context,
@NonNull Uri uri,
@ -559,5 +571,8 @@ public class AttachmentManager {
return DOCUMENT;
}
public String toFallbackMimeType() {
return fallbackMimeType;
}
}
}

View file

@ -135,8 +135,8 @@
<ViewStub
android:id="@+id/sticker_view_stub"
android:layout_width="@dimen/media_bubble_sticker_dimens"
android:layout_height="@dimen/media_bubble_sticker_dimens"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout="@layout/conversation_item_received_sticker" />
<ViewStub

View file

@ -73,8 +73,8 @@
<ViewStub
android:id="@+id/sticker_view_stub"
android:layout_width="@dimen/media_bubble_sticker_dimens"
android:layout_height="@dimen/media_bubble_sticker_dimens"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout="@layout/conversation_item_sent_sticker" />
<ViewStub
@ -150,6 +150,7 @@
android:layout_marginStart="@dimen/message_bubble_horizontal_padding"
android:layout_marginTop="6dp"
android:layout_marginEnd="@dimen/message_bubble_horizontal_padding"
android:layout_gravity="end"
android:clipChildren="false"
android:clipToPadding="false"
android:gravity="end"

View file

@ -15,9 +15,13 @@
<org.thoughtcrime.securesms.components.ThumbnailView
android:id="@+id/sticker_thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_width="@dimen/media_bubble_sticker_dimens"
android:layout_height="@dimen/media_bubble_sticker_dimens"
app:thumbnail_radius="0dp"
app:thumbnail_fit="fit_center"/>
app:thumbnail_fit="fit_center"
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>