Browse Source

Merge pull request #160 from NoodleMage/issue_118

Implements Issue #118 download from recent tab
inorichi 9 years ago
parent
commit
8581e4667a

+ 33 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersAdapter.java

@@ -16,13 +16,33 @@ import eu.davidea.flexibleadapter.FlexibleAdapter;
 import eu.kanade.tachiyomi.R;
 import eu.kanade.tachiyomi.data.database.models.MangaChapter;
 
+/**
+ * Adapter of RecentChaptersHolder.
+ * Connection between Fragment and Holder
+ * Holder updates should be called from here.
+ */
 public class RecentChaptersAdapter extends FlexibleAdapter<RecyclerView.ViewHolder, Object> {
 
-    private RecentChaptersFragment fragment;
+    /**
+     * Fragment of RecentChaptersFragment
+     */
+    private final RecentChaptersFragment fragment;
 
+    /**
+     * The id of the view type
+     */
     private static final int VIEW_TYPE_CHAPTER = 0;
+
+    /**
+     * The id of the view type
+     */
     private static final int VIEW_TYPE_SECTION = 1;
 
+    /**
+     * Constructor
+     *
+     * @param fragment fragment
+     */
     public RecentChaptersAdapter(RecentChaptersFragment fragment) {
         this.fragment = fragment;
         setHasStableIds(true);
@@ -37,6 +57,11 @@ public class RecentChaptersAdapter extends FlexibleAdapter<RecyclerView.ViewHold
             return item.hashCode();
     }
 
+    /**
+     * Update items
+     *
+     * @param items items
+     */
     public void setItems(List<Object> items) {
         mItems = items;
         notifyDataSetChanged();
@@ -56,6 +81,8 @@ public class RecentChaptersAdapter extends FlexibleAdapter<RecyclerView.ViewHold
     public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         LayoutInflater inflater = LayoutInflater.from(parent.getContext());
         View v;
+
+        // Check which view type and set correct values.
         switch (viewType) {
             case VIEW_TYPE_CHAPTER:
                 v = inflater.inflate(R.layout.item_recent_chapter, parent, false);
@@ -69,6 +96,7 @@ public class RecentChaptersAdapter extends FlexibleAdapter<RecyclerView.ViewHold
 
     @Override
     public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+        // Check which view type and set correct values.
         switch (holder.getItemViewType()) {
             case VIEW_TYPE_CHAPTER:
                 final MangaChapter chapter = (MangaChapter) getItem(position);
@@ -84,6 +112,10 @@ public class RecentChaptersAdapter extends FlexibleAdapter<RecyclerView.ViewHold
         holder.itemView.setActivated(isSelected(position));
     }
 
+    /**
+     * Returns fragment
+     * @return RecentChaptersFragment
+     */
     public RecentChaptersFragment getFragment() {
         return fragment;
     }

+ 126 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersFragment.java

@@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.recent;
 
 import android.content.Intent;
 import android.os.Bundle;
+import android.support.annotation.Nullable;
 import android.support.v4.content.ContextCompat;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
@@ -9,18 +10,32 @@ import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.afollestad.materialdialogs.MaterialDialog;
+
 import java.util.List;
 
 import butterknife.Bind;
 import butterknife.ButterKnife;
 import eu.kanade.tachiyomi.R;
+import eu.kanade.tachiyomi.data.database.models.Chapter;
+import eu.kanade.tachiyomi.data.database.models.Manga;
 import eu.kanade.tachiyomi.data.database.models.MangaChapter;
+import eu.kanade.tachiyomi.data.download.DownloadService;
+import eu.kanade.tachiyomi.data.download.model.Download;
 import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
 import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment;
 import eu.kanade.tachiyomi.ui.decoration.DividerItemDecoration;
 import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
 import nucleus.factory.RequiresPresenter;
+import rx.Observable;
+import rx.android.schedulers.AndroidSchedulers;
+import rx.schedulers.Schedulers;
 
+/**
+ * Fragment that shows recent chapters.
+ * Uses R.layout.fragment_recent_chapters.
+ * UI related actions should be called from here.
+ */
 @RequiresPresenter(RecentChaptersPresenter.class)
 public class RecentChaptersFragment extends BaseRxFragment<RecentChaptersPresenter> implements FlexibleViewHolder.OnListItemClickListener {
 
@@ -50,14 +65,21 @@ public class RecentChaptersFragment extends BaseRxFragment<RecentChaptersPresent
         return view;
     }
 
+    /**
+     * Populate adapter with chapters
+     *
+     * @param chapters list of chapters
+     */
     public void onNextMangaChapters(List<Object> chapters) {
         adapter.setItems(chapters);
     }
 
     @Override
     public boolean onListItemClick(int position) {
+        // Get item from position
         Object item = adapter.getItem(position);
         if (item instanceof MangaChapter) {
+            // Open chapter in reader
             openChapter((MangaChapter) item);
         }
         return false;
@@ -65,11 +87,114 @@ public class RecentChaptersFragment extends BaseRxFragment<RecentChaptersPresent
 
     @Override
     public void onListItemLongClick(int position) {
+        // Empty function
     }
 
-    protected void openChapter(MangaChapter chapter) {
+    /**
+     * Open chapter in reader
+     *
+     * @param chapter selected chapter
+     */
+    private void openChapter(MangaChapter chapter) {
         getPresenter().onOpenChapter(chapter);
         Intent intent = ReaderActivity.newIntent(getActivity());
         startActivity(intent);
     }
+
+    /**
+     * Update download status of chapter
+     *
+     * @param download download object containing download progress.
+     */
+    public void onChapterStatusChange(Download download) {
+        RecentChaptersHolder holder = getHolder(download.chapter);
+        if (holder != null)
+            holder.onStatusChange(download.getStatus());
+    }
+
+    @Nullable
+    private RecentChaptersHolder getHolder(Chapter chapter) {
+        return (RecentChaptersHolder) recyclerView.findViewHolderForItemId(chapter.id);
+    }
+
+    /**
+     * Start downloading chapter
+     *
+     * @param chapters selected chapters
+     * @param manga    manga that belongs to chapter
+     * @return true
+     */
+    @SuppressWarnings("SameReturnValue")
+    protected boolean onDownload(Observable<Chapter> chapters, Manga manga) {
+        // Start the download service.
+        DownloadService.start(getActivity());
+
+        // Refresh data on download competition.
+        Observable<Chapter> observable = chapters
+                .doOnCompleted(adapter::notifyDataSetChanged);
+
+        // Download chapter.
+        getPresenter().downloadChapter(observable, manga);
+        return true;
+    }
+
+    /**
+     * Start deleting chapter
+     * @param chapters selected chapters
+     * @param manga manga that belongs to chapter
+     * @return success of deletion.
+     */
+    protected boolean onDelete(Observable<Chapter> chapters, Manga manga) {
+        int size = adapter.getSelectedItemCount();
+
+        MaterialDialog dialog = new MaterialDialog.Builder(getActivity())
+                .title(R.string.deleting)
+                .progress(false, size, true)
+                .cancelable(false)
+                .show();
+
+        Observable<Chapter> observable = chapters
+                .concatMap(chapter -> {
+                    getPresenter().deleteChapter(chapter, manga);
+                    return Observable.just(chapter);
+                })
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .doOnNext(chapter -> {
+                    dialog.incrementProgress(1);
+                    chapter.status = Download.NOT_DOWNLOADED;
+                })
+                .doOnCompleted(adapter::notifyDataSetChanged)
+                .finallyDo(dialog::dismiss);
+
+        getPresenter().deleteChapters(observable);
+
+        return true;
+    }
+
+    /**
+     * Mark chapter as read
+     *
+     * @param chapters selected chapter
+     * @return true
+     */
+    @SuppressWarnings("SameReturnValue")
+    protected boolean onMarkAsRead(Observable<Chapter> chapters) {
+        getPresenter().markChaptersRead(chapters, true);
+        return true;
+    }
+
+    /**
+     * Mark chapter as unread
+     *
+     * @param chapters selected chapter
+     * @return true
+     */
+    @SuppressWarnings("SameReturnValue")
+    protected boolean onMarkAsUnread(Observable<Chapter> chapters) {
+        getPresenter().markChaptersRead(chapters, false);
+        return true;
+    }
+
+
 }

+ 145 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersHolder.java

@@ -1,35 +1,102 @@
 package eu.kanade.tachiyomi.ui.recent;
 
 import android.support.v4.content.ContextCompat;
+import android.view.Menu;
 import android.view.View;
+import android.widget.PopupMenu;
+import android.widget.RelativeLayout;
 import android.widget.TextView;
 
 import butterknife.Bind;
 import butterknife.ButterKnife;
 import eu.kanade.tachiyomi.R;
+import eu.kanade.tachiyomi.data.database.models.Chapter;
 import eu.kanade.tachiyomi.data.database.models.MangaChapter;
+import eu.kanade.tachiyomi.data.download.model.Download;
 import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
+import rx.Observable;
 
+/**
+ * Holder that contains chapter item
+ * Uses R.layout.item_recent_chapter.
+ * UI related actions should be called from here.
+ */
 public class RecentChaptersHolder extends FlexibleViewHolder {
 
+    /**
+     * Adapter for recent chapters
+     */
+    private final RecentChaptersAdapter adapter;
+
+    /**
+     * TextView containing chapter title
+     */
     @Bind(R.id.chapter_title) TextView chapterTitle;
+
+    /**
+     * TextView containing manga name
+     */
     @Bind(R.id.manga_title) TextView mangaTitle;
 
+    /**
+     * TextView containing download status
+     */
+    @Bind(R.id.download_text) TextView downloadText;
+
+    /**
+     * RelativeLayout containing popup menu with download options
+     */
+    @Bind(R.id.chapter_menu) RelativeLayout chapterMenu;
+
+    /**
+     * Color of read chapter
+     */
     private final int readColor;
+
+    /**
+     * Color of unread chapter
+     */
     private final int unreadColor;
 
+    /**
+     * Object containing chapter information
+     */
+    private MangaChapter mangaChapter;
+
+    /**
+     * Constructor of RecentChaptersHolder
+     * @param view view of ChapterHolder
+     * @param adapter adapter of ChapterHolder
+     * @param onListItemClickListener ClickListener
+     */
     public RecentChaptersHolder(View view, RecentChaptersAdapter adapter, OnListItemClickListener onListItemClickListener) {
         super(view, adapter, onListItemClickListener);
+        this.adapter = adapter;
         ButterKnife.bind(this, view);
 
+        // Set colors.
         readColor = ContextCompat.getColor(view.getContext(), R.color.hint_text);
         unreadColor = ContextCompat.getColor(view.getContext(), R.color.primary_text);
+
+        //Set OnClickListener for download menu
+        chapterMenu.setOnClickListener(v -> v.post(() -> showPopupMenu(v)));
     }
 
+    /**
+     * Set values of view
+     *
+     * @param item item containing chapter information
+     */
     public void onSetValues(MangaChapter item) {
+        this.mangaChapter = item;
+
+        // Set chapter title
         chapterTitle.setText(item.chapter.name);
+
+        // Set manga title
         mangaTitle.setText(item.manga.title);
 
+        // Check if chapter is read and set correct color
         if (item.chapter.read) {
             chapterTitle.setTextColor(readColor);
             mangaTitle.setTextColor(readColor);
@@ -37,6 +104,84 @@ public class RecentChaptersHolder extends FlexibleViewHolder {
             chapterTitle.setTextColor(unreadColor);
             mangaTitle.setTextColor(unreadColor);
         }
+
+        // Set chapter status
+        onStatusChange(item.chapter.status);
+    }
+
+    /**
+     * Updates chapter status in view.
+     *
+     * @param status download status
+     */
+    public void onStatusChange(int status) {
+        switch (status) {
+            case Download.QUEUE:
+                downloadText.setText(R.string.chapter_queued);
+                break;
+            case Download.DOWNLOADING:
+                downloadText.setText(R.string.chapter_downloading);
+                break;
+            case Download.DOWNLOADED:
+                downloadText.setText(R.string.chapter_downloaded);
+                break;
+            case Download.ERROR:
+                downloadText.setText(R.string.chapter_error);
+                break;
+            default:
+                downloadText.setText("");
+                break;
+        }
     }
 
+    /**
+     * Show pop up menu
+     * @param view view containing popup menu.
+     */
+    private void showPopupMenu(View view) {
+        // Create a PopupMenu, giving it the clicked view for an anchor
+        PopupMenu popup = new PopupMenu(adapter.getFragment().getActivity(), view);
+
+        // Inflate our menu resource into the PopupMenu's Menu
+        popup.getMenuInflater().inflate(R.menu.chapter_recent, popup.getMenu());
+
+        // Hide download and show delete if the chapter is downloaded and
+        if (mangaChapter.chapter.isDownloaded()) {
+            Menu menu = popup.getMenu();
+            menu.findItem(R.id.action_download).setVisible(false);
+            menu.findItem(R.id.action_delete).setVisible(true);
+        }
+
+        // Hide mark as unread when the chapter is unread
+        if (!mangaChapter.chapter.read /*&& mangaChapter.chapter.last_page_read == 0*/) {
+            popup.getMenu().findItem(R.id.action_mark_as_unread).setVisible(false);
+        }
+
+        // Hide mark as read when the chapter is read
+        if (mangaChapter.chapter.read) {
+            popup.getMenu().findItem(R.id.action_mark_as_read).setVisible(false);
+        }
+
+        // Set a listener so we are notified if a menu item is clicked
+        popup.setOnMenuItemClickListener(menuItem -> {
+            Observable<Chapter> chapterObservable = Observable.just(mangaChapter.chapter);
+
+            switch (menuItem.getItemId()) {
+                case R.id.action_download:
+                    return adapter.getFragment().onDownload(chapterObservable, mangaChapter.manga);
+                case R.id.action_delete:
+                    return adapter.getFragment().onDelete(chapterObservable, mangaChapter.manga);
+                case R.id.action_mark_as_read:
+                    return adapter.getFragment().onMarkAsRead(chapterObservable);
+                case R.id.action_mark_as_unread:
+                    return adapter.getFragment().onMarkAsUnread(chapterObservable);
+            }
+            return false;
+        });
+
+        // Finally show the PopupMenu
+        popup.show();
+    }
+
+
 }

+ 231 - 5
app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersPresenter.java

@@ -15,46 +15,184 @@ import java.util.TreeMap;
 import javax.inject.Inject;
 
 import eu.kanade.tachiyomi.data.database.DatabaseHelper;
+import eu.kanade.tachiyomi.data.database.models.Chapter;
+import eu.kanade.tachiyomi.data.database.models.Manga;
 import eu.kanade.tachiyomi.data.database.models.MangaChapter;
+import eu.kanade.tachiyomi.data.download.DownloadManager;
+import eu.kanade.tachiyomi.data.download.model.Download;
 import eu.kanade.tachiyomi.data.source.SourceManager;
 import eu.kanade.tachiyomi.data.source.base.Source;
+import eu.kanade.tachiyomi.event.DownloadChaptersEvent;
 import eu.kanade.tachiyomi.event.ReaderEvent;
 import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
 import rx.Observable;
 import rx.android.schedulers.AndroidSchedulers;
+import rx.schedulers.Schedulers;
+import timber.log.Timber;
 
+/**
+ * Presenter of RecentChaptersFragment.
+ * Contains information and data for fragment.
+ * Observable updates should be called from here.
+ */
 public class RecentChaptersPresenter extends BasePresenter<RecentChaptersFragment> {
 
+    /**
+     * The id of the restartable.
+     */
+    private static final int GET_RECENT_CHAPTERS = 1;
+
+    /**
+     * The id of the restartable.
+     */
+    private static final int CHAPTER_STATUS_CHANGES = 2;
+
+    /**
+     * Used to connect to database
+     */
     @Inject DatabaseHelper db;
+
+    /**
+     * Used to get information from download manager
+     */
+    @Inject DownloadManager downloadManager;
+
+    /**
+     * Used to get source from source id
+     */
     @Inject SourceManager sourceManager;
 
-    private static final int GET_RECENT_CHAPTERS = 1;
+    /**
+     * List containing chapter and manga information
+     */
+    private List<MangaChapter> mangaChapters;
 
     @Override
     protected void onCreate(Bundle savedState) {
         super.onCreate(savedState);
 
+        // Used to get recent chapters
         restartableLatestCache(GET_RECENT_CHAPTERS,
                 this::getRecentChaptersObservable,
-                RecentChaptersFragment::onNextMangaChapters);
+                (recentChaptersFragment, chapters) -> {
+                    // Update adapter to show recent manga's
+                    recentChaptersFragment.onNextMangaChapters(chapters);
+                    // Update download status
+                    updateChapterStatus(convertToMangaChaptersList(chapters));
+                });
+
+        // Used to update download status
+        startableLatestCache(CHAPTER_STATUS_CHANGES,
+                this::getChapterStatusObs,
+                RecentChaptersFragment::onChapterStatusChange,
+                (view, error) -> Timber.e(error.getCause(), error.getMessage()));
 
-        if (savedState == null)
+        if (savedState == null) {
+            // Start fetching recent chapters
             start(GET_RECENT_CHAPTERS);
+        }
+    }
+
+    /**
+     * Returns a list only containing MangaChapter objects.
+     *
+     * @param input the list that will be converted.
+     * @return list containing MangaChapters objects.
+     */
+    private List<MangaChapter> convertToMangaChaptersList(List<Object> input) {
+        // Create temp list
+        List<MangaChapter> tempMangaChapterList = new ArrayList<>();
+
+        // Only add MangaChapter objects
+        //noinspection Convert2streamapi
+        for (Object object : input) {
+            if (object instanceof MangaChapter) {
+                tempMangaChapterList.add((MangaChapter) object);
+            }
+        }
+
+        // Return temp list
+        return tempMangaChapterList;
+    }
+
+    /**
+     * Update status of chapters
+     *
+     * @param mangaChapters list containing recent chapters
+     */
+    private void updateChapterStatus(List<MangaChapter> mangaChapters) {
+        // Set global list of chapters.
+        this.mangaChapters = mangaChapters;
+
+        // Update status.
+        //noinspection Convert2streamapi
+        for (MangaChapter mangaChapter : mangaChapters)
+            setChapterStatus(mangaChapter);
+
+        // Start onChapterStatusChange restartable.
+        start(CHAPTER_STATUS_CHANGES);
+    }
+
+    /**
+     * Returns observable containing chapter status.
+     *
+     * @return download object containing download progress.
+     */
+    private Observable<Download> getChapterStatusObs() {
+        return downloadManager.getQueue().getStatusObservable()
+                .observeOn(AndroidSchedulers.mainThread())
+                .filter(download -> chapterIdEquals(download.chapter.id))
+                .doOnNext(this::updateChapterStatus);
     }
 
+    /**
+     * Function to check if chapter is in recent list
+     * @param chaptersId id of chapter
+     * @return exist in recent list
+     */
+    private boolean chapterIdEquals(Long chaptersId) {
+        for (MangaChapter mangaChapter : mangaChapters) {
+            if (chaptersId.equals(mangaChapter.chapter.id)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Update status of chapters.
+     *
+     * @param download download object containing progress.
+     */
+    private void updateChapterStatus(Download download) {
+        // Loop through list
+        for (MangaChapter item : mangaChapters) {
+            if (download.chapter.id.equals(item.chapter.id)) {
+                item.chapter.status = download.getStatus();
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Get observable containing recent chapters and date
+     * @return observable containing recent chapters and date
+     */
     private Observable<List<Object>> getRecentChaptersObservable() {
+        // Set date for recent chapters
         Calendar cal = Calendar.getInstance();
         cal.setTime(new Date());
         cal.add(Calendar.MONTH, -1);
 
+        // Get recent chapters from database.
         return db.getRecentChapters(cal.getTime()).asRxObservable()
-                // group chapters by the date they were fetched on a ordered map
+                // Group chapters by the date they were fetched on a ordered map.
                 .flatMap(recents -> Observable.from(recents)
                         .toMultimap(
                                 recent -> getMapKey(recent.chapter.date_fetch),
                                 recent -> recent,
                                 () -> new TreeMap<>((d1, d2) -> d2.compareTo(d1))))
-                // add every day and all its chapters to a single list
+                // Add every day and all its chapters to a single list.
                 .map(recents -> {
                     List<Object> items = new ArrayList<>();
                     for (Map.Entry<Date, Collection<MangaChapter>> recent : recents.entrySet()) {
@@ -66,6 +204,35 @@ public class RecentChaptersPresenter extends BasePresenter<RecentChaptersFragmen
                 .observeOn(AndroidSchedulers.mainThread());
     }
 
+    /**
+     * Set the chapter status
+     * @param mangaChapter MangaChapter which status gets updated
+     */
+    private void setChapterStatus(MangaChapter mangaChapter) {
+        // Check if chapter in queue
+        for (Download download : downloadManager.getQueue()) {
+            if (mangaChapter.chapter.id.equals(download.chapter.id)) {
+                mangaChapter.chapter.status = download.getStatus();
+                return;
+            }
+        }
+
+        // Get source of chapter
+        Source source = sourceManager.get(mangaChapter.manga.source);
+
+        // Check if chapter is downloaded
+        if (downloadManager.isChapterDownloaded(source, mangaChapter.manga, mangaChapter.chapter)) {
+            mangaChapter.chapter.status = Download.DOWNLOADED;
+        } else {
+            mangaChapter.chapter.status = Download.NOT_DOWNLOADED;
+        }
+    }
+
+    /**
+     * Get date as time key
+     * @param date desired date
+     * @return date as time key
+     */
     private Date getMapKey(long date) {
         Calendar cal = Calendar.getInstance();
         cal.setTime(new Date(date));
@@ -76,8 +243,67 @@ public class RecentChaptersPresenter extends BasePresenter<RecentChaptersFragmen
         return cal.getTime();
     }
 
+    /**
+     * Open chapter in reader
+     * @param item chapter that is opened
+     */
     public void onOpenChapter(MangaChapter item) {
         Source source = sourceManager.get(item.manga.source);
         EventBus.getDefault().postSticky(new ReaderEvent(source, item.manga, item.chapter));
     }
+
+    /**
+     * Download selected chapter
+     * @param selectedChapter chapter that is selected
+     * @param manga manga that belongs to chapter
+     */
+    public void downloadChapter(Observable<Chapter> selectedChapter, Manga manga) {
+        add(selectedChapter
+                .toList()
+                .subscribe(chapters -> {
+                    EventBus.getDefault().postSticky(new DownloadChaptersEvent(manga, chapters));
+                }));
+    }
+
+    /**
+     * Delete selected chapter
+     * @param chapter chapter that is selected
+     * @param manga manga that belongs to chapter
+     */
+    public void deleteChapter(Chapter chapter, Manga manga) {
+        Source source = sourceManager.get(manga.source);
+        downloadManager.deleteChapter(source, manga, chapter);
+    }
+
+    /**
+     * Delete selected chapter observable
+     * @param selectedChapters chapter that are selected
+     */
+    public void deleteChapters(Observable<Chapter> selectedChapters) {
+        add(selectedChapters
+                .subscribe(chapter -> {
+                    downloadManager.getQueue().remove(chapter);
+                }, error -> {
+                    Timber.e(error.getMessage());
+                }));
+    }
+
+    /**
+     * Mark selected chapter as read
+     * @param selectedChapters chapter that is selected
+     * @param read read status
+     */
+    public void markChaptersRead(Observable<Chapter> selectedChapters, boolean read) {
+        add(selectedChapters
+                .subscribeOn(Schedulers.io())
+                .map(chapter -> {
+                    chapter.read = read;
+                    if (!read) chapter.last_page_read = 0;
+                    return chapter;
+                })
+                .toList()
+                .flatMap(chapters -> db.insertChapters(chapters).asRxObservable())
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe());
+    }
 }

+ 50 - 3
app/src/main/res/layout/item_recent_chapter.xml

@@ -5,6 +5,7 @@
                 android:layout_height="?android:attr/listPreferredItemHeight"
                 android:background="@drawable/selector_chapter_light">
 
+
     <RelativeLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent"
@@ -13,13 +14,34 @@
         android:paddingRight="?android:attr/listPreferredItemPaddingRight"
         android:paddingStart="?android:attr/listPreferredItemPaddingStart">
 
+        <RelativeLayout
+            android:id="@+id/relativeLayout"
+            android:layout_width="fill_parent"
+            android:layout_height="18dp"
+            android:layout_alignParentBottom="true"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentStart="true">
+
+            <TextView
+                android:id="@+id/download_text"
+                android:layout_width="wrap_content"
+                android:layout_height="fill_parent"
+                android:layout_alignParentEnd="true"
+                android:layout_alignParentRight="true"
+                android:layout_centerVertical="true"
+                android:textAllCaps="true"
+                android:textColor="@color/accent_text"
+                android:textSize="12sp"/>
+        </RelativeLayout>
+
         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_marginRight="30dp"
             android:layout_alignParentLeft="true"
             android:layout_alignParentStart="true"
             android:layout_centerVertical="true"
+            android:layout_marginRight="30dp"
+            android:layout_marginEnd="30dp"
             android:orientation="vertical">
 
             <TextView
@@ -27,8 +49,8 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:ellipsize="end"
-                android:textAppearance="@style/TextAppearance.AppCompat.Medium"
                 android:singleLine="true"
+                android:textAppearance="@style/TextAppearance.AppCompat.Medium"
                 tools:text="My manga"/>
 
             <TextView
@@ -36,12 +58,37 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:ellipsize="end"
-                android:textAppearance="@style/TextAppearance.AppCompat.Small"
                 android:maxLines="1"
+                android:textAppearance="@style/TextAppearance.AppCompat.Small"
                 tools:text="Title"/>
 
         </LinearLayout>
 
+
     </RelativeLayout>
 
+    <RelativeLayout
+        android:id="@+id/chapter_menu"
+        android:layout_width="50dp"
+        android:layout_height="fill_parent"
+        android:layout_alignParentEnd="true"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentTop="true"
+        android:gravity="center|end"
+        android:paddingBottom="18dp"
+        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+        android:paddingRight="?android:attr/listPreferredItemPaddingRight">
+
+        <ImageView
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:layout_alignParentEnd="false"
+            android:layout_alignParentRight="true"
+            android:layout_alignParentTop="true"
+            android:background="?android:selectableItemBackground"
+            android:src="@drawable/ic_more_horiz_black_24dp"
+            />
+    </RelativeLayout>
+
+
 </RelativeLayout>

+ 32 - 0
app/src/main/res/menu/chapter_recent.xml

@@ -0,0 +1,32 @@
+<?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_download"
+        android:title="@string/action_download"
+        android:icon="@drawable/ic_file_download"
+        android:visible="true"
+        app:showAsAction="ifRoom"/>
+
+    <item
+        android:id="@+id/action_delete"
+        android:title="@string/action_delete"
+        android:icon="@drawable/ic_action_delete"
+        android:visible="false"
+        app:showAsAction="ifRoom"/>
+
+    <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"/>
+
+</menu>