|
1 |
| -/** |
2 |
| - * Copyright (C) 2013 The Android Open Source Project |
3 |
| - * |
4 |
| - * Licensed under the Apache License, Version 2.0 (the "License"); |
5 |
| - * you may not use this file except in compliance with the License. |
6 |
| - * You may obtain a copy of the License at |
7 |
| - * |
8 |
| - * http://www.apache.org/licenses/LICENSE-2.0 |
9 |
| - * |
10 |
| - * Unless required by applicable law or agreed to in writing, software |
11 |
| - * distributed under the License is distributed on an "AS IS" BASIS, |
12 |
| - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 |
| - * See the License for the specific language governing permissions and |
14 |
| - * limitations under the License. |
15 |
| - */ |
16 | 1 | package fr.free.nrw.commons;
|
17 | 2 |
|
18 | 3 | import android.content.Context;
|
19 |
| -import android.content.res.TypedArray; |
20 |
| -import android.graphics.drawable.BitmapDrawable; |
21 |
| -import android.text.TextUtils; |
| 4 | +import android.support.annotation.Nullable; |
| 5 | +import android.support.v4.util.LruCache; |
22 | 6 | import android.util.AttributeSet;
|
23 |
| -import android.view.View; |
24 |
| -import android.widget.ImageView; |
25 | 7 |
|
26 |
| -import com.android.volley.VolleyError; |
27 |
| -import com.android.volley.toolbox.ImageLoader; |
28 |
| -import com.android.volley.toolbox.ImageLoader.ImageContainer; |
29 |
| -import com.android.volley.toolbox.ImageLoader.ImageListener; |
| 8 | +import com.facebook.drawee.view.SimpleDraweeView; |
30 | 9 |
|
31 |
| -import fr.free.nrw.commons.contributions.Contribution; |
32 |
| -import fr.free.nrw.commons.contributions.ContributionsContentProvider; |
| 10 | +public class MediaWikiImageView extends SimpleDraweeView { |
| 11 | + private ThumbnailFetchTask currentThumbnailTask; |
| 12 | + LruCache<String, String> thumbnailUrlCache = new LruCache<>(1024); |
33 | 13 |
|
34 |
| -public class MediaWikiImageView extends ImageView { |
35 |
| - |
36 |
| - private Media mMedia; |
37 |
| - |
38 |
| - private ImageLoader mImageLoader; |
39 |
| - |
40 |
| - private ImageContainer mImageContainer; |
41 |
| - |
42 |
| - private View loadingView; |
43 |
| - |
44 |
| - private boolean isThumbnail; |
45 | 14 |
|
46 | 15 | public MediaWikiImageView(Context context) {
|
47 | 16 | this(context, null);
|
48 | 17 | }
|
49 | 18 |
|
50 | 19 | public MediaWikiImageView(Context context, AttributeSet attrs) {
|
51 | 20 | this(context, attrs, 0);
|
52 |
| - TypedArray actualAttrs = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MediaWikiImageView, 0, 0); |
53 |
| - isThumbnail = actualAttrs.getBoolean(0, false); |
54 |
| - actualAttrs.recycle(); |
55 | 21 | }
|
56 | 22 |
|
57 | 23 | public MediaWikiImageView(Context context, AttributeSet attrs, int defStyle) {
|
58 | 24 | super(context, attrs, defStyle);
|
59 | 25 | }
|
60 | 26 |
|
61 |
| - public void setMedia(Media media, ImageLoader imageLoader) { |
62 |
| - this.mMedia = media; |
63 |
| - mImageLoader = imageLoader; |
64 |
| - loadImageIfNecessary(false); |
65 |
| - } |
66 |
| - |
67 |
| - public void setLoadingView(View loadingView) { |
68 |
| - this.loadingView = loadingView; |
69 |
| - } |
70 |
| - |
71 |
| - public View getLoadingView() { |
72 |
| - return loadingView; |
73 |
| - } |
74 |
| - |
75 |
| - private void loadImageIfNecessary(final boolean isInLayoutPass) { |
76 |
| - loadImageIfNecessary(isInLayoutPass, false); |
77 |
| - } |
78 |
| - |
79 |
| - private void loadImageIfNecessary(final boolean isInLayoutPass, final boolean tryOriginal) { |
80 |
| - int width = getWidth(); |
81 |
| - int height = getHeight(); |
82 |
| - |
83 |
| - // if the view's bounds aren't known yet, hold off on loading the image. |
84 |
| - if (width == 0 && height == 0) { |
85 |
| - return; |
| 27 | + public void setMedia(Media media) { |
| 28 | + if (currentThumbnailTask != null) { |
| 29 | + currentThumbnailTask.cancel(true); |
86 | 30 | }
|
87 |
| - |
88 |
| - if(mMedia == null) { |
| 31 | + setImageURI((String) null); |
| 32 | + if(media == null) { |
89 | 33 | return;
|
90 | 34 | }
|
91 | 35 |
|
92 |
| - // Do not count for density when loading thumbnails. |
93 |
| - // FIXME: Use another 'algorithm' that doesn't punish low res devices |
94 |
| - if(isThumbnail) { |
95 |
| - float dpFactor = Math.max(getResources().getDisplayMetrics().density, 1.0f); |
96 |
| - width = (int) (width / dpFactor); |
97 |
| - height = (int) (height / dpFactor); |
98 |
| - } |
99 |
| - |
100 |
| - final String mUrl; |
101 |
| - if(tryOriginal) { |
102 |
| - mUrl = mMedia.getImageUrl(); |
| 36 | + if (thumbnailUrlCache.get(media.getFilename()) != null) { |
| 37 | + setImageUrl(thumbnailUrlCache.get(media.getFilename())); |
103 | 38 | } else {
|
104 |
| - // Round it to the nearest 320 |
105 |
| - // Possible a similar size image has already been generated. |
106 |
| - // Reduces Server cache fragmentation, also increases chance of cache hit |
107 |
| - // If width is less than 320, we round up to 320 |
108 |
| - int bucketedWidth = width <= 320 ? 320 : Math.round((float)width / 320.0f) * 320; |
109 |
| - if(mMedia.getWidth() != 0 && mMedia.getWidth() < bucketedWidth) { |
110 |
| - // If we know that the width of the image is lesser than the required width |
111 |
| - // We don't even try to load the thumbnai, go directly to the source |
112 |
| - loadImageIfNecessary(isInLayoutPass, true); |
113 |
| - return; |
114 |
| - } else { |
115 |
| - mUrl = mMedia.getThumbnailUrl(bucketedWidth); |
116 |
| - } |
| 39 | + currentThumbnailTask = new ThumbnailFetchTask(); |
| 40 | + currentThumbnailTask.execute(media.getFilename()); |
117 | 41 | }
|
118 |
| - |
119 |
| - // if the URL to be loaded in this view is empty, cancel any old requests and clear the |
120 |
| - // currently loaded image. |
121 |
| - if (TextUtils.isEmpty(mUrl)) { |
122 |
| - if (mImageContainer != null) { |
123 |
| - mImageContainer.cancelRequest(); |
124 |
| - mImageContainer = null; |
125 |
| - } |
126 |
| - setImageBitmap(null); |
127 |
| - return; |
128 |
| - } |
129 |
| - |
130 |
| - // Don't repeat work. Prevents onLayout cascades |
131 |
| - // We ignore it if the image request was for either the current URL of for the full URL |
132 |
| - // Since the full URL is always the second, and |
133 |
| - if (mImageContainer != null && mImageContainer.getRequestUrl() != null) { |
134 |
| - if (mImageContainer.getRequestUrl().equals(mMedia.getImageUrl()) || mImageContainer.getRequestUrl().equals(mUrl)) { |
135 |
| - return; |
136 |
| - } else { |
137 |
| - // if there is a pre-existing request, cancel it if it's fetching a different URL. |
138 |
| - mImageContainer.cancelRequest(); |
139 |
| - BitmapDrawable actualDrawable = (BitmapDrawable)getDrawable(); |
140 |
| - if(actualDrawable != null && actualDrawable.getBitmap() != null) { |
141 |
| - setImageBitmap(null); |
142 |
| - if(loadingView != null) { |
143 |
| - loadingView.setVisibility(View.VISIBLE); |
144 |
| - } |
145 |
| - } |
146 |
| - } |
147 |
| - } |
148 |
| - |
149 |
| - // The pre-existing content of this view didn't match the current URL. Load the new image |
150 |
| - // from the network. |
151 |
| - ImageContainer newContainer = mImageLoader.get(mUrl, |
152 |
| - new ImageListener() { |
153 |
| - @Override |
154 |
| - public void onErrorResponse(final VolleyError error) { |
155 |
| - if(!tryOriginal) { |
156 |
| - post(new Runnable() { |
157 |
| - @Override |
158 |
| - public void run() { |
159 |
| - loadImageIfNecessary(false, true); |
160 |
| - } |
161 |
| - }); |
162 |
| - } |
163 |
| - } |
164 |
| - |
165 |
| - @Override |
166 |
| - public void onResponse(final ImageContainer response, boolean isImmediate) { |
167 |
| - // If this was an immediate response that was delivered inside of a layout |
168 |
| - // pass do not set the image immediately as it will trigger a requestLayout |
169 |
| - // inside of a layout. Instead, defer setting the image by posting back to |
170 |
| - // the main thread. |
171 |
| - if (isImmediate && isInLayoutPass) { |
172 |
| - post(new Runnable() { |
173 |
| - @Override |
174 |
| - public void run() { |
175 |
| - onResponse(response, false); |
176 |
| - } |
177 |
| - }); |
178 |
| - return; |
179 |
| - } |
180 |
| - |
181 |
| - if (response.getBitmap() != null) { |
182 |
| - setImageBitmap(response.getBitmap()); |
183 |
| - if(tryOriginal && mMedia instanceof Contribution && (response.getBitmap().getWidth() > mMedia.getWidth() || response.getBitmap().getHeight() > mMedia.getHeight())) { |
184 |
| - // If there is no width information for this image, save it. This speeds up image loading massively for smaller images |
185 |
| - mMedia.setHeight(response.getBitmap().getHeight()); |
186 |
| - mMedia.setWidth(response.getBitmap().getWidth()); |
187 |
| - ((Contribution)mMedia).setContentProviderClient(MediaWikiImageView.this.getContext().getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY)); |
188 |
| - ((Contribution)mMedia).save(); |
189 |
| - } |
190 |
| - if(loadingView != null) { |
191 |
| - loadingView.setVisibility(View.GONE); |
192 |
| - } |
193 |
| - } else { |
194 |
| - // I'm not really sure where this would hit but not onError |
195 |
| - } |
196 |
| - } |
197 |
| - }); |
198 |
| - |
199 |
| - // update the ImageContainer to be the new bitmap container. |
200 |
| - mImageContainer = newContainer; |
201 |
| - } |
202 |
| - |
203 |
| - @Override |
204 |
| - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { |
205 |
| - super.onLayout(changed, left, top, right, bottom); |
206 |
| - loadImageIfNecessary(true); |
207 | 42 | }
|
208 | 43 |
|
209 | 44 | @Override
|
210 | 45 | protected void onDetachedFromWindow() {
|
211 |
| - if (mImageContainer != null) { |
212 |
| - // If the view was bound to an image request, cancel it and clear |
213 |
| - // out the image from the view. |
214 |
| - mImageContainer.cancelRequest(); |
215 |
| - setImageBitmap(null); |
216 |
| - // also clear out the container so we can reload the image if necessary. |
217 |
| - mImageContainer = null; |
| 46 | + if (currentThumbnailTask != null) { |
| 47 | + currentThumbnailTask.cancel(true); |
218 | 48 | }
|
219 | 49 | super.onDetachedFromWindow();
|
220 | 50 | }
|
221 | 51 |
|
222 |
| - @Override |
223 |
| - protected void drawableStateChanged() { |
224 |
| - super.drawableStateChanged(); |
225 |
| - invalidate(); |
| 52 | + private void setImageUrl(@Nullable String url) { |
| 53 | + setImageURI(url); |
| 54 | + } |
| 55 | + |
| 56 | + private class ThumbnailFetchTask extends MediaThumbnailFetchTask { |
| 57 | + @Override |
| 58 | + protected void onPostExecute(String result) { |
| 59 | + if (isCancelled()) { |
| 60 | + return; |
| 61 | + } |
| 62 | + setImageUrl(result); |
| 63 | + } |
226 | 64 | }
|
227 | 65 | }
|
0 commit comments