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 <[email protected]>
Gerrit-Reviewer: BearND <[email protected]>
Gerrit-Reviewer: Brion VIBBER <[email protected]>
Gerrit-Reviewer: Dbrant <[email protected]>
Gerrit-Reviewer: Intrications <[email protected]>
Gerrit-Reviewer: Mholloway <[email protected]>
Gerrit-Reviewer: Niedzielski <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits