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

This commit is contained in:
inorichi 2015-11-02 14:42:08 +01:00
parent 22ee78d256
commit faef785fc3
29 changed files with 299 additions and 79 deletions

View file

@ -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"

View file

@ -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 -> {
}));
}
}

View file

@ -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);
}
}

View file

@ -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));
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

View file

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -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>

View file

@ -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"

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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">

View file

@ -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" }
}
}