@ -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 ;
@ -64,10 +82,109 @@ 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 ) ;
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 Glide Request 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 {