浏览代码

Update chapters adapter

len 8 年之前
父节点
当前提交
112cdd54e3

+ 116 - 128
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersHolder.kt → app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt

@@ -1,128 +1,116 @@
-package eu.kanade.tachiyomi.ui.manga.chapter
-
-import android.view.View
-import android.widget.PopupMenu
-import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.data.database.models.Manga
-import eu.kanade.tachiyomi.data.download.model.Download
-import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
-import eu.kanade.tachiyomi.util.getResourceColor
-import kotlinx.android.synthetic.main.item_chapter.view.*
-import java.text.DateFormat
-import java.text.DecimalFormat
-import java.text.DecimalFormatSymbols
-import java.util.*
-
-class ChaptersHolder(
-        private val view: View,
-        private val adapter: ChaptersAdapter,
-        listener: FlexibleViewHolder.OnListItemClickListener)
-: FlexibleViewHolder(view, adapter, listener) {
-
-    private val readColor = view.context.getResourceColor(android.R.attr.textColorHint)
-    private val unreadColor = view.context.getResourceColor(android.R.attr.textColorPrimary)
-    private val bookmarkedColor = view.context.getResourceColor(R.attr.colorAccent)
-    private val decimalFormat = DecimalFormat("#.###", DecimalFormatSymbols().apply { decimalSeparator = '.' })
-    private val df = DateFormat.getDateInstance(DateFormat.SHORT)
-
-    private var item: ChapterModel? = null
-
-    init {
-        // We need to post a Runnable to show the popup to make sure that the PopupMenu is
-        // correctly positioned. The reason being that the view may change position before the
-        // PopupMenu is shown.
-        view.chapter_menu.setOnClickListener { it.post { showPopupMenu(it) } }
-    }
-
-    fun onSetValues(chapter: ChapterModel, manga: Manga?) = with(view) {
-        item = chapter
-
-        chapter_title.text = when (manga?.displayMode) {
-            Manga.DISPLAY_NUMBER -> {
-                val formattedNumber = decimalFormat.format(chapter.chapter_number.toDouble())
-                context.getString(R.string.display_mode_chapter, formattedNumber)
-            }
-            else -> chapter.name
-        }
-
-        // Set correct text color
-        chapter_title.setTextColor(if (chapter.read) readColor else unreadColor)
-        if (chapter.bookmark) chapter_title.setTextColor(bookmarkedColor)
-
-        if (chapter.date_upload > 0) {
-            chapter_date.text = df.format(Date(chapter.date_upload))
-            chapter_date.setTextColor(if (chapter.read) readColor else unreadColor)
-        } else {
-            chapter_date.text = ""
-        }
-
-        chapter_pages.text = if (!chapter.read && chapter.last_page_read > 0) {
-            context.getString(R.string.chapter_progress, chapter.last_page_read + 1)
-        } else {
-            ""
-        }
-
-        notifyStatus(chapter.status)
-    }
-
-    fun notifyStatus(status: Int) = with(view.download_text) {
-        when (status) {
-            Download.QUEUE -> setText(R.string.chapter_queued)
-            Download.DOWNLOADING -> setText(R.string.chapter_downloading)
-            Download.DOWNLOADED -> setText(R.string.chapter_downloaded)
-            Download.ERROR -> setText(R.string.chapter_error)
-            else -> text = ""
-        }
-    }
-
-    private fun showPopupMenu(view: View) = item?.let { chapter ->
-        // Create a PopupMenu, giving it the clicked view for an anchor
-        val popup = PopupMenu(view.context, view)
-
-        // Inflate our menu resource into the PopupMenu's Menu
-        popup.menuInflater.inflate(R.menu.chapter_single, popup.menu)
-
-        // Hide download and show delete if the chapter is downloaded
-        if (chapter.isDownloaded) {
-            popup.menu.findItem(R.id.action_download).isVisible = false
-            popup.menu.findItem(R.id.action_delete).isVisible = true
-        }
-
-        // Hide bookmark if bookmark
-        popup.menu.findItem(R.id.action_bookmark).isVisible = !chapter.bookmark
-        popup.menu.findItem(R.id.action_remove_bookmark).isVisible = chapter.bookmark
-
-        // Hide mark as unread when the chapter is unread
-        if (!chapter.read && chapter.last_page_read == 0) {
-            popup.menu.findItem(R.id.action_mark_as_unread).isVisible = false
-        }
-
-        // Hide mark as read when the chapter is read
-        if (chapter.read) {
-            popup.menu.findItem(R.id.action_mark_as_read).isVisible = false
-        }
-
-        // Set a listener so we are notified if a menu item is clicked
-        popup.setOnMenuItemClickListener { menuItem ->
-            val chapterList = listOf(chapter)
-
-            with(adapter.fragment) {
-                when (menuItem.itemId) {
-                    R.id.action_download -> downloadChapters(chapterList)
-                    R.id.action_bookmark -> bookmarkChapters(chapterList, true)
-                    R.id.action_remove_bookmark -> bookmarkChapters(chapterList, false)
-                    R.id.action_delete -> deleteChapters(chapterList)
-                    R.id.action_mark_as_read -> markAsRead(chapterList)
-                    R.id.action_mark_as_unread -> markAsUnread(chapterList)
-                    R.id.action_mark_previous_as_read -> markPreviousAsRead(chapter)
-                }
-            }
-
-            true
-        }
-
-        // Finally show the PopupMenu
-        popup.show()
-    }
-
-}
+package eu.kanade.tachiyomi.ui.manga.chapter
+
+import android.view.View
+import android.widget.PopupMenu
+import eu.davidea.viewholders.FlexibleViewHolder
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.database.models.Manga
+import eu.kanade.tachiyomi.data.download.model.Download
+import eu.kanade.tachiyomi.util.getResourceColor
+import kotlinx.android.synthetic.main.item_chapter.view.*
+import java.text.DateFormat
+import java.text.DecimalFormat
+import java.text.DecimalFormatSymbols
+import java.util.*
+
+class ChapterHolder(
+        private val view: View,
+        private val adapter: ChaptersAdapter)
+: FlexibleViewHolder(view, adapter) {
+
+    private val readColor = view.context.getResourceColor(android.R.attr.textColorHint)
+    private val unreadColor = view.context.getResourceColor(android.R.attr.textColorPrimary)
+    private val bookmarkedColor = view.context.getResourceColor(R.attr.colorAccent)
+    private val decimalFormat = DecimalFormat("#.###", DecimalFormatSymbols().apply { decimalSeparator = '.' })
+    private val df = DateFormat.getDateInstance(DateFormat.SHORT)
+
+    init {
+        // We need to post a Runnable to show the popup to make sure that the PopupMenu is
+        // correctly positioned. The reason being that the view may change position before the
+        // PopupMenu is shown.
+        view.chapter_menu.setOnClickListener { it.post { showPopupMenu(it) } }
+    }
+
+    fun bind(item: ChapterItem, manga: Manga) = with(view) {
+        val chapter = item.chapter
+
+        chapter_title.text = when (manga.displayMode) {
+            Manga.DISPLAY_NUMBER -> {
+                val formattedNumber = decimalFormat.format(chapter.chapter_number.toDouble())
+                context.getString(R.string.display_mode_chapter, formattedNumber)
+            }
+            else -> chapter.name
+        }
+
+        // Set correct text color
+        chapter_title.setTextColor(if (chapter.read) readColor else unreadColor)
+        if (chapter.bookmark) chapter_title.setTextColor(bookmarkedColor)
+
+        if (chapter.date_upload > 0) {
+            chapter_date.text = df.format(Date(chapter.date_upload))
+            chapter_date.setTextColor(if (chapter.read) readColor else unreadColor)
+        } else {
+            chapter_date.text = ""
+        }
+
+        chapter_pages.text = if (!chapter.read && chapter.last_page_read > 0) {
+            context.getString(R.string.chapter_progress, chapter.last_page_read + 1)
+        } else {
+            ""
+        }
+
+        notifyStatus(item.status)
+    }
+
+    fun notifyStatus(status: Int) = with(view.download_text) {
+        when (status) {
+            Download.QUEUE -> setText(R.string.chapter_queued)
+            Download.DOWNLOADING -> setText(R.string.chapter_downloading)
+            Download.DOWNLOADED -> setText(R.string.chapter_downloaded)
+            Download.ERROR -> setText(R.string.chapter_error)
+            else -> text = ""
+        }
+    }
+
+    private fun showPopupMenu(view: View) {
+        val item = adapter.getItem(adapterPosition) ?: return
+
+        // Create a PopupMenu, giving it the clicked view for an anchor
+        val popup = PopupMenu(view.context, view)
+
+        // Inflate our menu resource into the PopupMenu's Menu
+        popup.menuInflater.inflate(R.menu.chapter_single, popup.menu)
+
+        val chapter = item.chapter
+
+        // Hide download and show delete if the chapter is downloaded
+        if (item.isDownloaded) {
+            popup.menu.findItem(R.id.action_download).isVisible = false
+            popup.menu.findItem(R.id.action_delete).isVisible = true
+        }
+
+        // Hide bookmark if bookmark
+        popup.menu.findItem(R.id.action_bookmark).isVisible = !chapter.bookmark
+        popup.menu.findItem(R.id.action_remove_bookmark).isVisible = chapter.bookmark
+
+        // Hide mark as unread when the chapter is unread
+        if (!chapter.read && chapter.last_page_read == 0) {
+            popup.menu.findItem(R.id.action_mark_as_unread).isVisible = false
+        }
+
+        // Hide mark as read when the chapter is read
+        if (chapter.read) {
+            popup.menu.findItem(R.id.action_mark_as_read).isVisible = false
+        }
+
+        // Set a listener so we are notified if a menu item is clicked
+        popup.setOnMenuItemClickListener { menuItem ->
+            adapter.menuItemListener(adapterPosition, menuItem)
+            true
+        }
+
+        // Finally show the PopupMenu
+        popup.show()
+    }
+
+}

+ 50 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterItem.kt

@@ -0,0 +1,50 @@
+package eu.kanade.tachiyomi.ui.manga.chapter
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import eu.davidea.flexibleadapter.FlexibleAdapter
+import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
+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.download.model.Download
+
+class ChapterItem(val chapter: Chapter, val manga: Manga) : AbstractFlexibleItem<ChapterHolder>(),
+        Chapter by chapter {
+
+    private var _status: Int = 0
+
+    var status: Int
+        get() = download?.status ?: _status
+        set(value) { _status = value }
+
+    @Transient var download: Download? = null
+
+    val isDownloaded: Boolean
+        get() = status == Download.DOWNLOADED
+
+    override fun getLayoutRes(): Int {
+        return R.layout.item_chapter
+    }
+
+    override fun createViewHolder(adapter: FlexibleAdapter<*>, inflater: LayoutInflater, parent: ViewGroup): ChapterHolder {
+        return ChapterHolder(inflater.inflate(layoutRes, parent, false), adapter as ChaptersAdapter)
+    }
+
+    override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: ChapterHolder, position: Int, payloads: List<Any?>?) {
+        holder.bind(this, manga)
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other is ChapterItem) {
+            return chapter.id!! == other.chapter.id!!
+        }
+        return false
+    }
+
+    override fun hashCode(): Int {
+        return chapter.id!!.hashCode()
+    }
+
+}

+ 0 - 19
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterModel.kt

@@ -1,19 +0,0 @@
-package eu.kanade.tachiyomi.ui.manga.chapter
-
-import eu.kanade.tachiyomi.data.database.models.Chapter
-import eu.kanade.tachiyomi.data.download.model.Download
-
-class ChapterModel(c: Chapter) : Chapter by c {
-
-    private var _status: Int = 0
-
-    var status: Int
-        get() = download?.status ?: _status
-        set(value) { _status = value }
-
-    @Transient var download: Download? = null
-
-    val isDownloaded: Boolean
-        get() = status == Download.DOWNLOADED
-
-}

+ 9 - 32
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt

@@ -1,42 +1,19 @@
 package eu.kanade.tachiyomi.ui.manga.chapter
 
-import android.view.ViewGroup
-import eu.davidea.flexibleadapter4.FlexibleAdapter
-import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.util.inflate
+import android.view.MenuItem
+import eu.davidea.flexibleadapter.FlexibleAdapter
 
-class ChaptersAdapter(val fragment: ChaptersFragment) : FlexibleAdapter<ChaptersHolder, ChapterModel>() {
+class ChaptersAdapter(val fragment: ChaptersFragment) : FlexibleAdapter<ChapterItem>(null, fragment, true) {
 
-    init {
-        setHasStableIds(true)
-    }
-
-    var items: List<ChapterModel>
-        get() = mItems
-        set(value) {
-            mItems = value
-            notifyDataSetChanged()
-        }
-
-    override fun updateDataSet(param: String) {
-    }
-
-    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChaptersHolder {
-        val v = parent.inflate(R.layout.item_chapter)
-        return ChaptersHolder(v, this, fragment)
-    }
-
-    override fun onBindViewHolder(holder: ChaptersHolder, position: Int) {
-        val chapter = getItem(position)
-        val manga = fragment.presenter.manga
-        holder.onSetValues(chapter, manga)
+    var items: List<ChapterItem> = emptyList()
 
-        //When user scrolls this bind the correct selection status
-        holder.itemView.isActivated = isSelected(position)
+    val menuItemListener: (Int, MenuItem) -> Unit = { position, item ->
+        fragment.onItemMenuClick(position, item)
     }
 
-    override fun getItemId(position: Int): Long {
-        return mItems[position].id!!
+    override fun updateDataSet(items: List<ChapterItem>) {
+        this.items = items
+        super.updateDataSet(items.toList())
     }
 
 }

+ 47 - 37
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt

@@ -12,12 +12,11 @@ import android.support.v7.widget.DividerItemDecoration
 import android.support.v7.widget.LinearLayoutManager
 import android.view.*
 import com.afollestad.materialdialogs.MaterialDialog
-import eu.davidea.flexibleadapter4.FlexibleAdapter
+import eu.davidea.flexibleadapter.FlexibleAdapter
 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.download.model.Download
-import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
 import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
 import eu.kanade.tachiyomi.ui.manga.MangaActivity
 import eu.kanade.tachiyomi.ui.reader.ReaderActivity
@@ -30,7 +29,10 @@ import nucleus.factory.RequiresPresenter
 import timber.log.Timber
 
 @RequiresPresenter(ChaptersPresenter::class)
-class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener {
+class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(),
+        ActionMode.Callback,
+        FlexibleAdapter.OnItemClickListener,
+        FlexibleAdapter.OnItemLongClickListener {
 
     companion object {
         /**
@@ -71,38 +73,31 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
         recycler.layoutManager = LinearLayoutManager(activity)
         recycler.addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
         recycler.setHasFixedSize(true)
+//        TODO enable in a future commit
+//        adapter.setFastScroller(fast_scroller, context.getResourceColor(R.attr.colorAccent))
+//        adapter.toggleFastScroller()
 
         swipe_refresh.setOnRefreshListener { fetchChapters() }
 
         fab.setOnClickListener {
-            val chapter = presenter.getNextUnreadChapter()
-            if (chapter != null) {
+            val item = presenter.getNextUnreadChapter()
+            if (item != null) {
                 // Create animation listener
                 val revealAnimationListener: Animator.AnimatorListener = object : AnimatorListenerAdapter() {
                     override fun onAnimationStart(animation: Animator?) {
-                        openChapter(chapter, true)
+                        openChapter(item.chapter, true)
                     }
                 }
 
                 // Get coordinates and start animation
                 val coordinates = fab.getCoordinates()
                 if (!reveal_view.showRevealEffect(coordinates.x, coordinates.y, revealAnimationListener)) {
-                    openChapter(chapter)
+                    openChapter(item.chapter)
                 }
             } else {
                 context.toast(R.string.no_next_chapter)
             }
         }
-
-    }
-
-    override fun onPause() {
-        // Stop recycler's scrolling when onPause is called. If the activity is finishing
-        // the presenter will be destroyed, and it could cause NPE
-        // https://github.com/inorichi/tachiyomi/issues/159
-        recycler.stopScroll()
-
-        super.onPause()
     }
 
     override fun onResume() {
@@ -173,19 +168,20 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
         return true
     }
 
+    @Suppress("UNUSED_PARAMETER")
     fun onNextManga(manga: Manga) {
         // Set initial values
         activity.supportInvalidateOptionsMenu()
     }
 
-    fun onNextChapters(chapters: List<ChapterModel>) {
+    fun onNextChapters(chapters: List<ChapterItem>) {
         // If the list is empty, fetch chapters from source if the conditions are met
         // We use presenter chapters instead because they are always unfiltered
         if (presenter.chapters.isEmpty())
             initialFetchChapters()
 
         destroyActionModeIfNeeded()
-        adapter.items = chapters
+        adapter.updateDataSet(chapters)
     }
 
     private fun initialFetchChapters() {
@@ -230,7 +226,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
                 .title(R.string.action_display_mode)
                 .items(modes.map { getString(it) })
                 .itemsIds(ids)
-                .itemsCallbackSingleChoice(selectedIndex) { dialog, itemView, which, text ->
+                .itemsCallbackSingleChoice(selectedIndex) { _, itemView, _, _ ->
                     // Save the new display mode
                     presenter.setDisplayMode(itemView.id)
                     // Refresh ui
@@ -250,7 +246,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
                 .title(R.string.sorting_mode)
                 .items(modes.map { getString(it) })
                 .itemsIds(ids)
-                .itemsCallbackSingleChoice(selectedIndex) { dialog, itemView, which, text ->
+                .itemsCallbackSingleChoice(selectedIndex) { _, itemView, _, _ ->
                     // Save the new sorting mode
                     presenter.setSorting(itemView.id)
                     true
@@ -267,7 +263,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
                 .title(R.string.manga_download)
                 .negativeText(android.R.string.cancel)
                 .items(modes.map { getString(it) })
-                .itemsCallback { dialog, view, i, charSequence ->
+                .itemsCallback { _, _, i, _ ->
 
                     fun getUnreadChaptersSorted() = presenter.chapters
                             .filter { !it.read && it.status == Download.NOT_DOWNLOADED }
@@ -299,8 +295,8 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
         getHolder(download.chapter)?.notifyStatus(download.status)
     }
 
-    private fun getHolder(chapter: Chapter): ChaptersHolder? {
-        return recycler.findViewHolderForItemId(chapter.id!!) as? ChaptersHolder
+    private fun getHolder(chapter: Chapter): ChapterHolder? {
+        return recycler.findViewHolderForItemId(chapter.id!!) as? ChapterHolder
     }
 
     override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
@@ -324,7 +320,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
                         .content(R.string.confirm_delete_chapters)
                         .positiveText(android.R.string.yes)
                         .negativeText(android.R.string.no)
-                        .onPositive { dialog, action -> deleteChapters(getSelectedChapters()) }
+                        .onPositive { _, _ -> deleteChapters(getSelectedChapters()) }
                         .show()
             }
             else -> return false
@@ -338,8 +334,8 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
         actionMode = null
     }
 
-    fun getSelectedChapters(): List<ChapterModel> {
-        return adapter.selectedItems.map { adapter.getItem(it) }
+    fun getSelectedChapters(): List<ChapterItem> {
+        return adapter.selectedPositions.map { adapter.getItem(it) }
     }
 
     fun destroyActionModeIfNeeded() {
@@ -351,18 +347,18 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
         setContextTitle(adapter.selectedItemCount)
     }
 
-    fun markAsRead(chapters: List<ChapterModel>) {
+    fun markAsRead(chapters: List<ChapterItem>) {
         presenter.markChaptersRead(chapters, true)
         if (presenter.preferences.removeAfterMarkedAsRead()) {
             deleteChapters(chapters)
         }
     }
 
-    fun markAsUnread(chapters: List<ChapterModel>) {
+    fun markAsUnread(chapters: List<ChapterItem>) {
         presenter.markChaptersRead(chapters, false)
     }
 
-    fun markPreviousAsRead(chapter: ChapterModel) {
+    fun markPreviousAsRead(chapter: ChapterItem) {
         val chapters = if (presenter.sortDescending()) adapter.items.reversed() else adapter.items
         val chapterPos = chapters.indexOf(chapter)
         if (chapterPos != -1) {
@@ -370,7 +366,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
         }
     }
 
-    fun downloadChapters(chapters: List<ChapterModel>) {
+    fun downloadChapters(chapters: List<ChapterItem>) {
         destroyActionModeIfNeeded()
         presenter.downloadChapters(chapters)
         if (!presenter.manga.favorite){
@@ -382,12 +378,12 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
         }
     }
 
-    fun bookmarkChapters(chapters: List<ChapterModel>, bookmarked: Boolean) {
+    fun bookmarkChapters(chapters: List<ChapterItem>, bookmarked: Boolean) {
         destroyActionModeIfNeeded()
         presenter.bookmarkChapters(chapters, bookmarked)
     }
 
-    fun deleteChapters(chapters: List<ChapterModel>) {
+    fun deleteChapters(chapters: List<ChapterItem>) {
         destroyActionModeIfNeeded()
         DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG)
         presenter.deleteChapters(chapters)
@@ -408,26 +404,40 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
                 ?.dismissAllowingStateLoss()
     }
 
-    override fun onListItemClick(position: Int): Boolean {
+    override fun onItemClick(position: Int): Boolean {
         val item = adapter.getItem(position) ?: return false
         if (actionMode != null && adapter.mode == FlexibleAdapter.MODE_MULTI) {
             toggleSelection(position)
             return true
         } else {
-            openChapter(item)
+            openChapter(item.chapter)
             return false
         }
     }
 
-    override fun onListItemLongClick(position: Int) {
+    override fun onItemLongClick(position: Int) {
         if (actionMode == null)
             actionMode = (activity as AppCompatActivity).startSupportActionMode(this)
 
         toggleSelection(position)
     }
 
+    fun onItemMenuClick(position: Int, item: MenuItem) {
+        val chapter = adapter.getItem(position)?.let { listOf(it) } ?: return
+
+        when (item.itemId) {
+            R.id.action_download -> downloadChapters(chapter)
+            R.id.action_bookmark -> bookmarkChapters(chapter, true)
+            R.id.action_remove_bookmark -> bookmarkChapters(chapter, false)
+            R.id.action_delete -> deleteChapters(chapter)
+            R.id.action_mark_as_read -> markAsRead(chapter)
+            R.id.action_mark_as_unread -> markAsUnread(chapter)
+            R.id.action_mark_previous_as_read -> markPreviousAsRead(chapter[0])
+        }
+    }
+
     private fun toggleSelection(position: Int) {
-        adapter.toggleSelection(position, false)
+        adapter.toggleSelection(position)
 
         val count = adapter.selectedItemCount
         if (count == 0) {

+ 17 - 17
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt

@@ -65,14 +65,14 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
     /**
      * List of chapters of the manga. It's always unfiltered and unsorted.
      */
-    var chapters: List<ChapterModel> = emptyList()
+    var chapters: List<ChapterItem> = emptyList()
         private set
 
     /**
      * Subject of list of chapters to allow updating the view without going to DB.
      */
-    val chaptersRelay: PublishRelay<List<ChapterModel>>
-            by lazy { PublishRelay.create<List<ChapterModel>>() }
+    val chaptersRelay: PublishRelay<List<ChapterItem>>
+            by lazy { PublishRelay.create<List<ChapterItem>>() }
 
     /**
      * Whether the chapter list has been requested to the source.
@@ -103,7 +103,7 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
         chaptersRelay.flatMap { applyChapterFilters(it) }
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribeLatestCache(ChaptersFragment::onNextChapters,
-                        { view, error -> Timber.e(error) })
+                        { _, error -> Timber.e(error) })
 
         // Add the subscription that retrieves the chapters from the database, keeps subscribed to
         // changes, and sends the list of chapters to the relay.
@@ -135,15 +135,15 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
                 .filter { download -> download.manga.id == manga.id }
                 .doOnNext { onDownloadStatusChange(it) }
                 .subscribeLatestCache(ChaptersFragment::onChapterStatusChange,
-                        { view, error -> Timber.e(error) })
+                        { _, error -> Timber.e(error) })
     }
 
     /**
      * Converts a chapter from the database to an extended model, allowing to store new fields.
      */
-    private fun Chapter.toModel(): ChapterModel {
+    private fun Chapter.toModel(): ChapterItem {
         // Create the model object.
-        val model = ChapterModel(this)
+        val model = ChapterItem(this, manga)
 
         // Find an active download for this chapter.
         val download = downloadManager.queue.find { it.chapter.id == id }
@@ -160,7 +160,7 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
      *
      * @param chapters the list of chapter from the database.
      */
-    private fun setDownloadedChapters(chapters: List<ChapterModel>) {
+    private fun setDownloadedChapters(chapters: List<ChapterItem>) {
         val files = downloadManager.findMangaDir(source, manga)?.listFiles() ?: return
         val cached = mutableMapOf<Chapter, String>()
         files.mapNotNull { it.name }
@@ -181,7 +181,7 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
                 .subscribeOn(Schedulers.io())
                 .map { syncChaptersWithSource(db, it, manga, source) }
                 .observeOn(AndroidSchedulers.mainThread())
-                .subscribeFirst({ view, chapters ->
+                .subscribeFirst({ view, _ ->
                     view.onFetchChaptersDone()
                 }, ChaptersFragment::onFetchChaptersError)
     }
@@ -198,7 +198,7 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
      * @param chapters the list of chapters from the database
      * @return an observable of the list of chapters filtered and sorted.
      */
-    private fun applyChapterFilters(chapters: List<ChapterModel>): Observable<List<ChapterModel>> {
+    private fun applyChapterFilters(chapters: List<ChapterItem>): Observable<List<ChapterItem>> {
         var observable = Observable.from(chapters).subscribeOn(Schedulers.io())
         if (onlyUnread()) {
             observable = observable.filter { !it.read }
@@ -248,7 +248,7 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
     /**
      * Returns the next unread chapter or null if everything is read.
      */
-    fun getNextUnreadChapter(): ChapterModel? {
+    fun getNextUnreadChapter(): ChapterItem? {
         return chapters.sortedByDescending { it.source_order }.find { !it.read }
     }
 
@@ -257,7 +257,7 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
      * @param selectedChapters the list of selected chapters.
      * @param read whether to mark chapters as read or unread.
      */
-    fun markChaptersRead(selectedChapters: List<ChapterModel>, read: Boolean) {
+    fun markChaptersRead(selectedChapters: List<ChapterItem>, read: Boolean) {
         Observable.from(selectedChapters)
                 .doOnNext { chapter ->
                     chapter.read = read
@@ -275,7 +275,7 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
      * Downloads the given list of chapters with the manager.
      * @param chapters the list of chapters to download.
      */
-    fun downloadChapters(chapters: List<ChapterModel>) {
+    fun downloadChapters(chapters: List<ChapterItem>) {
         DownloadService.start(context)
         downloadManager.downloadChapters(manga, chapters)
     }
@@ -284,7 +284,7 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
      * Bookmarks the given list of chapters.
      * @param selectedChapters the list of chapters to bookmark.
      */
-    fun bookmarkChapters(selectedChapters: List<ChapterModel>, bookmarked: Boolean) {
+    fun bookmarkChapters(selectedChapters: List<ChapterItem>, bookmarked: Boolean) {
         Observable.from(selectedChapters)
                 .doOnNext { chapter ->
                     chapter.bookmark = bookmarked
@@ -299,14 +299,14 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
      * Deletes the given list of chapter.
      * @param chapters the list of chapters to delete.
      */
-    fun deleteChapters(chapters: List<ChapterModel>) {
+    fun deleteChapters(chapters: List<ChapterItem>) {
         Observable.from(chapters)
                 .doOnNext { deleteChapter(it) }
                 .toList()
                 .doOnNext { if (onlyDownloaded()) refreshChapters() }
                 .subscribeOn(Schedulers.io())
                 .observeOn(AndroidSchedulers.mainThread())
-                .subscribeFirst({ view, result ->
+                .subscribeFirst({ view, _ ->
                     view.onChaptersDeleted()
                 }, ChaptersFragment::onChaptersDeletedError)
     }
@@ -315,7 +315,7 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
      * Deletes a chapter from disk. This method is called in a background thread.
      * @param chapter the chapter to delete.
      */
-    private fun deleteChapter(chapter: ChapterModel) {
+    private fun deleteChapter(chapter: ChapterItem) {
         downloadManager.queue.remove(chapter)
         downloadManager.deleteChapter(source, manga, chapter)
         chapter.status = Download.NOT_DOWNLOADED

+ 12 - 2
app/src/main/res/layout/fragment_manga_chapters.xml

@@ -13,8 +13,7 @@
         android:layout_height="match_parent"
         android:background="?attr/colorAccent"
         android:elevation="5dp"
-        android:visibility="invisible"
-        />
+        android:visibility="invisible"/>
 
     <android.support.v4.widget.SwipeRefreshLayout
         android:id="@+id/swipe_refresh"
@@ -36,6 +35,17 @@
 
     </android.support.v4.widget.SwipeRefreshLayout>
 
+    <eu.davidea.fastscroller.FastScroller
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:tools="http://schemas.android.com/tools"
+        android:id="@+id/fast_scroller"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_centerHorizontal="true"
+        android:layout_gravity="end"
+        android:visibility="gone"
+        tools:visibility="visible"/>
+
     <android.support.design.widget.FloatingActionButton
         android:id="@+id/fab"
         style="@style/Theme.Widget.FAB"