mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-24 00:37:47 +00:00
Render images in a conversation true-to-size.
Previously, we were always rendering images as squares. Instead of doing that, we now render them as close to true-to-size as possible (within reasonable min/max width/height boundaries).
This commit is contained in:
parent
9f8b4cf892
commit
ea374735e1
@ -63,6 +63,7 @@ dependencies {
|
||||
compile 'com.android.support:preference-v14:27.0.2'
|
||||
compile 'com.android.support:gridlayout-v7:27.0.2'
|
||||
compile 'com.android.support:multidex:1.0.2'
|
||||
compile "com.android.support:exifinterface:27.0.2"
|
||||
|
||||
compile 'com.google.android.gms:play-services-gcm:9.6.1'
|
||||
compile 'com.google.android.gms:play-services-maps:9.6.1'
|
||||
@ -156,6 +157,7 @@ dependencyVerification {
|
||||
'com.android.support:cardview-v7:57f867a3c8f33e2d4dc0a03e2dfa03cad6267a908179f04a725a68ea9f0b8ccf',
|
||||
'com.android.support:gridlayout-v7:227b5fdffa20f53bd562503aab6d2293d52cf64b5a6ab1116d2150f87bff9e88',
|
||||
'com.android.support:multidex:7cd48755c7cfdb6dd2d21cbb02236ec390f6ac91cde87eb62f475b259ab5301d',
|
||||
'com.android.support:exifinterface:0e7cd526c4468895cd8549def46b3d33c8bcfb1ae4830569898d8c7326b15bb2',
|
||||
'com.google.android.gms:play-services-gcm:312e61253a236f2d9b750b9c04fc92fd190d23b0b2755c99de6ce4a28b259dae',
|
||||
'com.google.android.gms:play-services-places:abf3a4a3b146ec7e6e753be62775e512868cf37d6f88ffe2d81167b33b57132b',
|
||||
'com.google.android.gms:play-services-maps:45e8021e7ddac4a44a82a0e9698991389ded3023d35c58f38dbd86d54211ec0e',
|
||||
|
@ -27,7 +27,11 @@
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:visibility="gone"
|
||||
android:contentDescription="@string/conversation_activity__attachment_thumbnail"
|
||||
app:backgroundColorHint="?conversation_background" />
|
||||
app:backgroundColorHint="?conversation_background"
|
||||
app:minWidth="100dp"
|
||||
app:maxWidth="300dp"
|
||||
app:minHeight="100dp"
|
||||
app:maxHeight="300dp" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.AudioView
|
||||
android:id="@+id/attachment_audio"
|
||||
|
@ -78,8 +78,8 @@
|
||||
<ViewStub
|
||||
android:id="@+id/image_view_stub"
|
||||
android:layout="@layout/conversation_item_received_thumbnail"
|
||||
android:layout_width="@dimen/media_bubble_height"
|
||||
android:layout_height="@dimen/media_bubble_height"/>
|
||||
android:layout_width="@dimen/media_bubble_default_dimens"
|
||||
android:layout_height="@dimen/media_bubble_default_dimens"/>
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/audio_view_stub"
|
||||
|
@ -2,12 +2,17 @@
|
||||
<org.thoughtcrime.securesms.components.ThumbnailView
|
||||
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"
|
||||
android:id="@+id/image_view"
|
||||
android:layout_width="@dimen/media_bubble_height"
|
||||
android:layout_height="@dimen/media_bubble_height"
|
||||
android:layout_width="@dimen/media_bubble_default_dimens"
|
||||
android:layout_height="@dimen/media_bubble_default_dimens"
|
||||
android:scaleType="centerCrop"
|
||||
android:adjustViewBounds="true"
|
||||
android:contentDescription="@string/conversation_item__mms_image_description"
|
||||
android:visibility="gone"
|
||||
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"
|
||||
tools:src="@drawable/ic_video_light"
|
||||
tools:visibility="gone" />
|
||||
|
@ -40,9 +40,9 @@
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/image_view_stub"
|
||||
android:layout_width="@dimen/media_bubble_height"
|
||||
android:layout_height="@dimen/media_bubble_height"
|
||||
android:layout="@layout/conversation_item_sent_thumbnail"/>
|
||||
android:layout_width="@dimen/media_bubble_default_dimens"
|
||||
android:layout_height="@dimen/media_bubble_default_dimens"
|
||||
android:layout="@layout/conversation_item_sent_thumbnail" />
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/audio_view_stub"
|
||||
|
@ -2,14 +2,19 @@
|
||||
<org.thoughtcrime.securesms.components.ThumbnailView
|
||||
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"
|
||||
android:id="@+id/image_view"
|
||||
android:layout_width="@dimen/media_bubble_height"
|
||||
android:layout_height="@dimen/media_bubble_height"
|
||||
android:layout_width="@dimen/media_bubble_default_dimens"
|
||||
android:layout_height="@dimen/media_bubble_default_dimens"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:layout_gravity="center"
|
||||
android:scaleType="centerCrop"
|
||||
android:adjustViewBounds="true"
|
||||
android:contentDescription="@string/conversation_item__mms_image_description"
|
||||
android:visibility="gone"
|
||||
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"
|
||||
tools:src="@drawable/ic_video_light"
|
||||
tools:visibility="visible" />
|
||||
|
5
res/values-sw320dp/dimens.xml
Normal file
5
res/values-sw320dp/dimens.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="media_bubble_max_width">220dp</dimen>
|
||||
<dimen name="media_bubble_max_height">300dp</dimen>
|
||||
</resources>
|
@ -147,6 +147,10 @@
|
||||
|
||||
<declare-styleable name="ThumbnailView">
|
||||
<attr name="backgroundColorHint" format="color" />
|
||||
<attr name="minWidth" format="dimension" />
|
||||
<attr name="maxWidth" format="dimension" />
|
||||
<attr name="minHeight" format="dimension" />
|
||||
<attr name="maxHeight" format="dimension" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="DeliveryStatusView">
|
||||
|
@ -18,9 +18,13 @@
|
||||
|
||||
<dimen name="message_bubble_corner_radius">4dp</dimen>
|
||||
<dimen name="message_bubble_shadow_distance">1.5dp</dimen>
|
||||
<dimen name="media_bubble_height">210dp</dimen>
|
||||
<dimen name="media_bubble_remove_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_min_width">150dp</dimen>
|
||||
<dimen name="media_bubble_max_width">250dp</dimen>
|
||||
<dimen name="media_bubble_min_height">100dp</dimen>
|
||||
<dimen name="media_bubble_max_height">320dp</dimen>
|
||||
|
||||
<integer name="media_overview_cols">3</integer>
|
||||
<dimen name="message_details_table_row_pad">10dp</dimen>
|
||||
|
@ -112,6 +112,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.database.identity.IdentityRecordList;
|
||||
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
|
||||
import org.thoughtcrime.securesms.giph.ui.GiphyActivity;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
||||
import org.thoughtcrime.securesms.mms.AttachmentManager;
|
||||
@ -406,6 +407,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
else mediaType = MediaType.IMAGE;
|
||||
|
||||
setMedia(data.getData(), mediaType);
|
||||
|
||||
break;
|
||||
case PICK_DOCUMENT:
|
||||
setMedia(data.getData(), MediaType.DOCUMENT);
|
||||
@ -438,7 +440,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
attachmentManager.setLocation(place, getCurrentMediaConstraints());
|
||||
break;
|
||||
case PICK_GIF:
|
||||
setMedia(data.getData(), MediaType.GIF);
|
||||
setMedia(data.getData(),
|
||||
MediaType.GIF,
|
||||
data.getIntExtra(GiphyActivity.EXTRA_WIDTH, 0),
|
||||
data.getIntExtra(GiphyActivity.EXTRA_HEIGHT, 0));
|
||||
break;
|
||||
case ScribbleActivity.SCRIBBLE_REQUEST_CODE:
|
||||
setMedia(data.getData(), MediaType.IMAGE);
|
||||
@ -1377,8 +1382,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private void setMedia(@Nullable Uri uri, @NonNull MediaType mediaType) {
|
||||
setMedia(uri, mediaType, 0, 0);
|
||||
}
|
||||
|
||||
private void setMedia(@Nullable Uri uri, @NonNull MediaType mediaType, int width, int height) {
|
||||
if (uri == null) return;
|
||||
attachmentManager.setMedia(glideRequests, uri, mediaType, getCurrentMediaConstraints());
|
||||
attachmentManager.setMedia(glideRequests, uri, mediaType, getCurrentMediaConstraints(), width, height);
|
||||
}
|
||||
|
||||
private void addAttachmentContactInfo(Uri contactUri) {
|
||||
|
@ -45,6 +45,7 @@ import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.thoughtcrime.securesms.components.AlertView;
|
||||
import org.thoughtcrime.securesms.components.AudioView;
|
||||
@ -385,9 +386,14 @@ public class ConversationItem extends LinearLayout
|
||||
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||
|
||||
//noinspection ConstantConditions
|
||||
Slide thumbnailSlide = ((MmsMessageRecord) messageRecord).getSlideDeck().getThumbnailSlide();
|
||||
Attachment attachment = thumbnailSlide.asAttachment();
|
||||
mediaThumbnailStub.get().setImageResource(glideRequests,
|
||||
((MmsMessageRecord)messageRecord).getSlideDeck().getThumbnailSlide(),
|
||||
showControls, false);
|
||||
thumbnailSlide,
|
||||
showControls,
|
||||
false,
|
||||
attachment.getWidth(),
|
||||
attachment.getHeight());
|
||||
mediaThumbnailStub.get().setThumbnailClickListener(new ThumbnailClickListener());
|
||||
mediaThumbnailStub.get().setDownloadClickListener(downloadClickListener);
|
||||
mediaThumbnailStub.get().setOnLongClickListener(passthroughClickListener);
|
||||
|
@ -12,15 +12,15 @@ public class UriAttachment extends Attachment {
|
||||
public UriAttachment(@NonNull Uri uri, @NonNull String contentType, int transferState, long size,
|
||||
@Nullable String fileName, boolean voiceNote)
|
||||
{
|
||||
this(uri, uri, contentType, transferState, size, fileName, null, voiceNote);
|
||||
this(uri, uri, contentType, transferState, size, 0, 0, fileName, null, voiceNote);
|
||||
}
|
||||
|
||||
public UriAttachment(@NonNull Uri dataUri, @Nullable Uri thumbnailUri,
|
||||
@NonNull String contentType, int transferState, long size,
|
||||
@NonNull String contentType, int transferState, long size, int width, int height,
|
||||
@Nullable String fileName, @Nullable String fastPreflightId,
|
||||
boolean voiceNote)
|
||||
{
|
||||
super(contentType, transferState, size, fileName, null, null, null, null, fastPreflightId, voiceNote, 0, 0);
|
||||
super(contentType, transferState, size, fileName, null, null, null, null, fastPreflightId, voiceNote, width, height);
|
||||
this.dataUri = dataUri;
|
||||
this.thumbnailUri = thumbnailUri;
|
||||
}
|
||||
|
@ -5,20 +5,26 @@ import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.bumptech.glide.RequestBuilder;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
|
||||
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
|
||||
import com.bumptech.glide.load.resource.bitmap.FitCenter;
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequest;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||
@ -26,11 +32,19 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
|
||||
|
||||
public class ThumbnailView extends FrameLayout {
|
||||
|
||||
private static final String TAG = ThumbnailView.class.getSimpleName();
|
||||
private static final int WIDTH = 0;
|
||||
private static final int HEIGHT = 1;
|
||||
private static final int MIN_WIDTH = 0;
|
||||
private static final int MAX_WIDTH = 1;
|
||||
private static final int MIN_HEIGHT = 2;
|
||||
private static final int MAX_HEIGHT = 3;
|
||||
|
||||
private ImageView image;
|
||||
private ImageView playOverlay;
|
||||
@ -38,6 +52,10 @@ public class ThumbnailView extends FrameLayout {
|
||||
private int radius;
|
||||
private OnClickListener parentClickListener;
|
||||
|
||||
private final int[] dimens = new int[2];
|
||||
private final int[] bounds = new int[4];
|
||||
private final int[] measureDimens = new int[2];
|
||||
|
||||
private Optional<TransferControlView> transferControls = Optional.absent();
|
||||
private SlideClickListener thumbnailClickListener = null;
|
||||
private SlideClickListener downloadClickListener = null;
|
||||
@ -63,11 +81,110 @@ public class ThumbnailView extends FrameLayout {
|
||||
|
||||
if (attrs != null) {
|
||||
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ThumbnailView, 0, 0);
|
||||
backgroundColorHint = typedArray.getColor(R.styleable.ThumbnailView_backgroundColorHint, Color.BLACK);
|
||||
backgroundColorHint = typedArray.getColor(R.styleable.ThumbnailView_backgroundColorHint, Color.BLACK);
|
||||
bounds[MIN_WIDTH] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_minWidth, 0);
|
||||
bounds[MAX_WIDTH] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxWidth, 0);
|
||||
bounds[MIN_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_minHeight, 0);
|
||||
bounds[MAX_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxHeight, 0);
|
||||
typedArray.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int originalWidthMeasureSpec, int originalHeightMeasureSpec) {
|
||||
fillTargetDimensions(measureDimens, dimens, bounds);
|
||||
if (measureDimens[WIDTH] == 0 && measureDimens[HEIGHT] == 0) {
|
||||
super.onMeasure(originalWidthMeasureSpec, originalHeightMeasureSpec);
|
||||
return;
|
||||
}
|
||||
|
||||
int finalWidth = measureDimens[WIDTH] + getPaddingLeft() + getPaddingRight();
|
||||
int finalHeight = measureDimens[HEIGHT] + getPaddingTop() + getPaddingBottom();
|
||||
|
||||
super.onMeasure(MeasureSpec.makeMeasureSpec(finalWidth, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(finalHeight, MeasureSpec.EXACTLY));
|
||||
}
|
||||
|
||||
@SuppressWarnings("SuspiciousNameCombination")
|
||||
private void fillTargetDimensions(int[] targetDimens, int[] dimens, int[] bounds) {
|
||||
int dimensFilledCount = getNonZeroCount(dimens);
|
||||
int boundsFilledCount = getNonZeroCount(bounds);
|
||||
|
||||
if (dimensFilledCount == 0 || boundsFilledCount == 0) {
|
||||
targetDimens[WIDTH] = 0;
|
||||
targetDimens[HEIGHT] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
double naturalWidth = dimens[WIDTH];
|
||||
double naturalHeight = dimens[HEIGHT];
|
||||
|
||||
int minWidth = bounds[MIN_WIDTH];
|
||||
int maxWidth = bounds[MAX_WIDTH];
|
||||
int minHeight = bounds[MIN_HEIGHT];
|
||||
int maxHeight = bounds[MAX_HEIGHT];
|
||||
|
||||
if (dimensFilledCount > 0 && dimensFilledCount < dimens.length) {
|
||||
throw new IllegalStateException(String.format(Locale.ENGLISH, "Width or height has been specified, but not both. Dimens: %f x %f",
|
||||
naturalWidth, naturalHeight));
|
||||
}
|
||||
if (boundsFilledCount > 0 && boundsFilledCount < bounds.length) {
|
||||
throw new IllegalStateException(String.format(Locale.ENGLISH, "One or more min/max dimensions have been specified, but not all. Bounds: [%d, %d, %d, %d]",
|
||||
minWidth, maxWidth, minHeight, maxHeight));
|
||||
}
|
||||
|
||||
double measuredWidth = naturalWidth;
|
||||
double measuredHeight = naturalHeight;
|
||||
|
||||
boolean widthInBounds = measuredWidth >= minWidth && measuredWidth <= maxWidth;
|
||||
boolean heightInBounds = measuredHeight >= minHeight && measuredHeight <= maxHeight;
|
||||
|
||||
if (!widthInBounds || !heightInBounds) {
|
||||
double minWidthRatio = naturalWidth / minWidth;
|
||||
double maxWidthRatio = naturalWidth / maxWidth;
|
||||
double minHeightRatio = naturalHeight / minHeight;
|
||||
double maxHeightRatio = naturalHeight / maxHeight;
|
||||
|
||||
if (maxWidthRatio > 1 || maxHeightRatio > 1) {
|
||||
if (maxWidthRatio >= maxHeightRatio) {
|
||||
measuredWidth /= maxWidthRatio;
|
||||
measuredHeight /= maxWidthRatio;
|
||||
} else {
|
||||
measuredWidth /= maxHeightRatio;
|
||||
measuredHeight /= maxHeightRatio;
|
||||
}
|
||||
|
||||
measuredWidth = Math.max(measuredWidth, minWidth);
|
||||
measuredHeight = Math.max(measuredHeight, minHeight);
|
||||
|
||||
} else if (minWidthRatio < 1 || minHeightRatio < 1) {
|
||||
if (minWidthRatio <= minHeightRatio) {
|
||||
measuredWidth /= minWidthRatio;
|
||||
measuredHeight /= minWidthRatio;
|
||||
} else {
|
||||
measuredWidth /= minHeightRatio;
|
||||
measuredHeight /= minHeightRatio;
|
||||
}
|
||||
|
||||
measuredWidth = Math.min(measuredWidth, maxWidth);
|
||||
measuredHeight = Math.min(measuredHeight, maxHeight);
|
||||
}
|
||||
}
|
||||
|
||||
targetDimens[WIDTH] = (int) measuredWidth;
|
||||
targetDimens[HEIGHT] = (int) measuredHeight;
|
||||
}
|
||||
|
||||
private int getNonZeroCount(int[] vals) {
|
||||
int count = 0;
|
||||
for (int val : vals) {
|
||||
if (val > 0) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnClickListener(OnClickListener l) {
|
||||
parentClickListener = l;
|
||||
@ -96,9 +213,22 @@ public class ThumbnailView extends FrameLayout {
|
||||
this.backgroundColorHint = color;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void setImageResource(@NonNull GlideRequests glideRequests, @NonNull Slide slide,
|
||||
boolean showControls, boolean isPreview)
|
||||
{
|
||||
setImageResource(glideRequests, slide, showControls, isPreview, 0, 0);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void setImageResource(@NonNull GlideRequests glideRequests, @NonNull Slide slide,
|
||||
boolean showControls, boolean isPreview, int naturalWidth,
|
||||
int naturalHeight)
|
||||
{
|
||||
dimens[WIDTH] = naturalWidth;
|
||||
dimens[HEIGHT] = naturalHeight;
|
||||
invalidate();
|
||||
|
||||
if (showControls) {
|
||||
getTransferControls().setSlide(slide);
|
||||
getTransferControls().setDownloadClickListener(new DownloadClickDispatcher());
|
||||
@ -136,11 +266,11 @@ public class ThumbnailView extends FrameLayout {
|
||||
if (slide.getThumbnailUri() != null) buildThumbnailGlideRequest(glideRequests, slide).into(image);
|
||||
else if (slide.hasPlaceholder()) buildPlaceholderGlideRequest(glideRequests, slide).into(image);
|
||||
else glideRequests.clear(image);
|
||||
|
||||
}
|
||||
|
||||
public void setImageResource(@NonNull GlideRequests glideRequests, @NonNull Uri uri) {
|
||||
if (transferControls.isPresent()) getTransferControls().setVisibility(View.GONE);
|
||||
|
||||
glideRequests.load(new DecryptableUri(uri))
|
||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||
.transform(new RoundedCorners(radius))
|
||||
@ -171,22 +301,29 @@ public class ThumbnailView extends FrameLayout {
|
||||
getTransferControls().showProgressSpinner();
|
||||
}
|
||||
|
||||
private RequestBuilder buildThumbnailGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) {
|
||||
RequestBuilder builder = glideRequests.load(new DecryptableUri(slide.getThumbnailUri()))
|
||||
private GlideRequest buildThumbnailGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) {
|
||||
GlideRequest request = applySizing(glideRequests.load(new DecryptableUri(slide.getThumbnailUri()))
|
||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||
.transform(new RoundedCorners(radius))
|
||||
.centerCrop()
|
||||
.transition(withCrossFade());
|
||||
.transition(withCrossFade()), new CenterCrop());
|
||||
|
||||
if (slide.isInProgress()) return builder;
|
||||
else return builder.apply(RequestOptions.errorOf(R.drawable.ic_missing_thumbnail_picture));
|
||||
if (slide.isInProgress()) return request;
|
||||
else return request.apply(RequestOptions.errorOf(R.drawable.ic_missing_thumbnail_picture));
|
||||
}
|
||||
|
||||
private RequestBuilder buildPlaceholderGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) {
|
||||
return glideRequests.asBitmap()
|
||||
return applySizing(glideRequests.asBitmap()
|
||||
.load(slide.getPlaceholderRes(getContext().getTheme()))
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.fitCenter();
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE), new FitCenter());
|
||||
}
|
||||
|
||||
private GlideRequest applySizing(@NonNull GlideRequest request, @NonNull BitmapTransformation unavailableDimensSizing) {
|
||||
int[] size = new int[2];
|
||||
fillTargetDimensions(size, dimens, bounds);
|
||||
if (size[WIDTH] == 0 && size[HEIGHT] == 0) {
|
||||
return request.transforms(unavailableDimensSizing, new RoundedCorners(radius));
|
||||
}
|
||||
return request.override(size[WIDTH], size[HEIGHT])
|
||||
.transforms(new CenterCrop(), new RoundedCorners(radius));
|
||||
}
|
||||
|
||||
private class ThumbnailClickDispatcher implements View.OnClickListener {
|
||||
|
@ -28,6 +28,14 @@ public class GiphyImage {
|
||||
return (float)images.downsized.width / (float)images.downsized.height;
|
||||
}
|
||||
|
||||
public int getGifWidth() {
|
||||
return images.downsized.width;
|
||||
}
|
||||
|
||||
public int getGifHeight() {
|
||||
return images.downsized.height;
|
||||
}
|
||||
|
||||
public String getStillUrl() {
|
||||
return images.downsized_still.url;
|
||||
}
|
||||
|
@ -37,6 +37,8 @@ public class GiphyActivity extends PassphraseRequiredActionBarActivity
|
||||
private static final String TAG = GiphyActivity.class.getSimpleName();
|
||||
|
||||
public static final String EXTRA_IS_MMS = "extra_is_mms";
|
||||
public static final String EXTRA_WIDTH = "extra_width";
|
||||
public static final String EXTRA_HEIGHT = "extra_height";
|
||||
|
||||
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
@ -124,7 +126,11 @@ public class GiphyActivity extends PassphraseRequiredActionBarActivity
|
||||
if (uri == null) {
|
||||
Toast.makeText(GiphyActivity.this, R.string.GiphyActivity_error_while_retrieving_full_resolution_gif, Toast.LENGTH_LONG).show();
|
||||
} else if (viewHolder == finishingImage) {
|
||||
setResult(RESULT_OK, new Intent().setData(uri));
|
||||
Intent intent = new Intent();
|
||||
intent.setData(uri);
|
||||
intent.putExtra(EXTRA_WIDTH, viewHolder.image.getGifWidth());
|
||||
intent.putExtra(EXTRA_HEIGHT, viewHolder.image.getGifHeight());
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
} else {
|
||||
Log.w(TAG, "Resolved Uri is no longer the selected element...");
|
||||
|
@ -34,6 +34,7 @@ import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
@ -43,6 +44,7 @@ import com.google.android.gms.location.places.ui.PlacePicker;
|
||||
|
||||
import org.thoughtcrime.securesms.MediaPreviewActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.components.AudioView;
|
||||
import org.thoughtcrime.securesms.components.DocumentView;
|
||||
import org.thoughtcrime.securesms.components.RemovableEditableMediaView;
|
||||
@ -204,7 +206,9 @@ public class AttachmentManager {
|
||||
public void setMedia(@NonNull final GlideRequests glideRequests,
|
||||
@NonNull final Uri uri,
|
||||
@NonNull final MediaType mediaType,
|
||||
@NonNull final MediaConstraints constraints)
|
||||
@NonNull final MediaConstraints constraints,
|
||||
final int width,
|
||||
final int height)
|
||||
{
|
||||
inflateStub();
|
||||
|
||||
@ -220,11 +224,11 @@ public class AttachmentManager {
|
||||
protected @Nullable Slide doInBackground(Void... params) {
|
||||
try {
|
||||
if (PartAuthority.isLocalUri(uri)) {
|
||||
return getManuallyCalculatedSlideInfo(uri);
|
||||
return getManuallyCalculatedSlideInfo(uri, width, height);
|
||||
} else {
|
||||
Slide result = getContentResolverSlideInfo(uri);
|
||||
Slide result = getContentResolverSlideInfo(uri, width, height);
|
||||
|
||||
if (result == null) return getManuallyCalculatedSlideInfo(uri);
|
||||
if (result == null) return getManuallyCalculatedSlideInfo(uri, width, height);
|
||||
else return result;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
@ -256,7 +260,8 @@ public class AttachmentManager {
|
||||
documentView.setDocument((DocumentSlide) slide, false);
|
||||
removableMediaView.display(documentView, false);
|
||||
} else {
|
||||
thumbnail.setImageResource(glideRequests, slide, false, true);
|
||||
Attachment attachment = slide.asAttachment();
|
||||
thumbnail.setImageResource(glideRequests, slide, false, true, attachment.getWidth(), attachment.getHeight());
|
||||
removableMediaView.display(thumbnail, mediaType == MediaType.IMAGE);
|
||||
}
|
||||
|
||||
@ -264,7 +269,7 @@ public class AttachmentManager {
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Slide getContentResolverSlideInfo(Uri uri) {
|
||||
private @Nullable Slide getContentResolverSlideInfo(Uri uri, int width, int height) {
|
||||
Cursor cursor = null;
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
@ -276,8 +281,14 @@ public class AttachmentManager {
|
||||
long fileSize = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE));
|
||||
String mimeType = context.getContentResolver().getType(uri);
|
||||
|
||||
if (width == 0 || height == 0) {
|
||||
Pair<Integer, Integer> dimens = MediaUtil.getDimensions(context, mimeType, uri);
|
||||
width = dimens.first;
|
||||
height = dimens.second;
|
||||
}
|
||||
|
||||
Log.w(TAG, "remote slide with size " + fileSize + " took " + (System.currentTimeMillis() - start) + "ms");
|
||||
return mediaType.createSlide(context, uri, fileName, mimeType, fileSize);
|
||||
return mediaType.createSlide(context, uri, fileName, mimeType, fileSize, width, height);
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) cursor.close();
|
||||
@ -286,7 +297,7 @@ public class AttachmentManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
private @NonNull Slide getManuallyCalculatedSlideInfo(Uri uri) throws IOException {
|
||||
private @NonNull Slide getManuallyCalculatedSlideInfo(Uri uri, int width, int height) throws IOException {
|
||||
long start = System.currentTimeMillis();
|
||||
Long mediaSize = null;
|
||||
String fileName = null;
|
||||
@ -302,8 +313,18 @@ public class AttachmentManager {
|
||||
mediaSize = MediaUtil.getMediaSize(context, uri);
|
||||
}
|
||||
|
||||
if (mimeType == null) {
|
||||
mimeType = MediaUtil.getMimeType(context, uri);
|
||||
}
|
||||
|
||||
if (width == 0 || height == 0) {
|
||||
Pair<Integer, Integer> dimens = MediaUtil.getDimensions(context, mimeType, uri);
|
||||
width = dimens.first;
|
||||
height = dimens.second;
|
||||
}
|
||||
|
||||
Log.w(TAG, "local slide with size " + mediaSize + " took " + (System.currentTimeMillis() - start) + "ms");
|
||||
return mediaType.createSlide(context, uri, fileName, mimeType, mediaSize);
|
||||
return mediaType.createSlide(context, uri, fileName, mimeType, mediaSize, width, height);
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
@ -493,15 +514,17 @@ public class AttachmentManager {
|
||||
@NonNull Uri uri,
|
||||
@Nullable String fileName,
|
||||
@Nullable String mimeType,
|
||||
long dataSize)
|
||||
long dataSize,
|
||||
int width,
|
||||
int height)
|
||||
{
|
||||
if (mimeType == null) {
|
||||
mimeType = "application/octet-stream";
|
||||
}
|
||||
|
||||
switch (this) {
|
||||
case IMAGE: return new ImageSlide(context, uri, dataSize);
|
||||
case GIF: return new GifSlide(context, uri, dataSize);
|
||||
case IMAGE: return new ImageSlide(context, uri, dataSize, width, height);
|
||||
case GIF: return new GifSlide(context, uri, dataSize, width, height);
|
||||
case AUDIO: return new AudioSlide(context, uri, dataSize, false);
|
||||
case VIDEO: return new VideoSlide(context, uri, dataSize);
|
||||
case DOCUMENT: return new DocumentSlide(context, uri, mimeType, dataSize, fileName);
|
||||
|
@ -34,11 +34,11 @@ import org.thoughtcrime.securesms.util.ResUtil;
|
||||
public class AudioSlide extends Slide {
|
||||
|
||||
public AudioSlide(Context context, Uri uri, long dataSize, boolean voiceNote) {
|
||||
super(context, constructAttachmentFromUri(context, uri, MediaUtil.AUDIO_UNSPECIFIED, dataSize, false, null, voiceNote));
|
||||
super(context, constructAttachmentFromUri(context, uri, MediaUtil.AUDIO_UNSPECIFIED, dataSize, 0, 0, false, null, voiceNote));
|
||||
}
|
||||
|
||||
public AudioSlide(Context context, Uri uri, long dataSize, String contentType, boolean voiceNote) {
|
||||
super(context, new UriAttachment(uri, null, contentType, AttachmentDatabase.TRANSFER_PROGRESS_STARTED, dataSize, null, null, voiceNote));
|
||||
super(context, new UriAttachment(uri, null, contentType, AttachmentDatabase.TRANSFER_PROGRESS_STARTED, dataSize, 0, 0, null, null, voiceNote));
|
||||
}
|
||||
|
||||
public AudioSlide(Context context, Attachment attachment) {
|
||||
|
@ -19,7 +19,7 @@ public class DocumentSlide extends Slide {
|
||||
@NonNull String contentType, long size,
|
||||
@Nullable String fileName)
|
||||
{
|
||||
super(context, constructAttachmentFromUri(context, uri, contentType, size, true, StorageUtil.getCleanFileName(fileName), false));
|
||||
super(context, constructAttachmentFromUri(context, uri, contentType, size, 0, 0, true, StorageUtil.getCleanFileName(fileName), false));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -13,8 +13,8 @@ public class GifSlide extends ImageSlide {
|
||||
super(context, attachment);
|
||||
}
|
||||
|
||||
public GifSlide(Context context, Uri uri, long size) {
|
||||
super(context, constructAttachmentFromUri(context, uri, MediaUtil.IMAGE_GIF, size, true, null, false));
|
||||
public GifSlide(Context context, Uri uri, long size, int width, int height) {
|
||||
super(context, constructAttachmentFromUri(context, uri, MediaUtil.IMAGE_GIF, size, width, height, true, null, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -36,8 +36,8 @@ public class ImageSlide extends Slide {
|
||||
super(context, attachment);
|
||||
}
|
||||
|
||||
public ImageSlide(Context context, Uri uri, long size) {
|
||||
super(context, constructAttachmentFromUri(context, uri, MediaUtil.IMAGE_JPEG, size, true, null, false));
|
||||
public ImageSlide(Context context, Uri uri, long size, int width, int height) {
|
||||
super(context, constructAttachmentFromUri(context, uri, MediaUtil.IMAGE_JPEG, size, width, height, true, null, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -14,7 +14,7 @@ public class LocationSlide extends ImageSlide {
|
||||
|
||||
public LocationSlide(@NonNull Context context, @NonNull Uri uri, long size, @NonNull SignalPlace place)
|
||||
{
|
||||
super(context, uri, size);
|
||||
super(context, uri, size, 0, 0);
|
||||
this.place = place;
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ import android.net.Uri;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.UriAttachment;
|
||||
@ -132,14 +133,25 @@ public abstract class Slide {
|
||||
@NonNull Uri uri,
|
||||
@NonNull String defaultMime,
|
||||
long size,
|
||||
int width,
|
||||
int height,
|
||||
boolean hasThumbnail,
|
||||
@Nullable String fileName,
|
||||
boolean voiceNote)
|
||||
{
|
||||
try {
|
||||
Optional<String> resolvedType = Optional.fromNullable(MediaUtil.getMimeType(context, uri));
|
||||
String fastPreflightId = String.valueOf(SecureRandom.getInstance("SHA1PRNG").nextLong());
|
||||
return new UriAttachment(uri, hasThumbnail ? uri : null, resolvedType.or(defaultMime), AttachmentDatabase.TRANSFER_PROGRESS_STARTED, size, fileName, fastPreflightId, voiceNote);
|
||||
String resolvedType = Optional.fromNullable(MediaUtil.getMimeType(context, uri)).or(defaultMime);
|
||||
String fastPreflightId = String.valueOf(SecureRandom.getInstance("SHA1PRNG").nextLong());
|
||||
return new UriAttachment(uri,
|
||||
hasThumbnail ? uri : null,
|
||||
resolvedType,
|
||||
AttachmentDatabase.TRANSFER_PROGRESS_STARTED,
|
||||
size,
|
||||
width,
|
||||
height,
|
||||
fileName,
|
||||
fastPreflightId,
|
||||
voiceNote);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ import org.thoughtcrime.securesms.util.ResUtil;
|
||||
public class VideoSlide extends Slide {
|
||||
|
||||
public VideoSlide(Context context, Uri uri, long dataSize) {
|
||||
super(context, constructAttachmentFromUri(context, uri, MediaUtil.VIDEO_UNSPECIFIED, dataSize, MediaUtil.hasVideoThumbnail(uri), null, false));
|
||||
super(context, constructAttachmentFromUri(context, uri, MediaUtil.VIDEO_UNSPECIFIED, dataSize, 0, 0, MediaUtil.hasVideoThumbnail(uri), null, false));
|
||||
}
|
||||
|
||||
public VideoSlide(Context context, Attachment attachment) {
|
||||
|
@ -12,6 +12,7 @@ import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.*;
|
||||
import android.support.annotation.WorkerThread;
|
||||
import android.support.media.ExifInterface;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
@ -131,6 +132,26 @@ public class BitmapUtil {
|
||||
return options;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Pair<Integer, Integer> getExifDimensions(InputStream inputStream) throws IOException {
|
||||
ExifInterface exif = new ExifInterface(inputStream);
|
||||
int width = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, 0);
|
||||
int height = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, 0);
|
||||
if (width == 0 && height == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0);
|
||||
if (orientation == ExifInterface.ORIENTATION_ROTATE_90 ||
|
||||
orientation == ExifInterface.ORIENTATION_ROTATE_270 ||
|
||||
orientation == ExifInterface.ORIENTATION_TRANSVERSE ||
|
||||
orientation == ExifInterface.ORIENTATION_TRANSPOSE)
|
||||
{
|
||||
return new Pair<>(height, width);
|
||||
}
|
||||
return new Pair<>(width, height);
|
||||
}
|
||||
|
||||
public static Pair<Integer, Integer> getDimensions(InputStream inputStream) throws BitmapDecodingException {
|
||||
BitmapFactory.Options options = getImageDimensions(inputStream);
|
||||
return new Pair<>(options.outWidth, options.outHeight);
|
||||
|
@ -3,18 +3,27 @@ package org.thoughtcrime.securesms.util;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.net.Uri;
|
||||
import android.provider.MediaStore;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.WorkerThread;
|
||||
import android.support.media.ExifInterface;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.load.resource.gif.GifDrawable;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.mms.AudioSlide;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.mms.DocumentSlide;
|
||||
import org.thoughtcrime.securesms.mms.GifSlide;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.mms.ImageSlide;
|
||||
import org.thoughtcrime.securesms.mms.MmsSlide;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
@ -22,8 +31,10 @@ import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.VideoSlide;
|
||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class MediaUtil {
|
||||
|
||||
@ -100,6 +111,65 @@ public class MediaUtil {
|
||||
return size;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static Pair<Integer, Integer> getDimensions(@NonNull Context context, @Nullable String contentType, @Nullable Uri uri) {
|
||||
if (uri == null || !MediaUtil.isImageType(contentType)) {
|
||||
return new Pair<>(0, 0);
|
||||
}
|
||||
|
||||
Pair<Integer, Integer> dimens = null;
|
||||
|
||||
if (MediaUtil.isGif(contentType)) {
|
||||
try {
|
||||
GifDrawable drawable = GlideApp.with(context)
|
||||
.asGif()
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.load(new DecryptableUri(uri))
|
||||
.submit()
|
||||
.get();
|
||||
dimens = new Pair<>(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(TAG, "Was unable to complete work for GIF dimensions.", e);
|
||||
} catch (ExecutionException e) {
|
||||
Log.w(TAG, "Glide experienced an exception while trying to get GIF dimensions.", e);
|
||||
}
|
||||
} else {
|
||||
InputStream attachmentStream = null;
|
||||
try {
|
||||
if (MediaUtil.isJpegType(contentType)) {
|
||||
attachmentStream = PartAuthority.getAttachmentStream(context, uri);
|
||||
dimens = BitmapUtil.getExifDimensions(attachmentStream);
|
||||
attachmentStream.close();
|
||||
attachmentStream = null;
|
||||
}
|
||||
if (dimens == null) {
|
||||
attachmentStream = PartAuthority.getAttachmentStream(context, uri);
|
||||
dimens = BitmapUtil.getDimensions(attachmentStream);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.w(TAG, "Failed to find file when retrieving media dimensions.", e);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Experienced a read error when retrieving media dimensions.", e);
|
||||
} catch (BitmapDecodingException e) {
|
||||
Log.w(TAG, "Bitmap decoding error when retrieving dimensions.", e);
|
||||
} finally {
|
||||
if (attachmentStream != null) {
|
||||
try {
|
||||
attachmentStream.close();
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to close stream after retrieving dimensions.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dimens == null) {
|
||||
dimens = new Pair<>(0, 0);
|
||||
}
|
||||
Log.d(TAG, "Dimensions for [" + uri + "] are " + dimens.first + " x " + dimens.second);
|
||||
return dimens;
|
||||
}
|
||||
|
||||
public static boolean isMms(String contentType) {
|
||||
return !TextUtils.isEmpty(contentType) && contentType.trim().equals("application/mms");
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user