Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 package org.mozilla.gecko.home;
8 import org.mozilla.gecko.favicons.Favicons;
9 import org.mozilla.gecko.R;
11 import android.content.Context;
12 import android.graphics.Bitmap;
13 import android.text.TextUtils;
14 import android.util.AttributeSet;
15 import android.view.LayoutInflater;
16 import android.widget.ImageView;
17 import android.widget.ImageView.ScaleType;
18 import android.widget.RelativeLayout;
19 import android.widget.TextView;
21 /**
22 * A view that displays the thumbnail and the title/url for a top/pinned site.
23 * If the title/url is longer than the width of the view, they are faded out.
24 * If there is no valid url, a default string is shown at 50% opacity.
25 * This is denoted by the empty state.
26 */
27 public class TopSitesGridItemView extends RelativeLayout {
28 private static final String LOGTAG = "GeckoTopSitesGridItemView";
30 // Empty state, to denote there is no valid url.
31 private static final int[] STATE_EMPTY = { android.R.attr.state_empty };
33 private static final ScaleType SCALE_TYPE_FAVICON = ScaleType.CENTER;
34 private static final ScaleType SCALE_TYPE_RESOURCE = ScaleType.CENTER;
35 private static final ScaleType SCALE_TYPE_THUMBNAIL = ScaleType.CENTER_CROP;
37 // Child views.
38 private final TextView mTitleView;
39 private final ImageView mThumbnailView;
41 // Data backing this view.
42 private String mTitle;
43 private String mUrl;
44 private String mFaviconURL;
46 private boolean mThumbnailSet;
48 // Pinned state.
49 private boolean mIsPinned = false;
51 // Dirty state.
52 private boolean mIsDirty = false;
54 // Empty state.
55 private boolean mIsEmpty = true;
56 private int mLoadId = Favicons.NOT_LOADING;
58 public TopSitesGridItemView(Context context) {
59 this(context, null);
60 }
62 public TopSitesGridItemView(Context context, AttributeSet attrs) {
63 this(context, attrs, R.attr.topSitesGridItemViewStyle);
64 }
66 public TopSitesGridItemView(Context context, AttributeSet attrs, int defStyle) {
67 super(context, attrs, defStyle);
69 LayoutInflater.from(context).inflate(R.layout.top_sites_grid_item_view, this);
71 mTitleView = (TextView) findViewById(R.id.title);
72 mThumbnailView = (ImageView) findViewById(R.id.thumbnail);
73 }
75 /**
76 * {@inheritDoc}
77 */
78 @Override
79 public int[] onCreateDrawableState(int extraSpace) {
80 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
82 if (mIsEmpty) {
83 mergeDrawableStates(drawableState, STATE_EMPTY);
84 }
86 return drawableState;
87 }
89 /**
90 * @return The title shown by this view.
91 */
92 public String getTitle() {
93 return (!TextUtils.isEmpty(mTitle) ? mTitle : mUrl);
94 }
96 /**
97 * @return The url shown by this view.
98 */
99 public String getUrl() {
100 return mUrl;
101 }
103 /**
104 * @return true, if this view is pinned, false otherwise.
105 */
106 public boolean isPinned() {
107 return mIsPinned;
108 }
110 /**
111 * @return true, if this view has no content to show.
112 */
113 public boolean isEmpty() {
114 return mIsEmpty;
115 }
117 /**
118 * @param title The title for this view.
119 */
120 public void setTitle(String title) {
121 if (mTitle != null && mTitle.equals(title)) {
122 return;
123 }
125 mTitle = title;
126 updateTitleView();
127 }
129 /**
130 * @param url The url for this view.
131 */
132 public void setUrl(String url) {
133 if (mUrl != null && mUrl.equals(url)) {
134 return;
135 }
137 mUrl = url;
138 updateTitleView();
139 }
141 /**
142 * @param pinned The pinned state of this view.
143 */
144 public void setPinned(boolean pinned) {
145 mIsPinned = pinned;
146 mTitleView.setCompoundDrawablesWithIntrinsicBounds(pinned ? R.drawable.pin : 0, 0, 0, 0);
147 }
149 public void blankOut() {
150 mUrl = "";
151 mTitle = "";
152 mIsPinned = false;
153 updateTitleView();
154 mTitleView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
155 setLoadId(Favicons.NOT_LOADING);
156 displayThumbnail(R.drawable.top_site_add);
157 }
159 public void markAsDirty() {
160 mIsDirty = true;
161 }
163 /**
164 * Updates the title, URL, and pinned state of this view.
165 *
166 * Also resets our loadId to NOT_LOADING.
167 *
168 * Returns true if any fields changed.
169 */
170 public boolean updateState(final String title, final String url, final boolean pinned, final Bitmap thumbnail) {
171 boolean changed = false;
172 if (mUrl == null || !mUrl.equals(url)) {
173 mUrl = url;
174 changed = true;
175 }
177 if (mTitle == null || !mTitle.equals(title)) {
178 mTitle = title;
179 changed = true;
180 }
182 if (thumbnail != null) {
183 displayThumbnail(thumbnail);
184 } else if (changed) {
185 // Because we'll have a new favicon or thumbnail arriving shortly, and
186 // we need to not reject it because we already had a thumbnail.
187 mThumbnailSet = false;
188 }
190 if (changed) {
191 updateTitleView();
192 setLoadId(Favicons.NOT_LOADING);
193 }
195 if (mIsPinned != pinned) {
196 mIsPinned = pinned;
197 mTitleView.setCompoundDrawablesWithIntrinsicBounds(pinned ? R.drawable.pin : 0, 0, 0, 0);
198 changed = true;
199 }
201 // The dirty state forces the state update to return true
202 // so that the adapter loads favicons once the thumbnails
203 // are loaded in TopSitesPanel/TopSitesGridAdapter.
204 changed = (changed || mIsDirty);
205 mIsDirty = false;
207 return changed;
208 }
210 /**
211 * Display the thumbnail from a resource.
212 *
213 * @param resId Resource ID of the drawable to show.
214 */
215 public void displayThumbnail(int resId) {
216 mThumbnailView.setScaleType(SCALE_TYPE_RESOURCE);
217 mThumbnailView.setImageResource(resId);
218 mThumbnailView.setBackgroundColor(0x0);
219 mThumbnailSet = false;
220 }
222 /**
223 * Display the thumbnail from a bitmap.
224 *
225 * @param thumbnail The bitmap to show as thumbnail.
226 */
227 public void displayThumbnail(Bitmap thumbnail) {
228 if (thumbnail == null) {
229 // Show a favicon based view instead.
230 displayThumbnail(R.drawable.favicon);
231 return;
232 }
233 mThumbnailSet = true;
234 Favicons.cancelFaviconLoad(mLoadId);
236 mThumbnailView.setScaleType(SCALE_TYPE_THUMBNAIL);
237 mThumbnailView.setImageBitmap(thumbnail);
238 mThumbnailView.setBackgroundDrawable(null);
239 }
241 public void displayFavicon(Bitmap favicon, String faviconURL, int expectedLoadId) {
242 if (mLoadId != Favicons.NOT_LOADING &&
243 mLoadId != expectedLoadId) {
244 // View recycled.
245 return;
246 }
248 // Yes, there's a chance of a race here.
249 displayFavicon(favicon, faviconURL);
250 }
252 /**
253 * Display the thumbnail from a favicon.
254 *
255 * @param favicon The favicon to show as thumbnail.
256 */
257 public void displayFavicon(Bitmap favicon, String faviconURL) {
258 if (mThumbnailSet) {
259 // Already showing a thumbnail; do nothing.
260 return;
261 }
263 if (favicon == null) {
264 // Should show default favicon.
265 displayThumbnail(R.drawable.favicon);
266 return;
267 }
269 if (faviconURL != null) {
270 mFaviconURL = faviconURL;
271 }
273 mThumbnailView.setScaleType(SCALE_TYPE_FAVICON);
274 mThumbnailView.setImageBitmap(favicon);
276 if (mFaviconURL != null) {
277 mThumbnailView.setBackgroundColor(Favicons.getFaviconColor(mFaviconURL));
278 }
279 }
281 /**
282 * Update the title shown by this view. If both title and url
283 * are empty, mark the state as STATE_EMPTY and show a default text.
284 */
285 private void updateTitleView() {
286 String title = getTitle();
287 if (!TextUtils.isEmpty(title)) {
288 mTitleView.setText(title);
289 mIsEmpty = false;
290 } else {
291 mTitleView.setText(R.string.home_top_sites_add);
292 mIsEmpty = true;
293 }
295 // Refresh for state change.
296 refreshDrawableState();
297 }
299 public void setLoadId(int aLoadId) {
300 Favicons.cancelFaviconLoad(mLoadId);
301 mLoadId = aLoadId;
302 }
303 }