Browse Source

Add multiple chapters selection and allow to mark them as read/unread

inorichi 9 years ago
parent
commit
faef785fc3
29 changed files with 299 additions and 79 deletions
  1. 1 0
      app/build.gradle
  2. 21 0
      app/src/main/java/eu/kanade/mangafeed/presenter/MangaChaptersPresenter.java
  3. 52 0
      app/src/main/java/eu/kanade/mangafeed/ui/adapter/ChaptersAdapter.java
  4. 94 11
      app/src/main/java/eu/kanade/mangafeed/ui/fragment/MangaChaptersFragment.java
  5. 0 60
      app/src/main/java/eu/kanade/mangafeed/ui/holder/ChapterListHolder.java
  6. 74 0
      app/src/main/java/eu/kanade/mangafeed/ui/holder/ChaptersHolder.java
  7. BIN
      app/src/main/res/drawable-hdpi/ic_action_done_all.png
  8. BIN
      app/src/main/res/drawable-hdpi/ic_action_select_all.png
  9. BIN
      app/src/main/res/drawable-hdpi/ic_action_undone_all.png
  10. BIN
      app/src/main/res/drawable-mdpi/ic_action_done_all.png
  11. BIN
      app/src/main/res/drawable-mdpi/ic_action_select_all.png
  12. BIN
      app/src/main/res/drawable-mdpi/ic_action_undone_all.png
  13. 14 0
      app/src/main/res/drawable-v21/selector_chapter_light.xml
  14. BIN
      app/src/main/res/drawable-xhdpi/ic_action_done_all.png
  15. BIN
      app/src/main/res/drawable-xhdpi/ic_action_select_all.png
  16. BIN
      app/src/main/res/drawable-xhdpi/ic_action_undone_all.png
  17. BIN
      app/src/main/res/drawable-xxhdpi/ic_action_done_all.png
  18. BIN
      app/src/main/res/drawable-xxhdpi/ic_action_select_all.png
  19. BIN
      app/src/main/res/drawable-xxhdpi/ic_action_undone_all.png
  20. BIN
      app/src/main/res/drawable-xxxhdpi/ic_action_done_all.png
  21. BIN
      app/src/main/res/drawable-xxxhdpi/ic_action_select_all.png
  22. BIN
      app/src/main/res/drawable-xxxhdpi/ic_action_undone_all.png
  23. 10 0
      app/src/main/res/drawable/selector_chapter_light.xml
  24. 2 6
      app/src/main/res/layout/item_chapter.xml
  25. 21 0
      app/src/main/res/menu/chapter_selection.xml
  26. 1 0
      app/src/main/res/values/colors.xml
  27. 6 1
      app/src/main/res/values/strings.xml
  28. 1 0
      app/src/main/res/values/styles.xml
  29. 2 1
      build.gradle

+ 1 - 0
app/build.gradle

@@ -88,6 +88,7 @@ dependencies {
     compile "frankiesardo:icepick:$ICEPICK_VERSION"
     provided "frankiesardo:icepick-processor:$ICEPICK_VERSION"
     compile 'com.github.dmytrodanylyk.android-process-button:library:1.0.4'
+    compile 'eu.davidea:flexible-adapter:4.0.1@aar'
 
     compile "com.google.dagger:dagger:$DAGGER_VERSION"
     apt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"

+ 21 - 0
app/src/main/java/eu/kanade/mangafeed/presenter/MangaChaptersPresenter.java

@@ -18,6 +18,7 @@ import eu.kanade.mangafeed.ui.fragment.MangaChaptersFragment;
 import eu.kanade.mangafeed.util.EventBusHook;
 import eu.kanade.mangafeed.util.PostResult;
 import rx.Observable;
+import rx.Subscription;
 import rx.android.schedulers.AndroidSchedulers;
 import rx.schedulers.Schedulers;
 
@@ -32,6 +33,8 @@ public class MangaChaptersPresenter extends BasePresenter<MangaChaptersFragment>
     private static final int DB_CHAPTERS = 1;
     private static final int ONLINE_CHAPTERS = 2;
 
+    private Subscription menuOperationSubscription;
+
     @Override
     protected void onCreate(Bundle savedState) {
         super.onCreate(savedState);
@@ -106,4 +109,22 @@ public class MangaChaptersPresenter extends BasePresenter<MangaChaptersFragment>
     public void onChapterClicked(Chapter chapter) {
         EventBus.getDefault().postSticky(new SourceChapterEvent(source, chapter));
     }
+
+    public void markChaptersRead(Observable<Chapter> selectedChapters, boolean read) {
+        if (menuOperationSubscription != null)
+            remove(menuOperationSubscription);
+
+        add(menuOperationSubscription = selectedChapters
+                .subscribeOn(Schedulers.io())
+                .map(chapter -> {
+                    chapter.read = read;
+                    return chapter;
+                })
+                .toList()
+                .flatMap(db::insertChapters)
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe(result -> {
+
+                }));
+    }
 }

+ 52 - 0
app/src/main/java/eu/kanade/mangafeed/ui/adapter/ChaptersAdapter.java

@@ -0,0 +1,52 @@
+package eu.kanade.mangafeed.ui.adapter;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import eu.davidea.flexibleadapter.FlexibleAdapter;
+import eu.kanade.mangafeed.R;
+import eu.kanade.mangafeed.data.models.Chapter;
+import eu.kanade.mangafeed.ui.fragment.base.BaseFragment;
+import eu.kanade.mangafeed.ui.holder.ChaptersHolder;
+
+public class ChaptersAdapter extends FlexibleAdapter<ChaptersHolder, Chapter> {
+
+    private Context context;
+    public OnItemClickListener clickListener;
+
+    public ChaptersAdapter(BaseFragment fragment) {
+        this.context = fragment.getActivity();
+        mItems = new ArrayList<>();
+        clickListener = (OnItemClickListener) fragment;
+    }
+
+    @Override
+    public void updateDataSet(String param) {}
+
+    @Override
+    public ChaptersHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        View v = LayoutInflater.from(context).inflate(R.layout.item_chapter, parent, false);
+        return new ChaptersHolder(v, this);
+    }
+
+    @Override
+    public void onBindViewHolder(ChaptersHolder holder, int position) {
+        final Chapter chapter = getItem(position);
+        holder.onSetValues(context, chapter);
+    }
+
+    public void setItems(List<Chapter> chapters) {
+        mItems = chapters;
+        notifyDataSetChanged();
+    }
+
+    public interface OnItemClickListener {
+        boolean onListItemClick(int position);
+        void onListItemLongClick(int position);
+    }
+}

+ 94 - 11
app/src/main/java/eu/kanade/mangafeed/ui/fragment/MangaChaptersFragment.java

@@ -4,6 +4,7 @@ import android.content.Intent;
 import android.os.Bundle;
 import android.support.v4.app.Fragment;
 import android.support.v4.widget.SwipeRefreshLayout;
+import android.support.v7.view.ActionMode;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
@@ -22,18 +23,22 @@ import eu.kanade.mangafeed.data.models.Chapter;
 import eu.kanade.mangafeed.presenter.MangaChaptersPresenter;
 import eu.kanade.mangafeed.ui.activity.MangaDetailActivity;
 import eu.kanade.mangafeed.ui.activity.ReaderActivity;
-import eu.kanade.mangafeed.ui.holder.ChapterListHolder;
+import eu.kanade.mangafeed.ui.activity.base.BaseActivity;
+import eu.kanade.mangafeed.ui.adapter.ChaptersAdapter;
 import eu.kanade.mangafeed.ui.fragment.base.BaseRxFragment;
 import nucleus.factory.RequiresPresenter;
-import uk.co.ribot.easyadapter.EasyRecyclerAdapter;
+import rx.Observable;
 
 @RequiresPresenter(MangaChaptersPresenter.class)
-public class MangaChaptersFragment extends BaseRxFragment<MangaChaptersPresenter> {
+public class MangaChaptersFragment extends BaseRxFragment<MangaChaptersPresenter> implements
+        ActionMode.Callback, ChaptersAdapter.OnItemClickListener {
 
     @Bind(R.id.chapter_list) RecyclerView chapters;
     @Bind(R.id.swipe_refresh) SwipeRefreshLayout swipeRefresh;
 
-    private EasyRecyclerAdapter<Chapter> adapter;
+    private ChaptersAdapter adapter;
+
+    private ActionMode actionMode;
 
     public static Fragment newInstance() {
         return new MangaChaptersFragment();
@@ -76,13 +81,7 @@ public class MangaChaptersFragment extends BaseRxFragment<MangaChaptersPresenter
     }
 
     private void createAdapter() {
-        ChapterListHolder.ChapterListener listener = chapter -> {
-            getPresenter().onChapterClicked(chapter);
-            Intent intent = ReaderActivity.newInstance(getActivity());
-            startActivity(intent);
-        };
-
-        adapter = new EasyRecyclerAdapter<>(getActivity(), ChapterListHolder.class, listener);
+        adapter = new ChaptersAdapter(this);
         chapters.setAdapter(adapter);
     }
 
@@ -92,6 +91,7 @@ public class MangaChaptersFragment extends BaseRxFragment<MangaChaptersPresenter
 
     public void onNextChapters(List<Chapter> chapters) {
         adapter.setItems(chapters);
+        closeActionMode();
     }
 
     public void onNextOnlineChapters() {
@@ -105,4 +105,87 @@ public class MangaChaptersFragment extends BaseRxFragment<MangaChaptersPresenter
     public boolean isOnlineManga() {
         return ((MangaDetailActivity)getActivity()).isOnlineManga();
     }
+
+    @Override
+    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+        mode.getMenuInflater().inflate(R.menu.chapter_selection, menu);
+        adapter.setMode(ChaptersAdapter.MODE_MULTI);
+        return true;
+    }
+
+    @Override
+    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+        return false;
+    }
+
+    @Override
+    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.action_select_all:
+                adapter.selectAll();
+                return true;
+            case R.id.action_mark_as_read:
+                getPresenter().markChaptersRead(getSelectedChapters(), true);
+                return true;
+            case R.id.action_mark_as_unread:
+                getPresenter().markChaptersRead(getSelectedChapters(), false);
+                return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void onDestroyActionMode(ActionMode mode) {
+        adapter.setMode(ChaptersAdapter.MODE_SINGLE);
+        adapter.clearSelection();
+        actionMode = null;
+    }
+
+    private Observable<Chapter> getSelectedChapters() {
+        return Observable.from(adapter.getSelectedItems())
+                .map(adapter::getItem);
+    }
+
+    public void closeActionMode() {
+        if (actionMode != null)
+            actionMode.finish();
+    }
+
+    @Override
+    public boolean onListItemClick(int position) {
+        if (actionMode != null && adapter.getMode() == ChaptersAdapter.MODE_MULTI) {
+            toggleSelection(position);
+            return true;
+        } else {
+            getPresenter().onChapterClicked(adapter.getItem(position));
+            Intent intent = ReaderActivity.newInstance(getActivity());
+            startActivity(intent);
+            return false;
+        }
+    }
+
+    @Override
+    public void onListItemLongClick(int position) {
+        if (actionMode == null)
+            actionMode = ((BaseActivity)getActivity()).startSupportActionMode(this);
+
+        toggleSelection(position);
+    }
+
+    private void toggleSelection(int position) {
+        adapter.toggleSelection(position, false);
+
+        int count = adapter.getSelectedItemCount();
+
+        if (count == 0) {
+            actionMode.finish();
+        } else {
+            setContextTitle(count);
+            actionMode.invalidate();
+        }
+    }
+
+    private void setContextTitle(int count) {
+        actionMode.setTitle(getString(R.string.selected_chapters_title, count));
+    }
 }

+ 0 - 60
app/src/main/java/eu/kanade/mangafeed/ui/holder/ChapterListHolder.java

@@ -1,60 +0,0 @@
-package eu.kanade.mangafeed.ui.holder;
-
-import android.graphics.Color;
-import android.support.v4.content.ContextCompat;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import eu.kanade.mangafeed.R;
-import eu.kanade.mangafeed.data.models.Chapter;
-import uk.co.ribot.easyadapter.ItemViewHolder;
-import uk.co.ribot.easyadapter.PositionInfo;
-import uk.co.ribot.easyadapter.annotations.LayoutId;
-import uk.co.ribot.easyadapter.annotations.ViewId;
-
-@LayoutId(R.layout.item_chapter)
-public class ChapterListHolder extends ItemViewHolder<Chapter> {
-
-    @ViewId(R.id.chapter_title) TextView title;
-    @ViewId(R.id.chapter_download_image) ImageView download_icon;
-    @ViewId(R.id.chapter_pages) TextView pages;
-
-    View view;
-
-    public ChapterListHolder(View view) {
-        super(view);
-        this.view = view;
-    }
-
-    public void onSetValues(Chapter chapter, PositionInfo positionInfo) {
-        title.setText(chapter.name);
-        download_icon.setImageResource(R.drawable.ic_file_download_black_48dp);
-
-        if (chapter.read) {
-            title.setTextColor(ContextCompat.getColor(getContext(), R.color.chapter_read_text));
-        } else {
-            title.setTextColor(Color.BLACK);
-        }
-
-        if (chapter.last_page_read > 0 && !chapter.read) {
-            pages.setText(getContext().getString(R.string.chapter_progress, chapter.last_page_read+1));
-        } else {
-            pages.setText("");
-        }
-    }
-
-    @Override
-    public void onSetListeners() {
-        view.setOnClickListener(view -> {
-            ChapterListener listener = getListener(ChapterListener.class);
-            if (listener != null) {
-                listener.onRowClicked(getItem());
-            }
-        });
-    }
-
-    public interface ChapterListener {
-        void onRowClicked(Chapter chapter);
-    }
-}

+ 74 - 0
app/src/main/java/eu/kanade/mangafeed/ui/holder/ChaptersHolder.java

@@ -0,0 +1,74 @@
+package eu.kanade.mangafeed.ui.holder;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
+import eu.kanade.mangafeed.R;
+import eu.kanade.mangafeed.data.models.Chapter;
+import eu.kanade.mangafeed.ui.adapter.ChaptersAdapter;
+
+public class ChaptersHolder extends RecyclerView.ViewHolder implements
+        View.OnClickListener, View.OnLongClickListener {
+
+    private ChaptersAdapter adapter;
+
+    @Bind(R.id.chapter_title) TextView title;
+    @Bind(R.id.chapter_download_image) ImageView download_icon;
+    @Bind(R.id.chapter_pages) TextView pages;
+
+    public ChaptersHolder(View view) {
+        super(view);
+        ButterKnife.bind(this, view);
+    }
+
+    public ChaptersHolder(View view, final ChaptersAdapter adapter) {
+        this(view);
+
+        this.adapter = adapter;
+        itemView.setOnClickListener(this);
+        itemView.setOnLongClickListener(this);
+    }
+
+    public void onSetValues(Context context, Chapter chapter) {
+        title.setText(chapter.name);
+        download_icon.setImageResource(R.drawable.ic_file_download_black_48dp);
+
+        if (chapter.read) {
+            title.setTextColor(ContextCompat.getColor(context, R.color.chapter_read_text));
+        } else {
+            title.setTextColor(Color.BLACK);
+        }
+
+        if (chapter.last_page_read > 0 && !chapter.read) {
+            pages.setText(context.getString(R.string.chapter_progress, chapter.last_page_read + 1));
+        } else {
+            pages.setText("");
+        }
+
+        toggleActivation();
+    }
+
+    private void toggleActivation() {
+        itemView.setActivated(adapter.isSelected(getAdapterPosition()));
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (adapter.clickListener.onListItemClick(getAdapterPosition()))
+            toggleActivation();
+    }
+
+    @Override
+    public boolean onLongClick(View v) {
+        adapter.clickListener.onListItemLongClick(getAdapterPosition());
+        toggleActivation();
+        return true;
+    }
+}

BIN
app/src/main/res/drawable-hdpi/ic_action_done_all.png


BIN
app/src/main/res/drawable-hdpi/ic_action_select_all.png


BIN
app/src/main/res/drawable-hdpi/ic_action_undone_all.png


BIN
app/src/main/res/drawable-mdpi/ic_action_done_all.png


BIN
app/src/main/res/drawable-mdpi/ic_action_select_all.png


BIN
app/src/main/res/drawable-mdpi/ic_action_undone_all.png


+ 14 - 0
app/src/main/res/drawable-v21/selector_chapter_light.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:exitFadeDuration="@android:integer/config_shortAnimTime"
+    android:color="?android:attr/colorControlHighlight">
+
+    <item android:id="@android:id/mask"
+        android:drawable="@color/list_choice_pressed_bg_light" />
+    <item>
+        <selector>
+            <item android:state_activated="true" android:drawable="@color/list_choice_pressed_bg_light"/>
+        </selector>
+    </item>
+
+</ripple>

BIN
app/src/main/res/drawable-xhdpi/ic_action_done_all.png


BIN
app/src/main/res/drawable-xhdpi/ic_action_select_all.png


BIN
app/src/main/res/drawable-xhdpi/ic_action_undone_all.png


BIN
app/src/main/res/drawable-xxhdpi/ic_action_done_all.png


BIN
app/src/main/res/drawable-xxhdpi/ic_action_select_all.png


BIN
app/src/main/res/drawable-xxhdpi/ic_action_undone_all.png


BIN
app/src/main/res/drawable-xxxhdpi/ic_action_done_all.png


BIN
app/src/main/res/drawable-xxxhdpi/ic_action_select_all.png


BIN
app/src/main/res/drawable-xxxhdpi/ic_action_undone_all.png


+ 10 - 0
app/src/main/res/drawable/selector_chapter_light.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector android:exitFadeDuration="@android:integer/config_longAnimTime"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_focused="true" android:drawable="@color/list_choice_pressed_bg_light"/>
+    <item android:state_pressed="true" android:drawable="@color/list_choice_pressed_bg_light"/>
+    <item android:state_activated="true" android:drawable="@color/list_choice_pressed_bg_light"/>
+    <item android:drawable="@android:color/transparent"/>
+
+</selector>

+ 2 - 6
app/src/main/res/layout/item_chapter.xml

@@ -3,12 +3,8 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:orientation="horizontal"
     android:layout_width="match_parent"
-    android:layout_height="40dp">
-
-    <CheckBox
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:id="@+id/chapter_selection"/>
+    android:layout_height="40dp"
+    android:background="?attr/selectableItemBackground">
 
     <ImageView
         android:layout_width="wrap_content"

+ 21 - 0
app/src/main/res/menu/chapter_selection.xml

@@ -0,0 +1,21 @@
+<?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">
+
+    <item android:id="@+id/action_mark_as_read"
+        android:title="@string/action_mark_as_read"
+        android:icon="@drawable/ic_action_done_all"
+        app:showAsAction="ifRoom"/>
+
+    <item android:id="@+id/action_mark_as_unread"
+        android:title="@string/action_mark_as_unread"
+        android:icon="@drawable/ic_action_undone_all"
+        app:showAsAction="ifRoom"/>
+
+    <item android:id="@+id/action_select_all"
+        android:title="@string/action_select_all"
+        android:icon="@drawable/ic_action_select_all"
+        app:showAsAction="ifRoom"/>
+
+</menu>

+ 1 - 0
app/src/main/res/values/colors.xml

@@ -16,4 +16,5 @@
     <color name="black_87pc">#DD000000</color>
     <color name="library_text_background">#E8E8E8</color>
     <color name="chapter_read_text">#909090</color>
+    <color name="list_choice_pressed_bg_light">#607D8B</color>
 </resources>

+ 6 - 1
app/src/main/res/values/strings.xml

@@ -32,7 +32,7 @@
     <string name="action_settings">Settings</string>
     <string name="action_search">Search</string>
     <string name="action_refresh">Refresh</string>
-    <string name="library_search_hint">Title or author...</string>
+    <string name="library_search_hint">Title or author</string>
     <string name="action_delete">Delete</string>
     <string name="library_selection_title">Selected</string>
     <string name="title_activity_catalogue_list">CatalogueList</string>
@@ -77,4 +77,9 @@
     <string name="chapter_progress">Page: %1$d</string>
     <string name="source_requires_login">This source requires login</string>
 
+    <string name="action_select_all">Select all</string>
+    <string name="action_mark_as_read">Mark as read</string>
+    <string name="action_mark_as_unread">Mark as unread</string>
+    <string name="selected_chapters_title">Selected chapters: %1$d</string>
+
 </resources>

+ 1 - 0
app/src/main/res/values/styles.xml

@@ -11,6 +11,7 @@
         <item name="colorControlNormal">@color/white</item>
         <item name="windowActionModeOverlay">true</item>
         <item name="actionModeStyle">@style/Widget.ActionMode</item>
+        <item name="selectableItemBackground">@drawable/selector_chapter_light</item>
     </style>
 
     <style name="AppTheme.NoActionBar" parent="AppTheme">

+ 2 - 1
build.gradle

@@ -23,6 +23,7 @@ buildscript {
 allprojects {
     repositories {
         jcenter()
-        maven {url "https://clojars.org/repo/"}
+        maven { url "https://clojars.org/repo/" }
+        maven { url "http://dl.bintray.com/davideas/maven" }
     }
 }