Add multiple chapters selection and allow to mark them as read/unread
@ -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"
|
||||
|
@ -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 -> {
|
||||
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
Normal file
After Width: | Height: | Size: 282 B |
BIN
app/src/main/res/drawable-hdpi/ic_action_select_all.png
Normal file
After Width: | Height: | Size: 222 B |
BIN
app/src/main/res/drawable-hdpi/ic_action_undone_all.png
Normal file
After Width: | Height: | Size: 648 B |
BIN
app/src/main/res/drawable-mdpi/ic_action_done_all.png
Normal file
After Width: | Height: | Size: 213 B |
BIN
app/src/main/res/drawable-mdpi/ic_action_select_all.png
Normal file
After Width: | Height: | Size: 141 B |
BIN
app/src/main/res/drawable-mdpi/ic_action_undone_all.png
Normal file
After Width: | Height: | Size: 404 B |
14
app/src/main/res/drawable-v21/selector_chapter_light.xml
Normal 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>
|
BIN
app/src/main/res/drawable-xhdpi/ic_action_done_all.png
Normal file
After Width: | Height: | Size: 307 B |
BIN
app/src/main/res/drawable-xhdpi/ic_action_select_all.png
Normal file
After Width: | Height: | Size: 198 B |
BIN
app/src/main/res/drawable-xhdpi/ic_action_undone_all.png
Normal file
After Width: | Height: | Size: 816 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_action_done_all.png
Normal file
After Width: | Height: | Size: 399 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_action_select_all.png
Normal file
After Width: | Height: | Size: 284 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_action_undone_all.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_action_done_all.png
Normal file
After Width: | Height: | Size: 476 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_action_select_all.png
Normal file
After Width: | Height: | Size: 318 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_action_undone_all.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
10
app/src/main/res/drawable/selector_chapter_light.xml
Normal 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>
|
@ -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
app/src/main/res/menu/chapter_selection.xml
Normal 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>
|
@ -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>
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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" }
|
||||
}
|
||||
}
|
||||
|