diff -r 000000000000 -r 6474c204b198 mobile/android/base/home/TopSitesGridItemView.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mobile/android/base/home/TopSitesGridItemView.java Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,303 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.home; + +import org.mozilla.gecko.favicons.Favicons; +import org.mozilla.gecko.R; + +import android.content.Context; +import android.graphics.Bitmap; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.widget.ImageView; +import android.widget.ImageView.ScaleType; +import android.widget.RelativeLayout; +import android.widget.TextView; + +/** + * A view that displays the thumbnail and the title/url for a top/pinned site. + * If the title/url is longer than the width of the view, they are faded out. + * If there is no valid url, a default string is shown at 50% opacity. + * This is denoted by the empty state. + */ +public class TopSitesGridItemView extends RelativeLayout { + private static final String LOGTAG = "GeckoTopSitesGridItemView"; + + // Empty state, to denote there is no valid url. + private static final int[] STATE_EMPTY = { android.R.attr.state_empty }; + + private static final ScaleType SCALE_TYPE_FAVICON = ScaleType.CENTER; + private static final ScaleType SCALE_TYPE_RESOURCE = ScaleType.CENTER; + private static final ScaleType SCALE_TYPE_THUMBNAIL = ScaleType.CENTER_CROP; + + // Child views. + private final TextView mTitleView; + private final ImageView mThumbnailView; + + // Data backing this view. + private String mTitle; + private String mUrl; + private String mFaviconURL; + + private boolean mThumbnailSet; + + // Pinned state. + private boolean mIsPinned = false; + + // Dirty state. + private boolean mIsDirty = false; + + // Empty state. + private boolean mIsEmpty = true; + private int mLoadId = Favicons.NOT_LOADING; + + public TopSitesGridItemView(Context context) { + this(context, null); + } + + public TopSitesGridItemView(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.topSitesGridItemViewStyle); + } + + public TopSitesGridItemView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + LayoutInflater.from(context).inflate(R.layout.top_sites_grid_item_view, this); + + mTitleView = (TextView) findViewById(R.id.title); + mThumbnailView = (ImageView) findViewById(R.id.thumbnail); + } + + /** + * {@inheritDoc} + */ + @Override + public int[] onCreateDrawableState(int extraSpace) { + final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); + + if (mIsEmpty) { + mergeDrawableStates(drawableState, STATE_EMPTY); + } + + return drawableState; + } + + /** + * @return The title shown by this view. + */ + public String getTitle() { + return (!TextUtils.isEmpty(mTitle) ? mTitle : mUrl); + } + + /** + * @return The url shown by this view. + */ + public String getUrl() { + return mUrl; + } + + /** + * @return true, if this view is pinned, false otherwise. + */ + public boolean isPinned() { + return mIsPinned; + } + + /** + * @return true, if this view has no content to show. + */ + public boolean isEmpty() { + return mIsEmpty; + } + + /** + * @param title The title for this view. + */ + public void setTitle(String title) { + if (mTitle != null && mTitle.equals(title)) { + return; + } + + mTitle = title; + updateTitleView(); + } + + /** + * @param url The url for this view. + */ + public void setUrl(String url) { + if (mUrl != null && mUrl.equals(url)) { + return; + } + + mUrl = url; + updateTitleView(); + } + + /** + * @param pinned The pinned state of this view. + */ + public void setPinned(boolean pinned) { + mIsPinned = pinned; + mTitleView.setCompoundDrawablesWithIntrinsicBounds(pinned ? R.drawable.pin : 0, 0, 0, 0); + } + + public void blankOut() { + mUrl = ""; + mTitle = ""; + mIsPinned = false; + updateTitleView(); + mTitleView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); + setLoadId(Favicons.NOT_LOADING); + displayThumbnail(R.drawable.top_site_add); + } + + public void markAsDirty() { + mIsDirty = true; + } + + /** + * Updates the title, URL, and pinned state of this view. + * + * Also resets our loadId to NOT_LOADING. + * + * Returns true if any fields changed. + */ + public boolean updateState(final String title, final String url, final boolean pinned, final Bitmap thumbnail) { + boolean changed = false; + if (mUrl == null || !mUrl.equals(url)) { + mUrl = url; + changed = true; + } + + if (mTitle == null || !mTitle.equals(title)) { + mTitle = title; + changed = true; + } + + if (thumbnail != null) { + displayThumbnail(thumbnail); + } else if (changed) { + // Because we'll have a new favicon or thumbnail arriving shortly, and + // we need to not reject it because we already had a thumbnail. + mThumbnailSet = false; + } + + if (changed) { + updateTitleView(); + setLoadId(Favicons.NOT_LOADING); + } + + if (mIsPinned != pinned) { + mIsPinned = pinned; + mTitleView.setCompoundDrawablesWithIntrinsicBounds(pinned ? R.drawable.pin : 0, 0, 0, 0); + changed = true; + } + + // The dirty state forces the state update to return true + // so that the adapter loads favicons once the thumbnails + // are loaded in TopSitesPanel/TopSitesGridAdapter. + changed = (changed || mIsDirty); + mIsDirty = false; + + return changed; + } + + /** + * Display the thumbnail from a resource. + * + * @param resId Resource ID of the drawable to show. + */ + public void displayThumbnail(int resId) { + mThumbnailView.setScaleType(SCALE_TYPE_RESOURCE); + mThumbnailView.setImageResource(resId); + mThumbnailView.setBackgroundColor(0x0); + mThumbnailSet = false; + } + + /** + * Display the thumbnail from a bitmap. + * + * @param thumbnail The bitmap to show as thumbnail. + */ + public void displayThumbnail(Bitmap thumbnail) { + if (thumbnail == null) { + // Show a favicon based view instead. + displayThumbnail(R.drawable.favicon); + return; + } + mThumbnailSet = true; + Favicons.cancelFaviconLoad(mLoadId); + + mThumbnailView.setScaleType(SCALE_TYPE_THUMBNAIL); + mThumbnailView.setImageBitmap(thumbnail); + mThumbnailView.setBackgroundDrawable(null); + } + + public void displayFavicon(Bitmap favicon, String faviconURL, int expectedLoadId) { + if (mLoadId != Favicons.NOT_LOADING && + mLoadId != expectedLoadId) { + // View recycled. + return; + } + + // Yes, there's a chance of a race here. + displayFavicon(favicon, faviconURL); + } + + /** + * Display the thumbnail from a favicon. + * + * @param favicon The favicon to show as thumbnail. + */ + public void displayFavicon(Bitmap favicon, String faviconURL) { + if (mThumbnailSet) { + // Already showing a thumbnail; do nothing. + return; + } + + if (favicon == null) { + // Should show default favicon. + displayThumbnail(R.drawable.favicon); + return; + } + + if (faviconURL != null) { + mFaviconURL = faviconURL; + } + + mThumbnailView.setScaleType(SCALE_TYPE_FAVICON); + mThumbnailView.setImageBitmap(favicon); + + if (mFaviconURL != null) { + mThumbnailView.setBackgroundColor(Favicons.getFaviconColor(mFaviconURL)); + } + } + + /** + * Update the title shown by this view. If both title and url + * are empty, mark the state as STATE_EMPTY and show a default text. + */ + private void updateTitleView() { + String title = getTitle(); + if (!TextUtils.isEmpty(title)) { + mTitleView.setText(title); + mIsEmpty = false; + } else { + mTitleView.setText(R.string.home_top_sites_add); + mIsEmpty = true; + } + + // Refresh for state change. + refreshDrawableState(); + } + + public void setLoadId(int aLoadId) { + Favicons.cancelFaviconLoad(mLoadId); + mLoadId = aLoadId; + } +}