/*
* Copyright (C) 2013 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.picasso;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.widget.ImageView;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
//import org.jetbrains.annotations.TestOnly;
import static com.squareup.picasso.Request.LoadedFrom.MEMORY;
import static com.squareup.picasso.Utils.checkNotMain;
import static com.squareup.picasso.Utils.createKey;
/** Fluent API for building an image download request. */
@SuppressWarnings("UnusedDeclaration") // Public API.
public class RequestBuilder {
private final Picasso picasso;
private final Uri uri;
private final int resourceId;
PicassoBitmapOptions options;
private List<Transformation> transformations;
private boolean skipCache;
private boolean noFade;
private boolean hasNullPlaceholder;
private int placeholderResId;
private Drawable placeholderDrawable;
private int errorResId;
private Drawable errorDrawable;
RequestBuilder(Picasso picasso, Uri uri, int resourceId) {
this.picasso = picasso;
this.uri = uri;
this.resourceId = resourceId;
}
// @TestOnly RequestBuilder() {
// this.picasso = null;
// this.uri = null;
// this.resourceId = 0;
// }
RequestBuilder() {
this.picasso = null;
this.uri = null;
this.resourceId = 0;
}
private PicassoBitmapOptions getOptions() {
if (options == null) {
options = new PicassoBitmapOptions();
}
return options;
}
/**
* A placeholder drawable to be used while the image is being loaded. If the requested image is
* not immediately available in the memory cache then this resource will be set on the target
* {@link ImageView}.
*/
public RequestBuilder placeholder(int placeholderResId) {
if (placeholderResId == 0) {
throw new IllegalArgumentException("Placeholder image resource invalid.");
}
if (placeholderDrawable != null) {
throw new IllegalStateException("Placeholder image already set.");
}
this.placeholderResId = placeholderResId;
return this;
}
/**
* A placeholder drawable to be used while the image is being loaded. If the requested image is
* not immediately available in the memory cache then this resource will be set on the target
* {@link ImageView}.
* <p>
* If you are not using a placeholder image but want to clear an existing image (such as when
* used in an {@link android.widget.Adapter adapter}), pass in {@code null}.
*/
public RequestBuilder placeholder(Drawable placeholderDrawable) {
if (placeholderResId != 0) {
throw new IllegalStateException("Placeholder image already set.");
}
this.hasNullPlaceholder = placeholderDrawable == null;
this.placeholderDrawable = placeholderDrawable;
return this;
}
/** An error drawable to be used if the request image could not be loaded. */
public RequestBuilder error(int errorResId) {
if (errorResId == 0) {
throw new IllegalArgumentException("Error image resource invalid.");
}
if (errorDrawable != null) {
throw new IllegalStateException("Error image already set.");
}
this.errorResId = errorResId;
return this;
}
/** An error drawable to be used if the request image could not be loaded. */
public RequestBuilder error(Drawable errorDrawable) {
if (errorDrawable == null) {
throw new IllegalArgumentException("Error image may not be null.");
}
if (errorResId != 0) {
throw new IllegalStateException("Error image already set.");
}
this.errorDrawable = errorDrawable;
return this;
}
/**
* Attempt to resize the image to fit exactly into the target {@link ImageView}'s bounds. This
* will only have an affect if the target view has been measured when the image becomes
* available.
*/
public RequestBuilder fit() {
PicassoBitmapOptions options = getOptions();
if (options.targetWidth != 0 || options.targetHeight != 0) {
throw new IllegalStateException("Fit cannot be used with resize.");
}
options.deferredResize = true;
return this;
}
/** Resize the image to the specified dimension size. */
public RequestBuilder resizeDimen(int targetWidthResId, int targetHeightResId) {
Resources resources = picasso.context.getResources();
int targetWidth = resources.getDimensionPixelSize(targetWidthResId);
int targetHeight = resources.getDimensionPixelSize(targetHeightResId);
return resize(targetWidth, targetHeight);
}
/** Resize the image to the specified size in pixels. */
public RequestBuilder resize(int targetWidth, int targetHeight) {
if (targetWidth <= 0) {
throw new IllegalArgumentException("Width must be positive number.");
}
if (targetHeight <= 0) {
throw new IllegalArgumentException("Height must be positive number.");
}
PicassoBitmapOptions options = getOptions();
if (options.targetWidth != 0 || options.targetHeight != 0) {
throw new IllegalStateException("Resize may only be called once.");
}
if (options.deferredResize) {
throw new IllegalStateException("Resize cannot be used with fit.");
}
options.targetWidth = targetWidth;
options.targetHeight = targetHeight;
options.inJustDecodeBounds = true;
return this;
}
/**
* Crops an image inside of the bounds specified by {@link #resize(int, int)} rather than
* distorting the aspect ratio. This cropping technique scales the image so that it fills the
* requested bounds and then crops the extra.
*/
public RequestBuilder centerCrop() {
PicassoBitmapOptions options = getOptions();
if (options.targetWidth == 0 || options.targetHeight == 0) {
throw new IllegalStateException("Center crop can only be used after calling resize.");
}
if (options.centerInside) {
throw new IllegalStateException("Center crop can not be used after calling centerInside");
}
options.centerCrop = true;
return this;
}
/**
* Centers an image inside of the bounds specified by {@link #resize(int, int)}. This scales
* the image so that both dimensions are equal to or less than the requested bounds.
*/
public RequestBuilder centerInside() {
PicassoBitmapOptions options = getOptions();
if (options.targetWidth == 0 || options.targetHeight == 0) {
throw new IllegalStateException("Center inside can only be used after calling resize.");
}
if (options.centerCrop) {
throw new IllegalStateException("Center inside can not be used after calling centerCrop");
}
options.centerInside = true;
return this;
}
/** Scale the image using the specified factor. */
public RequestBuilder scale(float factor) {
if (factor != 1) {
scale(factor, factor);
}
return this;
}
/** Scale the image using the specified factors. */
public RequestBuilder scale(float factorX, float factorY) {
if (factorX == 0 || factorY == 0) {
throw new IllegalArgumentException("Scale factor must be positive number.");
}
if (factorX != 1 && factorY != 1) {
PicassoBitmapOptions options = getOptions();
if (options.targetScaleX != 0 || options.targetScaleY != 0) {
throw new IllegalStateException("Scale may only be called once.");
}
options.targetScaleX = factorX;
options.targetScaleY = factorY;
}
return this;
}
/** Rotate the image by the specified degrees. */
public RequestBuilder rotate(float degrees) {
if (degrees != 0) {
PicassoBitmapOptions options = getOptions();
options.targetRotation = degrees;
}
return this;
}
/** Rotate the image by the specified degrees around a pivot point. */
public RequestBuilder rotate(float degrees, float pivotX, float pivotY) {
if (degrees != 0) {
PicassoBitmapOptions options = getOptions();
options.targetRotation = degrees;
options.targetPivotX = pivotX;
options.targetPivotY = pivotY;
options.hasRotationPivot = true;
}
return this;
}
/**
* Add a custom transformation to be applied to the image.
* <p/>
* Custom transformations will always be run after the built-in transformations.
*/
// TODO show example of calling resize after a transform in the javadoc
public RequestBuilder transform(Transformation transformation) {
if (transformation == null) {
throw new IllegalArgumentException("Transformation must not be null.");
}
if (transformations == null) {
transformations = new ArrayList<Transformation>(2);
}
transformations.add(transformation);
return this;
}
/**
* Indicate that this request should not use the memory cache for attempting to load or save the
* image. This is useful for when you know an image will only ever be used once (e.g., loading
* an image from the filesystem and uploading to a remote server).
*/
public RequestBuilder skipCache() {
skipCache = true;
return this;
}
/** Disable brief fade in of images loaded from the disk cache or network. */
public RequestBuilder noFade() {
noFade = true;
return this;
}
/** Synchronously fulfill this request. Must not be called from the main thread. */
public Bitmap get() throws IOException {
checkNotMain();
if (uri == null && resourceId == 0) {
return null;
}
Request request =
new Request(picasso, uri, resourceId, null, options, transformations, skipCache, false, 0,
null);
return picasso.resolveRequest(request);
}
/**
* Asynchronously fulfills the request into the specified {@link Target}.
* <p/>
* <em>Note:</em> This method keeps a strong reference to the {@link Target} instance.
*/
public void fetch(Target target) {
makeTargetRequest(target, true);
}
/**
* Asynchronously fulfills the request into the specified {@link Target}.
* <p/>
* <em>Note:</em> This method keeps a weak reference to the {@link Target} instance and will
* automatically support object recycling.
*/
public void into(Target target) {
makeTargetRequest(target, false);
}
/**
* Asynchronously fulfills the request into the specified {@link ImageView}.
* <p/>
* This method keeps a weak reference to the view and will automatically support object
* recycling.
*/
public void into(ImageView target) {
if (target == null) {
throw new IllegalArgumentException("Target must not be null.");
}
boolean hasItemToLoad = uri != null || resourceId != 0;
if (hasItemToLoad) {
// Look for the target bitmap in the memory cache without moving to a background thread.
String requestKey = createKey(uri, resourceId, options, transformations);
Bitmap bitmap = picasso.quickMemoryCacheCheck(target, uri, requestKey);
if (bitmap != null) {
PicassoDrawable.setBitmap(target, picasso.context, bitmap, MEMORY, noFade,
picasso.debugging);
return;
}
}
if (placeholderResId != 0 || placeholderDrawable != null) {
PicassoDrawable.setPlaceholder(target, picasso.context, placeholderResId, placeholderDrawable,
picasso.debugging);
} else if (hasNullPlaceholder) {
target.setImageDrawable(null);
}
if (hasItemToLoad) {
Request request =
new Request(picasso, uri, resourceId, target, options, transformations, skipCache, noFade,
errorResId, errorDrawable);
picasso.submit(request);
} else {
picasso.cancelRequest(target);
}
}
private void makeTargetRequest(Target target, boolean strong) {
if (target == null) {
throw new IllegalArgumentException("Target must not be null.");
}
if (uri == null && resourceId == 0) {
picasso.cancelRequest(target);
return;
}
String requestKey = createKey(uri, resourceId, options, transformations);
Bitmap bitmap = picasso.quickMemoryCacheCheck(target, uri, requestKey);
if (bitmap != null) {
target.onSuccess(bitmap);
return;
}
Request request =
new TargetRequest(picasso, uri, resourceId, target, strong, options, transformations,
skipCache);
picasso.submit(request);
}
}