Bug 1157539 - Create "speed dial" dynamic home panel layout. r=mhaigh

--HG--
extra : commitid : J69tandSl6o
extra : rebase_source : 0b7b26a376938ae42beb7d0f872b3f1df68eb7c3
This commit is contained in:
Sebastian Kaspari 2015-06-16 13:53:59 +02:00
parent 7d0591a53a
commit f511089b41
18 changed files with 192 additions and 45 deletions

View File

@ -335,11 +335,13 @@ public class BrowserContract {
public static final String TITLE = "title";
public static final String DESCRIPTION = "description";
public static final String IMAGE_URL = "image_url";
public static final String BACKGROUND_COLOR = "background_color";
public static final String BACKGROUND_URL = "background_url";
public static final String CREATED = "created";
public static final String FILTER = "filter";
public static final String[] DEFAULT_PROJECTION =
new String[] { _ID, DATASET_ID, URL, TITLE, DESCRIPTION, IMAGE_URL, FILTER };
new String[] { _ID, DATASET_ID, URL, TITLE, DESCRIPTION, IMAGE_URL, BACKGROUND_COLOR, BACKGROUND_URL, FILTER };
}
@RobocopTarget

View File

@ -27,7 +27,7 @@ public class HomeProvider extends SQLiteBridgeContentProvider {
private static final String LOGTAG = "GeckoHomeProvider";
// This should be kept in sync with the db version in mobile/android/modules/HomeProvider.jsm
private static final int DB_VERSION = 2;
private static final int DB_VERSION = 3;
private static final String DB_FILENAME = "home.sqlite";
private static final String TELEMETRY_TAG = "SQLITEBRIDGE_PROVIDER_HOME";

View File

@ -524,7 +524,8 @@ public final class HomeConfig {
public static enum ItemType implements Parcelable {
ARTICLE("article"),
IMAGE("image");
IMAGE("image"),
ICON("icon");
private final String mId;

View File

@ -5,13 +5,8 @@
package org.mozilla.gecko.home;
import java.util.EnumSet;
import org.mozilla.gecko.R;
import org.mozilla.gecko.db.BrowserContract.HomeItems;
import org.mozilla.gecko.home.HomeConfig.ItemHandler;
import org.mozilla.gecko.home.HomeConfig.ViewConfig;
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
import org.mozilla.gecko.home.PanelLayout.DatasetBacked;
import org.mozilla.gecko.home.PanelLayout.FilterManager;
import org.mozilla.gecko.home.PanelLayout.OnItemOpenListener;
@ -36,7 +31,8 @@ public class PanelGridView extends GridView
private HomeContextMenuInfo.Factory mContextMenuInfoFactory;
public PanelGridView(Context context, ViewConfig viewConfig) {
super(context, null, R.attr.panelGridViewStyle);
super(context, null, (viewConfig.getItemType() == HomeConfig.ItemType.ICON) ?
R.attr.panelIconGridViewStyle : R.attr.panelGridViewStyle);
this.viewConfig = viewConfig;
itemHandler = new PanelViewItemHandler(viewConfig);

View File

@ -11,6 +11,7 @@ import org.mozilla.gecko.home.HomeConfig.ItemType;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Color;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
@ -18,22 +19,22 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
class PanelItemView extends LinearLayout {
private final TextView title;
private final TextView description;
private final ImageView image;
private final LinearLayout titleDescContainer;
private final TextView titleView;
private final TextView descriptionView;
private final ImageView imageView;
private final LinearLayout titleDescContainerView;
private final ImageView backgroundView;
private PanelItemView(Context context, int layoutId) {
super(context);
LayoutInflater.from(context).inflate(layoutId, this);
title = (TextView) findViewById(R.id.title);
description = (TextView) findViewById(R.id.description);
image = (ImageView) findViewById(R.id.image);
titleDescContainer = (LinearLayout) findViewById(R.id.title_desc_container);
titleView = (TextView) findViewById(R.id.title);
descriptionView = (TextView) findViewById(R.id.description);
imageView = (ImageView) findViewById(R.id.image);
backgroundView = (ImageView) findViewById(R.id.background);
titleDescContainerView = (LinearLayout) findViewById(R.id.title_desc_container);
}
public void updateFromCursor(Cursor cursor) {
@ -42,35 +43,58 @@ class PanelItemView extends LinearLayout {
// Only show title if the item has one
final boolean hasTitle = !TextUtils.isEmpty(titleText);
title.setVisibility(hasTitle ? View.VISIBLE : View.GONE);
titleDescContainer.setVisibility(hasTitle ? View.VISIBLE : View.GONE);
titleView.setVisibility(hasTitle ? View.VISIBLE : View.GONE);
if (hasTitle) {
title.setText(titleText);
titleView.setText(titleText);
}
int descriptionIndex = cursor.getColumnIndexOrThrow(HomeItems.DESCRIPTION);
final String descriptionText = cursor.getString(descriptionIndex);
// Only show description if the item has one
// Descriptions are not supported for IconItemView objects (Bug 1157539)
final boolean hasDescription = !TextUtils.isEmpty(descriptionText);
description.setVisibility(hasDescription ? View.VISIBLE : View.GONE);
if (descriptionView != null) {
descriptionView.setVisibility(hasDescription ? View.VISIBLE : View.GONE);
if (hasDescription) {
description.setText(descriptionText);
descriptionView.setText(descriptionText);
}
}
if (titleDescContainerView != null) {
titleDescContainerView.setVisibility(hasTitle || hasDescription ? View.VISIBLE : View.GONE);
}
titleDescContainer.setVisibility(hasTitle || hasDescription ? View.VISIBLE : View.GONE);
int imageIndex = cursor.getColumnIndexOrThrow(HomeItems.IMAGE_URL);
final String imageUrl = cursor.getString(imageIndex);
// Only try to load the image if the item has define image URL
final boolean hasImageUrl = !TextUtils.isEmpty(imageUrl);
image.setVisibility(hasImageUrl ? View.VISIBLE : View.GONE);
imageView.setVisibility(hasImageUrl ? View.VISIBLE : View.GONE);
if (hasImageUrl) {
ImageLoader.with(getContext())
.load(imageUrl)
.into(image);
.into(imageView);
}
final int columnIndexBackgroundColor = cursor.getColumnIndex(HomeItems.BACKGROUND_COLOR);
if (columnIndexBackgroundColor != -1) {
final String color = cursor.getString(columnIndexBackgroundColor);
if (!TextUtils.isEmpty(color)) {
setBackgroundColor(Color.parseColor(color));
}
}
// Backgrounds are only supported for IconItemView objects (Bug 1157539)
final int columnIndexBackgroundUrl = cursor.getColumnIndex(HomeItems.BACKGROUND_URL);
if (columnIndexBackgroundUrl != -1) {
final String backgroundUrl = cursor.getString(columnIndexBackgroundUrl);
if (backgroundView != null && !TextUtils.isEmpty(backgroundUrl)) {
ImageLoader.with(getContext())
.load(backgroundUrl)
.fit()
.into(backgroundView);
}
}
}
@ -88,6 +112,12 @@ class PanelItemView extends LinearLayout {
}
}
private static class IconItemView extends PanelItemView {
private IconItemView(Context context) {
super(context, R.layout.panel_icon_item);
}
}
public static PanelItemView create(Context context, ItemType itemType) {
switch(itemType) {
case ARTICLE:
@ -96,6 +126,9 @@ class PanelItemView extends LinearLayout {
case IMAGE:
return new ImageItemView(context);
case ICON:
return new IconItemView(context);
default:
throw new IllegalArgumentException("Could not create panel item view from " + itemType);
}

View File

@ -525,6 +525,7 @@ gbjar.sources += [
'widget/ResizablePathDrawable.java',
'widget/SiteLogins.java',
'widget/SquaredImageView.java',
'widget/SquaredRelativeLayout.java',
'widget/SwipeDismissListViewTouchListener.java',
'widget/TabThumbnailWrapper.java',
'widget/ThumbnailView.java',

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<org.mozilla.gecko.widget.SquaredRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
<ImageView
android:id="@+id/image"
android:layout_width="50dp"
android:layout_height="50dp"
android:scaleType="centerInside"
android:layout_centerInParent="true" />
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="end"
android:textColor="@android:color/white"
android:layout_alignParentBottom="true"
android:textSize="12sp"
android:padding="8dp"
android:background="@color/panel_icon_item_title_background"
android:layout_gravity="center_horizontal"/>
</org.mozilla.gecko.widget.SquaredRelativeLayout>

View File

@ -6,5 +6,6 @@
<resources>
<integer name="number_of_top_sites_cols">3</integer>
<integer name="panel_icon_grid_view_columns">6</integer>
</resources>

View File

@ -7,5 +7,6 @@
<integer name="number_of_top_sites">9</integer>
<integer name="number_of_top_sites_cols">3</integer>
<integer name="panel_icon_grid_view_columns">6</integer>
</resources>

View File

@ -37,8 +37,10 @@
<!-- Default style for the TopSitesGridItemView -->
<attr name="topSitesGridItemViewStyle" format="reference" />
<!-- Style for the PanelGridView -->
<!-- Styles for dynamic panel grid views -->
<attr name="panelGridViewStyle" format="reference" />
<attr name="panelIconGridViewStyle" format="reference" />
<attr name="panelIconViewStyle" format="reference" />
<!-- Style for the TabsGridLayout -->
<attr name="tabGridLayoutViewStyle" format="reference" />

View File

@ -125,6 +125,7 @@
<color name="home_button_bar_bg">#FFF5F7F9</color>
<color name="panel_image_item_background">#D1D9E1</color>
<color name="panel_icon_item_title_background">#32000000</color>
<!-- Swipe to refresh colors for dynamic panel -->
<color name="swipe_refresh_orange">#FFFFC26C</color>

View File

@ -8,5 +8,6 @@
<integer name="number_of_top_sites">6</integer>
<integer name="number_of_top_sites_cols">2</integer>
<integer name="max_icon_grid_columns">4</integer>
<integer name="panel_icon_grid_view_columns">3</integer>
</resources>

View File

@ -191,6 +191,17 @@
<item name="android:drawSelectorOnTop">true</item>
</style>
<style name="Widget.PanelIconGridView" parent="Widget.GridView">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">match_parent</item>
<item name="android:numColumns">@integer/panel_icon_grid_view_columns</item>
<item name="android:horizontalSpacing">6dp</item>
<item name="android:verticalSpacing">6dp</item>
<item name="android:drawSelectorOnTop">true</item>
<item name="android:padding">6dp</item>
<item name="android:clipToPadding">false</item>
</style>
<style name="Widget.TabsGridLayout" parent="Widget.GridView">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">match_parent</item>

View File

@ -95,6 +95,7 @@
<item name="menuItemActionModeStyle">@style/GeckoActionBar.Button</item>
<item name="menuItemShareActionButtonStyle">@style/Widget.MenuItemSecondaryActionBar</item>
<item name="panelGridViewStyle">@style/Widget.PanelGridView</item>
<item name="panelIconGridViewStyle">@style/Widget.PanelIconGridView</item>
<item name="topSitesGridItemViewStyle">@style/Widget.TopSitesGridItemView</item>
<item name="topSitesGridViewStyle">@style/Widget.TopSitesGridView</item>
<item name="topSitesThumbnailViewStyle">@style/Widget.TopSitesThumbnailView</item>

View File

@ -0,0 +1,33 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
public class SquaredRelativeLayout extends RelativeLayout {
public SquaredRelativeLayout(Context context) {
super(context);
}
public SquaredRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SquaredRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int squareMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
super.onMeasure(squareMeasureSpec, squareMeasureSpec);
}
}

View File

@ -301,7 +301,8 @@ let HomePanels = (function () {
// Valid item types for a panel view.
let Item = Object.freeze({
ARTICLE: "article",
IMAGE: "image"
IMAGE: "image",
ICON: "icon"
});
// Valid item handlers for a panel view.

View File

@ -21,8 +21,9 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
* SCHEMA_VERSION history:
* 1: Create HomeProvider (bug 942288)
* 2: Add filter column to items table (bug 942295/975841)
* 3: Add background_color and background_url columns (bug 1157539)
*/
const SCHEMA_VERSION = 2;
const SCHEMA_VERSION = 3;
// The maximum number of items you can attempt to save at once.
const MAX_SAVE_COUNT = 100;
@ -54,6 +55,8 @@ const SQL = {
"title TEXT," +
"description TEXT," +
"image_url TEXT," +
"background_color TEXT," +
"background_url TEXT," +
"filter TEXT," +
"created INTEGER" +
")",
@ -62,11 +65,17 @@ const SQL = {
"DROP TABLE items",
insertItem:
"INSERT INTO items (dataset_id, url, title, description, image_url, filter, created) " +
"VALUES (:dataset_id, :url, :title, :description, :image_url, :filter, :created)",
"INSERT INTO items (dataset_id, url, title, description, image_url, background_color, background_url, filter, created) " +
"VALUES (:dataset_id, :url, :title, :description, :image_url, :background_color, :background_url, :filter, :created)",
deleteFromDataset:
"DELETE FROM items WHERE dataset_id = :dataset_id"
"DELETE FROM items WHERE dataset_id = :dataset_id",
addColumnBackgroundColor:
"ALTER TABLE items ADD COLUMN background_color TEXT",
addColumnBackgroundUrl:
"ALTER TABLE items ADD COLUMN background_url TEXT",
}
/**
@ -214,15 +223,21 @@ function createDatabase(db) {
*/
function upgradeDatabase(db, oldVersion, newVersion) {
return Task.spawn(function upgrade_database_task() {
for (let v = oldVersion + 1; v <= newVersion; v++) {
switch(v) {
case 2:
switch (oldVersion) {
case 1:
// Migration from v1 to latest:
// Recreate the items table discarding any
// existing data.
yield db.execute(SQL.dropItemsTable);
yield db.execute(SQL.createItemsTable);
break;
}
case 2:
// Migration from v2 to latest:
// Add new columns: background_color, background_url
yield db.execute(SQL.addColumnBackgroundColor);
yield db.execute(SQL.addColumnBackgroundUrl);
break;
}
});
}
@ -354,6 +369,8 @@ HomeStorage.prototype = {
title: item.title,
description: item.description,
image_url: item.image_url,
background_color: item.background_color,
background_url: item.background_url,
filter: item.filter,
created: Date.now()
};

View File

@ -14,6 +14,8 @@ Cu.import("resource://gre/modules/Task.jsm");
const TEST_DATASET_ID = "test-dataset-id";
const TEST_URL = "http://test.com";
const TEST_TITLE = "Test";
const TEST_BACKGROUND_URL = "http://example.com/background";
const TEST_BACKGROUND_COLOR = "#FF9500";
const PREF_SYNC_CHECK_INTERVAL_SECS = "home.sync.checkIntervalSecs";
const TEST_INTERVAL_SECS = 1;
@ -48,7 +50,12 @@ add_test(function test_periodic_sync() {
add_task(function test_save_and_delete() {
// Use the HomeProvider API to save some data.
let storage = HomeProvider.getStorage(TEST_DATASET_ID);
yield storage.save([{ title: TEST_TITLE, url: TEST_URL }]);
yield storage.save([{
title: TEST_TITLE,
url: TEST_URL,
background_url: TEST_BACKGROUND_URL,
background_color: TEST_BACKGROUND_COLOR
}]);
// Peek in the DB to make sure we have the right data.
let db = yield Sqlite.openConnection({ path: DB_PATH });
@ -60,6 +67,8 @@ add_task(function test_save_and_delete() {
let result = yield db.execute("SELECT * FROM items", null, function onRow(row){
do_check_eq(row.getResultByName("dataset_id"), TEST_DATASET_ID);
do_check_eq(row.getResultByName("url"), TEST_URL);
do_check_eq(row.getResultByName("background_url"), TEST_BACKGROUND_URL);
do_check_eq(row.getResultByName("background_color"), TEST_BACKGROUND_COLOR);
});
// Use the HomeProvider API to delete the data.