Bug 1293790 - Implement AS TopSitesView draft
authorAndrzej Hunt <ahunt@mozilla.com>
Tue, 09 Aug 2016 14:30:29 -0700
changeset 398843 22c4c9830dc5e993572bb8392bd4bb7d37760d6e
parent 398842 d16a05e0982dfa67020602ef733853c36b1e8cc5
child 527769 d9c8bed98e75529c13e5e7dcf188a50ff0da403f
push id25653
push userahunt@mozilla.com
push dateTue, 09 Aug 2016 21:33:27 +0000
bugs1293790
milestone51.0a1
Bug 1293790 - Implement AS TopSitesView MozReview-Commit-ID: 6zJfHJv34tC
mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesAdapter.java
mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesCard.java
mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesCursorLoaderCallbacks.java
mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesLoader.java
mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesView.java
mobile/android/base/resources/layout/as_topsites_card.xml
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesAdapter.java
@@ -0,0 +1,52 @@
+/* -*- 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.activitystream.topsites;
+
+import android.database.Cursor;
+import android.support.v7.widget.CardView;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.db.BrowserContract;
+
+public class TopSitesAdapter extends RecyclerView.Adapter<TopSitesCard> {
+    private Cursor cursor;
+
+    @Override
+    public TopSitesCard onCreateViewHolder(ViewGroup parent, int viewType) {
+        final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+
+        CardView card = (CardView) inflater.inflate(R.layout.as_topsites_card, parent, false);
+
+        return new TopSitesCard(card);
+    }
+
+    @Override
+    public void onBindViewHolder(TopSitesCard holder, int position) {
+        cursor.moveToPosition(position);
+        holder.bind(cursor);
+    }
+
+    @Override
+    public int getItemCount() {
+        if (cursor != null && !cursor.isClosed()) {
+            return cursor.getCount();
+        } else {
+            return 0;
+        }
+    }
+
+    public void swapCursor(Cursor cursor) {
+        this.cursor = cursor;
+    }
+
+    public String getURLForPosition(int position) {
+        cursor.moveToPosition(position);
+
+        return cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.URL));
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesCard.java
@@ -0,0 +1,69 @@
+/* -*- 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.activitystream.topsites;
+
+import android.database.Cursor;
+import android.support.v7.widget.CardView;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.widget.TextView;
+
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.favicons.Favicons;
+import org.mozilla.gecko.home.HomePager;
+import org.mozilla.gecko.home.UpdateViewFaviconLoadedListener;
+import org.mozilla.gecko.widget.FaviconView;
+
+import java.util.EnumSet;
+
+class TopSitesCard extends RecyclerView.ViewHolder {
+    private final FaviconView faviconView;
+    private final View faviconBackground;
+
+    private final TextView title;
+    private final View menuButton;
+
+    private final UpdateViewFaviconLoadedListener mFaviconListener;
+
+    private String url = "";
+
+    private int mLoadFaviconJobId = Favicons.NOT_LOADING;
+
+    public TopSitesCard(CardView card) {
+        super(card);
+
+        faviconView = (FaviconView) card.findViewById(R.id.favicon);
+        faviconBackground = card.findViewById(R.id.favicon_frame);
+
+        title = (TextView) card.findViewById(R.id.title);
+        menuButton = card.findViewById(R.id.menu);
+
+        // Disable corners on < lollipop:
+        // CardView only supports clipping content on API >= 21 (for performance reasons). Without
+        // content clipping the "action bar" will look ugly because it has its own background:
+        // by default there's a 2px white edge along the top and sides (i.e. an inset corresponding
+        // to the corner radius), if we disable the inset then the corners overlap.
+        // It's possible to implement custom clipping, however given that the support library
+        // chose not to support this for performance reasons, we too have chosen to just disable
+        // corners on < 21, see Bug 1271428.
+        if (AppConstants.Versions.preLollipop) {
+            card.setRadius(0);
+        }
+
+        mFaviconListener = new UpdateViewFaviconLoadedListener(faviconView);
+    }
+
+    void bind(Cursor cursor) {
+        this.url = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.URL));
+
+        title.setText(cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.TITLE)));
+
+        Favicons.cancelFaviconLoad(mLoadFaviconJobId);
+
+        mLoadFaviconJobId = Favicons.getSizedFaviconForPageFromLocal(faviconView.getContext(), url, mFaviconListener);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesCursorLoaderCallbacks.java
@@ -0,0 +1,53 @@
+/* -*- 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.activitystream.topsites;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.Loader;
+
+import java.lang.ref.WeakReference;
+
+public class TopSitesCursorLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
+    private final WeakReference<Context> contextWeakRef;
+    private final WeakReference<TopSitesAdapter> adapterWeakRef;
+
+    public TopSitesCursorLoaderCallbacks(Context context, TopSitesView topSitesView) {
+        contextWeakRef = new WeakReference<>(context);
+        adapterWeakRef = new WeakReference<>(topSitesView.getAdapter());
+    }
+
+    @Override
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        Context context = contextWeakRef.get();
+        if (context == null) {
+            return null;
+        }
+
+        return new TopSitesLoader(context);
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        TopSitesAdapter adapter = adapterWeakRef.get();
+        if (adapter == null) {
+            return;
+        }
+
+        adapter.swapCursor(data);
+    }
+
+    @Override
+    public void onLoaderReset(Loader<Cursor> loader) {
+        TopSitesAdapter adapter = adapterWeakRef.get();
+        if (adapter == null) {
+            return;
+        }
+
+        adapter.swapCursor(null);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesLoader.java
@@ -0,0 +1,25 @@
+/* -*- 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.activitystream.topsites;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.support.v4.content.CursorLoader;
+
+import org.mozilla.gecko.GeckoProfile;
+import org.mozilla.gecko.db.BrowserDB;
+
+public class TopSitesLoader extends CursorLoader {
+    public TopSitesLoader(Context context) {
+        super(context);
+    }
+
+    @Override
+    protected Cursor onLoadInBackground() {
+        final BrowserDB db = GeckoProfile.get(getContext()).getDB();
+
+        return db.getASTopSites(getContext().getContentResolver(), 5);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesView.java
@@ -0,0 +1,56 @@
+/* -*- 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.activitystream.topsites;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.View;
+
+import org.mozilla.gecko.home.HomePager;
+import org.mozilla.gecko.widget.RecyclerViewClickSupport;
+
+import java.util.EnumSet;
+
+public class TopSitesView
+        extends RecyclerView
+        implements RecyclerViewClickSupport.OnItemClickListener {
+
+    private final TopSitesAdapter adapter;
+
+    private HomePager.OnUrlOpenListener onUrlOpenListener = null;
+
+    public TopSitesView(Context context,
+                        @Nullable AttributeSet attrs) {
+        super(context, attrs);
+
+        adapter = new TopSitesAdapter();
+        setAdapter(adapter);
+
+        setLayoutManager(new LinearLayoutManager(context, HORIZONTAL, false));
+
+        RecyclerViewClickSupport.addTo(this)
+                .setOnItemClickListener(this);
+    }
+
+    public TopSitesAdapter getAdapter() {
+        return adapter;
+    }
+
+    public void setOnUrlOpenListener(HomePager.OnUrlOpenListener onUrlOpenListener) {
+        this.onUrlOpenListener = onUrlOpenListener;
+    }
+
+    @Override
+    public void onItemClicked(RecyclerView recyclerView, int position, View v) {
+        if (onUrlOpenListener != null) {
+            final String url = getAdapter().getURLForPosition(position);
+
+            onUrlOpenListener.onUrlOpen(url, EnumSet.noneOf(HomePager.OnUrlOpenListener.Flags.class));
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/as_topsites_card.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
+                                    android:layout_width="140dp"
+                                    android:layout_height="match_parent"
+                                    xmlns:tools="http://schemas.android.com/tools"
+                                    xmlns:app="http://schemas.android.com/apk/res-auto"
+                                    android:layout_margin="1dp"
+                                    app:cardUseCompatPadding="true">
+
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:background="@android:color/black"
+            android:id="@+id/favicon_frame"
+            android:layout_above="@+id/title"
+            android:layout_alignParentTop="true">
+
+            <org.mozilla.gecko.widget.FaviconView android:id="@+id/favicon"
+                                                  android:layout_width="@dimen/favicon_bg"
+                                                  android:layout_height="@dimen/favicon_bg"
+                                                  tools:background="@drawable/favicon_globe"
+                                                  android:layout_gravity="center"/>
+
+        </FrameLayout>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/title"
+            tools:text="Lorem Ipsum here is a title"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentStart="true"
+            android:layout_alignParentBottom="true"
+            android:layout_toStartOf="@+id/menu_button"
+            android:padding="4dp"
+            android:gravity="center"
+            android:lines="1"
+            android:ellipsize="end"
+            android:textColor="@android:color/black"
+            android:layout_toLeftOf="@+id/menu_button"/>
+
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="24dp"
+            android:padding="6dp"
+            android:src="@drawable/menu"
+            android:id="@+id/menu_button"
+            android:layout_gravity="right|top"
+            android:layout_alignTop="@+id/title"
+            android:layout_alignParentRight="true"
+            android:layout_alignParentEnd="true"/>
+
+    </RelativeLayout>
+</android.support.v7.widget.CardView>
\ No newline at end of file