From 1fbec7bf3df2c98006f0bd0a07af56d2f89fc74b Mon Sep 17 00:00:00 2001 From: Bram van de Kerkhof Date: Sat, 4 Jun 2016 17:50:44 +0200 Subject: [PATCH] Added improvements for RecentChapters. Closes #320 (#324) --- .../ui/recent/RecentChaptersFragment.kt | 217 +++++++++++++++--- .../ui/recent/RecentChaptersHolder.kt | 4 +- .../ui/recent/RecentChaptersPresenter.kt | 77 +++++-- .../res/layout/fragment_recent_chapters.xml | 9 +- .../main/res/layout/item_recent_chapter.xml | 5 +- .../res/menu/chapter_recent_selection.xml | 30 +++ 6 files changed, 281 insertions(+), 61 deletions(-) create mode 100644 app/src/main/res/menu/chapter_recent_selection.xml diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersFragment.kt index 3f6b93ebd..535564901 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersFragment.kt @@ -2,9 +2,10 @@ package eu.kanade.tachiyomi.ui.recent import android.os.Bundle import android.support.v4.app.DialogFragment -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup +import android.support.v7.view.ActionMode +import android.view.* +import com.afollestad.materialdialogs.MaterialDialog +import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.MangaChapter import eu.kanade.tachiyomi.data.download.model.Download @@ -13,6 +14,7 @@ import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.util.getResourceDrawable +import eu.kanade.tachiyomi.util.toast import eu.kanade.tachiyomi.widget.DeletingChaptersDialog import eu.kanade.tachiyomi.widget.DividerItemDecoration import eu.kanade.tachiyomi.widget.NpaLinearLayoutManager @@ -22,15 +24,15 @@ import timber.log.Timber /** * Fragment that shows recent chapters. - * Uses R.layout.fragment_recent_chapters. + * Uses [R.layout.fragment_recent_chapters]. * UI related actions should be called from here. */ @RequiresPresenter(RecentChaptersPresenter::class) -class RecentChaptersFragment : BaseRxFragment(), FlexibleViewHolder.OnListItemClickListener { +class RecentChaptersFragment : BaseRxFragment(), ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener { companion object { /** * Create new RecentChaptersFragment. - * + * @return a new instance of [RecentChaptersFragment]. */ @JvmStatic fun newInstance(): RecentChaptersFragment { @@ -38,6 +40,59 @@ class RecentChaptersFragment : BaseRxFragment(), Flexib } } + override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean { + 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 -> { + MaterialDialog.Builder(activity) + .content(R.string.confirm_delete_chapters) + .positiveText(android.R.string.yes) + .negativeText(android.R.string.no) + .onPositive { dialog, action -> deleteChapters(getSelectedChapters()) } + .show() + } + else -> return false + } + return true + } + + /** + * 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 = FlexibleAdapter.MODE_MULTI + return true + } + + /** + * Called when ActionMode destroyed + * @param mode the ActionMode object + */ + override fun onDestroyActionMode(mode: ActionMode?) { + adapter.mode = FlexibleAdapter.MODE_SINGLE + adapter.clearSelection() + actionMode = null + } + + /** + * Action mode for multiple selection. + */ + private var actionMode: ActionMode? = null + /** * Adapter containing the recent chapters. */ @@ -46,7 +101,6 @@ class RecentChaptersFragment : BaseRxFragment(), Flexib /** * Called when view gets created - * * @param inflater layout inflater * @param container view group * @param savedState status of saved state @@ -58,7 +112,6 @@ class RecentChaptersFragment : BaseRxFragment(), Flexib /** * Called when view is created - * * @param view created view * @param savedInstanceState status of saved sate */ @@ -70,60 +123,108 @@ class RecentChaptersFragment : BaseRxFragment(), Flexib adapter = RecentChaptersAdapter(this) recycler.adapter = adapter + // Set swipe refresh listener + swipe_refresh.setOnRefreshListener { fetchChapters() } + // Update toolbar text setToolbarTitle(R.string.label_recent_updates) } + /** + * Returns selected chapters + * @return list of [MangaChapter]s + */ + fun getSelectedChapters(): List { + return adapter.selectedItems.map { adapter.getItem(it) as? MangaChapter }.filterNotNull() + } + /** * Called when item in list is clicked - * * @param position position of clicked item */ override fun onListItemClick(position: Int): Boolean { // Get item from position val item = adapter.getItem(position) if (item is MangaChapter) { - // Open chapter in reader - openChapter(item) + if (actionMode != null && adapter.mode == FlexibleAdapter.MODE_MULTI) { + toggleSelection(position) + return true + } else { + openChapter(item) + return false + } } return false } /** * Called when item in list is long clicked - * * @param position position of clicked item */ override fun onListItemLongClick(position: Int) { - // Empty function + if (actionMode == null) + actionMode = activity.startSupportActionMode(this) + + toggleSelection(position) + } + + /** + * Called to toggle selection + * @param position position of selected item + */ + private fun toggleSelection(position: Int) { + adapter.toggleSelection(position, false) + + val count = adapter.selectedItemCount + if (count == 0) { + actionMode?.finish() + } else { + setContextTitle(count) + actionMode?.invalidate() + } + } + + /** + * Set the context title + * @param count count of selected items + */ + private fun setContextTitle(count: Int) { + actionMode?.title = getString(R.string.label_selected, count) } /** * Open chapter in reader - * - * @param chapter selected chapter + * @param mangaChapter selected [MangaChapter] */ - private fun openChapter(chapter: MangaChapter) { - val intent = ReaderActivity.newIntent(activity, chapter.manga, chapter.chapter) + private fun openChapter(mangaChapter: MangaChapter) { + val intent = ReaderActivity.newIntent(activity, mangaChapter.manga, mangaChapter.chapter) startActivity(intent) } + /** + * Download selected items + * @param mangaChapters list of selected [MangaChapter]s + */ + fun downloadChapters(mangaChapters: List) { + destroyActionModeIfNeeded() + presenter.downloadChapters(mangaChapters) + } + /** * Populate adapter with chapters - * - * @param chapters list of chapters + * @param chapters list of [Any] */ fun onNextMangaChapters(chapters: List) { (activity as MainActivity).updateEmptyView(chapters.isEmpty(), R.string.information_no_recent, R.drawable.ic_history_black_128dp) + destroyActionModeIfNeeded() adapter.setItems(chapters) } /** * Update download status of chapter - - * @param download download object containing download progress. + * @param download [Download] object containing download progress. */ fun onChapterStatusChange(download: Download) { getHolder(download)?.onStatusChange(download.status) @@ -132,8 +233,7 @@ class RecentChaptersFragment : BaseRxFragment(), Flexib /** * Returns holder belonging to chapter - * - * @param download download object containing download progress. + * @param download [Download] object containing download progress. */ private fun getHolder(download: Download): RecentChaptersHolder? { return recycler.findViewHolderForItemId(download.chapter.id) as? RecentChaptersHolder @@ -141,28 +241,42 @@ class RecentChaptersFragment : BaseRxFragment(), Flexib /** * Mark chapter as read - * - * @param item selected chapter with manga + * @param mangaChapters list of [MangaChapter] objects */ - fun markAsRead(item: MangaChapter) { - presenter.markChapterRead(item.chapter, true) + fun markAsRead(mangaChapters: List) { + presenter.markChapterRead(mangaChapters, true) if (presenter.preferences.removeAfterMarkedAsRead()) { - deleteChapter(item) + deleteChapters(mangaChapters) } } /** - * Mark chapter as unread - * - * @param item selected chapter with manga + * Delete selected chapters + * @param mangaChapters list of [MangaChapter] objects */ - fun markAsUnread(item: MangaChapter) { - presenter.markChapterRead(item.chapter, false) + fun deleteChapters(mangaChapters: List) { + destroyActionModeIfNeeded() + DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG) + presenter.deleteChapters(mangaChapters) + } + + /** + * Destory [ActionMode] if it's shown + */ + fun destroyActionModeIfNeeded() { + actionMode?.finish() + } + + /** + * Mark chapter as unread + * @param mangaChapters list of selected [MangaChapter] + */ + fun markAsUnread(mangaChapters: List) { + presenter.markChapterRead(mangaChapters, false) } /** * Start downloading chapter - * * @param item selected chapter with manga */ fun downloadChapter(item: MangaChapter) { @@ -171,7 +285,6 @@ class RecentChaptersFragment : BaseRxFragment(), Flexib /** * Start deleting chapter - * * @param item selected chapter with manga */ fun deleteChapter(item: MangaChapter) { @@ -179,18 +292,52 @@ class RecentChaptersFragment : BaseRxFragment(), Flexib presenter.deleteChapter(item) } + /** + * Called when chapters are deleted + */ fun onChaptersDeleted() { dismissDeletingDialog() adapter.notifyDataSetChanged() } + /** + * Called when error while deleting + * @param error error message + */ fun onChaptersDeletedError(error: Throwable) { dismissDeletingDialog() Timber.e(error, error.message) } + /** + * Called to dismiss deleting dialog + */ fun dismissDeletingDialog() { (childFragmentManager.findFragmentByTag(DeletingChaptersDialog.TAG) as? DialogFragment)?.dismiss() } + /** + * Called when swipe refresh activated. + */ + fun fetchChapters() { + swipe_refresh.isRefreshing = true + presenter.fetchChaptersFromSource() + } + + /** + * Called after refresh is completed + */ + fun onFetchChaptersDone() { + swipe_refresh.isRefreshing = false + } + + /** + * Called when something went wrong while refreshing + * @param error information on what went wrong + */ + fun onFetchChaptersError(error: Throwable) { + swipe_refresh.isRefreshing = false + context.toast(error.message) + } + } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersHolder.kt index 13cb78fe0..1c02cc665 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersHolder.kt @@ -122,8 +122,8 @@ class RecentChaptersHolder(view: View, private val adapter: RecentChaptersAdapte when (menuItem.itemId) { R.id.action_download -> adapter.fragment.downloadChapter(it) R.id.action_delete -> adapter.fragment.deleteChapter(it) - R.id.action_mark_as_read -> adapter.fragment.markAsRead(it) - R.id.action_mark_as_unread -> adapter.fragment.markAsUnread(it) + R.id.action_mark_as_read -> adapter.fragment.markAsRead(listOf(it)) + R.id.action_mark_as_unread -> adapter.fragment.markAsUnread(listOf(it)) } true } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersPresenter.kt index b482048e7..e8f0d0889 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersPresenter.kt @@ -60,12 +60,16 @@ class RecentChaptersPresenter : BasePresenter() { // Used to get recent chapters restartableLatestCache(GET_RECENT_CHAPTERS, { getRecentChaptersObservable() }, - { recentChaptersFragment, chapters -> + { fragment, chapters -> // Update adapter to show recent manga's - recentChaptersFragment.onNextMangaChapters(chapters) + fragment.onNextMangaChapters(chapters) // Update download status updateChapterStatus(convertToMangaChaptersList(chapters)) - } + // Stop refresh + fragment.onFetchChaptersDone() + + }, + { fragment, error -> fragment.onFetchChaptersError(error) } ) // Used to update download status @@ -88,7 +92,6 @@ class RecentChaptersPresenter : BasePresenter() { /** * Returns observable containing chapter status. - * @return download object containing download progress. */ private fun getChapterStatusObs(): Observable { @@ -107,7 +110,6 @@ class RecentChaptersPresenter : BasePresenter() { /** * Function to check if chapter is in recent list * @param chaptersId id of chapter - * * * @return exist in recent list */ private fun chapterIdEquals(chaptersId: Long): Boolean { @@ -121,9 +123,7 @@ class RecentChaptersPresenter : BasePresenter() { /** * Returns a list only containing MangaChapter objects. - * @param input the list that will be converted. - * * * @return list containing MangaChapters objects. */ private fun convertToMangaChaptersList(input: List): List { @@ -144,7 +144,6 @@ class RecentChaptersPresenter : BasePresenter() { /** * Update status of chapters. - * @param download download object containing progress. */ private fun updateChapterStatus(download: Download) { @@ -162,7 +161,6 @@ class RecentChaptersPresenter : BasePresenter() { /** * Update status of chapters - * @param mangaChapters list containing recent chapters */ private fun updateChapterStatus(mangaChapters: List) { @@ -236,7 +234,6 @@ class RecentChaptersPresenter : BasePresenter() { /** * Get date as time key * @param date desired date - * * * @return date as time key */ private fun getMapKey(date: Long): Date { @@ -251,26 +248,62 @@ class RecentChaptersPresenter : BasePresenter() { /** * Mark selected chapter as read - * - * @param chapter selected chapter + * @param mangaChapters list of selected MangaChapters * @param read read status */ - fun markChapterRead(chapter: Chapter, read: Boolean) { - Observable.just(chapter) - .doOnNext { chapter -> - chapter.read = read + fun markChapterRead(mangaChapters: List, read: Boolean) { + Observable.from(mangaChapters) + .doOnNext { mangaChapter -> + mangaChapter.chapter.read = read if (!read) { - chapter.last_page_read = 0 + mangaChapter.chapter.last_page_read = 0 } } - .flatMap { db.updateChapterProgress(it).asRxObservable() } + .map { mangaChapter -> mangaChapter.chapter } + .toList() + .flatMap { db.updateChaptersProgress(it).asRxObservable() } .subscribeOn(Schedulers.io()) .subscribe() } + /** + * Delete selected chapters + * @param chapters list of MangaChapters + */ + fun deleteChapters(chapters: List) { + val wasRunning = downloadManager.isRunning + if (wasRunning) { + DownloadService.stop(context) + } + Observable.from(chapters) + .doOnNext { deleteChapter(it) } + .toList() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeFirst({ view, result -> + view.onChaptersDeleted() + if (wasRunning) { + DownloadService.start(context) + } + }, { view, error -> + view.onChaptersDeletedError(error) + }) + } + + /** + * Download selected chapters + * @param mangaChapters [MangaChapter] that is selected + */ + fun downloadChapters(mangaChapters: List) { + DownloadService.start(context) + Observable.from(mangaChapters) + .doOnNext { downloadChapter(it) } + .subscribeOn(AndroidSchedulers.mainThread()) + .subscribe() + } + /** * Download selected chapter - * * @param item chapter that is selected */ fun downloadChapter(item: MangaChapter) { @@ -280,7 +313,6 @@ class RecentChaptersPresenter : BasePresenter() { /** * Delete selected chapter - * * @param item chapter that are selected */ fun deleteChapter(item: MangaChapter) { @@ -304,7 +336,6 @@ class RecentChaptersPresenter : BasePresenter() { /** * Delete selected chapter - * * @param chapter chapter that is selected * @param manga manga that belongs to chapter */ @@ -315,4 +346,8 @@ class RecentChaptersPresenter : BasePresenter() { chapter.status = Download.NOT_DOWNLOADED } + fun fetchChaptersFromSource() { + start(GET_RECENT_CHAPTERS) + } + } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_recent_chapters.xml b/app/src/main/res/layout/fragment_recent_chapters.xml index a0cef992e..9fe931a69 100644 --- a/app/src/main/res/layout/fragment_recent_chapters.xml +++ b/app/src/main/res/layout/fragment_recent_chapters.xml @@ -5,7 +5,13 @@ android:layout_height="match_parent" android:orientation="vertical"> - + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_recent_chapter.xml b/app/src/main/res/layout/item_recent_chapter.xml index 8d515aedc..68877ceb5 100644 --- a/app/src/main/res/layout/item_recent_chapter.xml +++ b/app/src/main/res/layout/item_recent_chapter.xml @@ -1,9 +1,10 @@ + android:background="?attr/selectable_list_drawable"> + + + + + + + + + + + + \ No newline at end of file