Dbrant has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/389872 )

Change subject: New: Make Feed content completely configurable.
......................................................................

New: Make Feed content completely configurable.

Bug: T141397
Change-Id: Icace97fe0af8490983555bcd0661ec237d051af7
---
M app/src/main/AndroidManifest.xml
M app/src/main/java/org/wikipedia/Constants.java
A app/src/main/java/org/wikipedia/feed/FeedContentType.java
M app/src/main/java/org/wikipedia/feed/FeedCoordinator.java
M app/src/main/java/org/wikipedia/feed/FeedCoordinatorBase.java
M app/src/main/java/org/wikipedia/feed/FeedFragment.java
A app/src/main/java/org/wikipedia/feed/configure/ConfigureActivity.java
A app/src/main/java/org/wikipedia/feed/configure/ConfigureFragment.java
A app/src/main/java/org/wikipedia/feed/configure/ConfigureItemView.java
M app/src/main/java/org/wikipedia/settings/Prefs.java
M app/src/main/java/org/wikipedia/views/ExploreOverflowView.java
A app/src/main/res/drawable/ic_drag_handle_black_24dp.xml
A app/src/main/res/layout/fragment_feed_configure.xml
A app/src/main/res/layout/item_feed_content_type.xml
M app/src/main/res/layout/view_explore_overflow.xml
A app/src/main/res/menu/menu_feed_configure.xml
M app/src/main/res/values-qq/strings.xml
M app/src/main/res/values/preference_keys.xml
M app/src/main/res/values/strings.xml
19 files changed, 628 insertions(+), 26 deletions(-)


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

diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 02b4856..8448bf2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -236,6 +236,10 @@
             android:label="@string/on_this_day"
             android:theme="@style/AppTheme.ActionBar"/>
 
+        <activity android:name=".feed.configure.ConfigureActivity"
+            android:label="@string/feed_configure_activity_title"
+            android:theme="@style/AppTheme.ActionBar"/>
+
         <provider
             android:authorities="${applicationId}"
             android:name=".database.AppContentProvider"
diff --git a/app/src/main/java/org/wikipedia/Constants.java 
b/app/src/main/java/org/wikipedia/Constants.java
index 8d42d6e..2e7cef1 100644
--- a/app/src/main/java/org/wikipedia/Constants.java
+++ b/app/src/main/java/org/wikipedia/Constants.java
@@ -24,6 +24,7 @@
     public static final int ACTIVITY_REQUEST_DESCRIPTION_EDIT = 55;
     public static final int ACTIVITY_REQUEST_DESCRIPTION_EDIT_TUTORIAL = 56;
     public static final int ACTIVITY_REQUEST_OFFLINE_TUTORIAL = 57;
+    public static final int ACTIVITY_REQUEST_FEED_CONFIGURE = 58;
 
     public static final String INTENT_RETURN_TO_MAIN = "returnToMain";
     public static final String INTENT_SEARCH_FROM_WIDGET = "searchFromWidget";
diff --git a/app/src/main/java/org/wikipedia/feed/FeedContentType.java 
b/app/src/main/java/org/wikipedia/feed/FeedContentType.java
new file mode 100644
index 0000000..0731980
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/feed/FeedContentType.java
@@ -0,0 +1,153 @@
+package org.wikipedia.feed;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
+
+import org.wikipedia.R;
+import org.wikipedia.feed.aggregated.AggregatedFeedContentClient;
+import org.wikipedia.feed.becauseyouread.BecauseYouReadClient;
+import org.wikipedia.feed.continuereading.ContinueReadingClient;
+import org.wikipedia.feed.dataclient.FeedClient;
+import org.wikipedia.feed.mainpage.MainPageClient;
+import org.wikipedia.feed.onthisday.OnThisDayClient;
+import org.wikipedia.feed.random.RandomClient;
+import org.wikipedia.model.EnumCode;
+import org.wikipedia.model.EnumCodeMap;
+import org.wikipedia.settings.Prefs;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.wikipedia.util.ReleaseUtil.isPreBetaRelease;
+
+public enum FeedContentType implements EnumCode {
+    NEWS(0, R.string.view_card_news_title) {
+        @Nullable
+        @Override
+        public FeedClient newClient(AggregatedFeedContentClient 
aggregatedClient, int age, boolean isOnline) {
+            return isEnabled() && age == 0 && isOnline ? new 
AggregatedFeedContentClient.InTheNews(aggregatedClient) : null;
+        }
+    },
+    FEATURED_ARTICLE(1, R.string.view_featured_article_card_title) {
+        @Nullable
+        @Override
+        public FeedClient newClient(AggregatedFeedContentClient 
aggregatedClient, int age, boolean isOnline) {
+            return isEnabled() && isOnline ? new 
AggregatedFeedContentClient.FeaturedArticle(aggregatedClient) : null;
+        }
+    },
+    TRENDING_ARTICLES(2, R.string.most_read_list_card_title) {
+        @Nullable
+        @Override
+        public FeedClient newClient(AggregatedFeedContentClient 
aggregatedClient, int age, boolean isOnline) {
+            return isEnabled() && isOnline ? new 
AggregatedFeedContentClient.TrendingArticles(aggregatedClient) : null;
+        }
+    },
+    FEATURED_IMAGE(3, R.string.view_featured_image_card_title) {
+        @Nullable
+        @Override
+        public FeedClient newClient(AggregatedFeedContentClient 
aggregatedClient, int age, boolean isOnline) {
+            return isEnabled() && isOnline ? new 
AggregatedFeedContentClient.FeaturedImage(aggregatedClient) : null;
+        }
+    },
+    ON_THIS_DAY(4, R.string.on_this_day_card_title) {
+        @Nullable
+        @Override
+        public FeedClient newClient(AggregatedFeedContentClient 
aggregatedClient, int age, boolean isOnline) {
+            return isEnabled() && isOnline && isPreBetaRelease() ? new 
OnThisDayClient() : null;
+        }
+    },
+    CONTINUE_READING(5, R.string.view_continue_reading_card_title) {
+        @Nullable
+        @Override
+        public FeedClient newClient(AggregatedFeedContentClient 
aggregatedClient, int age, boolean isOnline) {
+            return isEnabled() ? new ContinueReadingClient() : null;
+        }
+    },
+    BECAUSE_YOU_READ(6, R.string.view_because_you_read_card_title) {
+        @Nullable
+        @Override
+        public FeedClient newClient(AggregatedFeedContentClient 
aggregatedClient, int age, boolean isOnline) {
+            return isEnabled() && isOnline ? new BecauseYouReadClient() : null;
+        }
+    },
+    MAIN_PAGE(7, R.string.view_main_page_card_title) {
+        @Nullable
+        @Override
+        public FeedClient newClient(AggregatedFeedContentClient 
aggregatedClient, int age, boolean isOnline) {
+            return isEnabled() && age == 0 ? new MainPageClient() : null;
+        }
+    },
+    RANDOM(8, R.string.view_random_card_title) {
+        @Nullable
+        @Override
+        public FeedClient newClient(AggregatedFeedContentClient 
aggregatedClient, int age, boolean isOnline) {
+            return isEnabled() && age % 2 == 0 ? new RandomClient() : null;
+        }
+    };
+
+    private static final EnumCodeMap<FeedContentType> MAP
+            = new EnumCodeMap<>(FeedContentType.class);
+    private final int code;
+    @StringRes private final int titleId;
+    private int order;
+    private boolean enabled = true;
+
+    @NonNull public static FeedContentType of(int code) {
+        return MAP.get(code);
+    }
+
+    @Nullable
+    public abstract FeedClient newClient(AggregatedFeedContentClient 
aggregatedClient,
+                                         int age, boolean isOnline);
+
+    @Override public int code() {
+        return code;
+    }
+
+    public int titleId() {
+        return titleId;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public Integer getOrder() {
+        return order;
+    }
+
+    public void setOrder(int order) {
+        this.order = order;
+    }
+
+    FeedContentType(int code, @StringRes int titleId) {
+        this.code = code;
+        this.order = code;
+        this.titleId = titleId;
+    }
+
+    public static void saveState() {
+        List<Boolean> enabledList = new ArrayList<>();
+        List<Integer> orderList = new ArrayList<>();
+        for (int i = 0; i < FeedContentType.values().length; i++) {
+            enabledList.add(FeedContentType.values()[i].isEnabled());
+            orderList.add(FeedContentType.values()[i].getOrder());
+        }
+        Prefs.setFeedCardsEnabled(enabledList);
+        Prefs.setFeedCardsOrder(orderList);
+    }
+
+    public static void restoreState() {
+        List<Boolean> enabledList = Prefs.getFeedCardsEnabled();
+        List<Integer> orderList = Prefs.getFeedCardsOrder();
+        for (int i = 0; i < FeedContentType.values().length; i++) {
+            FeedContentType.values()[i].setEnabled(i < enabledList.size() ? 
enabledList.get(i) : true);
+            FeedContentType.values()[i].setOrder(i < orderList.size() ? 
orderList.get(i) : i);
+        }
+    }
+}
diff --git a/app/src/main/java/org/wikipedia/feed/FeedCoordinator.java 
b/app/src/main/java/org/wikipedia/feed/FeedCoordinator.java
index 5af0e97..1d4a040 100644
--- a/app/src/main/java/org/wikipedia/feed/FeedCoordinator.java
+++ b/app/src/main/java/org/wikipedia/feed/FeedCoordinator.java
@@ -5,24 +5,23 @@
 
 import org.wikipedia.feed.aggregated.AggregatedFeedContentClient;
 import org.wikipedia.feed.announcement.AnnouncementClient;
-import org.wikipedia.feed.becauseyouread.BecauseYouReadClient;
-import org.wikipedia.feed.continuereading.ContinueReadingClient;
-import org.wikipedia.feed.mainpage.MainPageClient;
 import org.wikipedia.feed.offline.OfflineCompilationClient;
 import org.wikipedia.feed.onboarding.OnboardingClient;
-import org.wikipedia.feed.onthisday.OnThisDayClient;
-import org.wikipedia.feed.random.RandomClient;
 import org.wikipedia.feed.searchbar.SearchClient;
 import org.wikipedia.offline.OfflineManager;
 import org.wikipedia.util.DeviceUtil;
 
-import static org.wikipedia.util.ReleaseUtil.isPreBetaRelease;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 
-class FeedCoordinator extends FeedCoordinatorBase {
+public class FeedCoordinator extends FeedCoordinatorBase {
     @NonNull private AggregatedFeedContentClient aggregatedClient = new 
AggregatedFeedContentClient();
 
     FeedCoordinator(@NonNull Context context) {
         super(context);
+        FeedContentType.restoreState();
     }
 
     @Override
@@ -33,14 +32,14 @@
         conditionallyAddPendingClient(new OfflineCompilationClient(), age == 0 
&& !online && OfflineManager.hasCompilation());
         conditionallyAddPendingClient(new OnboardingClient(), age == 0);
         conditionallyAddPendingClient(new AnnouncementClient(), age == 0 && 
online);
-        conditionallyAddPendingClient(new 
AggregatedFeedContentClient.InTheNews(aggregatedClient), online);
-        conditionallyAddPendingClient(new 
AggregatedFeedContentClient.FeaturedArticle(aggregatedClient), online);
-        conditionallyAddPendingClient(new 
AggregatedFeedContentClient.TrendingArticles(aggregatedClient), online);
-        conditionallyAddPendingClient(new 
AggregatedFeedContentClient.FeaturedImage(aggregatedClient), online);
-        addPendingClient(new ContinueReadingClient());
-        conditionallyAddPendingClient(new OnThisDayClient(), online && 
isPreBetaRelease());
-        conditionallyAddPendingClient(new MainPageClient(), age == 0);
-        conditionallyAddPendingClient(new BecauseYouReadClient(), online);
-        conditionallyAddPendingClient(new RandomClient(), age == 0);
+
+        List<FeedContentType> orderedContentTypes = new ArrayList<>();
+        orderedContentTypes.addAll(Arrays.asList(FeedContentType.values()));
+        Collections.sort(orderedContentTypes, (FeedContentType a, 
FeedContentType b)
+                -> a.getOrder().compareTo(b.getOrder()));
+
+        for (FeedContentType contentType : orderedContentTypes) {
+            addPendingClient(contentType.newClient(aggregatedClient, age, 
online));
+        }
     }
 }
diff --git a/app/src/main/java/org/wikipedia/feed/FeedCoordinatorBase.java 
b/app/src/main/java/org/wikipedia/feed/FeedCoordinatorBase.java
index f5fc669..11a1894 100644
--- a/app/src/main/java/org/wikipedia/feed/FeedCoordinatorBase.java
+++ b/app/src/main/java/org/wikipedia/feed/FeedCoordinatorBase.java
@@ -69,15 +69,15 @@
         cards.clear();
     }
 
+    public void incrementAge() {
+        currentAge++;
+    }
+
     public void more(@NonNull WikiSite wiki) {
         this.wiki = wiki;
 
         if (cards.size() == 0) {
             insertCard(progressCard, 0);
-        }
-
-        if (cards.size() > 1) {
-            currentAge++;
         }
 
         buildScript(currentAge);
@@ -114,12 +114,14 @@
 
     protected abstract void buildScript(int age);
 
-    void addPendingClient(FeedClient client) {
-        pendingClients.add(client);
+    void addPendingClient(@Nullable FeedClient client) {
+        if (client != null) {
+            pendingClients.add(client);
+        }
     }
 
-    void conditionallyAddPendingClient(FeedClient client, boolean condition) {
-        if (condition) {
+    void conditionallyAddPendingClient(@Nullable FeedClient client, boolean 
condition) {
+        if (condition && client != null) {
             pendingClients.add(client);
         }
     }
diff --git a/app/src/main/java/org/wikipedia/feed/FeedFragment.java 
b/app/src/main/java/org/wikipedia/feed/FeedFragment.java
index afacfa9..8d4e294 100644
--- a/app/src/main/java/org/wikipedia/feed/FeedFragment.java
+++ b/app/src/main/java/org/wikipedia/feed/FeedFragment.java
@@ -19,10 +19,12 @@
 
 import org.wikipedia.BackPressedHandler;
 import org.wikipedia.BuildConfig;
+import org.wikipedia.Constants;
 import org.wikipedia.R;
 import org.wikipedia.WikipediaApp;
 import org.wikipedia.activity.FragmentUtil;
 import org.wikipedia.analytics.FeedFunnel;
+import org.wikipedia.feed.configure.ConfigureActivity;
 import org.wikipedia.feed.featured.FeaturedArticleCard;
 import org.wikipedia.feed.image.FeaturedImage;
 import org.wikipedia.feed.image.FeaturedImageCard;
@@ -51,6 +53,7 @@
 import butterknife.Unbinder;
 
 import static android.app.Activity.RESULT_OK;
+import static org.wikipedia.Constants.ACTIVITY_REQUEST_FEED_CONFIGURE;
 import static org.wikipedia.Constants.ACTIVITY_REQUEST_OFFLINE_TUTORIAL;
 
 public class FeedFragment extends Fragment implements BackPressedHandler {
@@ -196,6 +199,9 @@
             Prefs.setOfflineTutorialEnabled(false);
             refresh();
             feedCallback.onViewCompilations();
+        } else if (requestCode == ACTIVITY_REQUEST_FEED_CONFIGURE
+                && resultCode == 
ConfigureActivity.CONFIGURATION_CHANGED_RESULT) {
+            refresh();
         }
     }
 
@@ -311,7 +317,12 @@
         @Override
         public void onRequestMore() {
             funnel.requestMore(coordinator.getAge());
-            coordinator.more(app.getWikiSite());
+            feedView.post(() -> {
+                if (isAdded()) {
+                    coordinator.incrementAge();
+                    coordinator.more(app.getWikiSite());
+                }
+            });
         }
 
         @Override
@@ -530,6 +541,12 @@
         }
 
         @Override
+        public void configureCardsClick() {
+            startActivityForResult(ConfigureActivity.newIntent(getActivity()),
+                    Constants.ACTIVITY_REQUEST_FEED_CONFIGURE);
+        }
+
+        @Override
         public void logoutClick() {
             WikipediaApp.getInstance().logOut();
             FeedbackUtil.showMessage(FeedFragment.this, 
R.string.toast_logout_complete);
diff --git 
a/app/src/main/java/org/wikipedia/feed/configure/ConfigureActivity.java 
b/app/src/main/java/org/wikipedia/feed/configure/ConfigureActivity.java
new file mode 100644
index 0000000..b7341ea
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/feed/configure/ConfigureActivity.java
@@ -0,0 +1,20 @@
+package org.wikipedia.feed.configure;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.annotation.NonNull;
+
+import org.wikipedia.activity.SingleFragmentActivity;
+
+public class ConfigureActivity extends 
SingleFragmentActivity<ConfigureFragment> {
+    public static final int CONFIGURATION_CHANGED_RESULT = 1;
+
+    public static Intent newIntent(@NonNull Context context) {
+        return new Intent(context, ConfigureActivity.class);
+    }
+
+    @Override
+    protected ConfigureFragment createFragment() {
+        return ConfigureFragment.newInstance();
+    }
+}
diff --git 
a/app/src/main/java/org/wikipedia/feed/configure/ConfigureFragment.java 
b/app/src/main/java/org/wikipedia/feed/configure/ConfigureFragment.java
new file mode 100644
index 0000000..340021e
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/feed/configure/ConfigureFragment.java
@@ -0,0 +1,210 @@
+package org.wikipedia.feed.configure;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.Fragment;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchHelper;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.wikipedia.R;
+import org.wikipedia.feed.FeedContentType;
+import org.wikipedia.settings.Prefs;
+import org.wikipedia.views.DefaultViewHolder;
+import org.wikipedia.views.HeaderMarginItemDecoration;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.Unbinder;
+
+public class ConfigureFragment extends Fragment implements 
ConfigureItemView.Callback {
+    @BindView(R.id.content_types_recycler) RecyclerView recyclerView;
+    private Unbinder unbinder;
+    private ItemTouchHelper itemTouchHelper;
+    private List<FeedContentType> orderedContentTypes = new ArrayList<>();
+
+    @NonNull public static ConfigureFragment newInstance() {
+        return new ConfigureFragment();
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, 
Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.fragment_feed_configure, 
container, false);
+        unbinder = ButterKnife.bind(this, view);
+
+        prepareContentTypeList();
+        setupRecyclerView();
+        return view;
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        setHasOptionsMenu(true);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        FeedContentType.saveState();
+    }
+
+    @Override
+    public void onDestroyView() {
+        unbinder.unbind();
+        unbinder = null;
+        super.onDestroyView();
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        inflater.inflate(R.menu.menu_feed_configure, menu);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.menu_feed_configure_reset:
+                Prefs.resetFeedCustomizations();
+                FeedContentType.restoreState();
+                prepareContentTypeList();
+                
getActivity().setResult(ConfigureActivity.CONFIGURATION_CHANGED_RESULT);
+                recyclerView.getAdapter().notifyDataSetChanged();
+                return true;
+            default:
+                return super.onOptionsItemSelected(item);
+        }
+    }
+
+    private void prepareContentTypeList() {
+        orderedContentTypes.clear();
+        orderedContentTypes.addAll(Arrays.asList(FeedContentType.values()));
+        Collections.sort(orderedContentTypes, (FeedContentType a, 
FeedContentType b)
+                -> a.getOrder().compareTo(b.getOrder()));
+    }
+
+    private void setupRecyclerView() {
+        recyclerView.setHasFixedSize(true);
+        ConfigureItemAdapter adapter = new ConfigureItemAdapter();
+        recyclerView.setAdapter(adapter);
+        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
+
+        recyclerView.addItemDecoration(new 
HeaderMarginItemDecoration(getContext(),
+                R.dimen.view_feed_padding_top,
+                R.dimen.view_feed_padding_top));
+
+        itemTouchHelper = new ItemTouchHelper(new 
RearrangeableItemTouchHelperCallback(adapter));
+        itemTouchHelper.attachToRecyclerView(recyclerView);
+    }
+
+    @Override
+    public void onCheckedChanged(FeedContentType contentType, boolean checked) 
{
+        
getActivity().setResult(ConfigureActivity.CONFIGURATION_CHANGED_RESULT);
+        contentType.setEnabled(checked);
+    }
+
+    private void updateItemOrder() {
+        
getActivity().setResult(ConfigureActivity.CONFIGURATION_CHANGED_RESULT);
+        for (int i = 0; i < orderedContentTypes.size(); i++) {
+            orderedContentTypes.get(i).setOrder(i);
+        }
+    }
+
+    private class ConfigureItemHolder extends 
DefaultViewHolder<ConfigureItemView> {
+        ConfigureItemHolder(ConfigureItemView itemView) {
+            super(itemView);
+        }
+
+        void bindItem(FeedContentType contentType) {
+            getView().setContents(contentType);
+        }
+    }
+
+    private final class ConfigureItemAdapter extends 
RecyclerView.Adapter<ConfigureItemHolder> {
+        @Override
+        public int getItemCount() {
+            return orderedContentTypes.size();
+        }
+
+        @Override
+        public ConfigureItemHolder onCreateViewHolder(ViewGroup parent, int 
type) {
+            return new ConfigureItemHolder(new 
ConfigureItemView(getContext()));
+        }
+
+        @Override
+        public void onBindViewHolder(ConfigureItemHolder holder, int pos) {
+            holder.bindItem(orderedContentTypes.get(pos));
+        }
+
+        @Override public void onViewAttachedToWindow(ConfigureItemHolder 
holder) {
+            super.onViewAttachedToWindow(holder);
+            holder.getView().setDragHandleTouchListener(new 
View.OnTouchListener() {
+                @Override
+                public boolean onTouch(View v, MotionEvent event) {
+                    if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+                        itemTouchHelper.startDrag(holder);
+                    }
+                    return false;
+                }
+            });
+            holder.getView().setCallback(ConfigureFragment.this);
+        }
+
+        @Override public void onViewDetachedFromWindow(ConfigureItemHolder 
holder) {
+            holder.getView().setCallback(null);
+            holder.getView().setDragHandleTouchListener(null);
+            super.onViewDetachedFromWindow(holder);
+        }
+
+        void onMoveItem(int oldPosition, int newPosition) {
+            Collections.swap(orderedContentTypes, oldPosition, newPosition);
+            updateItemOrder();
+            notifyItemMoved(oldPosition, newPosition);
+        }
+    }
+
+    private final class RearrangeableItemTouchHelperCallback extends 
ItemTouchHelper.Callback {
+        private final ConfigureItemAdapter adapter;
+
+        RearrangeableItemTouchHelperCallback(ConfigureItemAdapter adapter) {
+            this.adapter = adapter;
+        }
+
+        @Override
+        public boolean isLongPressDragEnabled() {
+            return true;
+        }
+
+        @Override
+        public boolean isItemViewSwipeEnabled() {
+            return false;
+        }
+
+        @Override
+        public void onSwiped(RecyclerView.ViewHolder viewHolder, int 
direction) {
+        }
+
+        @Override
+        public int getMovementFlags(RecyclerView recyclerView, 
RecyclerView.ViewHolder viewHolder) {
+            return makeMovementFlags(ItemTouchHelper.UP | 
ItemTouchHelper.DOWN, 0);
+        }
+
+        @Override
+        public boolean onMove(RecyclerView recyclerView, 
RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {
+            adapter.onMoveItem(source.getAdapterPosition(), 
target.getAdapterPosition());
+            return true;
+        }
+    }
+}
diff --git 
a/app/src/main/java/org/wikipedia/feed/configure/ConfigureItemView.java 
b/app/src/main/java/org/wikipedia/feed/configure/ConfigureItemView.java
new file mode 100644
index 0000000..4e6bfe4
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/feed/configure/ConfigureItemView.java
@@ -0,0 +1,69 @@
+package org.wikipedia.feed.configure;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.FrameLayout;
+
+import org.wikipedia.R;
+import org.wikipedia.feed.FeedContentType;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnCheckedChanged;
+
+public class ConfigureItemView extends FrameLayout {
+    public interface Callback {
+        void onCheckedChanged(FeedContentType contentType, boolean checked);
+    }
+
+    @BindView(R.id.feed_content_type_checkbox) CheckBox titleView;
+    @BindView(R.id.feed_content_type_drag_handle) View dragHandleView;
+    @Nullable private Callback callback;
+    private FeedContentType contentType;
+
+    public ConfigureItemView(Context context) {
+        super(context);
+        init();
+    }
+
+    public ConfigureItemView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public ConfigureItemView(Context context, AttributeSet attrs, int 
defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+    }
+
+    public void setCallback(@Nullable Callback callback) {
+        this.callback = callback;
+    }
+
+    public void setContents(FeedContentType contentType) {
+        this.contentType = contentType;
+        titleView.setText(contentType.titleId());
+        titleView.setChecked(contentType.isEnabled());
+    }
+
+    public void setDragHandleTouchListener(OnTouchListener listener) {
+        dragHandleView.setOnTouchListener(listener);
+    }
+
+    private void init() {
+        inflate(getContext(), R.layout.item_feed_content_type, this);
+        ButterKnife.bind(this);
+        setLayoutParams(new 
ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT));
+    }
+
+    @OnCheckedChanged(R.id.feed_content_type_checkbox) void 
onCheckedChanged(boolean checked) {
+        if (callback != null) {
+            callback.onCheckedChanged(contentType, checked);
+        }
+    }
+}
diff --git a/app/src/main/java/org/wikipedia/settings/Prefs.java 
b/app/src/main/java/org/wikipedia/settings/Prefs.java
index bba8353..79fe9ed 100644
--- a/app/src/main/java/org/wikipedia/settings/Prefs.java
+++ b/app/src/main/java/org/wikipedia/settings/Prefs.java
@@ -585,5 +585,39 @@
         return getBoolean(R.string.preference_key_enable_offline_library, 
false);
     }
 
+    @NonNull public static List<Boolean> getFeedCardsEnabled() {
+        if (!contains(R.string.preference_key_feed_cards_enabled)) {
+            return Collections.emptyList();
+        }
+        //noinspection unchecked
+        List<Boolean> enabledList = GsonUnmarshaller.unmarshal(new 
TypeToken<ArrayList<Boolean>>(){},
+                getString(R.string.preference_key_feed_cards_enabled, null));
+        return enabledList != null ? enabledList : Collections.emptyList();
+    }
+
+    public static void setFeedCardsEnabled(@NonNull List<Boolean> enabledList) 
{
+        setString(R.string.preference_key_feed_cards_enabled, 
GsonMarshaller.marshal(enabledList));
+    }
+
+    @NonNull public static List<Integer> getFeedCardsOrder() {
+        if (!contains(R.string.preference_key_feed_cards_order)) {
+            return Collections.emptyList();
+        }
+        //noinspection unchecked
+        List<Integer> orderList = GsonUnmarshaller.unmarshal(new 
TypeToken<ArrayList<Integer>>(){},
+                getString(R.string.preference_key_feed_cards_order, null));
+        return orderList != null ? orderList : Collections.emptyList();
+    }
+
+    public static void setFeedCardsOrder(@NonNull List<Integer> orderList) {
+        setString(R.string.preference_key_feed_cards_order, 
GsonMarshaller.marshal(orderList));
+    }
+
+    public static void resetFeedCustomizations() {
+        remove(R.string.preference_key_feed_hidden_cards);
+        remove(R.string.preference_key_feed_cards_enabled);
+        remove(R.string.preference_key_feed_cards_order);
+    }
+
     private Prefs() { }
 }
diff --git a/app/src/main/java/org/wikipedia/views/ExploreOverflowView.java 
b/app/src/main/java/org/wikipedia/views/ExploreOverflowView.java
index 52ad376..6c80220 100644
--- a/app/src/main/java/org/wikipedia/views/ExploreOverflowView.java
+++ b/app/src/main/java/org/wikipedia/views/ExploreOverflowView.java
@@ -30,6 +30,7 @@
         void compilationsClick();
         void settingsClick();
         void donateClick();
+        void configureCardsClick();
     }
 
     @BindView(R.id.explore_overflow_compilations) View compilationsView;
@@ -57,7 +58,7 @@
 
     @OnClick({R.id.explore_overflow_settings, R.id.explore_overflow_donate,
             R.id.explore_overflow_account_container, 
R.id.explore_overflow_log_out,
-            R.id.explore_overflow_compilations})
+            R.id.explore_overflow_compilations, 
R.id.explore_overflow_configure_cards})
     void onItemClick(View view) {
         if (popupWindowHost != null) {
             popupWindowHost.dismiss();
@@ -72,6 +73,9 @@
                     callback.loginClick();
                 }
                 break;
+            case R.id.explore_overflow_configure_cards:
+                callback.configureCardsClick();
+                break;
             case R.id.explore_overflow_compilations:
                 callback.compilationsClick();
                 break;
diff --git a/app/src/main/res/drawable/ic_drag_handle_black_24dp.xml 
b/app/src/main/res/drawable/ic_drag_handle_black_24dp.xml
new file mode 100644
index 0000000..68a7190
--- /dev/null
+++ b/app/src/main/res/drawable/ic_drag_handle_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20,9H4v2h16V9zM4,15h16v-2H4v2z"/>
+</vector>
diff --git a/app/src/main/res/layout/fragment_feed_configure.xml 
b/app/src/main/res/layout/fragment_feed_configure.xml
new file mode 100644
index 0000000..42d7eeb
--- /dev/null
+++ b/app/src/main/res/layout/fragment_feed_configure.xml
@@ -0,0 +1,17 @@
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android";
+    xmlns:app="http://schemas.android.com/apk/res-auto";
+    xmlns:tools="http://schemas.android.com/tools";
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <android.support.v7.widget.RecyclerView
+        android:id="@+id/content_types_recycler"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginLeft="@dimen/activity_horizontal_margin"
+        android:layout_marginRight="@dimen/activity_horizontal_margin"
+        android:scrollbars="vertical" />
+
+</LinearLayout>
diff --git a/app/src/main/res/layout/item_feed_content_type.xml 
b/app/src/main/res/layout/item_feed_content_type.xml
new file mode 100644
index 0000000..dc62ce4
--- /dev/null
+++ b/app/src/main/res/layout/item_feed_content_type.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v7.widget.CardView
+    xmlns:android="http://schemas.android.com/apk/res/android";
+    xmlns:app="http://schemas.android.com/apk/res-auto";
+    xmlns:tools="http://schemas.android.com/tools";
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="64dp"
+    app:cardBackgroundColor="?attr/paper_color"
+    app:cardUseCompatPadding="true">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <CheckBox
+            android:id="@+id/feed_content_type_checkbox"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:layout_gravity="center_vertical"
+            android:layout_marginStart="8dp"
+            android:layout_marginEnd="8dp"
+            android:drawablePadding="8dp"
+            style="@style/RtlAwareTextView"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textColor="?attr/secondary_text_color"
+            tools:text="Lorem ipsum"/>
+
+        <ImageView
+            android:id="@+id/feed_content_type_drag_handle"
+            android:layout_width="48dp"
+            android:layout_height="match_parent"
+            app:srcCompat="@drawable/ic_drag_handle_black_24dp"
+            android:scaleType="center"
+            android:tint="?attr/secondary_text_color"
+            android:contentDescription="@null"/>
+
+    </LinearLayout>
+
+</android.support.v7.widget.CardView>
diff --git a/app/src/main/res/layout/view_explore_overflow.xml 
b/app/src/main/res/layout/view_explore_overflow.xml
index cf85ec7..bc89dca 100644
--- a/app/src/main/res/layout/view_explore_overflow.xml
+++ b/app/src/main/res/layout/view_explore_overflow.xml
@@ -46,6 +46,11 @@
         android:background="?attr/paper_color">
 
         <TextView
+            android:id="@+id/explore_overflow_configure_cards"
+            style="@style/OverflowMenuItem"
+            android:text="@string/feed_configure_menu_title"/>
+
+        <TextView
             android:id="@+id/explore_overflow_compilations"
             style="@style/OverflowMenuItem"
             android:text="@string/offline_compilations_title"/>
diff --git a/app/src/main/res/menu/menu_feed_configure.xml 
b/app/src/main/res/menu/menu_feed_configure.xml
new file mode 100644
index 0000000..93bcd19
--- /dev/null
+++ b/app/src/main/res/menu/menu_feed_configure.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android";
+    xmlns:app="http://schemas.android.com/apk/res-auto";
+    xmlns:tools="http://schemas.android.com/tools";
+    tools:ignore="AlwaysShowAction">
+    <item android:id="@+id/menu_feed_configure_reset"
+        android:title="@string/feed_configure_menu_reset"
+        app:showAsAction="never"/>
+</menu>
diff --git a/app/src/main/res/values-qq/strings.xml 
b/app/src/main/res/values-qq/strings.xml
index 755b5fc..d2df1dc 100644
--- a/app/src/main/res/values-qq/strings.xml
+++ b/app/src/main/res/values-qq/strings.xml
@@ -399,6 +399,9 @@
   <string name="view_featured_article_card_title">Title shown in the Featured 
Article card in the Explore feed\n{{Identical|Featured article}}</string>
   <string name="view_featured_article_footer_save_button_label">Button label 
for saving the current featured article to a reading list, if it is not already 
saved.\n{{Identical|Save}}</string>
   <string name="view_featured_article_footer_saved_button_label">Button label 
for when the current featured article is already saved to a reading list, but 
may now be removed from the list, or saved to a different 
list.\n{{Identical|Saved}}</string>
+  <string name="feed_configure_menu_title">Menu label for showing the screen 
where the user configures which cards are shown in the Explore feed.</string>
+  <string name="feed_configure_activity_title">Title shown at the top of the 
screen where the user configures which cards are shown in the Explore 
feed.</string>
+  <string name="feed_configure_menu_reset">Menu label for resetting the 
configuration of the Explore feed cards to the original defaults.</string>
   <string name="description_edit_text_hint">Hint text that is shown when the 
description field is empty.</string>
   <string name="description_edit_save">Hint for the button that is pressed for 
saving the new description.\n{{Identical|Publish}}</string>
   <string name="description_edit_add_description">Label that prompts the user 
to add a description to an article that does not yet have one.</string>
diff --git a/app/src/main/res/values/preference_keys.xml 
b/app/src/main/res/values/preference_keys.xml
index c238e01..9721fc0 100644
--- a/app/src/main/res/values/preference_keys.xml
+++ b/app/src/main/res/values/preference_keys.xml
@@ -65,4 +65,6 @@
     <string 
name="preference_key_suppress_notification_polling">suppressNotificationPolling</string>
     <string 
name="preference_key_prefer_offline_content">preferOfflineContent</string>
     <string 
name="preference_key_enable_offline_library">enableOfflineLibrary</string>
+    <string name="preference_key_feed_cards_order">feedCardsOrder</string>
+    <string name="preference_key_feed_cards_enabled">feedCardsEnabled</string>
 </resources>
diff --git a/app/src/main/res/values/strings.xml 
b/app/src/main/res/values/strings.xml
index 8fd8c41..fb85927 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -434,6 +434,9 @@
     <string name="view_featured_article_card_title">Featured article</string>
     <string name="view_featured_article_footer_save_button_label">Save</string>
     <string 
name="view_featured_article_footer_saved_button_label">Saved</string>
+    <string name="feed_configure_menu_title">Configure cards</string>
+    <string name="feed_configure_activity_title">Configure Explore 
cards</string>
+    <string name="feed_configure_menu_reset">Reset to defaults</string>
     <!-- /The Feed -->
 
     <!-- Description editing -->

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: Icace97fe0af8490983555bcd0661ec237d051af7
Gerrit-PatchSet: 1
Gerrit-Project: apps/android/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Dbrant <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to