mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Volley images
Add support for using Volley to load remote images instead of UIL 1. Gives us Caching 2. Load images at full resolution, rather than hardcode them. This is done by trying to fetch an image at highest width possible for the particular view. If it 500s, we assume that the image is smaller than the requested width and just request the full size image 3. Created a MediaWikiImageView, to which you can pass a Media object and it will display it. Takes care of sizing, etc. Optionally you can also specify a view to use as the 'loading' view. TODO: Loading from content:// URIs still use UIL. Need to write a Volley HTTP Stack that can fake responses for content:// URIs. GitHub: https://github.com/wikimedia/apps-android-commons/pull/1 Change-Id: Ia21a7b19fefa552d5a0b013085d0f5f1f80dc5ff
This commit is contained in:
parent
a28fc9a6ff
commit
57888260ec
8 changed files with 298 additions and 37 deletions
|
|
@ -52,6 +52,12 @@
|
|||
<artifactId>acra</artifactId>
|
||||
<version>4.4.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.android</groupId>
|
||||
<artifactId>volley</artifactId>
|
||||
<version>1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.keyboardsurfer.android.widget</groupId>
|
||||
<artifactId>crouton</artifactId>
|
||||
|
|
|
|||
|
|
@ -22,11 +22,10 @@
|
|||
android:visibility="gone"
|
||||
/>
|
||||
|
||||
<ImageView android:id="@+id/mediaDetailImage"
|
||||
<org.wikimedia.commons.MediaWikiImageView android:id="@+id/mediaDetailImage"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:scaleType="fitCenter"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
|
||||
<RelativeLayout
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
android:typeface="serif"
|
||||
android:layout_gravity="end|bottom"
|
||||
/>
|
||||
<ImageView android:id="@+id/contributionImage"
|
||||
<org.wikimedia.commons.MediaWikiImageView android:id="@+id/contributionImage"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="240dp"
|
||||
android:scaleType="centerCrop"
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import android.graphics.Bitmap;
|
|||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
||||
import android.support.v4.util.LruCache;
|
||||
import com.android.volley.RequestQueue;
|
||||
import com.nostra13.universalimageloader.cache.disc.impl.TotalSizeLimitedDiscCache;
|
||||
import com.nostra13.universalimageloader.cache.memory.impl.LimitedAgeMemoryCache;
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
|
|
@ -29,6 +31,8 @@ import org.apache.http.impl.client.*;
|
|||
import org.apache.http.params.CoreProtocolPNames;
|
||||
import org.wikimedia.commons.data.*;
|
||||
|
||||
import com.android.volley.toolbox.*;
|
||||
|
||||
// TODO: Use ProGuard to rip out reporting when publishing
|
||||
@ReportsCrashes(formKey = "",
|
||||
mailTo = "mobile-feedback-l@wikimedia.org",
|
||||
|
|
@ -62,6 +66,7 @@ public class CommonsApplication extends Application {
|
|||
public static final String FEEDBACK_EMAIL = "mobile-feedback-l@lists.wikimedia.org";
|
||||
public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App (%s) Feedback";
|
||||
|
||||
public RequestQueue volleyQueue;
|
||||
|
||||
public static AbstractHttpClient createHttpClient() {
|
||||
DefaultHttpClient client = new DefaultHttpClient();
|
||||
|
|
@ -106,6 +111,35 @@ public class CommonsApplication extends Application {
|
|||
|
||||
// Initialize EventLogging
|
||||
EventLog.setApp(this);
|
||||
|
||||
volleyQueue = Volley.newRequestQueue(this);
|
||||
}
|
||||
|
||||
private com.android.volley.toolbox.ImageLoader imageLoader;
|
||||
// based off https://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
|
||||
// Cache for 1/8th of available VM memory
|
||||
private LruCache<String, Bitmap> imageCache = new LruCache<String, Bitmap>((int) (Runtime.getRuntime().maxMemory() / (1024 * 8))) {
|
||||
@Override
|
||||
protected int sizeOf(String key, Bitmap bitmap) {
|
||||
// The cache size will be measured in kilobytes rather than
|
||||
// number of items.
|
||||
return bitmap.getByteCount() / 1024;
|
||||
}
|
||||
};
|
||||
|
||||
public com.android.volley.toolbox.ImageLoader getImageLoader() {
|
||||
if(imageLoader == null) {
|
||||
imageLoader = new com.android.volley.toolbox.ImageLoader(volleyQueue, new com.android.volley.toolbox.ImageLoader.ImageCache() {
|
||||
public Bitmap getBitmap(String key) {
|
||||
return imageCache.get(key);
|
||||
}
|
||||
|
||||
public void putBitmap(String key, Bitmap bitmap) {
|
||||
imageCache.put(key, bitmap);
|
||||
}
|
||||
});
|
||||
}
|
||||
return imageLoader;
|
||||
}
|
||||
|
||||
public MWApi getApi() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,201 @@
|
|||
/**
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* 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 org.wikimedia.commons;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.android.volley.VolleyError;
|
||||
import com.android.volley.toolbox.ImageLoader;
|
||||
import com.android.volley.toolbox.ImageLoader.ImageContainer;
|
||||
import com.android.volley.toolbox.ImageLoader.ImageListener;
|
||||
|
||||
|
||||
public class MediaWikiImageView extends ImageView {
|
||||
|
||||
private Media mMedia;
|
||||
|
||||
private ImageLoader mImageLoader;
|
||||
|
||||
private ImageContainer mImageContainer;
|
||||
|
||||
private View loadingView;
|
||||
|
||||
public MediaWikiImageView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public MediaWikiImageView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public MediaWikiImageView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
public void setMedia(Media media, ImageLoader imageLoader) {
|
||||
this.mMedia = media;
|
||||
mImageLoader = imageLoader;
|
||||
loadImageIfNecessary(false);
|
||||
}
|
||||
|
||||
public void setLoadingView(View loadingView) {
|
||||
this.loadingView = loadingView;
|
||||
}
|
||||
|
||||
public View getLoadingView() {
|
||||
return loadingView;
|
||||
}
|
||||
|
||||
private void loadImageIfNecessary(final boolean isInLayoutPass) {
|
||||
loadImageIfNecessary(isInLayoutPass, false);
|
||||
}
|
||||
|
||||
private void loadImageIfNecessary(final boolean isInLayoutPass, final boolean tryOriginal) {
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
|
||||
// if the view's bounds aren't known yet, hold off on loading the image.
|
||||
if (width == 0 && height == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String mUrl;
|
||||
if(tryOriginal) {
|
||||
mUrl = mMedia.getImageUrl();
|
||||
} else {
|
||||
// Round it down to the nearest 320
|
||||
// Possible a similar size image has already been generated.
|
||||
// Reduces Server cache fragmentation, also increases chance of cache hit
|
||||
// If width is less than 320, we just use that directly, to avoid a case of the Maths
|
||||
mUrl = mMedia.getThumbnailUrl(width <= 320 ? width : (width / 320) * 320);
|
||||
}
|
||||
|
||||
// if the URL to be loaded in this view is empty, cancel any old requests and clear the
|
||||
// currently loaded image.
|
||||
if (TextUtils.isEmpty(mUrl)) {
|
||||
if (mImageContainer != null) {
|
||||
mImageContainer.cancelRequest();
|
||||
mImageContainer = null;
|
||||
}
|
||||
setImageBitmap(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't repeat work. Prevents onLayout cascades
|
||||
// We ignore it if the image request was for either the current URL of for the full URL
|
||||
// Since the full URL is always the second, and
|
||||
if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
|
||||
Log.d("Commons", "Older one is " + mImageContainer.getRequestUrl() + " new one is " + mUrl);
|
||||
if (mImageContainer.getRequestUrl().equals(mMedia.getImageUrl()) || mImageContainer.getRequestUrl().equals(mUrl)) {
|
||||
return;
|
||||
} else {
|
||||
// if there is a pre-existing request, cancel it if it's fetching a different URL.
|
||||
mImageContainer.cancelRequest();
|
||||
BitmapDrawable actualDrawable = (BitmapDrawable)getDrawable();
|
||||
if(actualDrawable != null && actualDrawable.getBitmap() != null) {
|
||||
setImageBitmap(null);
|
||||
if(loadingView != null) {
|
||||
loadingView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The pre-existing content of this view didn't match the current URL. Load the new image
|
||||
// from the network.
|
||||
ImageContainer newContainer = mImageLoader.get(mUrl,
|
||||
new ImageListener() {
|
||||
@Override
|
||||
public void onErrorResponse(final VolleyError error) {
|
||||
Log.d("Commons", "Error: or Url " + mUrl + " value is " + tryOriginal);
|
||||
if(!tryOriginal) {
|
||||
post(new Runnable() {
|
||||
public void run() {
|
||||
loadImageIfNecessary(false, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(final ImageContainer response, boolean isImmediate) {
|
||||
// If this was an immediate response that was delivered inside of a layout
|
||||
// pass do not set the image immediately as it will trigger a requestLayout
|
||||
// inside of a layout. Instead, defer setting the image by posting back to
|
||||
// the main thread.
|
||||
if (isImmediate && isInLayoutPass) {
|
||||
post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onResponse(response, false);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Log.d("Commons", "No-Error: For Url " + mUrl + " value is " + tryOriginal);
|
||||
|
||||
if (response.getBitmap() != null) {
|
||||
setImageBitmap(response.getBitmap());
|
||||
if(loadingView != null) {
|
||||
loadingView.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
Log.d("Commons", "Whelp, fully can not load an image at all!");
|
||||
// We got nothing back, figure out some sort of a solution?
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// update the ImageContainer to be the new bitmap container.
|
||||
mImageContainer = newContainer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
Log.d("Commons", "Called via onLayout");
|
||||
loadImageIfNecessary(true);
|
||||
// Called via onLayout
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
if (mImageContainer != null) {
|
||||
// If the view was bound to an image request, cancel it and clear
|
||||
// out the image from the view.
|
||||
mImageContainer.cancelRequest();
|
||||
setImageBitmap(null);
|
||||
// also clear out the container so we can reload the image if necessary.
|
||||
mImageContainer = null;
|
||||
}
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawableStateChanged() {
|
||||
super.drawableStateChanged();
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
|
@ -22,14 +22,16 @@ import com.actionbarsherlock.app.SherlockFragment;
|
|||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuInflater;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
import com.nostra13.universalimageloader.core.*;
|
||||
import com.nostra13.universalimageloader.core.assist.*;
|
||||
|
||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
import com.nostra13.universalimageloader.core.assist.SimpleImageLoadingListener;
|
||||
import org.wikimedia.commons.*;
|
||||
import org.wikimedia.commons.R;
|
||||
|
||||
public class ContributionsListFragment extends SherlockFragment {
|
||||
|
||||
|
|
@ -85,17 +87,23 @@ public class ContributionsListFragment extends SherlockFragment {
|
|||
String actualUrl = TextUtils.isEmpty(contribution.getImageUrl()) ? contribution.getLocalUri().toString() : contribution.getThumbnailUrl(320);
|
||||
|
||||
if(views.url == null || !views.url.equals(actualUrl)) {
|
||||
ImageLoader.getInstance().displayImage(actualUrl, views.imageView, contributionDisplayOptions, new SimpleImageLoadingListener() {
|
||||
if(actualUrl.startsWith("http")) {
|
||||
MediaWikiImageView mwImageView = (MediaWikiImageView)views.imageView;
|
||||
mwImageView.setMedia(contribution, ((CommonsApplication) getActivity().getApplicationContext()).getImageLoader());
|
||||
// FIXME: For transparent images
|
||||
} else {
|
||||
com.nostra13.universalimageloader.core.ImageLoader.getInstance().displayImage(actualUrl, views.imageView, contributionDisplayOptions, new SimpleImageLoadingListener() {
|
||||
|
||||
@Override
|
||||
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
|
||||
if(loadedImage.hasAlpha()) {
|
||||
views.imageView.setBackgroundResource(android.R.color.white);
|
||||
@Override
|
||||
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
|
||||
if(loadedImage.hasAlpha()) {
|
||||
views.imageView.setBackgroundResource(android.R.color.white);
|
||||
}
|
||||
views.seqNumView.setVisibility(View.GONE);
|
||||
}
|
||||
views.seqNumView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
views.url = actualUrl;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,14 +3,17 @@ package org.wikimedia.commons.media;
|
|||
import android.graphics.*;
|
||||
import android.os.*;
|
||||
import android.text.*;
|
||||
import android.util.Log;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import com.actionbarsherlock.app.SherlockFragment;
|
||||
import com.android.volley.toolbox.NetworkImageView;
|
||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
||||
import com.nostra13.universalimageloader.core.assist.ImageLoadingListener;
|
||||
|
||||
import com.android.volley.toolbox.*;
|
||||
|
||||
import org.wikimedia.commons.*;
|
||||
|
||||
public class MediaDetailFragment extends SherlockFragment {
|
||||
|
|
@ -78,29 +81,38 @@ public class MediaDetailFragment extends SherlockFragment {
|
|||
}
|
||||
|
||||
String actualUrl = TextUtils.isEmpty(media.getImageUrl()) ? media.getLocalUri().toString() : media.getThumbnailUrl(640);
|
||||
ImageLoader.getInstance().displayImage(actualUrl, image, displayOptions, new ImageLoadingListener() {
|
||||
public void onLoadingStarted(String s, View view) {
|
||||
loadingProgress.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void onLoadingFailed(String s, View view, FailReason failReason) {
|
||||
loadingProgress.setVisibility(View.GONE);
|
||||
loadingFailed.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void onLoadingComplete(String s, View view, Bitmap bitmap) {
|
||||
loadingProgress.setVisibility(View.GONE);
|
||||
loadingFailed.setVisibility(View.GONE);
|
||||
image.setVisibility(View.VISIBLE);
|
||||
if(bitmap.hasAlpha()) {
|
||||
image.setBackgroundResource(android.R.color.white);
|
||||
if(actualUrl.startsWith("http")) {
|
||||
ImageLoader loader = ((CommonsApplication)getActivity().getApplicationContext()).getImageLoader();
|
||||
MediaWikiImageView mwImage = (MediaWikiImageView)image;
|
||||
mwImage.setLoadingView(loadingProgress); //FIXME: Set this as an attribute
|
||||
mwImage.setMedia(media, loader);
|
||||
Log.d("Volley", actualUrl);
|
||||
// FIXME: For transparent images
|
||||
} else {
|
||||
com.nostra13.universalimageloader.core.ImageLoader.getInstance().displayImage(actualUrl, image, displayOptions, new ImageLoadingListener() {
|
||||
public void onLoadingStarted(String s, View view) {
|
||||
loadingProgress.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
public void onLoadingCancelled(String s, View view) {
|
||||
throw new RuntimeException("Image loading cancelled. But why?");
|
||||
}
|
||||
});
|
||||
public void onLoadingFailed(String s, View view, FailReason failReason) {
|
||||
loadingProgress.setVisibility(View.GONE);
|
||||
loadingFailed.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void onLoadingComplete(String s, View view, Bitmap bitmap) {
|
||||
loadingProgress.setVisibility(View.GONE);
|
||||
loadingFailed.setVisibility(View.GONE);
|
||||
image.setVisibility(View.VISIBLE);
|
||||
if(bitmap.hasAlpha()) {
|
||||
image.setBackgroundResource(android.R.color.white);
|
||||
}
|
||||
}
|
||||
|
||||
public void onLoadingCancelled(String s, View view) {
|
||||
throw new RuntimeException("Image loading cancelled. But why?");
|
||||
}
|
||||
});
|
||||
}
|
||||
title.setText(media.getDisplayTitle());
|
||||
|
||||
title.addTextChangedListener(new TextWatcher() {
|
||||
|
|
|
|||
5
pom.xml
5
pom.xml
|
|
@ -205,10 +205,11 @@
|
|||
</profiles>
|
||||
|
||||
<repositories>
|
||||
|
||||
<repository>
|
||||
<id>yuvi.in</id>
|
||||
<name>Yuvi's Maven Repo</name>
|
||||
<url>http://yuvi.in/blog/maven</url>
|
||||
<name>Yuvi's Newer Maven Repo</name>
|
||||
<url>http://yuvi.in/maven</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue