Update chapters adapter

This commit is contained in:
len 2017-03-11 21:20:46 +01:00
parent b512c67b5d
commit 112cdd54e3
7 changed files with 252 additions and 236 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,8 +13,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/colorAccent" android:background="?attr/colorAccent"
android:elevation="5dp" android:elevation="5dp"
android:visibility="invisible" android:visibility="invisible"/>
/>
<android.support.v4.widget.SwipeRefreshLayout <android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh" android:id="@+id/swipe_refresh"
@ -36,6 +35,17 @@
</android.support.v4.widget.SwipeRefreshLayout> </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.support.design.widget.FloatingActionButton
android:id="@+id/fab" android:id="@+id/fab"
style="@style/Theme.Widget.FAB" style="@style/Theme.Widget.FAB"