diff --git a/mobile/android/base/BrowserApp.java b/mobile/android/base/BrowserApp.java index 8cb29d0fe09e..e082b94c319e 100644 --- a/mobile/android/base/BrowserApp.java +++ b/mobile/android/base/BrowserApp.java @@ -1395,7 +1395,7 @@ abstract public class BrowserApp extends GeckoApp animator.setUseHardwareLayer(false); mBrowserToolbar.startEditing(url, animator); - showHomePagerWithAnimator(HomePager.Page.HISTORY, animator); + showHomePagerWithAnimator(HomePager.Page.TOP_SITES, animator); animator.start(); } diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index 8becefd39a99..aa80e21021e2 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -217,7 +217,6 @@ FENNEC_JAVA_FILES = \ home/BookmarksListView.java \ home/BookmarksPage.java \ home/BookmarkFolderView.java \ - home/BookmarkThumbnailView.java \ home/BrowserSearch.java \ home/HistoryPage.java \ home/HomeFragment.java \ @@ -228,9 +227,8 @@ FENNEC_JAVA_FILES = \ home/FadedTextView.java \ home/LastTabsPage.java \ home/MostRecentPage.java \ - home/MostVisitedPage.java \ home/MultiTypeCursorAdapter.java \ - home/PinBookmarkDialog.java \ + home/PinSiteDialog.java \ home/ReadingListPage.java \ home/SearchEngine.java \ home/SearchEngineRow.java \ @@ -238,9 +236,10 @@ FENNEC_JAVA_FILES = \ home/SimpleCursorLoader.java \ home/SuggestClient.java \ home/TabMenuStrip.java \ - home/TopBookmarkItemView.java \ - home/TopBookmarksAdapter.java \ - home/TopBookmarksView.java \ + home/TopSitesGridItemView.java \ + home/TopSitesGridView.java \ + home/TopSitesPage.java \ + home/TopSitesThumbnailView.java \ home/TwoLinePageRow.java \ menu/GeckoMenu.java \ menu/GeckoMenuInflater.java \ @@ -476,12 +475,12 @@ RES_LAYOUT = \ res/layout/home_last_tabs_page.xml \ res/layout/home_history_list.xml \ res/layout/home_most_recent_page.xml \ - res/layout/home_most_visited_page.xml \ res/layout/home_pager.xml \ res/layout/home_reading_list_page.xml \ res/layout/home_search_item_row.xml \ res/layout/home_banner.xml \ res/layout/home_suggestion_prompt.xml \ + res/layout/home_top_sites_page.xml \ res/layout/web_app.xml \ res/layout/launch_app_list.xml \ res/layout/launch_app_listitem.xml \ @@ -491,7 +490,7 @@ RES_LAYOUT = \ res/layout/notification_icon_text.xml \ res/layout/notification_progress.xml \ res/layout/notification_progress_text.xml \ - res/layout/pin_bookmark_dialog.xml \ + res/layout/pin_site_dialog.xml \ res/layout/preference_rightalign_icon.xml \ res/layout/preference_search_engine.xml \ res/layout/preference_search_tip.xml \ @@ -510,7 +509,7 @@ RES_LAYOUT = \ res/layout/tabs_item_cell.xml \ res/layout/tabs_item_row.xml \ res/layout/text_selection_handles.xml \ - res/layout/top_bookmark_item_view.xml \ + res/layout/top_sites_grid_item_view.xml \ res/layout/two_line_page_row.xml \ res/layout/list_item_header.xml \ res/layout/select_dialog_list.xml \ @@ -709,12 +708,13 @@ RES_DRAWABLE_MDPI = \ res/drawable-mdpi/menu_item_check.png \ res/drawable-mdpi/menu_item_more.png \ res/drawable-mdpi/menu_item_uncheck.png \ + res/drawable-mdpi/pin.png \ res/drawable-mdpi/shield.png \ res/drawable-mdpi/shield_doorhanger.png \ res/drawable-mdpi/tabs_normal.png \ res/drawable-mdpi/tabs_private.png \ res/drawable-mdpi/tabs_synced.png \ - res/drawable-mdpi/top_bookmark_add.png \ + res/drawable-mdpi/top_site_add.png \ res/drawable-mdpi/urlbar_stop.png \ res/drawable-mdpi/reader.png \ res/drawable-mdpi/reader_cropped.png \ @@ -817,12 +817,13 @@ RES_DRAWABLE_HDPI = \ res/drawable-hdpi/menu_item_check.png \ res/drawable-hdpi/menu_item_more.png \ res/drawable-hdpi/menu_item_uncheck.png \ + res/drawable-hdpi/pin.png \ res/drawable-hdpi/shield.png \ res/drawable-hdpi/shield_doorhanger.png \ res/drawable-hdpi/tabs_normal.png \ res/drawable-hdpi/tabs_private.png \ res/drawable-hdpi/tabs_synced.png \ - res/drawable-hdpi/top_bookmark_add.png \ + res/drawable-hdpi/top_site_add.png \ res/drawable-hdpi/urlbar_stop.png \ res/drawable-hdpi/reader.png \ res/drawable-hdpi/reader_cropped.png \ @@ -897,7 +898,7 @@ RES_DRAWABLE_XHDPI = \ res/drawable-xhdpi/find_close.png \ res/drawable-xhdpi/find_next.png \ res/drawable-xhdpi/find_prev.png \ - res/drawable-xhdpi/top_bookmark_add.png \ + res/drawable-xhdpi/top_site_add.png \ res/drawable-xhdpi/urlbar_stop.png \ res/drawable-xhdpi/reader.png \ res/drawable-xhdpi/reader_cropped.png \ @@ -915,6 +916,7 @@ RES_DRAWABLE_XHDPI = \ res/drawable-xhdpi/menu_item_check.png \ res/drawable-xhdpi/menu_item_more.png \ res/drawable-xhdpi/menu_item_uncheck.png \ + res/drawable-xhdpi/pin.png \ res/drawable-xhdpi/shield.png \ res/drawable-xhdpi/shield_doorhanger.png \ res/drawable-xhdpi/tab_indicator_divider.9.png \ @@ -1073,7 +1075,7 @@ RES_COLOR = \ res/color/select_item_multichoice.xml \ res/color/tertiary_text.xml \ res/color/tertiary_text_inverse.xml \ - res/color/top_bookmark_item_title.xml \ + res/color/top_sites_grid_item_title.xml \ res/color/url_bar_title.xml \ res/color/url_bar_title_hint.xml \ $(NULL) @@ -1083,7 +1085,7 @@ RES_MENU = \ res/menu/gecko_app_menu.xml \ res/menu/home_contextmenu.xml \ res/menu/titlebar_contextmenu.xml \ - res/menu/top_bookmarks_contextmenu.xml \ + res/menu/top_sites_contextmenu.xml \ res/menu-large-v11/browser_app_menu.xml \ res/menu-v11/browser_app_menu.xml \ res/menu-xlarge-v11/browser_app_menu.xml \ @@ -1101,7 +1103,7 @@ RES_DRAWABLE += \ $(SYNC_RES_DRAWABLE) \ res/drawable/action_bar_button.xml \ res/drawable/action_bar_button_inverse.xml \ - res/drawable/bookmark_thumbnail_bg.xml \ + res/drawable/top_sites_thumbnail_bg.xml \ res/drawable/url_bar_bg.xml \ res/drawable/url_bar_entry.xml \ res/drawable/url_bar_nav_button.xml \ diff --git a/mobile/android/base/Tab.java b/mobile/android/base/Tab.java index 3224894d070c..60fb82f1f5bb 100644 --- a/mobile/android/base/Tab.java +++ b/mobile/android/base/Tab.java @@ -93,7 +93,7 @@ public class Tab { mUserSearch = ""; mExternal = external; mParentId = parentId; - mAboutHomePage = HomePager.Page.BOOKMARKS; + mAboutHomePage = HomePager.Page.TOP_SITES; mTitle = title == null ? "" : title; mFavicon = null; mFaviconUrl = null; diff --git a/mobile/android/base/db/BrowserDB.java b/mobile/android/base/db/BrowserDB.java index bac65660746c..2ee801de9f97 100644 --- a/mobile/android/base/db/BrowserDB.java +++ b/mobile/android/base/db/BrowserDB.java @@ -39,9 +39,9 @@ public class BrowserDB { public Cursor filter(ContentResolver cr, CharSequence constraint, int limit); - // This should only return frecent bookmarks, BrowserDB.getTopBookmarks will do the + // This should onlyl return frecent sites, BrowserDB.getTopSites will do the // work to combine that list with the pinned sites list - public Cursor getTopBookmarks(ContentResolver cr, int limit); + public Cursor getTopSites(ContentResolver cr, int limit); public void updateVisitedHistory(ContentResolver cr, String uri); @@ -138,12 +138,12 @@ public class BrowserDB { return sDb.filter(cr, constraint, limit); } - public static Cursor getTopBookmarks(ContentResolver cr, int limit) { - // Note this is not a single query anymore, but actually returns a mixture of two queries, - // one for top bookmarks, and one for pinned sites (which are actually bookmarks as well). - Cursor topBookmarks = sDb.getTopBookmarks(cr, limit); - Cursor pinnedSites = sDb.getPinnedSites(cr, limit); - return new TopSitesCursorWrapper(pinnedSites, topBookmarks, limit); + public static Cursor getTopSites(ContentResolver cr, int minLimit, int maxLimit) { + // Note this is not a single query anymore, but actually returns a mixture + // of two queries, one for topSites and one for pinned sites. + Cursor pinnedSites = sDb.getPinnedSites(cr, minLimit); + Cursor topSites = sDb.getTopSites(cr, maxLimit - pinnedSites.getCount()); + return new TopSitesCursorWrapper(pinnedSites, topSites, minLimit); } public static void updateVisitedHistory(ContentResolver cr, String uri) { @@ -342,12 +342,12 @@ public class BrowserDB { int mSize = 0; private SparseArray mPinnedSites = null; - public TopSitesCursorWrapper(Cursor pinnedCursor, Cursor normalCursor, int size) { + public TopSitesCursorWrapper(Cursor pinnedCursor, Cursor normalCursor, int minSize) { super(normalCursor); setPinnedSites(pinnedCursor); mCursor = normalCursor; - mSize = size; + mSize = Math.max(minSize, mPinnedSites.size() + mCursor.getCount()); } public void setPinnedSites(Cursor c) { diff --git a/mobile/android/base/db/LocalBrowserDB.java b/mobile/android/base/db/LocalBrowserDB.java index 87cec8073ba9..947685876d69 100644 --- a/mobile/android/base/db/LocalBrowserDB.java +++ b/mobile/android/base/db/LocalBrowserDB.java @@ -231,13 +231,9 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { } @Override - public Cursor getTopBookmarks(ContentResolver cr, int limit) { - // Only select bookmarks. Unfortunately, we need to query the combined view, - // instead of just the bookmarks table, in order to do the frecency calculation. - String selection = Combined.BOOKMARK_ID + " IS NOT NULL"; - - // Filter out sites that are pinned. - selection = DBUtils.concatenateWhere(selection, Combined.URL + " NOT IN (SELECT " + + public Cursor getTopSites(ContentResolver cr, int limit) { + // Filter out sites that are pinned + String selection = DBUtils.concatenateWhere("", Combined.URL + " NOT IN (SELECT " + Bookmarks.URL + " FROM bookmarks WHERE " + DBUtils.qualifyColumn("bookmarks", Bookmarks.PARENT) + " == ? AND " + DBUtils.qualifyColumn("bookmarks", Bookmarks.IS_DELETED) + " == 0)"); diff --git a/mobile/android/base/home/BookmarksPage.java b/mobile/android/base/home/BookmarksPage.java index 238a7e95385c..f61cc6e3b051 100644 --- a/mobile/android/base/home/BookmarksPage.java +++ b/mobile/android/base/home/BookmarksPage.java @@ -8,21 +8,12 @@ package org.mozilla.gecko.home; import org.mozilla.gecko.favicons.Favicons; import org.mozilla.gecko.R; import org.mozilla.gecko.Tabs; -import org.mozilla.gecko.animation.PropertyAnimator; -import org.mozilla.gecko.animation.PropertyAnimator.Property; -import org.mozilla.gecko.animation.ViewHelper; import org.mozilla.gecko.db.BrowserContract.Bookmarks; -import org.mozilla.gecko.db.BrowserContract.Thumbnails; import org.mozilla.gecko.db.BrowserDB; import org.mozilla.gecko.db.BrowserDB.URLColumns; import org.mozilla.gecko.gfx.BitmapUtils; import org.mozilla.gecko.home.BookmarksListAdapter.OnRefreshFolderListener; -import org.mozilla.gecko.home.HomeListView.HomeContextMenuInfo; import org.mozilla.gecko.home.HomePager.OnUrlOpenListener; -import org.mozilla.gecko.home.PinBookmarkDialog.OnBookmarkSelectedListener; -import org.mozilla.gecko.home.TopBookmarksAdapter.Thumbnail; -import org.mozilla.gecko.home.TopBookmarksView.OnPinBookmarkListener; -import org.mozilla.gecko.home.TopBookmarksView.TopBookmarksContextMenuInfo; import org.mozilla.gecko.util.ThreadUtils; import android.app.Activity; @@ -32,27 +23,14 @@ import android.content.res.Configuration; import android.database.Cursor; import android.graphics.Bitmap; import android.os.Bundle; -import android.support.v4.app.FragmentManager; import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager.LoaderCallbacks; -import android.support.v4.content.AsyncTaskLoader; import android.support.v4.content.Loader; import android.text.TextUtils; import android.util.Log; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.MotionEvent; import android.view.View; -import android.view.View.OnTouchListener; import android.view.ViewGroup; -import android.widget.Toast; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; /** * A page in about:home that displays a ListView of bookmarks. @@ -63,57 +41,24 @@ public class BookmarksPage extends HomeFragment { // Cursor loader ID for list of bookmarks. private static final int LOADER_ID_BOOKMARKS_LIST = 0; - // Cursor loader ID for grid of bookmarks. - private static final int LOADER_ID_TOP_BOOKMARKS = 1; - - // Loader ID for thumbnails. - private static final int LOADER_ID_THUMBNAILS = 2; - // Key for bookmarks folder id. private static final String BOOKMARKS_FOLDER_KEY = "folder_id"; - // Key for thumbnail urls. - private static final String THUMBNAILS_URLS_KEY = "urls"; - // List of bookmarks. private BookmarksListView mList; - // Grid of top bookmarks. - private TopBookmarksView mTopBookmarks; - - // Banner to show snippets. - private HomeBanner mBanner; - // Adapter for list of bookmarks. private BookmarksListAdapter mListAdapter; - // Adapter for grid of bookmarks. - private TopBookmarksAdapter mTopBookmarksAdapter; - // Callback for cursor loaders. private CursorLoaderCallbacks mLoaderCallbacks; - // Callback for thumbnail loader. - private ThumbnailsLoaderCallbacks mThumbnailsLoaderCallbacks; - - // Listener for pinning bookmarks. - private PinBookmarkListener mPinBookmarkListener; - - // Raw Y value of the last event that happened on the list view. - private float mListTouchY = -1; - - // Scrolling direction of the banner. - private boolean mSnapBannerToTop; - @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.home_bookmarks_page, container, false); mList = (BookmarksListView) view.findViewById(R.id.bookmarks_list); - mTopBookmarks = new TopBookmarksView(getActivity()); - mList.addHeaderView(mTopBookmarks); - return view; } @@ -129,26 +74,11 @@ public class BookmarksPage extends HomeFragment { + " must implement HomePager.OnUrlOpenListener"); } - mPinBookmarkListener = new PinBookmarkListener(); - mList.setTag(HomePager.LIST_TAG_BOOKMARKS); mList.setOnUrlOpenListener(listener); mList.setHeaderDividersEnabled(false); - mTopBookmarks.setOnUrlOpenListener(listener); - mTopBookmarks.setOnPinBookmarkListener(mPinBookmarkListener); - registerForContextMenu(mList); - registerForContextMenu(mTopBookmarks); - - mBanner = (HomeBanner) view.findViewById(R.id.home_banner); - mList.setOnTouchListener(new OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - BookmarksPage.this.handleListTouchEvent(event); - return false; - } - }); } @Override @@ -157,10 +87,6 @@ public class BookmarksPage extends HomeFragment { final Activity activity = getActivity(); - // Setup the top bookmarks adapter. - mTopBookmarksAdapter = new TopBookmarksAdapter(activity, null); - mTopBookmarks.setAdapter(mTopBookmarksAdapter); - // Setup the list adapter. mListAdapter = new BookmarksListAdapter(activity, null); mListAdapter.setOnRefreshFolderListener(new OnRefreshFolderListener() { @@ -180,7 +106,6 @@ public class BookmarksPage extends HomeFragment { // Create callbacks before the initial loader is started. mLoaderCallbacks = new CursorLoaderCallbacks(); - mThumbnailsLoaderCallbacks = new ThumbnailsLoaderCallbacks(); loadIfVisible(); } @@ -188,9 +113,6 @@ public class BookmarksPage extends HomeFragment { public void onDestroyView() { mList = null; mListAdapter = null; - mTopBookmarks = null; - mTopBookmarksAdapter = null; - mPinBookmarkListener = null; super.onDestroyView(); } @@ -214,204 +136,9 @@ public class BookmarksPage extends HomeFragment { } } - private void handleListTouchEvent(MotionEvent event) { - // Ignore the event if the banner is hidden for this session. - if (mBanner.isDismissed()) { - return; - } - - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: { - mListTouchY = event.getRawY(); - break; - } - - case MotionEvent.ACTION_MOVE: { - // There is a chance that we won't receive ACTION_DOWN, if the touch event - // actually started on the Grid instead of the List. Treat this as first event. - if (mListTouchY == -1) { - mListTouchY = event.getRawY(); - return; - } - - final float curY = event.getRawY(); - final float delta = mListTouchY - curY; - mSnapBannerToTop = (delta > 0.0f) ? false : true; - - final float height = mBanner.getHeight(); - float newTranslationY = ViewHelper.getTranslationY(mBanner) + delta; - - // Clamp the values to be between 0 and height. - if (newTranslationY < 0.0f) { - newTranslationY = 0.0f; - } else if (newTranslationY > height) { - newTranslationY = height; - } - - ViewHelper.setTranslationY(mBanner, newTranslationY); - mListTouchY = curY; - break; - } - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: { - mListTouchY = -1; - final float y = ViewHelper.getTranslationY(mBanner); - final float height = mBanner.getHeight(); - if (y > 0.0f && y < height) { - final PropertyAnimator animator = new PropertyAnimator(100); - animator.attach(mBanner, Property.TRANSLATION_Y, mSnapBannerToTop ? 0 : height); - animator.start(); - } - break; - } - } - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) { - if (menuInfo == null) { - return; - } - - // HomeFragment will handle the default case. - if (menuInfo instanceof HomeContextMenuInfo) { - super.onCreateContextMenu(menu, view, menuInfo); - } - - if (!(menuInfo instanceof TopBookmarksContextMenuInfo)) { - return; - } - - MenuInflater inflater = new MenuInflater(view.getContext()); - inflater.inflate(R.menu.top_bookmarks_contextmenu, menu); - - TopBookmarksContextMenuInfo info = (TopBookmarksContextMenuInfo) menuInfo; - menu.setHeaderTitle(info.getDisplayTitle()); - - if (!TextUtils.isEmpty(info.url)) { - if (info.isPinned) { - menu.findItem(R.id.top_bookmarks_pin).setVisible(false); - } else { - menu.findItem(R.id.top_bookmarks_unpin).setVisible(false); - } - } else { - menu.findItem(R.id.top_bookmarks_open_new_tab).setVisible(false); - menu.findItem(R.id.top_bookmarks_open_private_tab).setVisible(false); - menu.findItem(R.id.top_bookmarks_pin).setVisible(false); - menu.findItem(R.id.top_bookmarks_unpin).setVisible(false); - } - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - ContextMenuInfo menuInfo = item.getMenuInfo(); - - // HomeFragment will handle the default case. - if (menuInfo == null || !(menuInfo instanceof TopBookmarksContextMenuInfo)) { - return false; - } - - TopBookmarksContextMenuInfo info = (TopBookmarksContextMenuInfo) menuInfo; - final Activity activity = getActivity(); - - final int itemId = item.getItemId(); - if (itemId == R.id.top_bookmarks_open_new_tab || itemId == R.id.top_bookmarks_open_private_tab) { - if (info.url == null) { - Log.e(LOGTAG, "Can't open in new tab because URL is null"); - return false; - } - - int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_BACKGROUND; - if (item.getItemId() == R.id.top_bookmarks_open_private_tab) - flags |= Tabs.LOADURL_PRIVATE; - - Tabs.getInstance().loadUrl(info.url, flags); - Toast.makeText(activity, R.string.new_tab_opened, Toast.LENGTH_SHORT).show(); - return true; - } - - if (itemId == R.id.top_bookmarks_pin) { - final String url = info.url; - final String title = info.title; - final int position = info.position; - final Context context = getActivity().getApplicationContext(); - - ThreadUtils.postToBackgroundThread(new Runnable() { - @Override - public void run() { - BrowserDB.pinSite(context.getContentResolver(), url, title, position); - } - }); - - return true; - } - - if (itemId == R.id.top_bookmarks_unpin) { - final int position = info.position; - final Context context = getActivity().getApplicationContext(); - - ThreadUtils.postToBackgroundThread(new Runnable() { - @Override - public void run() { - BrowserDB.unpinSite(context.getContentResolver(), position); - } - }); - - return true; - } - - if (itemId == R.id.top_bookmarks_edit) { - mPinBookmarkListener.onPinBookmark(info.position); - return true; - } - - return false; - } - @Override protected void load() { - final LoaderManager manager = getLoaderManager(); - manager.initLoader(LOADER_ID_BOOKMARKS_LIST, null, mLoaderCallbacks); - manager.initLoader(LOADER_ID_TOP_BOOKMARKS, null, mLoaderCallbacks); - } - - /** - * Listener for pinning bookmarks. - */ - private class PinBookmarkListener implements OnPinBookmarkListener, - OnBookmarkSelectedListener { - // Tag for the PinBookmarkDialog fragment. - private static final String TAG_PIN_BOOKMARK = "pin_bookmark"; - - // Position of the pin. - private int mPosition; - - @Override - public void onPinBookmark(int position) { - mPosition = position; - - final FragmentManager manager = getActivity().getSupportFragmentManager(); - PinBookmarkDialog dialog = (PinBookmarkDialog) manager.findFragmentByTag(TAG_PIN_BOOKMARK); - if (dialog == null) { - dialog = PinBookmarkDialog.newInstance(); - } - - dialog.setOnBookmarkSelectedListener(this); - dialog.show(manager, TAG_PIN_BOOKMARK); - } - - @Override - public void onBookmarkSelected(final String url, final String title) { - final int position = mPosition; - final Context context = getActivity().getApplicationContext(); - ThreadUtils.postToBackgroundThread(new Runnable() { - @Override - public void run() { - BrowserDB.pinSite(context.getContentResolver(), url, title, position); - } - }); - } + getLoaderManager().initLoader(LOADER_ID_BOOKMARKS_LIST, null, mLoaderCallbacks); } /** @@ -435,221 +162,28 @@ public class BookmarksPage extends HomeFragment { } } - /** - * Loader for the grid for top bookmarks. - */ - private static class TopBookmarksLoader extends SimpleCursorLoader { - public TopBookmarksLoader(Context context) { - super(context); - } - - @Override - public Cursor loadCursor() { - final int max = getContext().getResources().getInteger(R.integer.number_of_top_sites); - return BrowserDB.getTopBookmarks(getContext().getContentResolver(), max); - } - } - /** * Loader callbacks for the LoaderManager of this fragment. */ private class CursorLoaderCallbacks implements LoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { - switch(id) { - case LOADER_ID_BOOKMARKS_LIST: { - if (args == null) { - return new BookmarksLoader(getActivity()); - } else { - return new BookmarksLoader(getActivity(), args.getInt(BOOKMARKS_FOLDER_KEY)); - } - } - - case LOADER_ID_TOP_BOOKMARKS: { - return new TopBookmarksLoader(getActivity()); - } + if (args == null) { + return new BookmarksLoader(getActivity()); + } else { + return new BookmarksLoader(getActivity(), args.getInt(BOOKMARKS_FOLDER_KEY)); } - - return null; } @Override public void onLoadFinished(Loader loader, Cursor c) { - final int loaderId = loader.getId(); - switch(loaderId) { - case LOADER_ID_BOOKMARKS_LIST: { - mListAdapter.swapCursor(c); - mList.setHeaderDividersEnabled(c != null && c.getCount() > 0); - break; - } - - case LOADER_ID_TOP_BOOKMARKS: { - mTopBookmarksAdapter.swapCursor(c); - - // Load the thumbnails. - if (c.getCount() > 0 && c.moveToFirst()) { - final ArrayList urls = new ArrayList(); - do { - final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL)); - urls.add(url); - } while (c.moveToNext()); - - if (urls.size() > 0) { - Bundle bundle = new Bundle(); - bundle.putStringArrayList(THUMBNAILS_URLS_KEY, urls); - getLoaderManager().restartLoader(LOADER_ID_THUMBNAILS, bundle, mThumbnailsLoaderCallbacks); - } - } - break; - } - } + mListAdapter.swapCursor(c); } @Override public void onLoaderReset(Loader loader) { - final int loaderId = loader.getId(); - switch(loaderId) { - case LOADER_ID_BOOKMARKS_LIST: { - if (mList != null) { - mListAdapter.swapCursor(null); - } - break; - } - - case LOADER_ID_TOP_BOOKMARKS: { - if (mTopBookmarks != null) { - mTopBookmarksAdapter.swapCursor(null); - break; - } - } - } - } - } - - /** - * An AsyncTaskLoader to load the thumbnails from a cursor. - */ - private static class ThumbnailsLoader extends AsyncTaskLoader> { - private Map mThumbnails; - private ArrayList mUrls; - - public ThumbnailsLoader(Context context, ArrayList urls) { - super(context); - mUrls = urls; - } - - @Override - public Map loadInBackground() { - if (mUrls == null || mUrls.size() == 0) { - return null; - } - - final Map thumbnails = new HashMap(); - - // Query the DB for thumbnails. - final ContentResolver cr = getContext().getContentResolver(); - final Cursor cursor = BrowserDB.getThumbnailsForUrls(cr, mUrls); - - try { - if (cursor != null && cursor.moveToFirst()) { - do { - // Try to get the thumbnail, if cursor is valid. - String url = cursor.getString(cursor.getColumnIndexOrThrow(Thumbnails.URL)); - final byte[] b = cursor.getBlob(cursor.getColumnIndexOrThrow(Thumbnails.DATA)); - final Bitmap bitmap = (b == null ? null : BitmapUtils.decodeByteArray(b)); - - if (bitmap != null) { - thumbnails.put(url, new Thumbnail(bitmap, true)); - } - } while (cursor.moveToNext()); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - // Query the DB for favicons for the urls without thumbnails. - for (String url : mUrls) { - if (!thumbnails.containsKey(url)) { - final Bitmap bitmap = BrowserDB.getFaviconForUrl(cr, url); - if (bitmap != null) { - // Favicons.scaleImage can return several different size favicons, - // but will at least prevent this from being too large. - thumbnails.put(url, new Thumbnail(Favicons.scaleImage(bitmap), false)); - } - } - } - - return thumbnails; - } - - @Override - public void deliverResult(Map thumbnails) { - if (isReset()) { - mThumbnails = null; - return; - } - - mThumbnails = thumbnails; - - if (isStarted()) { - super.deliverResult(thumbnails); - } - } - - @Override - protected void onStartLoading() { - if (mThumbnails != null) { - deliverResult(mThumbnails); - } - - if (takeContentChanged() || mThumbnails == null) { - forceLoad(); - } - } - - @Override - protected void onStopLoading() { - cancelLoad(); - } - - @Override - public void onCanceled(Map thumbnails) { - mThumbnails = null; - } - - @Override - protected void onReset() { - super.onReset(); - - // Ensure the loader is stopped. - onStopLoading(); - - mThumbnails = null; - } - } - - /** - * Loader callbacks for the thumbnails on TopBookmarksView. - */ - private class ThumbnailsLoaderCallbacks implements LoaderCallbacks> { - @Override - public Loader> onCreateLoader(int id, Bundle args) { - return new ThumbnailsLoader(getActivity(), args.getStringArrayList(THUMBNAILS_URLS_KEY)); - } - - @Override - public void onLoadFinished(Loader> loader, Map thumbnails) { - if (mTopBookmarksAdapter != null) { - mTopBookmarksAdapter.updateThumbnails(thumbnails); - } - } - - @Override - public void onLoaderReset(Loader> loader) { - if (mTopBookmarksAdapter != null) { - mTopBookmarksAdapter.updateThumbnails(null); + if (mList != null) { + mListAdapter.swapCursor(null); } } } diff --git a/mobile/android/base/home/FadedTextView.java b/mobile/android/base/home/FadedTextView.java index dee9f5163d10..8394be13aa13 100644 --- a/mobile/android/base/home/FadedTextView.java +++ b/mobile/android/base/home/FadedTextView.java @@ -10,6 +10,7 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.LinearGradient; import android.graphics.Shader; +import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.widget.TextView; @@ -24,6 +25,9 @@ public class FadedTextView extends TextView { // Width of the fade effect from end of the view. private int mFadeWidth; + // Padding for compound drawables. + private int mCompoundPadding; + public FadedTextView(Context context) { this(context, null); } @@ -38,6 +42,8 @@ public class FadedTextView extends TextView { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FadedTextView); mFadeWidth = a.getDimensionPixelSize(R.styleable.FadedTextView_fadeWidth, 0); a.recycle(); + + mCompoundPadding = getCompoundDrawablePadding(); } /** @@ -50,11 +56,18 @@ public class FadedTextView extends TextView { // Layout doesn't return a proper width for getWidth(). // Instead check the width of the first line, as we've restricted to just one line. if (getLayout().getLineWidth(0) > width) { + final Drawable leftDrawable = getCompoundDrawables()[0]; + int drawableWidth = 0; + if (leftDrawable != null) { + drawableWidth = leftDrawable.getIntrinsicWidth() + mCompoundPadding; + width -= drawableWidth; + } + int color = getCurrentTextColor(); float stop = ((float) (width - mFadeWidth) / (float) width); LinearGradient gradient = new LinearGradient(0, 0, width, 0, new int[] { color, color, 0x0 }, - new float[] { 0, stop, 1.0f }, + new float[] { 0, stop, 1.0f - (drawableWidth / width) }, Shader.TileMode.CLAMP); getPaint().setShader(gradient); } else { diff --git a/mobile/android/base/home/HistoryPage.java b/mobile/android/base/home/HistoryPage.java index d57ec60b7051..af91078daf08 100644 --- a/mobile/android/base/home/HistoryPage.java +++ b/mobile/android/base/home/HistoryPage.java @@ -25,7 +25,7 @@ public class HistoryPage extends HomeFragment private static final String LOGTAG = "GeckoHistoryPage"; private IconTabWidget mTabWidget; private int mSelectedTab; - private boolean initializeVisitedPage; + private boolean initializeRecentPage; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -38,7 +38,6 @@ public class HistoryPage extends HomeFragment mTabWidget = (IconTabWidget) view.findViewById(R.id.tab_icon_widget); - mTabWidget.addTab(R.drawable.icon_most_visited, R.string.home_most_visited_title); mTabWidget.addTab(R.drawable.icon_most_recent, R.string.home_most_recent_title); mTabWidget.addTab(R.drawable.icon_last_tabs, R.string.home_last_tabs_title); @@ -50,11 +49,11 @@ public class HistoryPage extends HomeFragment @Override public void load() { - // Show most visited page as the initial page. + // Show most recent page as the initial page. // Since we detach/attach on config change, this prevents from replacing current fragment. - if (!initializeVisitedPage) { - showMostVisitedPage(); - initializeVisitedPage = true; + if (!initializeRecentPage) { + showMostRecentPage(); + initializeRecentPage = true; } } @@ -65,10 +64,8 @@ public class HistoryPage extends HomeFragment } if (index == 0) { - showMostVisitedPage(); - } else if (index == 1) { showMostRecentPage(); - } else if (index == 2) { + } else if (index == 1) { showLastTabsPage(); } @@ -95,15 +92,10 @@ public class HistoryPage extends HomeFragment subPage.setArguments(args); getChildFragmentManager().beginTransaction() - .addToBackStack(null).replace(R.id.visited_page_container, subPage) + .addToBackStack(null).replace(R.id.history_page_container, subPage) .commitAllowingStateLoss(); } - private void showMostVisitedPage() { - final MostVisitedPage mostVisitedPage = MostVisitedPage.newInstance(); - showSubPage(mostVisitedPage); - } - private void showMostRecentPage() { final MostRecentPage mostRecentPage = MostRecentPage.newInstance(); showSubPage(mostRecentPage); diff --git a/mobile/android/base/home/HomePager.java b/mobile/android/base/home/HomePager.java index 11bea987304d..6edd29e12a91 100644 --- a/mobile/android/base/home/HomePager.java +++ b/mobile/android/base/home/HomePager.java @@ -8,6 +8,7 @@ package org.mozilla.gecko.home; import org.mozilla.gecko.R; import org.mozilla.gecko.animation.PropertyAnimator; import org.mozilla.gecko.animation.ViewHelper; +import org.mozilla.gecko.util.HardwareUtils; import android.content.Context; import android.os.Build; @@ -38,6 +39,7 @@ public class HomePager extends ViewPager { // List of pages in order. public enum Page { HISTORY, + TOP_SITES, BOOKMARKS, READING_LIST } @@ -90,9 +92,9 @@ public class HomePager extends ViewPager { super(context, attrs); mContext = context; - // This is to keep all 3 pages in memory after they are + // This is to keep all 4 pages in memory after they are // selected in the pager. - setOffscreenPageLimit(2); + setOffscreenPageLimit(3); } @Override @@ -131,14 +133,19 @@ public class HomePager extends ViewPager { // Only animate on post-HC devices, when a non-null animator is given final boolean shouldAnimate = (animator != null && Build.VERSION.SDK_INT >= 11); - // Add the pages to the adapter in order. - adapter.addTab(Page.HISTORY, HistoryPage.class, new Bundle(), - getContext().getString(R.string.home_history_title)); + adapter.addTab(Page.TOP_SITES, TopSitesPage.class, new Bundle(), + getContext().getString(R.string.home_top_sites_title)); adapter.addTab(Page.BOOKMARKS, BookmarksPage.class, new Bundle(), getContext().getString(R.string.bookmarks_title)); adapter.addTab(Page.READING_LIST, ReadingListPage.class, new Bundle(), getContext().getString(R.string.reading_list_title)); + // On phones, the history tab is the first tab. On tablets, the + // history tab is the last tab. + adapter.addTab(HardwareUtils.isTablet() ? -1 : 0, + Page.HISTORY, HistoryPage.class, new Bundle(), + getContext().getString(R.string.home_history_title)); + adapter.setCanLoadHint(!shouldAnimate); setAdapter(adapter); @@ -226,8 +233,18 @@ public class HomePager extends ViewPager { } public void addTab(Page page, Class clss, Bundle args, String title) { + addTab(-1, page, clss, args, title); + } + + public void addTab(int index, Page page, Class clss, Bundle args, String title) { TabInfo info = new TabInfo(page, clss, args, title); - mTabs.add(info); + + if (index >= 0) { + mTabs.add(index, info); + } else { + mTabs.add(info); + } + notifyDataSetChanged(); if (mDecor != null) { diff --git a/mobile/android/base/home/MostVisitedPage.java b/mobile/android/base/home/MostVisitedPage.java deleted file mode 100644 index 9315049aa13f..000000000000 --- a/mobile/android/base/home/MostVisitedPage.java +++ /dev/null @@ -1,225 +0,0 @@ -/* -*- 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.R; -import org.mozilla.gecko.db.BrowserDB; -import org.mozilla.gecko.db.BrowserDB.URLColumns; -import org.mozilla.gecko.home.HomePager.OnUrlOpenListener; - -import android.app.Activity; -import android.content.ContentResolver; -import android.content.Context; -import android.database.Cursor; -import android.os.Bundle; -import android.support.v4.app.LoaderManager; -import android.support.v4.app.LoaderManager.LoaderCallbacks; -import android.support.v4.content.Loader; -import android.support.v4.widget.CursorAdapter; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewStub; -import android.widget.AdapterView; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.TextView; - -import java.util.EnumSet; - -/** - * Fragment that displays frecency search results in a ListView. - */ -public class MostVisitedPage extends HomeFragment { - // Logging tag name - private static final String LOGTAG = "GeckoMostVisitedPage"; - - // Cursor loader ID for search query - private static final int LOADER_ID_FRECENCY = 0; - - // Adapter for the list of search results - private VisitedAdapter mAdapter; - - // The view shown by the fragment. - private ListView mList; - - // The title for this HomeFragment page. - private TextView mTitle; - - // Reference to the View to display when there are no results. - private View mEmptyView; - - // Callbacks used for the search and favicon cursor loaders - private CursorLoaderCallbacks mCursorLoaderCallbacks; - - // On URL open listener - private OnUrlOpenListener mUrlOpenListener; - - public static MostVisitedPage newInstance() { - return new MostVisitedPage(); - } - - public MostVisitedPage() { - mUrlOpenListener = null; - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - - try { - mUrlOpenListener = (OnUrlOpenListener) activity; - } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() - + " must implement HomePager.OnUrlOpenListener"); - } - } - - @Override - public void onDetach() { - super.onDetach(); - - mUrlOpenListener = null; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.home_most_visited_page, container, false); - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - mTitle = (TextView) view.findViewById(R.id.title); - if (mTitle != null) { - mTitle.setText(R.string.home_most_visited_title); - } - - mList = (HomeListView) view.findViewById(R.id.list); - mList.setTag(HomePager.LIST_TAG_MOST_VISITED); - - mList.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - final Cursor c = mAdapter.getCursor(); - if (c == null || !c.moveToPosition(position)) { - return; - } - - final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL)); - - // This item is a TwoLinePageRow, so we allow switch-to-tab. - mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB)); - } - }); - - registerForContextMenu(mList); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - mList = null; - mTitle = null; - mEmptyView = null; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - // Intialize the search adapter - mAdapter = new VisitedAdapter(getActivity(), null); - mList.setAdapter(mAdapter); - - // Create callbacks before the initial loader is started - mCursorLoaderCallbacks = new CursorLoaderCallbacks(); - loadIfVisible(); - } - - @Override - protected void load() { - getLoaderManager().initLoader(LOADER_ID_FRECENCY, null, mCursorLoaderCallbacks); - } - - private void updateUiFromCursor(Cursor c) { - if (c != null && c.getCount() > 0) { - if (mTitle != null) { - mTitle.setVisibility(View.VISIBLE); - } - return; - } - - // Cursor is empty, so hide the title and set the - // empty view if it hasn't been set already. - if (mTitle != null) { - mTitle.setVisibility(View.GONE); - } - - if (mEmptyView == null) { - // Set empty page view. We delay this so that the empty view won't flash. - ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub); - mEmptyView = emptyViewStub.inflate(); - - final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image); - emptyIcon.setImageResource(R.drawable.icon_most_visited_empty); - - final TextView emptyText = (TextView) mEmptyView.findViewById(R.id.home_empty_text); - emptyText.setText(R.string.home_most_visited_empty); - - mList.setEmptyView(mEmptyView); - } - } - - private static class FrecencyCursorLoader extends SimpleCursorLoader { - // Max number of search results - private static final int SEARCH_LIMIT = 50; - - public FrecencyCursorLoader(Context context) { - super(context); - } - - @Override - public Cursor loadCursor() { - final ContentResolver cr = getContext().getContentResolver(); - return BrowserDB.filter(cr, "", SEARCH_LIMIT); - } - } - - private class VisitedAdapter extends CursorAdapter { - public VisitedAdapter(Context context, Cursor cursor) { - super(context, cursor); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - final TwoLinePageRow row = (TwoLinePageRow) view; - row.updateFromCursor(cursor); - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return LayoutInflater.from(parent.getContext()).inflate(R.layout.home_item_row, parent, false); - } - } - - private class CursorLoaderCallbacks implements LoaderCallbacks { - @Override - public Loader onCreateLoader(int id, Bundle args) { - return new FrecencyCursorLoader(getActivity()); - } - - @Override - public void onLoadFinished(Loader loader, Cursor c) { - mAdapter.swapCursor(c); - updateUiFromCursor(c); - } - - @Override - public void onLoaderReset(Loader loader) { - mAdapter.swapCursor(null); - } - } -} diff --git a/mobile/android/base/home/PinBookmarkDialog.java b/mobile/android/base/home/PinSiteDialog.java similarity index 88% rename from mobile/android/base/home/PinBookmarkDialog.java rename to mobile/android/base/home/PinSiteDialog.java index 90b0ab9082a9..840d4c404d99 100644 --- a/mobile/android/base/home/PinBookmarkDialog.java +++ b/mobile/android/base/home/PinSiteDialog.java @@ -28,12 +28,12 @@ import android.widget.EditText; import android.widget.ListView; /** - * Dialog fragment that displays frecency search results, for pinning as a bookmark, in a ListView. + * Dialog fragment that displays frecency search results, for pinning a site, in a GridView. */ -class PinBookmarkDialog extends DialogFragment { +class PinSiteDialog extends DialogFragment { // Listener for url selection - public static interface OnBookmarkSelectedListener { - public void onBookmarkSelected(String url, String title); + public static interface OnSiteSelectedListener { + public void onSiteSelected(String url, String title); } // Cursor loader ID for search query @@ -55,13 +55,13 @@ class PinBookmarkDialog extends DialogFragment { private CursorLoaderCallbacks mLoaderCallbacks; // Bookmark selected listener - private OnBookmarkSelectedListener mOnBookmarkSelectedListener; + private OnSiteSelectedListener mOnSiteSelectedListener; - public static PinBookmarkDialog newInstance() { - return new PinBookmarkDialog(); + public static PinSiteDialog newInstance() { + return new PinSiteDialog(); } - private PinBookmarkDialog() { + private PinSiteDialog() { } @Override @@ -75,7 +75,7 @@ class PinBookmarkDialog extends DialogFragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // All list views are styled to look the same with a global activity theme. // If the style of the list changes, inflate it from an XML. - return inflater.inflate(R.layout.pin_bookmark_dialog, container, false); + return inflater.inflate(R.layout.pin_site_dialog, container, false); } @Override @@ -102,7 +102,7 @@ class PinBookmarkDialog extends DialogFragment { mList.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { - if (mOnBookmarkSelectedListener != null) { + if (mOnSiteSelectedListener != null) { final Cursor c = mAdapter.getCursor(); if (c == null || !c.moveToPosition(position)) { return; @@ -110,7 +110,7 @@ class PinBookmarkDialog extends DialogFragment { final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL)); final String title = c.getString(c.getColumnIndexOrThrow(URLColumns.TITLE)); - mOnBookmarkSelectedListener.onBookmarkSelected(url, title); + mOnSiteSelectedListener.onSiteSelected(url, title); } // Dismiss the fragment and the dialog. @@ -151,8 +151,8 @@ class PinBookmarkDialog extends DialogFragment { SearchLoader.restart(getLoaderManager(), LOADER_ID_SEARCH, mLoaderCallbacks, mSearchTerm); } - public void setOnBookmarkSelectedListener(OnBookmarkSelectedListener listener) { - mOnBookmarkSelectedListener = listener; + public void setOnSiteSelectedListener(OnSiteSelectedListener listener) { + mOnSiteSelectedListener = listener; } private static class SearchAdapter extends CursorAdapter { diff --git a/mobile/android/base/home/TopBookmarksAdapter.java b/mobile/android/base/home/TopBookmarksAdapter.java deleted file mode 100644 index e848ba649d3e..000000000000 --- a/mobile/android/base/home/TopBookmarksAdapter.java +++ /dev/null @@ -1,113 +0,0 @@ -/* -*- 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.R; -import org.mozilla.gecko.db.BrowserDB.TopSitesCursorWrapper; -import org.mozilla.gecko.db.BrowserDB.URLColumns; - -import android.content.Context; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.support.v4.widget.CursorAdapter; -import android.text.TextUtils; -import android.view.View; -import android.view.ViewGroup; - -import java.util.Map; - -/** - * A cursor adapter holding the pinned and top bookmarks. - */ -public class TopBookmarksAdapter extends CursorAdapter { - // Cache to store the thumbnails. - private Map mThumbnails; - - /** - * Class to hold the bitmap of cached thumbnails/favicons. - */ - public static class Thumbnail { - // Thumbnail or favicon. - private final boolean isThumbnail; - - // Bitmap of thumbnail/favicon. - private final Bitmap bitmap; - - public Thumbnail(Bitmap bitmap, boolean isThumbnail) { - this.bitmap = bitmap; - this.isThumbnail = isThumbnail; - } - } - - public TopBookmarksAdapter(Context context, Cursor cursor) { - super(context, cursor); - } - - /** - * {@inheritDoc} - */ - @Override - protected void onContentChanged() { - // Don't do anything. We don't want to regenerate every time - // our database is updated. - return; - } - - /** - * Update the thumbnails returned by the db. - * - * @param thumbnails A map of urls and their thumbnail bitmaps. - */ - public void updateThumbnails(Map thumbnails) { - mThumbnails = thumbnails; - notifyDataSetChanged(); - } - - /** - * {@inheritDoc} - */ - @Override - public void bindView(View bindView, Context context, Cursor cursor) { - String url = ""; - String title = ""; - boolean pinned = false; - - // Cursor is already moved to required position. - if (!cursor.isAfterLast()) { - url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL)); - title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE)); - pinned = ((TopSitesCursorWrapper) cursor).isPinned(); - } - - TopBookmarkItemView view = (TopBookmarkItemView) bindView; - view.setTitle(title); - view.setUrl(url); - view.setPinned(pinned); - - // If there is no url, then show "add bookmark". - if (TextUtils.isEmpty(url)) { - view.displayThumbnail(R.drawable.top_bookmark_add); - } else { - // Show the thumbnail. - Thumbnail thumbnail = (mThumbnails != null ? mThumbnails.get(url) : null); - if (thumbnail == null) { - view.displayThumbnail(null); - } else if (thumbnail.isThumbnail) { - view.displayThumbnail(thumbnail.bitmap); - } else { - view.displayFavicon(thumbnail.bitmap); - } - } - } - - /** - * {@inheritDoc} - */ - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return new TopBookmarkItemView(context); - } -} diff --git a/mobile/android/base/home/TopBookmarkItemView.java b/mobile/android/base/home/TopSitesGridItemView.java similarity index 73% rename from mobile/android/base/home/TopBookmarkItemView.java rename to mobile/android/base/home/TopSitesGridItemView.java index a6b21f4ddc1b..9c228e0fe2f6 100644 --- a/mobile/android/base/home/TopBookmarkItemView.java +++ b/mobile/android/base/home/TopSitesGridItemView.java @@ -10,38 +10,29 @@ import org.mozilla.gecko.R; import android.content.Context; import android.graphics.Bitmap; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.PathShape; 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; -import android.widget.ImageView.ScaleType; /** - * A view that displays the thumbnail and the title/url for a bookmark. + * 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 TopBookmarkItemView extends RelativeLayout { - private static final String LOGTAG = "GeckoTopBookmarkItemView"; +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 }; - // A Pin Drawable to denote pinned sites. - private static Drawable sPinDrawable = null; - // Child views. private final TextView mTitleView; private final ImageView mThumbnailView; - private final ImageView mPinView; // Data backing this view. private String mTitle; @@ -53,22 +44,21 @@ public class TopBookmarkItemView extends RelativeLayout { // Empty state. private boolean mIsEmpty = true; - public TopBookmarkItemView(Context context) { + public TopSitesGridItemView(Context context) { this(context, null); } - public TopBookmarkItemView(Context context, AttributeSet attrs) { - this(context, attrs, R.attr.topBookmarkItemViewStyle); + public TopSitesGridItemView(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.topSitesGridItemViewStyle); } - public TopBookmarkItemView(Context context, AttributeSet attrs, int defStyle) { + public TopSitesGridItemView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - LayoutInflater.from(context).inflate(R.layout.top_bookmark_item_view, this); + LayoutInflater.from(context).inflate(R.layout.top_sites_grid_item_view, this); mTitleView = (TextView) findViewById(R.id.title); mThumbnailView = (ImageView) findViewById(R.id.thumbnail); - mPinView = (ImageView) findViewById(R.id.pin); } /** @@ -135,7 +125,7 @@ public class TopBookmarkItemView extends RelativeLayout { */ public void setPinned(boolean pinned) { mIsPinned = pinned; - mPinView.setBackgroundDrawable(pinned ? getPinDrawable() : null); + mTitleView.setCompoundDrawablesWithIntrinsicBounds(pinned ? R.drawable.pin : 0, 0, 0, 0); } /** @@ -200,26 +190,4 @@ public class TopBookmarkItemView extends RelativeLayout { // Refresh for state change. refreshDrawableState(); } - - /** - * @return Drawable to be used as a pin. - */ - private Drawable getPinDrawable() { - if (sPinDrawable == null) { - int size = getResources().getDimensionPixelSize(R.dimen.top_bookmark_pinsize); - - // Draw a little triangle in the upper right corner. - Path path = new Path(); - path.moveTo(0, 0); - path.lineTo(size, 0); - path.lineTo(size, size); - path.close(); - - sPinDrawable = new ShapeDrawable(new PathShape(path, size, size)); - Paint p = ((ShapeDrawable) sPinDrawable).getPaint(); - p.setColor(getResources().getColor(R.color.top_bookmark_pin)); - } - - return sPinDrawable; - } } diff --git a/mobile/android/base/home/TopBookmarksView.java b/mobile/android/base/home/TopSitesGridView.java similarity index 76% rename from mobile/android/base/home/TopBookmarksView.java rename to mobile/android/base/home/TopSitesGridView.java index 18debb1dc670..514d167648de 100644 --- a/mobile/android/base/home/TopBookmarksView.java +++ b/mobile/android/base/home/TopSitesGridView.java @@ -26,19 +26,19 @@ import android.widget.GridView; import java.util.EnumSet; /** - * A grid view of top bookmarks and pinned tabs. - * Each cell in the grid is a TopBookmarkItemView. + * A grid view of top and pinned sites. + * Each cell in the grid is a TopSitesGridItemView. */ -public class TopBookmarksView extends GridView { - private static final String LOGTAG = "GeckoTopBookmarksView"; +public class TopSitesGridView extends GridView { + private static final String LOGTAG = "GeckoTopSitesGridView"; - // Listener for pinning bookmarks. - public static interface OnPinBookmarkListener { - public void onPinBookmark(int position); + // Listener for pinning sites. + public static interface OnPinSiteListener { + public void onPinSite(int position); } - // Max number of bookmarks that needs to be shown. - private final int mMaxBookmarks; + // Max number of top sites that needs to be shown. + private final int mMaxSites; // Number of columns to show. private final int mNumColumns; @@ -58,29 +58,29 @@ public class TopBookmarksView extends GridView { // On URL open listener. private OnUrlOpenListener mUrlOpenListener; - // Pin bookmark listener. - private OnPinBookmarkListener mPinBookmarkListener; + // Pin site listener. + private OnPinSiteListener mPinSiteListener; // Context menu info. - private TopBookmarksContextMenuInfo mContextMenuInfo; + private TopSitesGridContextMenuInfo mContextMenuInfo; - public TopBookmarksView(Context context) { + public TopSitesGridView(Context context) { this(context, null); } - public TopBookmarksView(Context context, AttributeSet attrs) { - this(context, attrs, R.attr.topBookmarksViewStyle); + public TopSitesGridView(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.topSitesGridViewStyle); } - public TopBookmarksView(Context context, AttributeSet attrs, int defStyle) { + public TopSitesGridView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - mMaxBookmarks = getResources().getInteger(R.integer.number_of_top_sites); + mMaxSites = getResources().getInteger(R.integer.number_of_top_sites); mNumColumns = getResources().getInteger(R.integer.number_of_top_sites_cols); setNumColumns(mNumColumns); - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TopBookmarksView, defStyle, 0); - mHorizontalSpacing = a.getDimensionPixelOffset(R.styleable.TopBookmarksView_android_horizontalSpacing, 0x00); - mVerticalSpacing = a.getDimensionPixelOffset(R.styleable.TopBookmarksView_android_verticalSpacing, 0x00); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TopSitesGridView, defStyle, 0); + mHorizontalSpacing = a.getDimensionPixelOffset(R.styleable.TopSitesGridView_android_horizontalSpacing, 0x00); + mVerticalSpacing = a.getDimensionPixelOffset(R.styleable.TopSitesGridView_android_verticalSpacing, 0x00); a.recycle(); } @@ -94,18 +94,18 @@ public class TopBookmarksView extends GridView { setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { - TopBookmarkItemView row = (TopBookmarkItemView) view; + TopSitesGridItemView row = (TopSitesGridItemView) view; String url = row.getUrl(); - // If the url is empty, the user can pin a bookmark. + // If the url is empty, the user can pin a site. // If not, navigate to the page given by the url. if (!TextUtils.isEmpty(url)) { if (mUrlOpenListener != null) { mUrlOpenListener.onUrlOpen(url, EnumSet.noneOf(OnUrlOpenListener.Flags.class)); } } else { - if (mPinBookmarkListener != null) { - mPinBookmarkListener.onPinBookmark(position); + if (mPinSiteListener != null) { + mPinSiteListener.onPinSite(position); } } } @@ -115,8 +115,8 @@ public class TopBookmarksView extends GridView { @Override public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { Cursor cursor = (Cursor) parent.getItemAtPosition(position); - mContextMenuInfo = new TopBookmarksContextMenuInfo(view, position, id, cursor); - return showContextMenuForChild(TopBookmarksView.this); + mContextMenuInfo = new TopSitesGridContextMenuInfo(view, position, id, cursor); + return showContextMenuForChild(TopSitesGridView.this); } }); } @@ -126,7 +126,7 @@ public class TopBookmarksView extends GridView { super.onDetachedFromWindow(); mUrlOpenListener = null; - mPinBookmarkListener = null; + mPinSiteListener = null; } /** @@ -161,7 +161,7 @@ public class TopBookmarksView extends GridView { ThumbnailHelper.getInstance().setThumbnailWidth(childWidth); // Get the first child from the adapter. - final View child = new TopBookmarkItemView(getContext()); + final View child = new TopSitesGridItemView(getContext()); // Set a default LayoutParams on the child, if it doesn't have one on its own. AbsListView.LayoutParams params = (AbsListView.LayoutParams) child.getLayoutParams(); @@ -172,14 +172,14 @@ public class TopBookmarksView extends GridView { } // Measure the exact width of the child, and the height based on the width. - // Note: the child (and BookmarkThumbnailView) takes care of calculating its height. + // Note: the child (and TopSitesThumbnailView) takes care of calculating its height. int childWidthSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY); int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); child.measure(childWidthSpec, childHeightSpec); final int childHeight = child.getMeasuredHeight(); - // Number of rows required to show these bookmarks. - final int rows = (int) Math.ceil((double) mMaxBookmarks / mNumColumns); + // Number of rows required to show these top sites. + final int rows = (int) Math.ceil((double) mMaxSites / mNumColumns); final int childrenHeight = childHeight * rows; final int totalVerticalSpacing = rows > 0 ? (rows - 1) * mVerticalSpacing : 0; @@ -205,24 +205,24 @@ public class TopBookmarksView extends GridView { } /** - * Set a pin bookmark listener to be used by this view. + * Set a pin site listener to be used by this view. * - * @param listener A pin bookmark listener for this view. + * @param listener A pin site listener for this view. */ - public void setOnPinBookmarkListener(OnPinBookmarkListener listener) { - mPinBookmarkListener = listener; + public void setOnPinSiteListener(OnPinSiteListener listener) { + mPinSiteListener = listener; } /** * A ContextMenuInfo for TopBoomarksView that adds details from the cursor. */ - public static class TopBookmarksContextMenuInfo extends AdapterContextMenuInfo { + public static class TopSitesGridContextMenuInfo extends AdapterContextMenuInfo { public String url; public String title; public boolean isPinned; - public TopBookmarksContextMenuInfo(View targetView, int position, long id, Cursor cursor) { + public TopSitesGridContextMenuInfo(View targetView, int position, long id, Cursor cursor) { super(targetView, position, id); if (cursor == null) { diff --git a/mobile/android/base/home/TopSitesPage.java b/mobile/android/base/home/TopSitesPage.java new file mode 100644 index 000000000000..7740cb14b2b0 --- /dev/null +++ b/mobile/android/base/home/TopSitesPage.java @@ -0,0 +1,767 @@ +/* -*- 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 org.mozilla.gecko.Tabs; +import org.mozilla.gecko.animation.PropertyAnimator; +import org.mozilla.gecko.animation.PropertyAnimator.Property; +import org.mozilla.gecko.animation.ViewHelper; +import org.mozilla.gecko.db.BrowserContract.Thumbnails; +import org.mozilla.gecko.db.BrowserDB; +import org.mozilla.gecko.db.BrowserDB.URLColumns; +import org.mozilla.gecko.db.BrowserDB.TopSitesCursorWrapper; +import org.mozilla.gecko.gfx.BitmapUtils; +import org.mozilla.gecko.home.HomeListView.HomeContextMenuInfo; +import org.mozilla.gecko.home.HomePager.OnUrlOpenListener; +import org.mozilla.gecko.home.PinSiteDialog.OnSiteSelectedListener; +import org.mozilla.gecko.home.TopSitesGridView.OnPinSiteListener; +import org.mozilla.gecko.home.TopSitesGridView.TopSitesGridContextMenuInfo; +import org.mozilla.gecko.util.ThreadUtils; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Configuration; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.LoaderManager; +import android.support.v4.app.LoaderManager.LoaderCallbacks; +import android.support.v4.content.AsyncTaskLoader; +import android.support.v4.content.Loader; +import android.support.v4.widget.CursorAdapter; +import android.text.TextUtils; +import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.LayoutInflater; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnTouchListener; +import android.view.ViewGroup; +import android.view.ViewStub; +import android.widget.AdapterView; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import java.util.EnumSet; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * Fragment that displays frecency search results in a ListView. + */ +public class TopSitesPage extends HomeFragment { + // Logging tag name + private static final String LOGTAG = "GeckoTopSitesPage"; + + // Cursor loader ID for the top sites + private static final int LOADER_ID_TOP_SITES = 0; + + // Loader ID for thumbnails + private static final int LOADER_ID_THUMBNAILS = 1; + + // Key for thumbnail urls + private static final String THUMBNAILS_URLS_KEY = "urls"; + + // Adapter for the list of top sites + private VisitedAdapter mListAdapter; + + // Adapter for the grid of top sites + private TopSitesGridAdapter mGridAdapter; + + // List of top sites + private ListView mList; + + // Grid of top sites + private TopSitesGridView mGrid; + + // Reference to the View to display when there are no results. + private View mEmptyView; + + // Banner to show snippets. + private HomeBanner mBanner; + + // Raw Y value of the last event that happened on the list view. + private float mListTouchY = -1; + + // Scrolling direction of the banner. + private boolean mSnapBannerToTop; + + // Callbacks used for the search and favicon cursor loaders + private CursorLoaderCallbacks mCursorLoaderCallbacks; + + // Callback for thumbnail loader + private ThumbnailsLoaderCallbacks mThumbnailsLoaderCallbacks; + + // Listener for pinning sites + private PinSiteListener mPinSiteListener; + + // On URL open listener + private OnUrlOpenListener mUrlOpenListener; + + // Max number of entries shown in the grid from the cursor. + private int mMaxGridEntries; + + /** + * Class to hold the bitmap of cached thumbnails/favicons. + */ + public static class Thumbnail { + // Thumbnail or favicon. + private final boolean isThumbnail; + + // Bitmap of thumbnail/favicon. + private final Bitmap bitmap; + + public Thumbnail(Bitmap bitmap, boolean isThumbnail) { + this.bitmap = bitmap; + this.isThumbnail = isThumbnail; + } + } + + public static TopSitesPage newInstance() { + return new TopSitesPage(); + } + + public TopSitesPage() { + mUrlOpenListener = null; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + mMaxGridEntries = activity.getResources().getInteger(R.integer.number_of_top_sites); + + try { + mUrlOpenListener = (OnUrlOpenListener) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + + " must implement HomePager.OnUrlOpenListener"); + } + } + + @Override + public void onDetach() { + super.onDetach(); + + mUrlOpenListener = null; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.home_top_sites_page, container, false); + + mList = (HomeListView) view.findViewById(R.id.list); + + mGrid = new TopSitesGridView(getActivity()); + mList.addHeaderView(mGrid); + + return view; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + mPinSiteListener = new PinSiteListener(); + + mList.setTag(HomePager.LIST_TAG_MOST_VISITED); + + mList.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + final ListView list = (ListView) parent; + final int headerCount = list.getHeaderViewsCount(); + if (position < headerCount) { + // The click is on a header, don't do anything. + return; + } + + // Absolute position for the adapter. + position += (mGridAdapter.getCount() - headerCount); + + final Cursor c = mListAdapter.getCursor(); + if (c == null || !c.moveToPosition(position)) { + return; + } + + final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL)); + + // This item is a TwoLinePageRow, so we allow switch-to-tab. + mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB)); + } + }); + + mGrid.setOnUrlOpenListener(mUrlOpenListener); + mGrid.setOnPinSiteListener(mPinSiteListener); + + registerForContextMenu(mList); + registerForContextMenu(mGrid); + + mBanner = (HomeBanner) view.findViewById(R.id.home_banner); + mList.setOnTouchListener(new OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + TopSitesPage.this.handleListTouchEvent(event); + return false; + } + }); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + mList = null; + mGrid = null; + mEmptyView = null; + mListAdapter = null; + mGridAdapter = null; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + // Detach and reattach the fragment as the layout changes. + if (isVisible()) { + getFragmentManager().beginTransaction() + .detach(this) + .attach(this) + .commitAllowingStateLoss(); + } + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + final Activity activity = getActivity(); + + // Setup the top sites grid adapter. + mGridAdapter = new TopSitesGridAdapter(activity, null); + mGrid.setAdapter(mGridAdapter); + + // Setup the top sites list adapter. + mListAdapter = new VisitedAdapter(activity, null); + mList.setAdapter(mListAdapter); + + // Create callbacks before the initial loader is started + mCursorLoaderCallbacks = new CursorLoaderCallbacks(); + mThumbnailsLoaderCallbacks = new ThumbnailsLoaderCallbacks(); + loadIfVisible(); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) { + if (menuInfo == null) { + return; + } + + // HomeFragment will handle the default case. + if (menuInfo instanceof HomeContextMenuInfo) { + super.onCreateContextMenu(menu, view, menuInfo); + } + + if (!(menuInfo instanceof TopSitesGridContextMenuInfo)) { + return; + } + + MenuInflater inflater = new MenuInflater(view.getContext()); + inflater.inflate(R.menu.top_sites_contextmenu, menu); + + TopSitesGridContextMenuInfo info = (TopSitesGridContextMenuInfo) menuInfo; + menu.setHeaderTitle(info.getDisplayTitle()); + + if (!TextUtils.isEmpty(info.url)) { + if (info.isPinned) { + menu.findItem(R.id.top_sites_pin).setVisible(false); + } else { + menu.findItem(R.id.top_sites_unpin).setVisible(false); + } + } else { + menu.findItem(R.id.top_sites_open_new_tab).setVisible(false); + menu.findItem(R.id.top_sites_open_private_tab).setVisible(false); + menu.findItem(R.id.top_sites_pin).setVisible(false); + menu.findItem(R.id.top_sites_unpin).setVisible(false); + } + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + ContextMenuInfo menuInfo = item.getMenuInfo(); + + // HomeFragment will handle the default case. + if (menuInfo == null || !(menuInfo instanceof TopSitesGridContextMenuInfo)) { + return false; + } + + TopSitesGridContextMenuInfo info = (TopSitesGridContextMenuInfo) menuInfo; + final Activity activity = getActivity(); + + final int itemId = item.getItemId(); + if (itemId == R.id.top_sites_open_new_tab || itemId == R.id.top_sites_open_private_tab) { + if (info.url == null) { + Log.e(LOGTAG, "Can't open in new tab because URL is null"); + return false; + } + + int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_BACKGROUND; + if (item.getItemId() == R.id.top_sites_open_private_tab) + flags |= Tabs.LOADURL_PRIVATE; + + Tabs.getInstance().loadUrl(info.url, flags); + Toast.makeText(activity, R.string.new_tab_opened, Toast.LENGTH_SHORT).show(); + return true; + } + + if (itemId == R.id.top_sites_pin) { + final String url = info.url; + final String title = info.title; + final int position = info.position; + final Context context = getActivity().getApplicationContext(); + + ThreadUtils.postToBackgroundThread(new Runnable() { + @Override + public void run() { + BrowserDB.pinSite(context.getContentResolver(), url, title, position); + } + }); + + return true; + } + + if (itemId == R.id.top_sites_unpin) { + final int position = info.position; + final Context context = getActivity().getApplicationContext(); + + ThreadUtils.postToBackgroundThread(new Runnable() { + @Override + public void run() { + BrowserDB.unpinSite(context.getContentResolver(), position); + } + }); + + return true; + } + + if (itemId == R.id.top_sites_edit) { + mPinSiteListener.onPinSite(info.position); + return true; + } + + return false; + } + + @Override + protected void load() { + getLoaderManager().initLoader(LOADER_ID_TOP_SITES, null, mCursorLoaderCallbacks); + } + + /** + * Listener for pinning sites. + */ + private class PinSiteListener implements OnPinSiteListener, + OnSiteSelectedListener { + // Tag for the PinSiteDialog fragment. + private static final String TAG_PIN_SITE = "pin_site"; + + // Position of the pin. + private int mPosition; + + @Override + public void onPinSite(int position) { + mPosition = position; + + final FragmentManager manager = getActivity().getSupportFragmentManager(); + PinSiteDialog dialog = (PinSiteDialog) manager.findFragmentByTag(TAG_PIN_SITE); + if (dialog == null) { + dialog = PinSiteDialog.newInstance(); + } + + dialog.setOnSiteSelectedListener(this); + dialog.show(manager, TAG_PIN_SITE); + } + + @Override + public void onSiteSelected(final String url, final String title) { + final int position = mPosition; + final Context context = getActivity().getApplicationContext(); + ThreadUtils.postToBackgroundThread(new Runnable() { + @Override + public void run() { + BrowserDB.pinSite(context.getContentResolver(), url, title, position); + } + }); + } + } + + private void handleListTouchEvent(MotionEvent event) { + // Ignore the event if the banner is hidden for this session. + if (mBanner.isDismissed()) { + return; + } + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: { + mListTouchY = event.getRawY(); + break; + } + + case MotionEvent.ACTION_MOVE: { + // There is a chance that we won't receive ACTION_DOWN, if the touch event + // actually started on the Grid instead of the List. Treat this as first event. + if (mListTouchY == -1) { + mListTouchY = event.getRawY(); + return; + } + + final float curY = event.getRawY(); + final float delta = mListTouchY - curY; + mSnapBannerToTop = (delta > 0.0f) ? false : true; + + final float height = mBanner.getHeight(); + float newTranslationY = ViewHelper.getTranslationY(mBanner) + delta; + + // Clamp the values to be between 0 and height. + if (newTranslationY < 0.0f) { + newTranslationY = 0.0f; + } else if (newTranslationY > height) { + newTranslationY = height; + } + + ViewHelper.setTranslationY(mBanner, newTranslationY); + mListTouchY = curY; + break; + } + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: { + mListTouchY = -1; + final float y = ViewHelper.getTranslationY(mBanner); + final float height = mBanner.getHeight(); + if (y > 0.0f && y < height) { + final PropertyAnimator animator = new PropertyAnimator(100); + animator.attach(mBanner, Property.TRANSLATION_Y, mSnapBannerToTop ? 0 : height); + animator.start(); + } + break; + } + } + } + + private void updateUiFromCursor(Cursor c) { + mList.setHeaderDividersEnabled(c != null && c.getCount() > 0); + + if (c != null && c.getCount() > 0) { + return; + } + + if (mEmptyView == null) { + // Set empty page view. We delay this so that the empty view won't flash. + ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub); + mEmptyView = emptyViewStub.inflate(); + + final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image); + emptyIcon.setImageResource(R.drawable.icon_most_visited_empty); + + final TextView emptyText = (TextView) mEmptyView.findViewById(R.id.home_empty_text); + emptyText.setText(R.string.home_most_visited_empty); + + mList.setEmptyView(mEmptyView); + } + } + + private static class TopSitesLoader extends SimpleCursorLoader { + // Max number of search results + private static final int SEARCH_LIMIT = 30; + private int mMaxGridEntries; + + public TopSitesLoader(Context context) { + super(context); + mMaxGridEntries = context.getResources().getInteger(R.integer.number_of_top_sites); + } + + @Override + public Cursor loadCursor() { + return BrowserDB.getTopSites(getContext().getContentResolver(), mMaxGridEntries, SEARCH_LIMIT); + } + } + + private class VisitedAdapter extends CursorAdapter { + public VisitedAdapter(Context context, Cursor cursor) { + super(context, cursor); + } + + @Override + public int getCount() { + return Math.max(0, super.getCount() - mMaxGridEntries); + } + + @Override + public Object getItem(int position) { + return super.getItem(position + mMaxGridEntries); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + final int position = cursor.getPosition(); + cursor.moveToPosition(position + mMaxGridEntries); + + final TwoLinePageRow row = (TwoLinePageRow) view; + row.updateFromCursor(cursor); + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return LayoutInflater.from(context).inflate(R.layout.home_item_row, parent, false); + } + } + + public class TopSitesGridAdapter extends CursorAdapter { + // Cache to store the thumbnails. + private Map mThumbnails; + + public TopSitesGridAdapter(Context context, Cursor cursor) { + super(context, cursor); + } + + @Override + public int getCount() { + return Math.min(mMaxGridEntries, super.getCount()); + } + + @Override + protected void onContentChanged() { + // Don't do anything. We don't want to regenerate every time + // our database is updated. + return; + } + + /** + * Update the thumbnails returned by the db. + * + * @param thumbnails A map of urls and their thumbnail bitmaps. + */ + public void updateThumbnails(Map thumbnails) { + mThumbnails = thumbnails; + notifyDataSetChanged(); + } + + @Override + public void bindView(View bindView, Context context, Cursor cursor) { + String url = ""; + String title = ""; + boolean pinned = false; + + // Cursor is already moved to required position. + if (!cursor.isAfterLast()) { + url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL)); + title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE)); + pinned = ((TopSitesCursorWrapper) cursor).isPinned(); + } + + TopSitesGridItemView view = (TopSitesGridItemView) bindView; + view.setTitle(title); + view.setUrl(url); + view.setPinned(pinned); + + // If there is no url, then show "add bookmark". + if (TextUtils.isEmpty(url)) { + view.displayThumbnail(R.drawable.top_site_add); + } else { + // Show the thumbnail. + Thumbnail thumbnail = (mThumbnails != null ? mThumbnails.get(url) : null); + if (thumbnail == null) { + view.displayThumbnail(null); + } else if (thumbnail.isThumbnail) { + view.displayThumbnail(thumbnail.bitmap); + } else { + view.displayFavicon(thumbnail.bitmap); + } + } + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return new TopSitesGridItemView(context); + } + } + + private class CursorLoaderCallbacks implements LoaderCallbacks { + @Override + public Loader onCreateLoader(int id, Bundle args) { + return new TopSitesLoader(getActivity()); + } + + @Override + public void onLoadFinished(Loader loader, Cursor c) { + mListAdapter.swapCursor(c); + mGridAdapter.swapCursor(c); + updateUiFromCursor(c); + + // Load the thumbnails. + if (c.getCount() > 0 && c.moveToFirst()) { + final ArrayList urls = new ArrayList(); + do { + final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL)); + urls.add(url); + } while (c.moveToNext()); + + if (urls.size() > 0) { + Bundle bundle = new Bundle(); + bundle.putStringArrayList(THUMBNAILS_URLS_KEY, urls); + getLoaderManager().restartLoader(LOADER_ID_THUMBNAILS, bundle, mThumbnailsLoaderCallbacks); + } + } + } + + @Override + public void onLoaderReset(Loader loader) { + if (mListAdapter != null) { + mListAdapter.swapCursor(null); + } + + if (mGridAdapter != null) { + mGridAdapter.swapCursor(null); + } + } + } + + /** + * An AsyncTaskLoader to load the thumbnails from a cursor. + */ + private static class ThumbnailsLoader extends AsyncTaskLoader> { + private Map mThumbnails; + private ArrayList mUrls; + + public ThumbnailsLoader(Context context, ArrayList urls) { + super(context); + mUrls = urls; + } + + @Override + public Map loadInBackground() { + if (mUrls == null || mUrls.size() == 0) { + return null; + } + + final Map thumbnails = new HashMap(); + + // Query the DB for thumbnails. + final ContentResolver cr = getContext().getContentResolver(); + final Cursor cursor = BrowserDB.getThumbnailsForUrls(cr, mUrls); + + try { + if (cursor != null && cursor.moveToFirst()) { + do { + // Try to get the thumbnail, if cursor is valid. + String url = cursor.getString(cursor.getColumnIndexOrThrow(Thumbnails.URL)); + final byte[] b = cursor.getBlob(cursor.getColumnIndexOrThrow(Thumbnails.DATA)); + final Bitmap bitmap = (b == null ? null : BitmapUtils.decodeByteArray(b)); + + if (bitmap != null) { + thumbnails.put(url, new Thumbnail(bitmap, true)); + } + } while (cursor.moveToNext()); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + + // Query the DB for favicons for the urls without thumbnails. + for (String url : mUrls) { + if (!thumbnails.containsKey(url)) { + final Bitmap bitmap = BrowserDB.getFaviconForUrl(cr, url); + if (bitmap != null) { + // Favicons.scaleImage can return several different size favicons, + // but will at least prevent this from being too large. + thumbnails.put(url, new Thumbnail(Favicons.scaleImage(bitmap), false)); + } + } + } + + return thumbnails; + } + + @Override + public void deliverResult(Map thumbnails) { + if (isReset()) { + mThumbnails = null; + return; + } + + mThumbnails = thumbnails; + + if (isStarted()) { + super.deliverResult(thumbnails); + } + } + + @Override + protected void onStartLoading() { + if (mThumbnails != null) { + deliverResult(mThumbnails); + } + + if (takeContentChanged() || mThumbnails == null) { + forceLoad(); + } + } + + @Override + protected void onStopLoading() { + cancelLoad(); + } + + @Override + public void onCanceled(Map thumbnails) { + mThumbnails = null; + } + + @Override + protected void onReset() { + super.onReset(); + + // Ensure the loader is stopped. + onStopLoading(); + + mThumbnails = null; + } + } + + /** + * Loader callbacks for the thumbnails on TopSitesGridView. + */ + private class ThumbnailsLoaderCallbacks implements LoaderCallbacks> { + @Override + public Loader> onCreateLoader(int id, Bundle args) { + return new ThumbnailsLoader(getActivity(), args.getStringArrayList(THUMBNAILS_URLS_KEY)); + } + + @Override + public void onLoadFinished(Loader> loader, Map thumbnails) { + if (mGridAdapter != null) { + mGridAdapter.updateThumbnails(thumbnails); + } + } + + @Override + public void onLoaderReset(Loader> loader) { + if (mGridAdapter != null) { + mGridAdapter.updateThumbnails(null); + } + } + } +} diff --git a/mobile/android/base/home/BookmarkThumbnailView.java b/mobile/android/base/home/TopSitesThumbnailView.java similarity index 86% rename from mobile/android/base/home/BookmarkThumbnailView.java rename to mobile/android/base/home/TopSitesThumbnailView.java index bc8d04dd30e5..7e7982a992a9 100644 --- a/mobile/android/base/home/BookmarkThumbnailView.java +++ b/mobile/android/base/home/TopSitesThumbnailView.java @@ -17,10 +17,10 @@ import android.util.AttributeSet; import android.widget.ImageView; /** - * A height constrained ImageView to show thumbnails of top bookmarks. + * A height constrained ImageView to show thumbnails of top and pinned sites. */ -public class BookmarkThumbnailView extends ImageView { - private static final String LOGTAG = "GeckoBookmarkThumbnailView"; +public class TopSitesThumbnailView extends ImageView { + private static final String LOGTAG = "GeckoTopSitesThumbnailView"; // 27.34% opacity filter for the dominant color. private static final int COLOR_FILTER = 0x46FFFFFF; @@ -41,18 +41,18 @@ public class BookmarkThumbnailView extends ImageView { sBorderPaint.setStyle(Paint.Style.STROKE); } - public BookmarkThumbnailView(Context context) { + public TopSitesThumbnailView(Context context) { this(context, null); // A border will be drawn if needed. setWillNotDraw(false); } - public BookmarkThumbnailView(Context context, AttributeSet attrs) { - this(context, attrs, R.attr.bookmarkThumbnailViewStyle); + public TopSitesThumbnailView(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.topSitesThumbnailViewStyle); } - public BookmarkThumbnailView(Context context, AttributeSet attrs, int defStyle) { + public TopSitesThumbnailView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @@ -95,7 +95,7 @@ public class BookmarkThumbnailView extends ImageView { @Override public void setBackgroundColor(int color) { int colorFilter = color == 0 ? DEFAULT_COLOR : color & COLOR_FILTER; - Drawable drawable = getResources().getDrawable(R.drawable.bookmark_thumbnail_bg); + Drawable drawable = getResources().getDrawable(R.drawable.top_sites_thumbnail_bg); drawable.setColorFilter(colorFilter, Mode.SRC_ATOP); setBackgroundDrawable(drawable); } diff --git a/mobile/android/base/locales/en-US/android_strings.dtd b/mobile/android/base/locales/en-US/android_strings.dtd index f2497fefc02f..83a2c757faf2 100644 --- a/mobile/android/base/locales/en-US/android_strings.dtd +++ b/mobile/android/base/locales/en-US/android_strings.dtd @@ -225,9 +225,9 @@ size. --> - - - + + + @@ -270,6 +270,7 @@ size. --> + @@ -288,7 +289,7 @@ size. --> - + diff --git a/mobile/android/base/resources/color/top_bookmark_item_title.xml b/mobile/android/base/resources/color/top_sites_grid_item_title.xml similarity index 100% rename from mobile/android/base/resources/color/top_bookmark_item_title.xml rename to mobile/android/base/resources/color/top_sites_grid_item_title.xml diff --git a/mobile/android/base/resources/drawable-hdpi/pin.png b/mobile/android/base/resources/drawable-hdpi/pin.png new file mode 100644 index 000000000000..0c69abab0853 Binary files /dev/null and b/mobile/android/base/resources/drawable-hdpi/pin.png differ diff --git a/mobile/android/base/resources/drawable-hdpi/top_bookmark_add.png b/mobile/android/base/resources/drawable-hdpi/top_site_add.png similarity index 100% rename from mobile/android/base/resources/drawable-hdpi/top_bookmark_add.png rename to mobile/android/base/resources/drawable-hdpi/top_site_add.png diff --git a/mobile/android/base/resources/drawable-mdpi/pin.png b/mobile/android/base/resources/drawable-mdpi/pin.png new file mode 100644 index 000000000000..67df7fc50c47 Binary files /dev/null and b/mobile/android/base/resources/drawable-mdpi/pin.png differ diff --git a/mobile/android/base/resources/drawable-mdpi/top_bookmark_add.png b/mobile/android/base/resources/drawable-mdpi/top_site_add.png similarity index 100% rename from mobile/android/base/resources/drawable-mdpi/top_bookmark_add.png rename to mobile/android/base/resources/drawable-mdpi/top_site_add.png diff --git a/mobile/android/base/resources/drawable-xhdpi/pin.png b/mobile/android/base/resources/drawable-xhdpi/pin.png new file mode 100644 index 000000000000..565266bb7cc0 Binary files /dev/null and b/mobile/android/base/resources/drawable-xhdpi/pin.png differ diff --git a/mobile/android/base/resources/drawable-xhdpi/top_bookmark_add.png b/mobile/android/base/resources/drawable-xhdpi/top_site_add.png similarity index 100% rename from mobile/android/base/resources/drawable-xhdpi/top_bookmark_add.png rename to mobile/android/base/resources/drawable-xhdpi/top_site_add.png diff --git a/mobile/android/base/resources/drawable/bookmark_thumbnail_bg.xml b/mobile/android/base/resources/drawable/top_sites_thumbnail_bg.xml similarity index 100% rename from mobile/android/base/resources/drawable/bookmark_thumbnail_bg.xml rename to mobile/android/base/resources/drawable/top_sites_thumbnail_bg.xml diff --git a/mobile/android/base/resources/layout-large-land-v11/home_history_page.xml b/mobile/android/base/resources/layout-large-land-v11/home_history_page.xml index b21e2cf87aee..03e18fe73207 100644 --- a/mobile/android/base/resources/layout-large-land-v11/home_history_page.xml +++ b/mobile/android/base/resources/layout-large-land-v11/home_history_page.xml @@ -16,7 +16,7 @@ android:layout="@layout/home_history_tabs_indicator" gecko:display="text"/> - diff --git a/mobile/android/base/resources/layout-xlarge-v11/home_history_page.xml b/mobile/android/base/resources/layout-xlarge-v11/home_history_page.xml index b21e2cf87aee..03e18fe73207 100644 --- a/mobile/android/base/resources/layout-xlarge-v11/home_history_page.xml +++ b/mobile/android/base/resources/layout-xlarge-v11/home_history_page.xml @@ -16,7 +16,7 @@ android:layout="@layout/home_history_tabs_indicator" gecko:display="text"/> - diff --git a/mobile/android/base/resources/layout/home_bookmarks_page.xml b/mobile/android/base/resources/layout/home_bookmarks_page.xml index d55b25da0351..740fc62e3733 100644 --- a/mobile/android/base/resources/layout/home_bookmarks_page.xml +++ b/mobile/android/base/resources/layout/home_bookmarks_page.xml @@ -12,15 +12,4 @@ android:layout_width="fill_parent" android:layout_height="fill_parent"/> - - diff --git a/mobile/android/base/resources/layout/home_history_page.xml b/mobile/android/base/resources/layout/home_history_page.xml index 70e0589ad9f6..74bf48074a9a 100644 --- a/mobile/android/base/resources/layout/home_history_page.xml +++ b/mobile/android/base/resources/layout/home_history_page.xml @@ -8,7 +8,7 @@ android:layout_height="fill_parent" android:orientation="vertical"> - diff --git a/mobile/android/base/resources/layout/home_most_visited_page.xml b/mobile/android/base/resources/layout/home_most_visited_page.xml deleted file mode 100644 index 8122a0fb9d1a..000000000000 --- a/mobile/android/base/resources/layout/home_most_visited_page.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - diff --git a/mobile/android/base/resources/layout/home_top_sites_page.xml b/mobile/android/base/resources/layout/home_top_sites_page.xml new file mode 100644 index 000000000000..5a1d8bbbb5af --- /dev/null +++ b/mobile/android/base/resources/layout/home_top_sites_page.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + diff --git a/mobile/android/base/resources/layout/pin_bookmark_dialog.xml b/mobile/android/base/resources/layout/pin_site_dialog.xml similarity index 96% rename from mobile/android/base/resources/layout/pin_bookmark_dialog.xml rename to mobile/android/base/resources/layout/pin_site_dialog.xml index 6d58a4edeffe..080be86851ba 100644 --- a/mobile/android/base/resources/layout/pin_bookmark_dialog.xml +++ b/mobile/android/base/resources/layout/pin_site_dialog.xml @@ -19,7 +19,7 @@ android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="6dip" - android:hint="@string/pin_bookmark_dialog_hint" + android:hint="@string/pin_site_dialog_hint" android:background="@drawable/url_bar_entry" android:textColor="@color/url_bar_title" android:textColorHint="@color/url_bar_title_hint" diff --git a/mobile/android/base/resources/layout/top_bookmark_item_view.xml b/mobile/android/base/resources/layout/top_sites_grid_item_view.xml similarity index 66% rename from mobile/android/base/resources/layout/top_bookmark_item_view.xml rename to mobile/android/base/resources/layout/top_sites_grid_item_view.xml index 00a7a0ebafae..c249bdf40c30 100644 --- a/mobile/android/base/resources/layout/top_bookmark_item_view.xml +++ b/mobile/android/base/resources/layout/top_sites_grid_item_view.xml @@ -6,7 +6,7 @@ - - - diff --git a/mobile/android/base/resources/menu/top_bookmarks_contextmenu.xml b/mobile/android/base/resources/menu/top_sites_contextmenu.xml similarity index 50% rename from mobile/android/base/resources/menu/top_bookmarks_contextmenu.xml rename to mobile/android/base/resources/menu/top_sites_contextmenu.xml index ccf554714105..511ff03c2069 100644 --- a/mobile/android/base/resources/menu/top_bookmarks_contextmenu.xml +++ b/mobile/android/base/resources/menu/top_sites_contextmenu.xml @@ -5,19 +5,19 @@ - - - + - + - + diff --git a/mobile/android/base/resources/values-large-land-v11/styles.xml b/mobile/android/base/resources/values-large-land-v11/styles.xml index 8533bb732ee8..0f773a38c40a 100644 --- a/mobile/android/base/resources/values-large-land-v11/styles.xml +++ b/mobile/android/base/resources/values-large-land-v11/styles.xml @@ -26,7 +26,7 @@ outsideOverlay - - - + diff --git a/mobile/android/base/resources/values-xlarge-land-v11/styles.xml b/mobile/android/base/resources/values-xlarge-land-v11/styles.xml index 555e632c2600..445f79120ad1 100644 --- a/mobile/android/base/resources/values-xlarge-land-v11/styles.xml +++ b/mobile/android/base/resources/values-xlarge-land-v11/styles.xml @@ -5,7 +5,7 @@ - - - - - + diff --git a/mobile/android/base/strings.xml.in b/mobile/android/base/strings.xml.in index f8af1d7b17f3..bc05ef389ee9 100644 --- a/mobile/android/base/strings.xml.in +++ b/mobile/android/base/strings.xml.in @@ -222,9 +222,9 @@ &contextmenu_edit_bookmark; &contextmenu_subscribe; &contextmenu_site_settings; - &contextmenu_top_bookmarks_edit; - &contextmenu_top_bookmarks_pin; - &contextmenu_top_bookmarks_unpin; + &contextmenu_top_sites_edit; + &contextmenu_top_sites_pin; + &contextmenu_top_sites_unpin; &pref_titlebar_mode; &pref_titlebar_mode_title; @@ -252,6 +252,7 @@ &button_yes; &button_no; + &home_top_sites_title; &home_history_title; &home_last_tabs_title; &home_last_tabs_open; @@ -263,7 +264,7 @@ &home_reading_list_empty; &home_reading_list_hint; &home_reading_list_hint_accessible; - &pin_bookmark_dialog_hint; + &pin_site_dialog_hint; &filepicker_title; &filepicker_audio_title;