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