Niedzielski has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/292054

Change subject: Add header & footer View support to Feed list card
......................................................................

Add header & footer View support to Feed list card

The continue reading card, and other cards, have a header View that is a
little different than their item views. Add support for header and
footer Views without requiring adapter index and type logic.

Bug: T130963
Change-Id: I53bbf5dc70223c2775e83f38df49cce6480c7700
---
A app/src/main/java/org/wikipedia/feed/view/CardFooterView.java
A app/src/main/java/org/wikipedia/feed/view/CardHeaderView.java
M app/src/main/java/org/wikipedia/feed/view/ListCardView.java
M app/src/main/java/org/wikipedia/views/DrawableItemDecoration.java
M app/src/main/java/org/wikipedia/views/MarginItemDecoration.java
M app/src/main/java/org/wikipedia/views/ViewUtil.java
A app/src/main/res/drawable/ic_arrow_forward_black_24dp.xml
A app/src/main/res/layout/view_card_footer.xml
A app/src/main/res/layout/view_card_header.xml
M app/src/main/res/layout/view_list_card.xml
M app/src/main/res/values/strings_no_translate.xml
11 files changed, 297 insertions(+), 25 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/apps/android/wikipedia 
refs/changes/54/292054/1

diff --git a/app/src/main/java/org/wikipedia/feed/view/CardFooterView.java 
b/app/src/main/java/org/wikipedia/feed/view/CardFooterView.java
new file mode 100644
index 0000000..ee1b21e
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/feed/view/CardFooterView.java
@@ -0,0 +1,27 @@
+package org.wikipedia.feed.view;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import org.wikipedia.R;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+public class CardFooterView extends RelativeLayout {
+    @BindView(R.id.view_card_footer_text) TextView textView;
+
+    public CardFooterView(Context context) {
+        super(context);
+
+        inflate(getContext(), R.layout.view_card_footer, this);
+        ButterKnife.bind(this);
+    }
+
+    public CardFooterView setText(@Nullable CharSequence text) {
+        textView.setText(text);
+        return this;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/wikipedia/feed/view/CardHeaderView.java 
b/app/src/main/java/org/wikipedia/feed/view/CardHeaderView.java
new file mode 100644
index 0000000..539c247
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/feed/view/CardHeaderView.java
@@ -0,0 +1,44 @@
+package org.wikipedia.feed.view;
+
+import android.content.Context;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.facebook.drawee.view.SimpleDraweeView;
+
+import org.wikipedia.R;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+public class CardHeaderView extends RelativeLayout {
+    @BindView(R.id.view_card_header_image) SimpleDraweeView imageView;
+    @BindView(R.id.view_card_header_title) TextView titleView;
+    @BindView(R.id.view_card_header_subtitle) TextView subtitleView;
+
+    public CardHeaderView(Context context) {
+        super(context);
+
+        inflate(getContext(), R.layout.view_card_header, this);
+        ButterKnife.bind(this);
+    }
+
+    @NonNull
+    public CardHeaderView setImage(@NonNull Uri uri) {
+        imageView.setImageURI(uri);
+        return this;
+    }
+
+    @NonNull public CardHeaderView setTitle(@Nullable CharSequence title) {
+        titleView.setText(title);
+        return this;
+    }
+
+    @NonNull public CardHeaderView setSubtitle(@Nullable CharSequence 
subtitle) {
+        subtitleView.setText(subtitle);
+        return this;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/wikipedia/feed/view/ListCardView.java 
b/app/src/main/java/org/wikipedia/feed/view/ListCardView.java
index 99b9bd8..2beaecf 100644
--- a/app/src/main/java/org/wikipedia/feed/view/ListCardView.java
+++ b/app/src/main/java/org/wikipedia/feed/view/ListCardView.java
@@ -5,6 +5,7 @@
 import android.support.v7.widget.CardView;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
+import android.view.View;
 import android.view.ViewGroup;
 
 import org.wikipedia.R;
@@ -12,6 +13,7 @@
 import org.wikipedia.views.DefaultViewHolder;
 import org.wikipedia.views.DrawableItemDecoration;
 import org.wikipedia.views.MarginItemDecoration;
+import org.wikipedia.views.ViewUtil;
 
 import java.util.Collections;
 import java.util.List;
@@ -20,6 +22,9 @@
 import butterknife.ButterKnife;
 
 public class ListCardView extends CardView {
+    @BindView(R.id.view_list_card_header) View headerView;
+    @BindView(R.id.view_list_card_footer) View footerView;
+
     @BindView(R.id.view_list_card_list) RecyclerView recyclerView;
     private RecyclerAdapter recyclerAdapter;
 
@@ -32,12 +37,44 @@
     }
 
     public void set(@NonNull ListCard card) {
+        header(card);
+        footer(card);
+
         recyclerAdapter = new RecyclerAdapter(card.items());
         recyclerView.setAdapter(recyclerAdapter);
     }
 
-    public void update() {
+    public void update(@NonNull ListCard card) {
+        header(card);
+        footer(card);
+
         recyclerAdapter.notifyDataSetChanged();
+    }
+
+    private void header(@NonNull ListCard card) {
+        // TODO: [Feed] replace.
+        CardHeaderView header = new CardHeaderView(getContext())
+                .setTitle("Continue reading")
+                .setSubtitle("2 days ago");
+        header(header);
+    }
+
+    private void header(@NonNull View view) {
+        ViewUtil.replace(headerView, view);
+        headerView = view;
+    }
+
+    private void footer(@NonNull ListCard card) {
+        // TODO: [Feed] replace.
+        CardFooterView footer = new CardFooterView(getContext())
+                .setText("All top read articles on Fri, April 08");
+        footer.setVisibility(card.items().size() > 2 ? VISIBLE : GONE);
+        footer(footer);
+    }
+
+    private void footer(@NonNull View view) {
+        ViewUtil.replace(footerView, view);
+        footerView = view;
     }
 
     private void initRecycler() {
@@ -45,7 +82,7 @@
         recyclerView.addItemDecoration(new MarginItemDecoration(getContext(),
                 R.dimen.view_list_card_item_margin));
         recyclerView.addItemDecoration(new DrawableItemDecoration(getContext(),
-                R.drawable.divider));
+                R.drawable.divider, true));
         recyclerAdapter = new 
RecyclerAdapter(Collections.<Integer>emptyList());
         recyclerView.setAdapter(recyclerAdapter);
     }
diff --git a/app/src/main/java/org/wikipedia/views/DrawableItemDecoration.java 
b/app/src/main/java/org/wikipedia/views/DrawableItemDecoration.java
index 9e3a2be..ffb794d 100644
--- a/app/src/main/java/org/wikipedia/views/DrawableItemDecoration.java
+++ b/app/src/main/java/org/wikipedia/views/DrawableItemDecoration.java
@@ -2,6 +2,7 @@
 
 import android.content.Context;
 import android.graphics.Canvas;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.support.annotation.DrawableRes;
 import android.support.annotation.NonNull;
@@ -12,32 +13,73 @@
 
 public class DrawableItemDecoration extends RecyclerView.ItemDecoration {
     @Nullable private final Drawable drawable;
+    private final boolean drawEnds;
 
-    public DrawableItemDecoration(@NonNull Context context, @DrawableRes int 
id) {
-        this(ContextCompat.getDrawable(context, id));
+    public DrawableItemDecoration(@NonNull Context context, @DrawableRes int 
id,
+                                  boolean drawEnds) {
+        this(ContextCompat.getDrawable(context, id), drawEnds);
     }
 
-    public DrawableItemDecoration(@Nullable Drawable drawable) {
+    public DrawableItemDecoration(@Nullable Drawable drawable, boolean 
drawEnds) {
         this.drawable = drawable;
+        this.drawEnds = drawEnds;
     }
 
-    @Override public void onDraw(Canvas canvas, RecyclerView parent, 
RecyclerView.State state) {
+    @Override public void onDraw(Canvas canvas, @NonNull RecyclerView parent,
+                                 RecyclerView.State state) {
         super.onDraw(canvas, parent, state);
 
-        if (drawable == null) {
+        if (drawable == null || parent.getChildCount() == 0) {
             return;
         }
 
-        int left = parent.getPaddingLeft();
-        int right = parent.getWidth() - parent.getPaddingRight();
-        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
-
-        for (int i = 0; i < parent.getChildCount(); ++i) {
+        int end = parent.getChildCount() - 1;
+        onDrawHeader(canvas, parent, state, parent.getChildAt(0));
+        for (int i = 1; i < end; ++i) {
             View child = parent.getChildAt(i);
-            int top = layoutManager.getDecoratedBottom(child);
-            int bottom = top + drawable.getIntrinsicHeight();
-            drawable.setBounds(left, top, right, bottom);
-            drawable.draw(canvas);
+            onDrawItem(canvas, parent, state, child);
+        }
+        onDrawFooter(canvas, parent, state, parent.getChildAt(end));
+    }
+
+    private void onDrawHeader(Canvas canvas, @NonNull RecyclerView parent,
+                              RecyclerView.State state, @NonNull View child) {
+        if (drawEnds) {
+            draw(canvas, bounds(parent, child, true));
+        }
+        draw(canvas, bounds(parent, child, false));
+    }
+
+    private void onDrawFooter(Canvas canvas, @NonNull RecyclerView parent,
+                              RecyclerView.State state, @NonNull View child) {
+        if (drawEnds) {
+            draw(canvas, bounds(parent, child, false));
         }
     }
-}
\ No newline at end of file
+
+    private void onDrawItem(Canvas canvas, @NonNull RecyclerView parent, 
RecyclerView.State state,
+                            @NonNull View child) {
+
+        draw(canvas, bounds(parent, child, false));
+    }
+
+    private Rect bounds(@NonNull RecyclerView parent, @NonNull View child, 
boolean top) {
+        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
+
+        Rect bounds = new Rect();
+        bounds.right = parent.getWidth() - parent.getPaddingRight();
+        bounds.left = parent.getPaddingLeft();
+        int height = drawable.getIntrinsicHeight();
+        bounds.top = top
+                ? layoutManager.getDecoratedTop(child)
+                : layoutManager.getDecoratedBottom(child) - height;
+        bounds.bottom = bounds.top + height;
+
+        return bounds;
+    }
+
+    private void draw(Canvas canvas, @NonNull Rect bounds) {
+        drawable.setBounds(bounds);
+        drawable.draw(canvas);
+    }
+}
diff --git a/app/src/main/java/org/wikipedia/views/MarginItemDecoration.java 
b/app/src/main/java/org/wikipedia/views/MarginItemDecoration.java
index 88b18bf..d19927d 100644
--- a/app/src/main/java/org/wikipedia/views/MarginItemDecoration.java
+++ b/app/src/main/java/org/wikipedia/views/MarginItemDecoration.java
@@ -8,7 +8,7 @@
 import android.view.View;
 
 public class MarginItemDecoration extends RecyclerView.ItemDecoration {
-    private final Rect rect = new Rect();
+    private final Rect offsets = new Rect();
 
     public MarginItemDecoration(@NonNull Context context, @DimenRes int id) {
         this(pixelSize(context, id));
@@ -25,13 +25,13 @@
     }
 
     public MarginItemDecoration(int leftMargin, int topMargin, int 
rightMargin, int bottomMargin) {
-        rect.set(leftMargin, topMargin, rightMargin, bottomMargin);
+        offsets.set(leftMargin, topMargin, rightMargin, bottomMargin);
     }
 
     @Override public void getItemOffsets(Rect outRect, View view, RecyclerView 
parent,
                                          RecyclerView.State state) {
         super.getItemOffsets(outRect, view, parent, state);
-        outRect.set(rect);
+        outRect.set(offsets);
     }
 
     private static int pixelSize(@NonNull Context context, @DimenRes int id) {
diff --git a/app/src/main/java/org/wikipedia/views/ViewUtil.java 
b/app/src/main/java/org/wikipedia/views/ViewUtil.java
index ede9913..7c7f5f0 100644
--- a/app/src/main/java/org/wikipedia/views/ViewUtil.java
+++ b/app/src/main/java/org/wikipedia/views/ViewUtil.java
@@ -8,9 +8,10 @@
 import android.os.Build;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
-import android.view.ActionMode;
 import android.text.TextUtils;
+import android.view.ActionMode;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.ViewManager;
 import android.view.animation.Animation;
 
@@ -42,7 +43,11 @@
         return false;
     }
 
-    public static void setBottomPaddingDp(View view, int padding) {
+    public static void setPadding(@NonNull View view, int padding) {
+        view.setPadding(padding, padding, padding, padding);
+    }
+
+    public static void setBottomPaddingDp(@NonNull View view, int padding) {
         view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), 
view.getPaddingRight(),
                 (int) (padding * DimenUtil.getDensityScalar()));
     }
@@ -84,5 +89,32 @@
         return returnedBitmap;
     }
 
+    @Nullable public static ViewGroup parent(@NonNull View view) {
+        return view.getParent() instanceof ViewGroup ? (ViewGroup) 
view.getParent() : null;
+    }
+
+    public static void remove(@NonNull View view) {
+        ViewManager parent = parent(view);
+        if (parent != null) {
+            parent.removeView(view);
+        }
+    }
+
+    /** Replace the current View with a new View by copying the ID and 
LayoutParams (by reference). */
+    public static void replace(@NonNull View current, @NonNull View next) {
+        ViewGroup parent = parent(current);
+        if (parent == null || parent(next) != null) {
+            String msg = "Parent of current View must be nonnull; parent of 
next View must be null.";
+            throw new IllegalStateException(msg);
+        }
+
+        next.setId(current.getId());
+        next.setLayoutParams(current.getLayoutParams());
+
+        int index = parent.indexOfChild(current);
+        remove(current);
+        parent.addView(next, index);
+    }
+
     private ViewUtil() { }
 }
diff --git a/app/src/main/res/drawable/ic_arrow_forward_black_24dp.xml 
b/app/src/main/res/drawable/ic_arrow_forward_black_24dp.xml
new file mode 100644
index 0000000..0d8872d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_arrow_forward_black_24dp.xml
@@ -0,0 +1,5 @@
+<vector android:autoMirrored="true" android:height="24dp"
+    android:viewportHeight="24.0" android:viewportWidth="24.0"
+    android:width="24dp" 
xmlns:android="http://schemas.android.com/apk/res/android";>
+    <path android:fillColor="#f666" 
android:pathData="M12,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z"/>
+</vector>
diff --git a/app/src/main/res/layout/view_card_footer.xml 
b/app/src/main/res/layout/view_card_footer.xml
new file mode 100644
index 0000000..60d76aa
--- /dev/null
+++ b/app/src/main/res/layout/view_card_footer.xml
@@ -0,0 +1,29 @@
+<merge
+    xmlns:android="http://schemas.android.com/apk/res/android";
+    xmlns:app="http://schemas.android.com/apk/res-auto";>
+
+    <ImageButton
+        style="@style/Widget.AppCompat.Toolbar.Button.Navigation"
+        android:id="@+id/view_card_footer_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentEnd="true"
+        app:srcCompat="@drawable/ic_arrow_forward_black_24dp"
+        android:contentDescription="@string/view_card_footer_button" />
+
+    <!-- TODO: [Feed] If making the whole cell clickable, consider using a 
compound drawable:
+               1 Drop the ImageButton.
+               2 Set the TextView width to match_parent.
+               3 Add android:drawableEnd/Right. -->
+    <TextView
+        android:id="@+id/view_card_footer_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentStart="true"
+        android:layout_alignRight="@id/view_card_footer_button"
+        android:layout_alignEnd="@id/view_card_footer_button"
+        android:textAppearance="@style/TextAppearance.AppCompat.Small" />
+
+</merge>
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_card_header.xml 
b/app/src/main/res/layout/view_card_header.xml
new file mode 100644
index 0000000..8539553
--- /dev/null
+++ b/app/src/main/res/layout/view_card_header.xml
@@ -0,0 +1,36 @@
+<merge xmlns:android="http://schemas.android.com/apk/res/android";>
+
+    <com.facebook.drawee.view.SimpleDraweeView
+        style="@style/SimpleDraweeViewPlaceholder.Article"
+        android:id="@+id/view_card_header_image"
+        android:layout_width="@dimen/defaultListItemSize"
+        android:layout_height="@dimen/defaultListItemSize"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentStart="true" />
+
+    <TextView
+        android:id="@+id/view_card_header_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_toRightOf="@id/view_card_header_image"
+        android:layout_toEndOf="@id/view_card_header_image"
+        android:layout_marginLeft="24dp"
+        android:layout_marginRight="24dp"
+        android:textAppearance="@style/TextAppearance.AppCompat.Medium"
+        android:textSize="16sp" />
+
+    <TextView
+        android:id="@+id/view_card_header_subtitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_toRightOf="@id/view_card_header_image"
+        android:layout_toEndOf="@id/view_card_header_image"
+        android:layout_below="@id/view_card_header_title"
+        android:layout_marginLeft="24dp"
+        android:layout_marginRight="24dp"
+        android:textAppearance="@style/TextAppearance.AppCompat.Large"
+        android:textSize="18sp"
+        android:textColor="?android:attr/textColorSecondary"
+        android:textStyle="bold" />
+
+</merge>
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_list_card.xml 
b/app/src/main/res/layout/view_list_card.xml
index 329ebd1..cc1b448 100644
--- a/app/src/main/res/layout/view_list_card.xml
+++ b/app/src/main/res/layout/view_list_card.xml
@@ -1,8 +1,27 @@
-<merge xmlns:android="http://schemas.android.com/apk/res/android";>
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android";
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <View
+        android:id="@+id/view_list_card_header"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/view_list_card_item_margin"
+        android:visibility="gone" />
 
     <android.support.v7.widget.RecyclerView
         android:id="@+id/view_list_card_list"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
+        android:layout_height="wrap_content"
+        android:layout_below="@id/view_list_card_header" />
 
-</merge>
\ No newline at end of file
+    <View
+        android:id="@+id/view_list_card_footer"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/view_list_card_list"
+        android:layout_margin="@dimen/view_list_card_item_margin"
+        android:visibility="gone" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings_no_translate.xml 
b/app/src/main/res/values/strings_no_translate.xml
index 9822eb0..4d51ced 100644
--- a/app/src/main/res/values/strings_no_translate.xml
+++ b/app/src/main/res/values/strings_no_translate.xml
@@ -45,5 +45,6 @@
     <string name="feed">Home</string>
     <string name="activity_feed_title">@string/feed</string>
     <string name="nav_item_feed">@string/feed</string>
+    <string name="view_card_footer_button">See more</string>
     <!-- /The Feed -->
 </resources>

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: I53bbf5dc70223c2775e83f38df49cce6480c7700
Gerrit-PatchSet: 1
Gerrit-Project: apps/android/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Niedzielski <sniedziel...@wikimedia.org>

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

Reply via email to