1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/home/TopSitesGridItemView.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,303 @@ 1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +package org.mozilla.gecko.home; 1.10 + 1.11 +import org.mozilla.gecko.favicons.Favicons; 1.12 +import org.mozilla.gecko.R; 1.13 + 1.14 +import android.content.Context; 1.15 +import android.graphics.Bitmap; 1.16 +import android.text.TextUtils; 1.17 +import android.util.AttributeSet; 1.18 +import android.view.LayoutInflater; 1.19 +import android.widget.ImageView; 1.20 +import android.widget.ImageView.ScaleType; 1.21 +import android.widget.RelativeLayout; 1.22 +import android.widget.TextView; 1.23 + 1.24 +/** 1.25 + * A view that displays the thumbnail and the title/url for a top/pinned site. 1.26 + * If the title/url is longer than the width of the view, they are faded out. 1.27 + * If there is no valid url, a default string is shown at 50% opacity. 1.28 + * This is denoted by the empty state. 1.29 + */ 1.30 +public class TopSitesGridItemView extends RelativeLayout { 1.31 + private static final String LOGTAG = "GeckoTopSitesGridItemView"; 1.32 + 1.33 + // Empty state, to denote there is no valid url. 1.34 + private static final int[] STATE_EMPTY = { android.R.attr.state_empty }; 1.35 + 1.36 + private static final ScaleType SCALE_TYPE_FAVICON = ScaleType.CENTER; 1.37 + private static final ScaleType SCALE_TYPE_RESOURCE = ScaleType.CENTER; 1.38 + private static final ScaleType SCALE_TYPE_THUMBNAIL = ScaleType.CENTER_CROP; 1.39 + 1.40 + // Child views. 1.41 + private final TextView mTitleView; 1.42 + private final ImageView mThumbnailView; 1.43 + 1.44 + // Data backing this view. 1.45 + private String mTitle; 1.46 + private String mUrl; 1.47 + private String mFaviconURL; 1.48 + 1.49 + private boolean mThumbnailSet; 1.50 + 1.51 + // Pinned state. 1.52 + private boolean mIsPinned = false; 1.53 + 1.54 + // Dirty state. 1.55 + private boolean mIsDirty = false; 1.56 + 1.57 + // Empty state. 1.58 + private boolean mIsEmpty = true; 1.59 + private int mLoadId = Favicons.NOT_LOADING; 1.60 + 1.61 + public TopSitesGridItemView(Context context) { 1.62 + this(context, null); 1.63 + } 1.64 + 1.65 + public TopSitesGridItemView(Context context, AttributeSet attrs) { 1.66 + this(context, attrs, R.attr.topSitesGridItemViewStyle); 1.67 + } 1.68 + 1.69 + public TopSitesGridItemView(Context context, AttributeSet attrs, int defStyle) { 1.70 + super(context, attrs, defStyle); 1.71 + 1.72 + LayoutInflater.from(context).inflate(R.layout.top_sites_grid_item_view, this); 1.73 + 1.74 + mTitleView = (TextView) findViewById(R.id.title); 1.75 + mThumbnailView = (ImageView) findViewById(R.id.thumbnail); 1.76 + } 1.77 + 1.78 + /** 1.79 + * {@inheritDoc} 1.80 + */ 1.81 + @Override 1.82 + public int[] onCreateDrawableState(int extraSpace) { 1.83 + final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 1.84 + 1.85 + if (mIsEmpty) { 1.86 + mergeDrawableStates(drawableState, STATE_EMPTY); 1.87 + } 1.88 + 1.89 + return drawableState; 1.90 + } 1.91 + 1.92 + /** 1.93 + * @return The title shown by this view. 1.94 + */ 1.95 + public String getTitle() { 1.96 + return (!TextUtils.isEmpty(mTitle) ? mTitle : mUrl); 1.97 + } 1.98 + 1.99 + /** 1.100 + * @return The url shown by this view. 1.101 + */ 1.102 + public String getUrl() { 1.103 + return mUrl; 1.104 + } 1.105 + 1.106 + /** 1.107 + * @return true, if this view is pinned, false otherwise. 1.108 + */ 1.109 + public boolean isPinned() { 1.110 + return mIsPinned; 1.111 + } 1.112 + 1.113 + /** 1.114 + * @return true, if this view has no content to show. 1.115 + */ 1.116 + public boolean isEmpty() { 1.117 + return mIsEmpty; 1.118 + } 1.119 + 1.120 + /** 1.121 + * @param title The title for this view. 1.122 + */ 1.123 + public void setTitle(String title) { 1.124 + if (mTitle != null && mTitle.equals(title)) { 1.125 + return; 1.126 + } 1.127 + 1.128 + mTitle = title; 1.129 + updateTitleView(); 1.130 + } 1.131 + 1.132 + /** 1.133 + * @param url The url for this view. 1.134 + */ 1.135 + public void setUrl(String url) { 1.136 + if (mUrl != null && mUrl.equals(url)) { 1.137 + return; 1.138 + } 1.139 + 1.140 + mUrl = url; 1.141 + updateTitleView(); 1.142 + } 1.143 + 1.144 + /** 1.145 + * @param pinned The pinned state of this view. 1.146 + */ 1.147 + public void setPinned(boolean pinned) { 1.148 + mIsPinned = pinned; 1.149 + mTitleView.setCompoundDrawablesWithIntrinsicBounds(pinned ? R.drawable.pin : 0, 0, 0, 0); 1.150 + } 1.151 + 1.152 + public void blankOut() { 1.153 + mUrl = ""; 1.154 + mTitle = ""; 1.155 + mIsPinned = false; 1.156 + updateTitleView(); 1.157 + mTitleView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); 1.158 + setLoadId(Favicons.NOT_LOADING); 1.159 + displayThumbnail(R.drawable.top_site_add); 1.160 + } 1.161 + 1.162 + public void markAsDirty() { 1.163 + mIsDirty = true; 1.164 + } 1.165 + 1.166 + /** 1.167 + * Updates the title, URL, and pinned state of this view. 1.168 + * 1.169 + * Also resets our loadId to NOT_LOADING. 1.170 + * 1.171 + * Returns true if any fields changed. 1.172 + */ 1.173 + public boolean updateState(final String title, final String url, final boolean pinned, final Bitmap thumbnail) { 1.174 + boolean changed = false; 1.175 + if (mUrl == null || !mUrl.equals(url)) { 1.176 + mUrl = url; 1.177 + changed = true; 1.178 + } 1.179 + 1.180 + if (mTitle == null || !mTitle.equals(title)) { 1.181 + mTitle = title; 1.182 + changed = true; 1.183 + } 1.184 + 1.185 + if (thumbnail != null) { 1.186 + displayThumbnail(thumbnail); 1.187 + } else if (changed) { 1.188 + // Because we'll have a new favicon or thumbnail arriving shortly, and 1.189 + // we need to not reject it because we already had a thumbnail. 1.190 + mThumbnailSet = false; 1.191 + } 1.192 + 1.193 + if (changed) { 1.194 + updateTitleView(); 1.195 + setLoadId(Favicons.NOT_LOADING); 1.196 + } 1.197 + 1.198 + if (mIsPinned != pinned) { 1.199 + mIsPinned = pinned; 1.200 + mTitleView.setCompoundDrawablesWithIntrinsicBounds(pinned ? R.drawable.pin : 0, 0, 0, 0); 1.201 + changed = true; 1.202 + } 1.203 + 1.204 + // The dirty state forces the state update to return true 1.205 + // so that the adapter loads favicons once the thumbnails 1.206 + // are loaded in TopSitesPanel/TopSitesGridAdapter. 1.207 + changed = (changed || mIsDirty); 1.208 + mIsDirty = false; 1.209 + 1.210 + return changed; 1.211 + } 1.212 + 1.213 + /** 1.214 + * Display the thumbnail from a resource. 1.215 + * 1.216 + * @param resId Resource ID of the drawable to show. 1.217 + */ 1.218 + public void displayThumbnail(int resId) { 1.219 + mThumbnailView.setScaleType(SCALE_TYPE_RESOURCE); 1.220 + mThumbnailView.setImageResource(resId); 1.221 + mThumbnailView.setBackgroundColor(0x0); 1.222 + mThumbnailSet = false; 1.223 + } 1.224 + 1.225 + /** 1.226 + * Display the thumbnail from a bitmap. 1.227 + * 1.228 + * @param thumbnail The bitmap to show as thumbnail. 1.229 + */ 1.230 + public void displayThumbnail(Bitmap thumbnail) { 1.231 + if (thumbnail == null) { 1.232 + // Show a favicon based view instead. 1.233 + displayThumbnail(R.drawable.favicon); 1.234 + return; 1.235 + } 1.236 + mThumbnailSet = true; 1.237 + Favicons.cancelFaviconLoad(mLoadId); 1.238 + 1.239 + mThumbnailView.setScaleType(SCALE_TYPE_THUMBNAIL); 1.240 + mThumbnailView.setImageBitmap(thumbnail); 1.241 + mThumbnailView.setBackgroundDrawable(null); 1.242 + } 1.243 + 1.244 + public void displayFavicon(Bitmap favicon, String faviconURL, int expectedLoadId) { 1.245 + if (mLoadId != Favicons.NOT_LOADING && 1.246 + mLoadId != expectedLoadId) { 1.247 + // View recycled. 1.248 + return; 1.249 + } 1.250 + 1.251 + // Yes, there's a chance of a race here. 1.252 + displayFavicon(favicon, faviconURL); 1.253 + } 1.254 + 1.255 + /** 1.256 + * Display the thumbnail from a favicon. 1.257 + * 1.258 + * @param favicon The favicon to show as thumbnail. 1.259 + */ 1.260 + public void displayFavicon(Bitmap favicon, String faviconURL) { 1.261 + if (mThumbnailSet) { 1.262 + // Already showing a thumbnail; do nothing. 1.263 + return; 1.264 + } 1.265 + 1.266 + if (favicon == null) { 1.267 + // Should show default favicon. 1.268 + displayThumbnail(R.drawable.favicon); 1.269 + return; 1.270 + } 1.271 + 1.272 + if (faviconURL != null) { 1.273 + mFaviconURL = faviconURL; 1.274 + } 1.275 + 1.276 + mThumbnailView.setScaleType(SCALE_TYPE_FAVICON); 1.277 + mThumbnailView.setImageBitmap(favicon); 1.278 + 1.279 + if (mFaviconURL != null) { 1.280 + mThumbnailView.setBackgroundColor(Favicons.getFaviconColor(mFaviconURL)); 1.281 + } 1.282 + } 1.283 + 1.284 + /** 1.285 + * Update the title shown by this view. If both title and url 1.286 + * are empty, mark the state as STATE_EMPTY and show a default text. 1.287 + */ 1.288 + private void updateTitleView() { 1.289 + String title = getTitle(); 1.290 + if (!TextUtils.isEmpty(title)) { 1.291 + mTitleView.setText(title); 1.292 + mIsEmpty = false; 1.293 + } else { 1.294 + mTitleView.setText(R.string.home_top_sites_add); 1.295 + mIsEmpty = true; 1.296 + } 1.297 + 1.298 + // Refresh for state change. 1.299 + refreshDrawableState(); 1.300 + } 1.301 + 1.302 + public void setLoadId(int aLoadId) { 1.303 + Favicons.cancelFaviconLoad(mLoadId); 1.304 + mLoadId = aLoadId; 1.305 + } 1.306 +}