From 83206ded5e8ce5947d851ae788e36a4449150ef2 Mon Sep 17 00:00:00 2001 From: Jay Date: Sat, 4 Apr 2020 20:18:27 -0400 Subject: [PATCH] Update Recents updates items to new style Also removed newly added manga from flooding recent updates (closing #52) --- .../data/database/queries/RawQueries.kt | 4 +- .../ui/manga/chapter/ChapterHolder.kt | 3 +- .../ui/recent_updates/RecentChapterHolder.kt | 95 ++---- .../ui/recent_updates/RecentChapterItem.kt | 31 +- .../recent_updates/RecentChaptersAdapter.kt | 19 +- .../RecentChaptersController.kt | 289 +++++++----------- .../recent_updates/RecentChaptersPresenter.kt | 218 ++++++------- .../recently_read/RecentlyReadController.kt | 2 +- .../main/res/layout/recent_chapters_item.xml | 137 +++++---- app/src/main/res/values/strings.xml | 2 +- 10 files changed, 318 insertions(+), 482 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt index 18d5c76f4a..a14fd3b41e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt @@ -57,7 +57,9 @@ fun getLibraryMangaQuery(id: Long) = """ fun getRecentsQuery() = """ SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, * FROM ${Manga.TABLE} JOIN ${Chapter.TABLE} ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} - WHERE ${Manga.COL_FAVORITE} = 1 AND ${Chapter.COL_DATE_UPLOAD} > ? + WHERE ${Manga.COL_FAVORITE} = 1 + AND ${Chapter.COL_DATE_UPLOAD} > ? + AND ${Chapter.COL_DATE_FETCH} > ${Manga.COL_DATE_ADDED} ORDER BY ${Chapter.COL_DATE_UPLOAD} DESC """ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt index 8a650ebf7e..0917c367de 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt @@ -114,8 +114,7 @@ class ChapterHolder( } private fun resetFrontView() { - if (front_view.translationX == 0f) return - itemView.post { adapter.notifyItemChanged(adapterPosition) } + if (front_view.translationX != 0f) itemView.post { adapter.notifyItemChanged(adapterPosition) } } fun notifyStatus(status: Int, locked: Boolean, progress: Int) = with(download_button) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChapterHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChapterHolder.kt index 360ae19630..d7dae74691 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChapterHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChapterHolder.kt @@ -1,14 +1,13 @@ package eu.kanade.tachiyomi.ui.recent_updates import android.view.View -import androidx.appcompat.widget.PopupMenu +import androidx.core.content.ContextCompat import com.bumptech.glide.load.engine.DiskCacheStrategy import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.glide.GlideApp -import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder +import eu.kanade.tachiyomi.ui.manga.chapter.BaseChapterHolder import eu.kanade.tachiyomi.util.system.getResourceColor -import eu.kanade.tachiyomi.util.view.setVectorCompat +import kotlinx.android.synthetic.main.download_button.* import kotlinx.android.synthetic.main.recent_chapters_item.* /** @@ -22,7 +21,7 @@ import kotlinx.android.synthetic.main.recent_chapters_item.* * @constructor creates a new recent chapter holder. */ class RecentChapterHolder(private val view: View, private val adapter: RecentChaptersAdapter) : - BaseFlexibleViewHolder(view, adapter) { + BaseChapterHolder(view, adapter) { /** * Color of read chapter @@ -40,10 +39,6 @@ class RecentChapterHolder(private val view: View, private val adapter: RecentCha private var item: RecentChapterItem? = 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. - chapter_menu.setOnClickListener { it.post { showPopupMenu(it) } } manga_cover.setOnClickListener { adapter.coverClickListener.onCoverClick(adapterPosition) } @@ -63,8 +58,14 @@ class RecentChapterHolder(private val view: View, private val adapter: RecentCha // Set manga title manga_full_title.text = item.manga.title - // Set the correct drawable for dropdown and update the tint to match theme. - chapter_menu_icon.setVectorCompat(R.drawable.ic_more_horiz_black_24dp, view.context.getResourceColor(R.attr.icon_color)) + if (front_view.translationX == 0f) { + read.setImageDrawable( + ContextCompat.getDrawable( + read.context, if (item.read) R.drawable.eye_off + else R.drawable.eye + ) + ) + } // Set cover GlideApp.with(itemView.context).clear(manga_cover) @@ -86,7 +87,20 @@ class RecentChapterHolder(private val view: View, private val adapter: RecentCha } // Set chapter status - notifyStatus(item.status) + notifyStatus(item.status, item.progress) + resetFrontView() + } + + private fun resetFrontView() { + if (front_view.translationX != 0f) itemView.post { adapter.notifyItemChanged(adapterPosition) } + } + + override fun getFrontView(): View { + return front_view + } + + override fun getRearRightView(): View { + return right_view } /** @@ -94,59 +108,6 @@ class RecentChapterHolder(private val view: View, private val adapter: RecentCha * * @param status download status */ - fun notifyStatus(status: Int) = with(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 = "" - } - } - - /** - * Show pop up menu - * - * @param view view containing popup menu. - */ - private fun showPopupMenu(view: View) = item?.let { item -> - // 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_recent, popup.menu) - - // Hide download and show delete if the chapter is downloaded and - if (item.isDownloaded) { - popup.menu.findItem(R.id.action_download).isVisible = false - popup.menu.findItem(R.id.action_delete).isVisible = true - } - - // Hide mark as unread when the chapter is unread - if (!item.chapter.read /*&& mangaChapter.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 (item.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 -> - with(adapter.controller) { - when (menuItem.itemId) { - R.id.action_download -> downloadChapter(item) - R.id.action_delete -> deleteChapter(item) - R.id.action_mark_as_read -> markAsRead(listOf(item)) - R.id.action_mark_as_unread -> markAsUnread(listOf(item)) - } - } - - true - } - - // Finally show the PopupMenu - popup.show() - } + fun notifyStatus(status: Int, progress: Int) = + download_button.setDownloadStatus(status, progress) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChapterItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChapterItem.kt index e1acb6b04d..4f221d1ca7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChapterItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChapterItem.kt @@ -3,26 +3,14 @@ package eu.kanade.tachiyomi.ui.recent_updates import android.view.View import androidx.recyclerview.widget.RecyclerView import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.items.AbstractSectionableItem import eu.davidea.flexibleadapter.items.IFlexible 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.manga.chapter.BaseChapterItem -class RecentChapterItem(val chapter: Chapter, val manga: Manga, header: DateItem) : - AbstractSectionableItem(header) { - - 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 +class RecentChapterItem(chapter: Chapter, val manga: Manga, header: DateItem) : + BaseChapterItem(chapter, header) { override fun getLayoutRes(): Int { return R.layout.recent_chapters_item @@ -38,7 +26,6 @@ class RecentChapterItem(val chapter: Chapter, val manga: Manga, header: DateItem position: Int, payloads: MutableList? ) { - holder.bind(this) } @@ -46,16 +33,4 @@ class RecentChapterItem(val chapter: Chapter, val manga: Manga, header: DateItem return chapter.name.contains(text, false) || manga.title.contains(text, false) } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other is RecentChapterItem) { - return chapter.id!! == other.chapter.id!! - } - return false - } - - override fun hashCode(): Int { - return chapter.id!!.hashCode() - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersAdapter.kt index 691776593a..6d72497641 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersAdapter.kt @@ -1,13 +1,15 @@ package eu.kanade.tachiyomi.ui.recent_updates -import eu.davidea.flexibleadapter.FlexibleAdapter +import androidx.recyclerview.widget.ItemTouchHelper import eu.davidea.flexibleadapter.items.IFlexible +import eu.kanade.tachiyomi.ui.manga.chapter.BaseChapterAdapter class RecentChaptersAdapter(val controller: RecentChaptersController) : - FlexibleAdapter>(null, controller, true) { + BaseChapterAdapter>(controller) { val coverClickListener: OnCoverClickListener = controller var recents = emptyList() + private var isAnimating = false init { setDisplayHeadersAtStartUp(true) @@ -22,13 +24,22 @@ class RecentChaptersAdapter(val controller: RecentChaptersController) : fun performFilter() { val s = getFilter(String::class.java) if (s.isNullOrBlank()) { - updateDataSet(recents) + updateDataSet(recents, isAnimating) } else { - updateDataSet(recents.filter { it.filter(s) }) + updateDataSet(recents.filter { it.filter(s) }, isAnimating) } + isAnimating = false } interface OnCoverClickListener { fun onCoverClick(position: Int) } + + override fun onItemSwiped(position: Int, direction: Int) { + super.onItemSwiped(position, direction) + isAnimating = true + when (direction) { + ItemTouchHelper.LEFT -> controller.toggleMarkAsRead(position) + } + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersController.kt index 4602001187..c0d402d57d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersController.kt @@ -1,33 +1,35 @@ package eu.kanade.tachiyomi.ui.recent_updates +import android.app.Activity +import android.os.Bundle import android.view.LayoutInflater -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem import android.view.View import android.view.ViewGroup -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.view.ActionMode import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager -import com.jakewharton.rxbinding.support.v4.widget.refreshes -import com.jakewharton.rxbinding.support.v7.widget.scrollStateChanges +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.snackbar.BaseTransientBottomBar +import com.google.android.material.snackbar.Snackbar import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.davidea.flexibleadapter.SelectableAdapter import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.notification.Notifications -import eu.kanade.tachiyomi.ui.base.controller.NucleusController +import eu.kanade.tachiyomi.ui.base.controller.BaseController import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.manga.MangaDetailsController +import eu.kanade.tachiyomi.ui.manga.chapter.BaseChapterAdapter import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.view.scrollViewWith import eu.kanade.tachiyomi.util.view.snack +import kotlinx.android.synthetic.main.download_bottom_sheet.* import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.recent_chapters_controller.* +import kotlinx.android.synthetic.main.recent_chapters_controller.empty_view import timber.log.Timber /** @@ -35,21 +37,10 @@ import timber.log.Timber * Uses [R.layout.recent_chapters_controller]. * UI related actions should be called from here. */ -class RecentChaptersController : NucleusController(), - ActionMode.Callback, - FlexibleAdapter.OnItemClickListener, - FlexibleAdapter.OnItemLongClickListener, - FlexibleAdapter.OnUpdateListener, - ConfirmDeleteChaptersDialog.Listener, - RecentChaptersAdapter.OnCoverClickListener { - - init { - setHasOptionsMenu(true) - } - /** - * Action mode for multiple selection. - */ - private var actionMode: ActionMode? = null +class RecentChaptersController(bundle: Bundle? = null) : BaseController(bundle), + FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnUpdateListener, + FlexibleAdapter.OnItemMoveListener, + RecentChaptersAdapter.OnCoverClickListener, BaseChapterAdapter.DownloadInterface { /** * Adapter containing the recent chapters. @@ -57,16 +48,14 @@ class RecentChaptersController : NucleusController(), var adapter: RecentChaptersAdapter? = null private set - private var query = "" + private var presenter = RecentChaptersPresenter(this) + private var snack: Snackbar? = null + private var lastChapterId: Long? = null override fun getTitle(): String? { return resources?.getString(R.string.label_recent_updates) } - override fun createPresenter(): RecentChaptersPresenter { - return RecentChaptersPresenter() - } - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { return inflater.inflate(R.layout.recent_chapters_controller, container, false) } @@ -88,14 +77,14 @@ class RecentChaptersController : NucleusController(), adapter = RecentChaptersAdapter(this@RecentChaptersController) recycler.adapter = adapter - recycler.scrollStateChanges().subscribeUntilDestroy { - // Disable swipe refresh when view is not at the top - val firstPos = layoutManager.findFirstCompletelyVisibleItemPosition() - swipe_refresh.isEnabled = firstPos <= 0 - } + adapter?.isSwipeEnabled = true + adapter?.itemTouchHelperCallback?.setSwipeFlags( + ItemTouchHelper.LEFT + ) + if (presenter.chapters.isNotEmpty()) adapter?.updateDataSet(presenter.chapters.toList()) swipe_refresh.setDistanceToTriggerSync((2 * 64 * view.resources.displayMetrics.density).toInt()) - swipe_refresh.refreshes().subscribeUntilDestroy { + swipe_refresh.setOnRefreshListener { if (!LibraryUpdateService.isRunning()) { LibraryUpdateService.start(view.context) view.snack(R.string.updating_library) { @@ -107,24 +96,32 @@ class RecentChaptersController : NucleusController(), swipe_refresh.isRefreshing = false } - scrollViewWith(recycler, swipeRefreshLayout = swipe_refresh) + scrollViewWith(recycler, swipeRefreshLayout = swipe_refresh, padBottom = true) + + presenter.onCreate() + } + + override fun onDestroy() { + super.onDestroy() + presenter.onDestroy() } override fun onDestroyView(view: View) { adapter = null - actionMode = null + snack = null super.onDestroyView(view) } - /** - * Returns selected chapters - * @return list of selected chapters - */ - fun getSelectedChapters(): List { - val adapter = adapter ?: return emptyList() - return adapter.selectedPositions.mapNotNull { adapter.getItem(it) as? RecentChapterItem } + override fun onActivityResumed(activity: Activity) { + super.onActivityResumed(activity) + if (view != null) { + refresh() + dl_bottom_sheet?.update() + } } + fun refresh() = presenter.getUpdates() + /** * Called when item in list is clicked * @param position position of clicked item @@ -134,34 +131,8 @@ class RecentChaptersController : NucleusController(), // Get item from position val item = adapter.getItem(position) as? RecentChapterItem ?: return false - if (actionMode != null && adapter.mode == SelectableAdapter.Mode.MULTI) { - toggleSelection(position) - return true - } else { - openChapter(item) - return false - } - } - - /** - * Called when item in list is long clicked - * @param position position of clicked item - */ - override fun onItemLongClick(position: Int) { - if (actionMode == null) - actionMode = (activity as AppCompatActivity).startSupportActionMode(this) - - toggleSelection(position) - } - - /** - * Called to toggle selection - * @param position position of selected item - */ - private fun toggleSelection(position: Int) { - val adapter = adapter ?: return - adapter.toggleSelection(position) - actionMode?.invalidate() + openChapter(item) + return false } /** @@ -174,24 +145,21 @@ class RecentChaptersController : NucleusController(), startActivity(intent) } - /** - * Download selected items - * @param chapters list of selected [RecentChapter]s - */ - fun downloadChapters(chapters: List) { - destroyActionModeIfNeeded() - presenter.downloadChapters(chapters) - } - /** * Populate adapter with chapters * @param chapters list of [Any] */ fun onNextRecentChapters(chapters: List) { - destroyActionModeIfNeeded() adapter?.setItems(chapters) } + fun updateChapterDownload(download: Download) { + if (view == null) return + val id = download.chapter.id ?: return + val holder = recycler.findViewHolderForItemId(id) as? RecentChapterHolder ?: return + holder.notifyStatus(download.status, download.progress) + } + override fun onUpdateEmptyView(size: Int) { if (size > 0) { empty_view?.hide() @@ -200,12 +168,19 @@ class RecentChaptersController : NucleusController(), } } + override fun onItemMove(fromPosition: Int, toPosition: Int) { } + override fun shouldMoveItem(fromPosition: Int, toPosition: Int) = true + + override fun onActionStateChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { + swipe_refresh.isEnabled = actionState != ItemTouchHelper.ACTION_STATE_SWIPE + } + /** * Update download status of chapter * @param download [Download] object containing download progress. */ fun onChapterStatusChange(download: Download) { - getHolder(download)?.notifyStatus(download.status) + getHolder(download)?.notifyStatus(download.status, download.progress) } /** @@ -218,49 +193,54 @@ class RecentChaptersController : NucleusController(), /** * Mark chapter as read - * @param chapters list of chapters + * @param position position of chapter item */ - fun markAsRead(chapters: List) { - presenter.markChapterRead(chapters, true) - if (presenter.preferences.removeAfterMarkedAsRead()) { - deleteChapters(chapters) + fun toggleMarkAsRead(position: Int) { + val item = adapter?.getItem(position) as? RecentChapterItem ?: return + val chapter = item.chapter + val lastRead = chapter.last_page_read + val pagesLeft = chapter.pages_left + val read = item.chapter.read + lastChapterId = chapter.id + presenter.markChapterRead(item, !read) + if (!read) { + snack = view?.snack(R.string.marked_as_read, Snackbar.LENGTH_INDEFINITE) { + var undoing = false + setAction(R.string.action_undo) { + presenter.markChapterRead(item, !item.chapter.read, lastRead, pagesLeft) + undoing = true + } + addCallback(object : BaseTransientBottomBar.BaseCallback() { + override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { + super.onDismissed(transientBottomBar, event) + if (!undoing && presenter.preferences.removeAfterMarkedAsRead()) { + lastChapterId = chapter.id + presenter.deleteChapter(chapter, item.manga) + } + } + }) + } + (activity as? MainActivity)?.setUndoSnackBar(snack) + } + // presenter.markChapterRead(item, !item.chapter.read) + } + + override fun downloadChapter(position: Int) { + val view = view ?: return + val item = adapter?.getItem(position) as? RecentChapterItem ?: return + val chapter = item.chapter + val manga = item.manga + if (item.status != Download.NOT_DOWNLOADED && item.status != Download.ERROR) { + presenter.deleteChapter(chapter, manga) + } else { + if (item.status == Download.ERROR) DownloadService.start(view.context) + else presenter.downloadChapters(listOf(item)) } } - override fun deleteChapters(chaptersToDelete: List) { - destroyActionModeIfNeeded() - presenter.deleteChapters(chaptersToDelete) - } - - /** - * Destory [ActionMode] if it's shown - */ - private fun destroyActionModeIfNeeded() { - actionMode?.finish() - } - - /** - * Mark chapter as unread - * @param chapters list of selected [RecentChapter] - */ - fun markAsUnread(chapters: List) { - presenter.markChapterRead(chapters, false) - } - - /** - * Start downloading chapter - * @param chapter selected chapter with manga - */ - fun downloadChapter(chapter: RecentChapterItem) { - presenter.downloadChapters(listOf(chapter)) - } - - /** - * Start deleting chapter - * @param chapter selected chapter with manga - */ - fun deleteChapter(chapter: RecentChapterItem) { - presenter.deleteChapters(listOf(chapter)) + override fun startDownloadNow(position: Int) { + val chapter = (adapter?.getItem(position) as? RecentChapterItem)?.chapter ?: return + presenter.startDownloadChapterNow(chapter) } override fun onCoverClick(position: Int) { @@ -286,69 +266,4 @@ class RecentChaptersController : NucleusController(), fun onChaptersDeletedError(error: Throwable) { Timber.e(error) } - - /** - * Called when ActionMode created. - * @param mode the ActionMode object - * @param menu menu object of ActionMode - */ - override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { - mode.menuInflater.inflate(R.menu.chapter_recent_selection, menu) - adapter?.mode = SelectableAdapter.Mode.MULTI - return true - } - - override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { - val count = adapter?.selectedItemCount ?: 0 - if (count == 0) { - // Destroy action mode if there are no items selected. - destroyActionModeIfNeeded() - } else { - mode.title = resources?.getString(R.string.label_selected, count) - } - return false - } - - /** - * Called when ActionMode item clicked - * @param mode the ActionMode object - * @param item item from ActionMode. - */ - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { - when (item.itemId) { - R.id.action_mark_as_read -> markAsRead(getSelectedChapters()) - R.id.action_mark_as_unread -> markAsUnread(getSelectedChapters()) - R.id.action_download -> downloadChapters(getSelectedChapters()) - R.id.action_delete -> ConfirmDeleteChaptersDialog(this, getSelectedChapters()) - .showDialog(router) - else -> return false - } - return true - } - - /** - * Called when ActionMode destroyed - * @param mode the ActionMode object - */ - override fun onDestroyActionMode(mode: ActionMode?) { - adapter?.mode = SelectableAdapter.Mode.IDLE - adapter?.clearSelection() - actionMode = null - } - - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.recent_updates, menu) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.action_sort -> { - /*router.setRoot( - RecentlyReadController().withFadeTransaction().tag(R.id.nav_recents.toString())) - Injekt.get().showRecentUpdates().set(false) - (activity as? MainActivity)?.updateRecentsIcon()*/ - } - } - return super.onOptionsItemSelected(item) - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt index 64311a7191..9309054b30 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt @@ -1,17 +1,24 @@ package eu.kanade.tachiyomi.ui.recent_updates -import android.os.Bundle import eu.kanade.tachiyomi.data.database.DatabaseHelper +import eu.kanade.tachiyomi.data.database.models.Chapter +import eu.kanade.tachiyomi.data.database.models.LibraryManga +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.download.model.DownloadQueue +import eu.kanade.tachiyomi.data.library.LibraryServiceListener +import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.source.SourceManager -import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter -import rx.Observable -import rx.android.schedulers.AndroidSchedulers -import rx.schedulers.Schedulers -import timber.log.Timber +import eu.kanade.tachiyomi.util.system.executeOnIO +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.util.Calendar @@ -19,67 +26,64 @@ import java.util.Date import java.util.TreeMap class RecentChaptersPresenter( + private val controller: RecentChaptersController, val preferences: PreferencesHelper = Injekt.get(), private val db: DatabaseHelper = Injekt.get(), private val downloadManager: DownloadManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get() -) : BasePresenter() { +) : DownloadQueue.DownloadListener, LibraryServiceListener { /** * List containing chapter and manga information */ - private var chapters: List = emptyList() + var chapters: List = emptyList() - override fun onCreate(savedState: Bundle?) { - super.onCreate(savedState) + private var scope = CoroutineScope(Job() + Dispatchers.Default) - getRecentChaptersObservable() - .observeOn(AndroidSchedulers.mainThread()) - .subscribeLatestCache(RecentChaptersController::onNextRecentChapters) - - getChapterStatusObservable() - .subscribeLatestCache(RecentChaptersController::onChapterStatusChange) { - _, error -> Timber.e(error) - } + fun onCreate() { + downloadManager.addListener(this) + LibraryUpdateService.setListener(this) + getUpdates() } - /** - * Get observable containing recent chapters and date - * - * @return observable containing recent chapters and date - */ - fun getRecentChaptersObservable(): Observable> { - // Set date limit for recent chapters - val cal = Calendar.getInstance().apply { - time = Date() - add(Calendar.MONTH, -1) + fun getUpdates() { + scope.launch { + val cal = Calendar.getInstance().apply { + time = Date() + add(Calendar.MONTH, -1) + } + val mangaChapters = db.getRecentChapters(cal.time).executeOnIO() + val map = TreeMap> { d1, d2 -> d2.compareTo(d1) } + val byDay = mangaChapters.groupByTo(map, { getMapKey(it.chapter.date_fetch) }) + val items = byDay.flatMap { + val dateItem = DateItem(it.key) + it.value.map { mc -> + RecentChapterItem(mc.chapter, mc.manga, dateItem) } + } + setDownloadedChapters(items) + chapters = items + withContext(Dispatchers.Main) { controller.onNextRecentChapters(chapters) } } + } - return db.getRecentChapters(cal.time).asRxObservable() - // Convert to a list of recent chapters. - .map { mangaChapters -> - val map = TreeMap> { d1, d2 -> d2.compareTo(d1) } - val byDay = mangaChapters - .groupByTo(map, { getMapKey(it.chapter.date_fetch) }) - byDay.flatMap { - val dateItem = DateItem(it.key) - it.value.map { RecentChapterItem(it.chapter, it.manga, dateItem) } - } - } - .doOnNext { - it.forEach { item -> - // Find an active download for this chapter. - val download = downloadManager.queue.find { it.chapter.id == item.chapter.id } + fun onDestroy() { + downloadManager.removeListener(this) + LibraryUpdateService.removeListener(this) + } - // If there's an active download, assign it, otherwise ask the manager if - // the chapter is downloaded and assign it to the status. - if (download != null) { - item.download = download - } - } - setDownloadedChapters(it) - chapters = it - } + fun cancelScope() { + scope.cancel() + } + + override fun updateDownload(download: Download) { + chapters.find { it.chapter.id == download.chapter.id }?.download = download + scope.launch(Dispatchers.Main) { + controller.updateChapterDownload(download) + } + } + + override fun onUpdateManga(manga: LibraryManga) { + getUpdates() } /** @@ -98,44 +102,18 @@ class RecentChaptersPresenter( return cal.time } - /** - * Returns observable containing chapter status. - * - * @return download object containing download progress. - */ - private fun getChapterStatusObservable(): Observable { - return downloadManager.queue.getStatusObservable() - .observeOn(AndroidSchedulers.mainThread()) - .doOnNext { download -> onDownloadStatusChange(download) } - } - /** * Finds and assigns the list of downloaded chapters. * - * @param items the list of chapter from the database. + * @param chapters the list of chapter from the database. */ - private fun setDownloadedChapters(items: List) { - for (item in items) { - val manga = item.manga - val chapter = item.chapter - - if (downloadManager.isChapterDownloaded(chapter, manga)) { + private fun setDownloadedChapters(chapters: List) { + for (item in chapters) { + if (downloadManager.isChapterDownloaded(item.chapter, item.manga)) { item.status = Download.DOWNLOADED - } - } - } - - /** - * Update status of chapters. - * - * @param download download object containing progress. - */ - private fun onDownloadStatusChange(download: Download) { - // Assign the download to the model object. - if (download.status == Download.QUEUE) { - val chapter = chapters.find { it.chapter.id == download.chapter.id } - if (chapter != null && chapter.download == null) { - chapter.download = download + } else if (downloadManager.hasQueue()) { + item.status = downloadManager.queue.find { it.chapter.id == item.chapter.id } + ?.status ?: 0 } } } @@ -146,34 +124,44 @@ class RecentChaptersPresenter( * @param items list of selected chapters * @param read read status */ - fun markChapterRead(items: List, read: Boolean) { - val chapters = items.map { it.chapter } - chapters.forEach { - it.read = read + fun markChapterRead( + item: RecentChapterItem, + read: Boolean, + lastRead: Int? = null, + pagesLeft: Int? = null + ) { + item.chapter.apply { + this.read = read if (!read) { - it.last_page_read = 0 - it.pages_left = 0 + last_page_read = lastRead ?: 0 + pages_left = pagesLeft ?: 0 } } + db.updateChapterProgress(item.chapter).executeAsBlocking() + controller.onNextRecentChapters(this.chapters) + } - Observable.fromCallable { db.updateChaptersProgress(chapters).executeAsBlocking() } - .subscribeOn(Schedulers.io()) - .subscribe() + fun startDownloadChapterNow(chapter: Chapter) { + downloadManager.startDownloadNow(chapter) } /** - * Delete selected chapters - * - * @param chapters list of chapters + * Deletes the given list of chapter. + * @param chapter the chapter to delete. */ - fun deleteChapters(chapters: List) { - Observable.just(chapters) - .doOnNext { deleteChaptersInternal(it) } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribeFirst({ view, _ -> - view.onChaptersDeleted() - }, RecentChaptersController::onChaptersDeletedError) + fun deleteChapter(chapter: Chapter, manga: Manga, update: Boolean = true) { + val source = Injekt.get().getOrStub(manga.source) + downloadManager.deleteChapters(listOf(chapter), manga, source) + + if (update) { + val item = chapters.find { it.chapter.id == chapter.id } ?: return + item.apply { + status = Download.NOT_DOWNLOADED + download = null + } + + controller.onNextRecentChapters(chapters) + } } /** @@ -183,24 +171,4 @@ class RecentChaptersPresenter( fun downloadChapters(items: List) { items.forEach { downloadManager.downloadChapters(it.manga, listOf(it.chapter)) } } - - /** - * Delete selected chapters - * - * @param items chapters selected - */ - private fun deleteChaptersInternal(chapterItems: List) { - val itemsByManga = chapterItems.groupBy { it.manga.id } - for ((_, items) in itemsByManga) { - val manga = items.first().manga - val source = sourceManager.get(manga.source) ?: continue - val chapters = items.map { it.chapter } - - downloadManager.deleteChapters(chapters, manga, source) - items.forEach { - it.status = Download.NOT_DOWNLOADED - it.download = null - } - } - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadController.kt index d6b590d14f..a1f0c939e7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadController.kt @@ -82,7 +82,7 @@ class RecentlyReadController(bundle: Bundle? = null) : BaseController(bundle), recycler.setHasFixedSize(true) recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) resetProgressItem() - scrollViewWith(recycler) + scrollViewWith(recycler, padBottom = true) if (recentItems != null) adapter?.updateDataSet(recentItems!!.toList()) diff --git a/app/src/main/res/layout/recent_chapters_item.xml b/app/src/main/res/layout/recent_chapters_item.xml index ce23f5bfd7..c89398b78f 100644 --- a/app/src/main/res/layout/recent_chapters_item.xml +++ b/app/src/main/res/layout/recent_chapters_item.xml @@ -1,81 +1,86 @@ - - - - - - - - - + android:layout_height="@dimen/material_component_lists_two_line_height"> - + android:tint="@color/md_white_1000" + android:layout_gravity="end|center" + android:layout_marginEnd="21dp" + android:src="@drawable/eye" /> - + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 65856f1c1a..b926e7c8ad 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -137,7 +137,7 @@ Restore Back Forward - Auto-check for updates + Notify for extension updates Search manually Migrate now Copy now