package com.koushikdutta.ion;
import android.content.ContentUris;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Looper;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import com.koushikdutta.async.future.Future;
import com.koushikdutta.async.future.SimpleFuture;
import com.koushikdutta.async.http.ResponseCacheMiddleware;
import com.koushikdutta.async.http.libcore.DiskLruCache;
import com.koushikdutta.ion.bitmap.BitmapInfo;
import com.koushikdutta.ion.bitmap.Transform;
import com.koushikdutta.ion.builder.BitmapFutureBuilder;
import com.koushikdutta.ion.builder.Builders;
import com.koushikdutta.ion.builder.ImageViewFutureBuilder;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
/**
* Created by koush on 5/23/13.
*/
class IonBitmapRequestBuilder implements Builders.IV.F, ImageViewFutureBuilder, BitmapFutureBuilder, Builders.Any.BF {
private static final SimpleFuture<ImageView> FUTURE_IMAGEVIEW_NULL_URI = new SimpleFuture<ImageView>() {
{
setComplete(new NullPointerException("uri"));
}
};
private static final SimpleFuture<Bitmap> FUTURE_BITMAP_NULL_URI = new SimpleFuture<Bitmap>() {
{
setComplete(new NullPointerException("uri"));
}
};
IonRequestBuilder builder;
Ion ion;
WeakReference<ImageView> imageViewPostRef;
ArrayList<Transform> transforms;
Drawable placeholderDrawable;
int placeholderResource;
Drawable errorDrawable;
int errorResource;
Animation inAnimation;
Animation loadAnimation;
int loadAnimationResource;
int inAnimationResource;
ScaleMode scaleMode = ScaleMode.FitXY;
int resizeWidth;
int resizeHeight;
boolean disableFadeIn;
boolean animateGif = true;
boolean deepZoom;
void reset() {
placeholderDrawable = null;
placeholderResource = 0;
errorDrawable = null;
errorResource = 0;
ion = null;
imageViewPostRef = null;
transforms = null;
inAnimation = null;
inAnimationResource = 0;
loadAnimation = null;
loadAnimationResource = 0;
scaleMode = ScaleMode.FitXY;
resizeWidth = 0;
resizeHeight = 0;
disableFadeIn = false;
animateGif = true;
builder = null;
deepZoom = false;
}
public IonBitmapRequestBuilder(IonRequestBuilder builder) {
this.builder = builder;
ion = builder.ion;
}
public IonBitmapRequestBuilder(Ion ion) {
this.ion = ion;
}
static void doAnimation(ImageView imageView, Animation animation, int animationResource) {
if (imageView == null)
return;
if (animation == null && animationResource != 0)
animation = AnimationUtils.loadAnimation(imageView.getContext(), animationResource);
if (animation == null) {
imageView.setAnimation(null);
return;
}
imageView.startAnimation(animation);
}
private IonRequestBuilder ensureBuilder() {
if (builder == null)
builder = new IonRequestBuilder(imageViewPostRef.get().getContext(), ion);
return builder;
}
@Override
public Future<ImageView> load(String uri) {
ensureBuilder();
builder.load(uri);
return intoImageView(imageViewPostRef.get());
}
@Override
public Future<ImageView> load(String method, String url) {
ensureBuilder();
builder.load(method, url);
return intoImageView(imageViewPostRef.get());
}
IonBitmapRequestBuilder withImageView(ImageView imageView) {
imageViewPostRef = new WeakReference<ImageView>(imageView);
return this;
}
@Override
public IonBitmapRequestBuilder transform(Transform transform) {
if (transforms == null)
transforms = new ArrayList<Transform>();
transforms.add(transform);
return this;
}
private String computeDownloadKey() {
String downloadKey = builder.uri;
// although a gif is always same download, the initial decode is different
if (!animateGif)
downloadKey += ":!animateGif";
if (deepZoom)
downloadKey += ":deepZoom";
return ResponseCacheMiddleware.toKeyString(downloadKey);
}
@Override
public BitmapInfo asCachedBitmap() {
final String downloadKey = computeDownloadKey();
assert Thread.currentThread() == Looper.getMainLooper().getThread() || imageViewPostRef == null;
assert downloadKey != null;
if (resizeHeight > 0 || resizeWidth > 0) {
transform(new DefaultTransform(resizeWidth, resizeHeight, scaleMode));
}
// determine the key for this bitmap after all transformations
String bitmapKey = downloadKey;
boolean hasTransforms = transforms != null && transforms.size() > 0;
if (hasTransforms) {
for (Transform transform : transforms) {
bitmapKey += transform.key();
}
bitmapKey = ResponseCacheMiddleware.toKeyString(bitmapKey);
}
return builder.ion.bitmapCache.get(bitmapKey);
}
BitmapFetcher executeCache() {
final String downloadKey = computeDownloadKey();
assert Thread.currentThread() == Looper.getMainLooper().getThread() || imageViewPostRef == null;
assert downloadKey != null;
if (resizeHeight > 0 || resizeWidth > 0) {
transform(new DefaultTransform(resizeWidth, resizeHeight, scaleMode));
}
// determine the key for this bitmap after all transformations
String bitmapKey = downloadKey;
boolean hasTransforms = transforms != null && transforms.size() > 0;
if (hasTransforms) {
for (Transform transform : transforms) {
bitmapKey += transform.key();
}
bitmapKey = ResponseCacheMiddleware.toKeyString(bitmapKey);
}
// TODO: eliminate this allocation?
BitmapFetcher ret = new BitmapFetcher();
ret.downloadKey = downloadKey;
ret.bitmapKey = bitmapKey;
ret.hasTransforms = hasTransforms;
ret.resizeWidth = resizeWidth;
ret.resizeHeight = resizeHeight;
ret.builder = builder;
ret.transforms = transforms;
ret.animateGif = animateGif;
ret.deepZoom = deepZoom;
// see if this request can be fulfilled from the cache
if (!builder.noCache) {
BitmapInfo bitmap = builder.ion.bitmapCache.get(bitmapKey);
if (bitmap != null) {
ret.info = bitmap;
return ret;
}
}
return ret;
}
private IonDrawable setIonDrawable(ImageView imageView, BitmapInfo info, int loadedFrom) {
IonDrawable ret = IonDrawable.getOrCreateIonDrawable(imageView)
.ion(ion)
.setBitmap(info, loadedFrom)
.setSize(resizeWidth, resizeHeight)
.setError(errorResource, errorDrawable)
.setPlaceholder(placeholderResource, placeholderDrawable)
.setInAnimation(inAnimation, inAnimationResource)
.setDisableFadeIn(disableFadeIn);
imageView.setImageDrawable(ret);
return ret;
}
@Override
public Future<ImageView> intoImageView(ImageView imageView) {
if (imageView == null)
throw new IllegalArgumentException("imageView");
assert Thread.currentThread() == Looper.getMainLooper().getThread();
if (builder.uri != null && builder.uri.startsWith("android.resource:/")) {
IonDrawable drawable = setIonDrawable(imageView, null, 0);
SimpleFuture<ImageView> imageViewFuture = drawable.getFuture();
imageViewFuture.reset();
imageView.setImageURI(Uri.parse(builder.uri));
imageViewFuture.setComplete(null, imageView);
return imageViewFuture;
}
// no uri? just set a placeholder and bail
if (builder.uri == null) {
setIonDrawable(imageView, null, 0).cancel();
return FUTURE_IMAGEVIEW_NULL_URI;
}
// executeCache the request, see if we get a bitmap from cache.
BitmapFetcher bitmapFetcher = executeCache();
if (bitmapFetcher.info != null) {
doAnimation(imageView, null, 0);
IonDrawable drawable = setIonDrawable(imageView, bitmapFetcher.info, Loader.LoaderEmitter.LOADED_FROM_MEMORY);
drawable.cancel();
SimpleFuture<ImageView> imageViewFuture = drawable.getFuture();
imageViewFuture.reset();
imageViewFuture.setComplete(bitmapFetcher.info.exception, imageView);
return imageViewFuture;
}
// nothing from cache, check to see if there's too many imageview loads
// already in progress
if (BitmapFetcher.shouldDeferImageView(ion)) {
bitmapFetcher.defer();
}
else {
bitmapFetcher.execute();
}
IonDrawable drawable = setIonDrawable(imageView, null, 0);
doAnimation(imageView, loadAnimation, loadAnimationResource);
SimpleFuture<ImageView> imageViewFuture = drawable.getFuture();
imageViewFuture.reset();
drawable.register(ion, bitmapFetcher.bitmapKey);
return imageViewFuture;
}
@Override
public Future<Bitmap> asBitmap() {
if (builder.uri == null) {
return FUTURE_BITMAP_NULL_URI;
}
// see if we get something back synchronously
BitmapFetcher bitmapFetcher = executeCache();
if (bitmapFetcher.info != null) {
SimpleFuture<Bitmap> ret = new SimpleFuture<Bitmap>();
Bitmap bitmap = bitmapFetcher.info.bitmaps == null ? null : bitmapFetcher.info.bitmaps[0];
ret.setComplete(bitmapFetcher.info.exception, bitmap);
return ret;
}
bitmapFetcher.execute();
// we're loading, so let's register for the result.
BitmapInfoToBitmap ret = new BitmapInfoToBitmap(builder.context);
ion.bitmapsPending.add(bitmapFetcher.bitmapKey, ret);
return ret;
}
@Override
public IonBitmapRequestBuilder placeholder(Drawable drawable) {
placeholderDrawable = drawable;
return this;
}
@Override
public IonBitmapRequestBuilder placeholder(int resourceId) {
placeholderResource = resourceId;
return this;
}
@Override
public IonBitmapRequestBuilder error(Drawable drawable) {
errorDrawable = drawable;
return this;
}
@Override
public IonBitmapRequestBuilder error(int resourceId) {
errorResource = resourceId;
return this;
}
@Override
public IonBitmapRequestBuilder animateIn(Animation in) {
inAnimation = in;
return this;
}
@Override
public IonBitmapRequestBuilder animateLoad(Animation load) {
loadAnimation = load;
return this;
}
@Override
public IonBitmapRequestBuilder animateLoad(int animationResource) {
loadAnimationResource = animationResource;
return this;
}
@Override
public IonBitmapRequestBuilder animateIn(int animationResource) {
inAnimationResource = animationResource;
return this;
}
@Override
public IonBitmapRequestBuilder centerCrop() {
if (resizeWidth <= 0 || resizeHeight <= 0)
throw new IllegalStateException("must call resize first");
scaleMode = ScaleMode.CenterCrop;
return this;
}
@Override
public IonBitmapRequestBuilder centerInside() {
if (resizeWidth <= 0 || resizeHeight <= 0)
throw new IllegalStateException("must call resize first");
scaleMode = ScaleMode.CenterInside;
return this;
}
@Override
public IonBitmapRequestBuilder resize(int width, int height) {
resizeWidth = width;
resizeHeight = height;
return this;
}
@Override
public IonBitmapRequestBuilder disableFadeIn() {
this.disableFadeIn = true;
return this;
}
public IonBitmapRequestBuilder smartSize(boolean smartSize) {
//don't want to disable device resize if user has already resized the Bitmap.
if (resizeWidth > 0 || resizeHeight > 0)
throw new IllegalStateException("Can't change smart size after resize has been called.");
if (!smartSize) {
resizeWidth = -1;
resizeHeight = -1;
}
else {
resizeWidth = 0;
resizeHeight = 0;
}
return this;
}
@Override
public IonBitmapRequestBuilder animateGif(boolean animateGif) {
this.animateGif = animateGif;
return this;
}
@Override
public IonBitmapRequestBuilder deepZoom() {
if (Build.VERSION.SDK_INT < 10)
return this;
this.deepZoom = true;
if (resizeWidth > 0 || resizeHeight > 0)
throw new IllegalStateException("Can't decoder after resize has been called.");
resizeWidth = 0;
resizeHeight = 0;
return this;
}
}