Bug 722020 - Bookmarks UI with folders. r=lucasr

This commit is contained in:
Margaret Leibovic 2012-02-23 10:48:47 -08:00
Родитель d9a94d3b2b
Коммит d0bc6b91d7
11 изменённых файлов: 239 добавлений и 119 удалений

Просмотреть файл

@ -20,6 +20,7 @@
*
* Contributor(s):
* Lucas Rocha <lucasr@mozilla.com>
* Margaret Leibovic <margaret.leibovic@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -41,7 +42,6 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
@ -64,7 +64,6 @@ import android.widget.FilterQueryProvider;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.SimpleCursorTreeAdapter;
import android.widget.SimpleExpandableListAdapter;
import android.widget.TabHost;
import android.widget.TextView;
@ -81,9 +80,9 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.BrowserDB.URLColumns;
import org.mozilla.gecko.sync.repositories.android.BrowserContract.Bookmarks;
public class AwesomeBarTabs extends TabHost {
private static final String LOGTAG = "GeckoAwesomeBarTabs";
@ -149,8 +148,7 @@ public class AwesomeBarTabs extends TabHost {
}
}
private class AwesomeCursorViewBinder implements SimpleCursorAdapter.ViewBinder,
SimpleCursorTreeAdapter.ViewBinder {
private class AwesomeCursorViewBinder implements SimpleCursorAdapter.ViewBinder {
private boolean updateFavicon(View view, Cursor cursor, int faviconIndex) {
byte[] b = cursor.getBlob(faviconIndex);
ImageView favicon = (ImageView) view;
@ -180,12 +178,6 @@ public class AwesomeBarTabs extends TabHost {
}
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
// If we're updating a folder header in the bookmarks UI,
// the apadter updates the title for us automatically.
int isFolderIndex = cursor.getColumnIndex(Bookmarks.IS_FOLDER);
if (isFolderIndex >= 0 && cursor.getInt(isFolderIndex) == 1)
return false;
int faviconIndex = cursor.getColumnIndexOrThrow(URLColumns.FAVICON);
if (columnIndex == faviconIndex) {
return updateFavicon(view, cursor, faviconIndex);
@ -201,78 +193,134 @@ public class AwesomeBarTabs extends TabHost {
}
}
private class RefreshChildrenCursorTask extends AsyncTask<String, Void, Cursor> {
private int mGroupPosition;
private class BookmarksListAdapter extends SimpleCursorAdapter {
private static final int VIEW_TYPE_ITEM = 0;
private static final int VIEW_TYPE_FOLDER = 1;
private static final int VIEW_TYPE_COUNT = 2;
public RefreshChildrenCursorTask(int groupPosition) {
mGroupPosition = groupPosition;
private LayoutInflater mInflater;
private LinkedList<Pair<Integer, String>> mParentStack;
private RefreshBookmarkCursorTask mRefreshTask = null;
private TextView mBookmarksTitleView;
public BookmarksListAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
super(context, layout, c, from, to);
mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
// mParentStack holds folder id/title pairs that allow us to navigate
// back up the folder heirarchy
mParentStack = new LinkedList<Pair<Integer, String>>();
// Add the root folder to the stack
Pair<Integer, String> rootFolder = new Pair<Integer, String>(Bookmarks.FIXED_ROOT_ID, "");
mParentStack.push(rootFolder);
}
public void refreshCurrentFolder() {
// Cancel any pre-existing async refresh tasks
if (mRefreshTask != null)
mRefreshTask.cancel(false);
Pair<Integer, String> folderPair = mParentStack.getFirst();
mRefreshTask = new RefreshBookmarkCursorTask(folderPair.first, folderPair.second);
mRefreshTask.execute();
}
public void moveToParentFolder() {
mParentStack.pop();
refreshCurrentFolder();
}
public void moveToChildFolder(int folderId, String folderTitle) {
Pair<Integer, String> folderPair = new Pair<Integer, String>(folderId, folderTitle);
mParentStack.push(folderPair);
refreshCurrentFolder();
}
public int getItemViewType(int position) {
Cursor c = getCursor();
if (c.moveToPosition(position) &&
c.getInt(c.getColumnIndexOrThrow(Bookmarks.IS_FOLDER)) == 1)
return VIEW_TYPE_FOLDER;
// Default to retuning normal item type
return VIEW_TYPE_ITEM;
}
@Override
public int getViewTypeCount() {
return VIEW_TYPE_COUNT;
}
public String getFolderTitle(int position) {
Cursor c = getCursor();
if (!c.moveToPosition(position))
return "";
return c.getString(c.getColumnIndexOrThrow(Bookmarks.TITLE));
}
@Override
protected Cursor doInBackground(String... params) {
String guid = params[0];
if (guid != null && guid.equals(Bookmarks.MOBILE_FOLDER_GUID))
return BrowserDB.getMobileBookmarks(mContentResolver);
public View getView(int position, View convertView, ViewGroup parent) {
int viewType = getItemViewType(position);
// If we don't have the mobile bookmarks folder, we must have
// the desktop bookmarks folder
return BrowserDB.getDesktopBookmarks(mContentResolver);
if (viewType == VIEW_TYPE_ITEM)
return super.getView(position, convertView, parent);
if (convertView == null)
convertView = mInflater.inflate(R.layout.awesomebar_folder_row, null);
TextView titleView = (TextView) convertView.findViewById(R.id.title);
titleView.setText(getFolderTitle(position));
return convertView;
}
@Override
protected void onPostExecute(Cursor childrenCursor) {
mBookmarksAdapter.setChildrenCursor(mGroupPosition, childrenCursor);
}
}
public class BookmarksListAdapter extends SimpleCursorTreeAdapter {
public BookmarksListAdapter(Context context, Cursor cursor,
int groupLayout, String[] groupFrom, int[] groupTo,
int childLayout, String[] childFrom, int[] childTo) {
super(context, cursor, groupLayout, groupFrom, groupTo, childLayout, childFrom, childTo);
public void setBookmarksTitleView(TextView titleView) {
mBookmarksTitleView = titleView;
}
@Override
protected Cursor getChildrenCursor(Cursor groupCursor) {
String guid = groupCursor.getString(groupCursor.getColumnIndexOrThrow(Bookmarks.GUID));
private class RefreshBookmarkCursorTask extends AsyncTask<Void, Void, Cursor> {
private int mFolderId;
private String mFolderTitle;
// We need to do this in a AsyncTask because we're on the main thread
new RefreshChildrenCursorTask(groupCursor.getPosition()).execute(guid);
public RefreshBookmarkCursorTask(int folderId, String folderTitle) {
mFolderId = folderId;
mFolderTitle = folderTitle;
}
// Return an empty Cursor to avoid possible NPE
return new MatrixCursor(new String [] { Bookmarks._ID,
Bookmarks.URL,
Bookmarks.TITLE,
Bookmarks.FAVICON });
protected Cursor doInBackground(Void... params) {
return BrowserDB.getBookmarksInFolder(mContentResolver, mFolderId);
}
protected void onPostExecute(Cursor cursor) {
mRefreshTask = null;
mBookmarksAdapter.changeCursor(cursor);
// Hide the header text if we're at the root folder
if (mFolderId == Bookmarks.FIXED_ROOT_ID) {
mBookmarksTitleView.setVisibility(View.GONE);
} else {
mBookmarksTitleView.setText(mFolderTitle);
mBookmarksTitleView.setVisibility(View.VISIBLE);
}
}
}
}
private class BookmarksQueryTask extends AsyncTask<Void, Void, Cursor> {
protected Cursor doInBackground(Void... arg0) {
// Make our own cursor to group mobile bookmarks and desktop bookmarks.
// This data is used in BookmarksListAdapter and AwesomeCursorViewBinder.
MatrixCursor c = new MatrixCursor(new String[] { Bookmarks._ID,
Bookmarks.IS_FOLDER,
Bookmarks.GUID,
URLColumns.TITLE }, 2);
Resources resources = mContext.getResources();
c.addRow(new Object[] { 0, 1, Bookmarks.MOBILE_FOLDER_GUID,
resources.getString(R.string.bookmarks_folder_mobile)} );
c.addRow(new Object[] { 1, 1, null,
resources.getString(R.string.bookmarks_folder_desktop)} );
return c;
return BrowserDB.getBookmarksInFolder(mContentResolver, Bookmarks.FIXED_ROOT_ID);
}
protected void onPostExecute(Cursor cursor) {
// Load the list using a custom adapter so we can create the bitmaps
mBookmarksAdapter = new BookmarksListAdapter(
mContext,
cursor,
R.layout.awesomebar_header_row,
new String[] { URLColumns.TITLE },
new int[] { R.id.title },
R.layout.awesomebar_row,
cursor,
new String[] { URLColumns.TITLE,
URLColumns.URL,
URLColumns.FAVICON },
@ -281,25 +329,26 @@ public class AwesomeBarTabs extends TabHost {
mBookmarksAdapter.setViewBinder(new AwesomeCursorViewBinder());
final ExpandableListView bookmarksList = (ExpandableListView) findViewById(R.id.bookmarks_list);
// This listener only applies to child items, not group header items.
bookmarksList.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
public boolean onChildClick(ExpandableListView parent, View view,
int groupPosition, int childPosition, long id) {
handleBookmarkItemClick(groupPosition, childPosition);
return true;
ListView bookmarksList = (ListView) findViewById(R.id.bookmarks_list);
bookmarksList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
handleBookmarkItemClick(position);
}
});
bookmarksList.setAdapter(mBookmarksAdapter);
// We need to add the header before we set the adapter
LayoutInflater inflater =
(LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View headerView = inflater.inflate(R.layout.awesomebar_folder_header_row, null);
// Expand the "Mobile Bookmarks" section
bookmarksList.expandGroup(0);
// Expand the "Desktop Bookmarks" section
// TODO: Once we update the UI to include a better "expand" affordance,
// we can collapse this at first if we want.
bookmarksList.expandGroup(1);
// Hide the header title view to begin with
TextView titleView = (TextView) headerView.findViewById(R.id.title);
titleView.setVisibility(View.GONE);
mBookmarksAdapter.setBookmarksTitleView(titleView);
bookmarksList.addHeaderView(headerView, null, true);
bookmarksList.setAdapter(mBookmarksAdapter);
}
}
@ -766,10 +815,29 @@ public class AwesomeBarTabs extends TabHost {
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
private void handleBookmarkItemClick(int groupPosition, int childPosition) {
Cursor cursor = mBookmarksAdapter.getChild(groupPosition, childPosition);
String url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
private void handleBookmarkItemClick(int position) {
// If we tap on the header view, go up a level
if (position == 0) {
mBookmarksAdapter.moveToParentFolder();
return;
}
Cursor cursor = mBookmarksAdapter.getCursor();
// The header view takes up a spot in the list
cursor.moveToPosition(position - 1);
int isFolder = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks.IS_FOLDER));
if (isFolder == 1) {
// If we're clicking on a folder, update mBookmarksAdapter to move to that folder
int folderId = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID));
String folderTitle = mBookmarksAdapter.getFolderTitle(position - 1);
mBookmarksAdapter.moveToChildFolder(folderId, folderTitle);
return;
}
// Otherwise, just open the URL
String url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
if (mUrlOpenListener != null)
mUrlOpenListener.onUrlOpen(url);
}

Просмотреть файл

@ -221,6 +221,8 @@ RES_LAYOUT = \
$(SYNC_RES_LAYOUT) \
res/layout/autocomplete_list_item.xml \
res/layout/awesomebar.xml \
res/layout/awesomebar_folder_row.xml \
res/layout/awesomebar_folder_header_row.xml \
res/layout/awesomebar_header_row.xml \
res/layout/awesomebar_row.xml \
res/layout/awesomebar_search.xml \
@ -289,6 +291,7 @@ RES_DRAWABLE_NODPI = \
RES_DRAWABLE_BASE = \
res/drawable/favicon.png \
res/drawable/folder.png \
res/drawable/abouthome_icon.png \
res/drawable/abouthome_logo.png \
res/drawable/abouthome_separator.9.png \
@ -335,6 +338,7 @@ RES_DRAWABLE_LDPI = \
RES_DRAWABLE_HDPI = \
res/drawable-hdpi/favicon.png \
res/drawable-hdpi/folder.png \
res/drawable-hdpi/home_bg.png \
res/drawable-hdpi/home_star.png \
res/drawable-hdpi/abouthome_icon.png \
@ -399,6 +403,7 @@ RES_DRAWABLE_HDPI_V11 = \
RES_DRAWABLE_XHDPI_V11 = \
res/drawable-xhdpi-v11/favicon.png \
res/drawable-xhdpi-v11/folder.png \
res/drawable-xhdpi-v11/abouthome_icon.png \
res/drawable-xhdpi-v11/abouthome_logo.png \
res/drawable-xhdpi-v11/abouthome_separator.9.png \

Просмотреть файл

@ -188,12 +188,7 @@ public class AndroidBrowserDB implements BrowserDB.BrowserDBIface {
Browser.clearHistory(cr);
}
public Cursor getMobileBookmarks(ContentResolver cr) {
Cursor c = cr.query(null, null, null, null, null);
return new AndroidDBCursor(c);
}
public Cursor getDesktopBookmarks(ContentResolver cr) {
public Cursor getBookmarksInFolder(ContentResolver cr, long folderId) {
Cursor c = cr.query(null, null, null, null, null);
return new AndroidDBCursor(c);
}

Просмотреть файл

@ -77,9 +77,7 @@ public class BrowserDB {
public void clearHistory(ContentResolver cr);
public Cursor getMobileBookmarks(ContentResolver cr);
public Cursor getDesktopBookmarks(ContentResolver cr);
public Cursor getBookmarksInFolder(ContentResolver cr, long folderId);
public boolean isBookmark(ContentResolver cr, String uri);
@ -146,12 +144,8 @@ public class BrowserDB {
sDb.clearHistory(cr);
}
public static Cursor getMobileBookmarks(ContentResolver cr) {
return sDb.getMobileBookmarks(cr);
}
public static Cursor getDesktopBookmarks(ContentResolver cr) {
return sDb.getDesktopBookmarks(cr);
public static Cursor getBookmarksInFolder(ContentResolver cr, long folderId) {
return sDb.getBookmarksInFolder(cr, folderId);
}
public static String getUrlForKeyword(ContentResolver cr, String keyword) {

Просмотреть файл

@ -21,6 +21,7 @@
* Contributor(s):
* Lucas Rocha <lucasr@mozilla.com>
* Richard Newman <rnewman@mozilla.com>
* Margaret Leibovic <margaret.leibovic@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -52,7 +53,6 @@ import android.content.ContentValues;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.CursorWrapper;
import android.database.MatrixCursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
@ -79,6 +79,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
private final String mProfile;
private long mMobileFolderId;
private long mTagsFolderId;
private final Uri mBookmarksUriWithProfile;
private final Uri mParentsUriWithProfile;
@ -86,9 +87,18 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
private final Uri mImagesUriWithProfile;
private final Uri mDeletedHistoryUriWithProfile;
private static final String[] DEFAULT_BOOKMARK_COLUMNS =
new String[] { Bookmarks._ID,
Bookmarks.URL,
Bookmarks.TITLE,
Bookmarks.IS_FOLDER,
Bookmarks.PARENT,
Bookmarks.FAVICON };
public LocalBrowserDB(String profile) {
mProfile = profile;
mMobileFolderId = -1;
mTagsFolderId = -1;
mBookmarksUriWithProfile = appendProfile(Bookmarks.CONTENT_URI);
mParentsUriWithProfile = appendProfile(Bookmarks.PARENTS_CONTENT_URI);
@ -296,26 +306,18 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
cr.delete(mHistoryUriWithProfile, null, null);
}
public Cursor getMobileBookmarks(ContentResolver cr) {
return getBookmarks(cr, true);
}
public Cursor getDesktopBookmarks(ContentResolver cr) {
return getBookmarks(cr, false);
}
private Cursor getBookmarks(ContentResolver cr, boolean mobileBookmarks) {
String parentSelection = mobileBookmarks ? " = ?" : " != ?";
long mobileFolderId = getMobileBookmarksFolderId(cr);
// This method filters out the root folder and the tags folder, since we
// don't want to see those in the UI
public Cursor getBookmarksInFolder(ContentResolver cr, long folderId) {
Cursor c = cr.query(mBookmarksUriWithProfile,
new String[] { Bookmarks._ID,
Bookmarks.URL,
Bookmarks.TITLE,
Bookmarks.FAVICON },
Bookmarks.IS_FOLDER + " = 0 AND " +
Bookmarks.PARENT + parentSelection,
new String[] { String.valueOf(mobileFolderId) },
Bookmarks.DATE_MODIFIED + " DESC");
DEFAULT_BOOKMARK_COLUMNS,
Bookmarks.PARENT + " = ? AND " +
Bookmarks._ID + " <> ? AND " +
Bookmarks._ID + " <> ?",
new String[] { String.valueOf(folderId),
String.valueOf(Bookmarks.FIXED_ROOT_ID),
String.valueOf(getTagsBookmarksFolderId(cr))},
null);
return new LocalDBCursor(c);
}
@ -355,23 +357,37 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
if (mMobileFolderId >= 0)
return mMobileFolderId;
mMobileFolderId = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID);
return mMobileFolderId;
}
private long getTagsBookmarksFolderId(ContentResolver cr) {
if (mTagsFolderId >= 0)
return mTagsFolderId;
mTagsFolderId = getFolderIdFromGuid(cr, Bookmarks.TAGS_FOLDER_GUID);
return mTagsFolderId;
}
private long getFolderIdFromGuid(ContentResolver cr, String guid) {
long folderId = -1;
Cursor c = null;
try {
c = cr.query(mBookmarksUriWithProfile,
new String[] { Bookmarks._ID },
Bookmarks.GUID + " = ?",
new String[] { Bookmarks.MOBILE_FOLDER_GUID },
new String[] { guid },
null);
if (c.moveToFirst())
mMobileFolderId = c.getLong(c.getColumnIndexOrThrow(Bookmarks._ID));
folderId = c.getLong(c.getColumnIndexOrThrow(Bookmarks._ID));
} finally {
if (c != null)
c.close();
}
return mMobileFolderId;
return folderId;
}
/**

Двоичные данные
mobile/android/base/resources/drawable-hdpi/folder.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.6 KiB

Двоичные данные
mobile/android/base/resources/drawable-xhdpi-v11/folder.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.8 KiB

Двоичные данные
mobile/android/base/resources/drawable/folder.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.4 KiB

Просмотреть файл

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorPrimary"
android:singleLine="true"
android:padding="6dip"
android:ellipsize="middle"/>
</LinearLayout>

Просмотреть файл

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="6dip">
<ImageView android:id="@+id/favicon"
android:src="@drawable/folder"
android:layout_width="32dip"
android:layout_height="32dip"
android:layout_marginRight="10dip"
android:layout_centerVertical="true"
android:minWidth="32dip"
android:minHeight="32dip"
android:scaleType="fitCenter"/>
<TextView android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorPrimary"
android:layout_toRightOf="@id/favicon"
android:singleLine="true"
android:ellipsize="middle"/>
</RelativeLayout>

Просмотреть файл

@ -18,9 +18,8 @@
<ListView android:id="@+id/all_pages_list"
style="@style/AwesomeBarList"/>
<ExpandableListView android:id="@+id/bookmarks_list"
style="@style/AwesomeBarList"
android:groupIndicator="@android:color/transparent"/>
<ListView android:id="@+id/bookmarks_list"
style="@style/AwesomeBarList"/>
<ExpandableListView android:id="@+id/history_list"
style="@style/AwesomeBarList"