jenkins-bot has submitted this change and it was merged.

Change subject: Add Feed Activity, Fragment, & card aggregator UI
......................................................................


Add Feed Activity, Fragment, & card aggregator UI

• Add FeedActivity and wire it to the nav drawer. An Activity is used
  to help enforce separation of concerns. It's sparse and may be removed
  for a pure View or Fragment implementation at any time.

• Add bare bones FeedFragment with a FeedView and a button to add
  dummy cards.

• Add supporting components (a couple placeholders and
  AutoFitRecyclerView).

Bug: T129078
Change-Id: Icd6f10097d6b32f40f27b1409ead4ec803acb7ea
---
M app/src/main/AndroidManifest.xml
A app/src/main/java/org/wikipedia/feed/FeedActivity.java
A app/src/main/java/org/wikipedia/feed/FeedFragment.java
A app/src/main/java/org/wikipedia/feed/model/FeedCard.java
A app/src/main/java/org/wikipedia/feed/view/FeedCardView.java
A app/src/main/java/org/wikipedia/feed/view/FeedView.java
M app/src/main/java/org/wikipedia/page/NavDrawerHelper.java
A app/src/main/java/org/wikipedia/views/AutoFitRecyclerView.java
A app/src/main/java/org/wikipedia/views/DefaultViewHolder.java
A app/src/main/res/layout/fragment_feed.xml
A app/src/main/res/layout/view_feed.xml
A app/src/main/res/layout/view_feed_card.xml
M app/src/main/res/values/attrs.xml
M app/src/main/res/values/dimens.xml
M app/src/main/res/values/strings_no_translate.xml
15 files changed, 426 insertions(+), 5 deletions(-)

Approvals:
  Dbrant: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2d34780..dcfbd88 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -54,6 +54,11 @@
         <meta-data android:name="net.hockeyapp.android.appIdentifier"
             android:value="@string/hockeyapp_app_id" />
 
+        <activity android:name=".feed.FeedActivity"
+                  android:label="@string/activity_feed_title"
+                  
android:configChanges="orientation|keyboardHidden|keyboard|screenSize"
+                  />
+
         <activity android:name=".page.PageActivity"
                   android:theme="@style/PageTheme"
                   android:windowSoftInputMode="stateHidden"
diff --git a/app/src/main/java/org/wikipedia/feed/FeedActivity.java 
b/app/src/main/java/org/wikipedia/feed/FeedActivity.java
new file mode 100644
index 0000000..fec2f57
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/feed/FeedActivity.java
@@ -0,0 +1,16 @@
+package org.wikipedia.feed;
+
+import android.content.Context;
+import android.content.Intent;
+
+import org.wikipedia.activity.SingleFragmentActivity;
+
+public class FeedActivity extends SingleFragmentActivity<FeedFragment> {
+    public static Intent newIntent(Context context) {
+        return new Intent(context, FeedActivity.class);
+    }
+
+    @Override protected FeedFragment createFragment() {
+        return FeedFragment.newInstance();
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/wikipedia/feed/FeedFragment.java 
b/app/src/main/java/org/wikipedia/feed/FeedFragment.java
new file mode 100644
index 0000000..69c6244
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/feed/FeedFragment.java
@@ -0,0 +1,63 @@
+package org.wikipedia.feed;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.wikipedia.R;
+import org.wikipedia.activity.CallbackFragment;
+import org.wikipedia.activity.FragmentUtil;
+import org.wikipedia.feed.model.FeedCard;
+import org.wikipedia.feed.view.FeedView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import butterknife.Unbinder;
+
+public class FeedFragment extends Fragment
+        implements CallbackFragment<CallbackFragment.Callback> {
+    @BindView(R.id.fragment_feed_feed) FeedView feedView;
+    private Unbinder unbinder;
+
+    @NonNull private final List<FeedCard> cards = new ArrayList<>();
+
+    public static FeedFragment newInstance() {
+        return new FeedFragment();
+    }
+
+    @Nullable @Override public View onCreateView(LayoutInflater inflater,
+                                                 @Nullable ViewGroup container,
+                                                 @Nullable Bundle 
savedInstanceState) {
+        super.onCreateView(inflater, container, savedInstanceState);
+
+        View view = inflater.inflate(R.layout.fragment_feed, container, false);
+
+        unbinder = ButterKnife.bind(this, view);
+        feedView.set(cards);
+
+        return view;
+    }
+
+    @Override public void onDestroyView() {
+        unbinder.unbind();
+        super.onDestroyView();
+    }
+
+    @Override @Nullable public Callback getCallback() {
+        return FragmentUtil.getCallback(this, Callback.class);
+    }
+
+    // TODO: [Feed] remove.
+    @OnClick(R.id.fragment_feed_add_card) void addCard() {
+        cards.add(new FeedCard());
+        feedView.update();
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/wikipedia/feed/model/FeedCard.java 
b/app/src/main/java/org/wikipedia/feed/model/FeedCard.java
new file mode 100644
index 0000000..f8fabe6
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/feed/model/FeedCard.java
@@ -0,0 +1,37 @@
+package org.wikipedia.feed.model;
+
+import android.support.annotation.ColorInt;
+
+import java.util.Random;
+
+public class FeedCard {
+    // TODO: [Feed] remove this fun data and fill in model data.
+    private final int height;
+    @ColorInt private final int color;
+
+    public FeedCard() {
+        height = randomHeight();
+        color = randomColor();
+    }
+
+    public int height() {
+        return height;
+    }
+
+    @ColorInt public int color() {
+        return color;
+    }
+
+    @ColorInt int randomColor() {
+        final int alphaMask = 0xff000000;
+        final int colorMask = 0x00ffffff;
+        return new Random().nextInt(colorMask) | alphaMask;
+    }
+
+    private int randomHeight() {
+        final int minHeight = 128;
+        final int range = 384;
+        return minHeight + new Random().nextInt(range);
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/wikipedia/feed/view/FeedCardView.java 
b/app/src/main/java/org/wikipedia/feed/view/FeedCardView.java
new file mode 100644
index 0000000..4fa4be4
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/feed/view/FeedCardView.java
@@ -0,0 +1,40 @@
+package org.wikipedia.feed.view;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.CardView;
+
+import org.wikipedia.R;
+import org.wikipedia.feed.model.FeedCard;
+import org.wikipedia.util.DimenUtil;
+
+import butterknife.ButterKnife;
+
+public class FeedCardView extends CardView {
+    public FeedCardView(Context context) {
+        super(context);
+
+        inflate(getContext(), R.layout.view_feed_card, this);
+        ButterKnife.bind(this);
+    }
+
+    public void update(@NonNull FeedCard card) {
+        // TODO: [Feed] replace with real data.
+        setBackgroundColor(card.color());
+        setMinimumHeight(card.height());
+    }
+
+    @Override protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        setLayoutAttrs();
+    }
+
+    private void setLayoutAttrs() {
+        final int margin = Math.round(DimenUtil.getDimension(R.dimen.margin));
+        MarginLayoutParams params = ((MarginLayoutParams) getLayoutParams());
+        params.topMargin = margin;
+        params.rightMargin = margin;
+        params.bottomMargin = margin;
+        params.leftMargin = margin;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/wikipedia/feed/view/FeedView.java 
b/app/src/main/java/org/wikipedia/feed/view/FeedView.java
new file mode 100644
index 0000000..9c2bda8
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/feed/view/FeedView.java
@@ -0,0 +1,101 @@
+package org.wikipedia.feed.view;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView.Adapter;
+import android.support.v7.widget.StaggeredGridLayoutManager;
+import android.util.AttributeSet;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import org.wikipedia.R;
+import org.wikipedia.feed.model.FeedCard;
+import org.wikipedia.views.AutoFitRecyclerView;
+import org.wikipedia.views.DefaultViewHolder;
+
+import java.util.Collections;
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+public class FeedView extends FrameLayout {
+    @BindView(R.id.view_feed_recycler) AutoFitRecyclerView recyclerView;
+    private StaggeredGridLayoutManager recyclerLayoutManager;
+    private RecyclerAdapter recyclerAdapter;
+
+    public FeedView(Context context) {
+        super(context);
+        init();
+    }
+
+    public FeedView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public FeedView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+    }
+
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    public FeedView(Context context, AttributeSet attrs, int defStyleAttr, int 
defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init();
+    }
+
+    public void set(@NonNull List<FeedCard> cards) {
+        // TODO: should this class be responsible for showing a "no items in 
collection" view? It
+        //       would be nice to show placeholder elements while it loads.
+        recyclerAdapter = new RecyclerAdapter(cards);
+        recyclerView.setAdapter(recyclerAdapter);
+    }
+
+    public void update() {
+        recyclerAdapter.notifyDataSetChanged();
+    }
+
+    private void init() {
+        inflate(getContext(), R.layout.view_feed, this);
+        ButterKnife.bind(this);
+        initRecycler();
+    }
+
+    private void initRecycler() {
+        recyclerLayoutManager = new 
StaggeredGridLayoutManager(recyclerView.getColumns(),
+                StaggeredGridLayoutManager.VERTICAL);
+        recyclerView.setLayoutManager(recyclerLayoutManager);
+        recyclerView.callback(new RecyclerViewColumnCallback());
+        set(Collections.<FeedCard>emptyList());
+    }
+
+    private class RecyclerAdapter extends 
Adapter<DefaultViewHolder<FeedCardView>> {
+        @NonNull private final List<FeedCard> cards;
+
+        RecyclerAdapter(@NonNull List<FeedCard> cards) {
+            this.cards = cards;
+        }
+
+        @Override public DefaultViewHolder<FeedCardView> 
onCreateViewHolder(ViewGroup parent,
+                                                                            
int viewType) {
+            return new DefaultViewHolder<>(new FeedCardView(getContext()));
+        }
+
+        @Override public void onBindViewHolder(DefaultViewHolder<FeedCardView> 
holder, int position) {
+            holder.getView().update(cards.get(position));
+        }
+
+        @Override public int getItemCount() {
+            return cards.size();
+        }
+    }
+
+    private class RecyclerViewColumnCallback implements 
AutoFitRecyclerView.Callback {
+        @Override public void onColumns(int columns) {
+            recyclerLayoutManager.setSpanCount(columns);
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/wikipedia/page/NavDrawerHelper.java 
b/app/src/main/java/org/wikipedia/page/NavDrawerHelper.java
index 776334a..4ca6b49 100644
--- a/app/src/main/java/org/wikipedia/page/NavDrawerHelper.java
+++ b/app/src/main/java/org/wikipedia/page/NavDrawerHelper.java
@@ -18,6 +18,8 @@
 import org.wikipedia.WikipediaApp;
 import org.wikipedia.analytics.LoginFunnel;
 import org.wikipedia.analytics.NavMenuFunnel;
+import org.wikipedia.feed.FeedActivity;
+import org.wikipedia.feed.FeedFragment;
 import org.wikipedia.history.HistoryEntry;
 import org.wikipedia.history.HistoryFragment;
 import org.wikipedia.login.LoginActivity;
@@ -76,7 +78,7 @@
             public boolean onNavigationItemSelected(MenuItem menuItem) {
                 switch (menuItem.getItemId()) {
                     case R.id.nav_item_feed:
-                        // TODO: [Feed] show Feed Fragment.
+                        launchFeedActivity();
                         // TODO: [Feed] add Feed logging.
                         break;
                     case R.id.nav_item_today:
@@ -198,10 +200,9 @@
     }
 
     @Nullable @IdRes private Integer fragmentToMenuId(Class<? extends 
Fragment> fragment) {
-        // TODO: [Feed] add Feed Fragment.
-        /*if (fragment == FeedFragment.class) {
+        if (fragment == FeedFragment.class) {
             return R.id.nav_item_feed;
-        } else*/ if (fragment == PageFragment.class) {
+        } else if (fragment == PageFragment.class) {
             return R.id.nav_item_today;
         } else if (fragment == HistoryFragment.class) {
             return R.id.nav_item_history;
@@ -246,6 +247,11 @@
         }
     }
 
+    private void launchFeedActivity() {
+        activity.closeNavDrawer();
+        activity.startActivity(FeedActivity.newIntent(activity));
+    }
+
     private void launchSettingsActivity() {
         activity.closeNavDrawer();
         activity.startActivityForResult(new Intent().setClass(app, 
SettingsActivity.class),
diff --git a/app/src/main/java/org/wikipedia/views/AutoFitRecyclerView.java 
b/app/src/main/java/org/wikipedia/views/AutoFitRecyclerView.java
new file mode 100644
index 0000000..b900c85
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/views/AutoFitRecyclerView.java
@@ -0,0 +1,82 @@
+package org.wikipedia.views;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.annotation.AttrRes;
+import android.support.annotation.IntRange;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+
+import org.wikipedia.R;
+
+/** {@link RecyclerView} that invokes a callback when the number of columns 
should be updated. */
+public class AutoFitRecyclerView extends RecyclerView {
+    public interface Callback {
+        void onColumns(int columns);
+    }
+
+    private static final int MIN_COLUMNS = 1;
+
+    @IntRange(from = MIN_COLUMNS) private int columns = MIN_COLUMNS;
+    private int minColumnWidth;
+
+    @NonNull private Callback callback = new DefaultCallback();
+
+    public AutoFitRecyclerView(Context context) {
+        super(context, null);
+        init(null, 0);
+    }
+
+    public AutoFitRecyclerView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(attrs, 0);
+    }
+
+    public AutoFitRecyclerView(Context context, AttributeSet attrs, int 
defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init(attrs, defStyleAttr);
+    }
+
+    @IntRange(from = MIN_COLUMNS) public int getColumns() {
+        return columns;
+    }
+
+    public void minColumnWidth(int minColumnWidth) {
+        if (this.minColumnWidth != minColumnWidth) {
+            this.minColumnWidth = minColumnWidth;
+            requestLayout();
+        }
+    }
+
+    public void callback(@Nullable Callback callback) {
+        this.callback = callback == null ? new DefaultCallback() : callback;
+    }
+
+    @Override protected void onMeasure(int widthSpec, int heightSpec) {
+        super.onMeasure(widthSpec, heightSpec);
+        int cols = calculateColumns(minColumnWidth, getMeasuredWidth());
+        if (this.columns != cols) {
+            this.columns = cols;
+            callback.onColumns(this.columns);
+        }
+    }
+
+    private int calculateColumns(int columnWidth, int availableWidth) {
+        return columnWidth > 0 ? Math.max(MIN_COLUMNS, availableWidth / 
columnWidth) : MIN_COLUMNS;
+    }
+
+    private void init(@Nullable AttributeSet attrs, @AttrRes int defStyleAttr) 
{
+        if (attrs != null) {
+            TypedArray array = getContext().obtainStyledAttributes(attrs,
+                    R.styleable.AutoFitRecyclerView, defStyleAttr, 0);
+            minColumnWidth = 
array.getDimensionPixelSize(R.styleable.AutoFitRecyclerView_minColumnWidth, 0);
+            array.recycle();
+        }
+    }
+
+    public static class DefaultCallback implements Callback {
+        @Override public void onColumns(int columns) { }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/wikipedia/views/DefaultViewHolder.java 
b/app/src/main/java/org/wikipedia/views/DefaultViewHolder.java
new file mode 100644
index 0000000..42d9d5d
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/views/DefaultViewHolder.java
@@ -0,0 +1,17 @@
+package org.wikipedia.views;
+
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+/** The minimum boilerplate required by the view holder pattern for use with 
custom views. */
+public class DefaultViewHolder<T extends View> extends RecyclerView.ViewHolder 
{
+    public DefaultViewHolder(@NonNull T view) {
+        super(view);
+    }
+
+    @NonNull public T getView() {
+        //noinspection unchecked
+        return (T) itemView;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_feed.xml 
b/app/src/main/res/layout/fragment_feed.xml
new file mode 100644
index 0000000..f8bcbf4
--- /dev/null
+++ b/app/src/main/res/layout/fragment_feed.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android";
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    android:paddingLeft="@dimen/activity_horizontal_margin">
+
+    <TextView
+        style="@style/TextAppearance.AppCompat.Large"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/activity_feed_title" />
+
+    <!-- TODO: [Feed] remove. -->
+    <Button
+        android:id="@+id/fragment_feed_add_card"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Add card" />
+
+    <org.wikipedia.feed.view.FeedView
+        android:id="@+id/fragment_feed_feed"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_feed.xml 
b/app/src/main/res/layout/view_feed.xml
new file mode 100644
index 0000000..bcb3412
--- /dev/null
+++ b/app/src/main/res/layout/view_feed.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge
+    xmlns:android="http://schemas.android.com/apk/res/android";
+    xmlns:app="http://schemas.android.com/apk/res-auto";>
+
+    <org.wikipedia.views.AutoFitRecyclerView
+        android:id="@+id/view_feed_recycler"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:minColumnWidth="@dimen/view_feed_min_column_width" />
+
+</merge>
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_feed_card.xml 
b/app/src/main/res/layout/view_feed_card.xml
new file mode 100644
index 0000000..20fdd3e
--- /dev/null
+++ b/app/src/main/res/layout/view_feed_card.xml
@@ -0,0 +1,4 @@
+<merge
+    xmlns:android="http://schemas.android.com/apk/res/android";>
+    <!-- TODO: [Feed] fill in. -->
+</merge>
\ No newline at end of file
diff --git a/app/src/main/res/values/attrs.xml 
b/app/src/main/res/values/attrs.xml
index 20245f0..9dab66b 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -34,6 +34,10 @@
         <attr name="cabEnabled" format="boolean" />
     </declare-styleable>
 
+    <declare-styleable name="AutoFitRecyclerView">
+        <attr name="minColumnWidth" format="dimension" />
+    </declare-styleable>
+
     <declare-styleable name="EditTextAutoSummarizePreference">
         <attr name="editTextAutoSummarizePreferenceStyle" format="reference" />
         <attr name="autoSummarize" format="boolean" />
diff --git a/app/src/main/res/values/dimens.xml 
b/app/src/main/res/values/dimens.xml
index 04cd6f2..c12dbd9 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -87,4 +87,7 @@
 
     <!-- Article menu bar -->
     <dimen name="view_article_menu_bar_button_padding">12dp</dimen>
+
+    <!-- The Feed -->
+    <dimen name="view_feed_min_column_width">192dp</dimen>
 </resources>
diff --git a/app/src/main/res/values/strings_no_translate.xml 
b/app/src/main/res/values/strings_no_translate.xml
index a9b74d2..9822eb0 100644
--- a/app/src/main/res/values/strings_no_translate.xml
+++ b/app/src/main/res/values/strings_no_translate.xml
@@ -42,6 +42,8 @@
 
     <!-- The Feed -->
     <!-- TODO: [Feed] move translatable strings to strings.xml. -->
-    <string name="nav_item_feed">Home</string>
+    <string name="feed">Home</string>
+    <string name="activity_feed_title">@string/feed</string>
+    <string name="nav_item_feed">@string/feed</string>
     <!-- /The Feed -->
 </resources>

-- 
To view, visit https://gerrit.wikimedia.org/r/289792
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Icd6f10097d6b32f40f27b1409ead4ec803acb7ea
Gerrit-PatchSet: 7
Gerrit-Project: apps/android/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Niedzielski <sniedziel...@wikimedia.org>
Gerrit-Reviewer: BearND <bsitzm...@wikimedia.org>
Gerrit-Reviewer: Brion VIBBER <br...@wikimedia.org>
Gerrit-Reviewer: Dbrant <dbr...@wikimedia.org>
Gerrit-Reviewer: Intrications <mich...@intrications.com>
Gerrit-Reviewer: Mholloway <mhollo...@wikimedia.org>
Gerrit-Reviewer: Niedzielski <sniedziel...@wikimedia.org>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to