From 6a7762212bada93bbb897b605d044ef27384a7dc Mon Sep 17 00:00:00 2001 From: Jay Date: Sat, 28 Mar 2020 01:23:22 -0400 Subject: [PATCH] New Recents Page + Download updates Recent tab is now a combination of recent reads and updates, filtered by unread and an option to go to both old pages (which will be updated later) Download button now has an option to start downloading a chapter now, which unpauses downloading and moves it to the top of the queue If a chapter is added to a paused download queue with the notification swiped away, the notification returns --- .../data/database/queries/ChapterQueries.kt | 12 + .../data/database/queries/HistoryQueries.kt | 15 + .../data/database/queries/RawQueries.kt | 62 +++++ .../MangaChapterHistoryGetResolver.kt | 6 +- .../data/download/DownloadManager.kt | 14 + .../tachiyomi/data/download/Downloader.kt | 2 + .../kanade/tachiyomi/ui/main/MainActivity.kt | 37 +-- .../tachiyomi/ui/manga/MangaDetailsAdapter.kt | 12 +- .../ui/manga/MangaDetailsController.kt | 9 +- .../ui/manga/MangaDetailsPresenter.kt | 7 + .../tachiyomi/ui/manga/MangaHeaderHolder.kt | 40 +-- .../ui/manga/chapter/ChapterHolder.kt | 13 +- .../RecentChaptersController.kt | 45 +-- .../ui/recently_read/RecentlyReadAdapter.kt | 2 +- .../recently_read/RecentlyReadController.kt | 17 +- .../ui/recently_read/RecentlyReadPresenter.kt | 7 +- .../ui/recents/RecentMangaAdapter.kt | 32 +++ .../tachiyomi/ui/recents/RecentMangaHolder.kt | 116 ++++++++ .../tachiyomi/ui/recents/RecentMangaItem.kt | 63 +++++ .../tachiyomi/ui/recents/RecentsAdapter.kt | 21 ++ .../tachiyomi/ui/recents/RecentsController.kt | 256 ++++++++++++++++++ .../tachiyomi/ui/recents/RecentsHolder.kt | 132 +++++++++ .../tachiyomi/ui/recents/RecentsItem.kt | 51 ++++ .../tachiyomi/ui/recents/RecentsPresenter.kt | 189 +++++++++++++ .../util/system/DatabaseExtensions.kt | 19 ++ .../tachiyomi/util/view/ViewExtensions.kt | 3 +- .../drawable/recent_read_outline_128dp.xml | 8 + .../drawable/round_play_background_large.xml | 24 ++ app/src/main/res/drawable/search_128dp.xml | 9 + .../res/layout/recent_chapters_controller.xml | 42 +-- app/src/main/res/layout/recent_manga_item.xml | 177 ++++++++++++ app/src/main/res/layout/recents_item.xml | 53 ++++ app/src/main/res/menu/bottom_navigation.xml | 2 +- app/src/main/res/menu/chapter_download.xml | 4 + app/src/main/res/menu/recent_updates.xml | 24 +- app/src/main/res/menu/recently_read.xml | 14 - app/src/main/res/menu/recents.xml | 23 ++ app/src/main/res/values/strings.xml | 16 +- 38 files changed, 1414 insertions(+), 164 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaAdapter.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaHolder.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaItem.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsAdapter.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsHolder.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsItem.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/util/system/DatabaseExtensions.kt create mode 100644 app/src/main/res/drawable/recent_read_outline_128dp.xml create mode 100644 app/src/main/res/drawable/round_play_background_large.xml create mode 100644 app/src/main/res/drawable/search_128dp.xml create mode 100644 app/src/main/res/layout/recent_manga_item.xml create mode 100644 app/src/main/res/layout/recents_item.xml create mode 100644 app/src/main/res/menu/recents.xml diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt index 9af8e810a2..c0f1a44668 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt @@ -6,10 +6,12 @@ import eu.kanade.tachiyomi.data.database.DbProvider import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.MangaChapter +import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory import eu.kanade.tachiyomi.data.database.resolvers.ChapterBackupPutResolver import eu.kanade.tachiyomi.data.database.resolvers.ChapterProgressPutResolver import eu.kanade.tachiyomi.data.database.resolvers.ChapterSourceOrderPutResolver import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterGetResolver +import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterHistoryGetResolver import eu.kanade.tachiyomi.data.database.tables.ChapterTable import java.util.Date @@ -34,6 +36,16 @@ interface ChapterQueries : DbProvider { .withGetResolver(MangaChapterGetResolver.INSTANCE) .prepare() + fun getUpdatedManga(date: Date, search: String = "") = db.get() + .listOfObjects(MangaChapterHistory::class.java) + .withQuery(RawQuery.builder() + .query(getRecentsQueryDistinct(search)) + .args(date.time) + .observesTables(ChapterTable.TABLE) + .build()) + .withGetResolver(MangaChapterHistoryGetResolver.INSTANCE) + .prepare() + fun getChapter(id: Long) = db.get() .`object`(Chapter::class.java) .withQuery(Query.builder() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt index e9755efec7..621d9b0fc3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt @@ -48,6 +48,21 @@ interface HistoryQueries : DbProvider { .withGetResolver(MangaChapterHistoryGetResolver.INSTANCE) .prepare() + /** + * Returns history of recent manga containing last read chapter in 25s + * @param date recent date range + * @offset offset the db by + */ + fun getRecentsWithUnread(date: Date, search: String = "") = db.get() + .listOfObjects(MangaChapterHistory::class.java) + .withQuery(RawQuery.builder() + .query(getRecentReadWithUnreadChapters(search)) + .args(date.time) + .observesTables(HistoryTable.TABLE) + .build()) + .withGetResolver(MangaChapterHistoryGetResolver.INSTANCE) + .prepare() + fun getHistoryByMangaId(mangaId: Long) = db.get() .listOfObjects(History::class.java) .withQuery(RawQuery.builder() 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 319d0c854d..01ebcff9ef 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 @@ -61,6 +61,29 @@ fun getRecentsQuery() = """ ORDER BY ${Chapter.COL_DATE_UPLOAD} DESC """ +/** + * Query to get the recent chapters of manga from the library up to a date. + */ +fun getRecentsQueryDistinct(search: String) = """ + SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.* + FROM ${Manga.TABLE} + JOIN ${Chapter.TABLE} + ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} + JOIN ( + SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID},${Chapter.TABLE}.${Chapter.COL_ID} as ${History.COL_CHAPTER_ID},MAX(${Chapter.TABLE}.${Chapter.COL_DATE_UPLOAD}) + FROM ${Chapter.TABLE} JOIN ${Manga.TABLE} + ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} + WHERE ${Chapter.COL_DATE_FETCH} > ? + AND ${Chapter.COL_READ} = 0 + GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS newest_chapter + ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = newest_chapter.${Chapter.COL_MANGA_ID} + WHERE ${Manga.COL_FAVORITE} = 1 + AND newest_chapter.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID} + AND lower(${Manga.COL_TITLE}) LIKE '%$search%' + ORDER BY ${Chapter.COL_DATE_UPLOAD} DESC + LIMIT 8 +""" + /** * Query to get the recently read chapters of manga from the library up to a date. * The max_last_read table contains the most recent chapters grouped by manga @@ -114,6 +137,45 @@ fun getRecentMangasLimitQuery(limit: Int = 25, search: String = "") = """ LIMIT $limit """ +/** + * Query to get the recently read chapters of manga from the library up to a date. + * The max_last_read table contains the most recent chapters grouped by manga + * The select statement returns all information of chapters that have the same id as the chapter in max_last_read + * and are read after the given time period + */ +fun getRecentReadWithUnreadChapters(search: String = "") = """ + SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.* + FROM ( + SELECT ${Manga.TABLE}.* + FROM ${Manga.TABLE} + LEFT JOIN ( + SELECT ${Chapter.COL_MANGA_ID}, COUNT(*) AS unread + FROM ${Chapter.TABLE} + WHERE ${Chapter.COL_READ} = 0 + GROUP BY ${Chapter.COL_MANGA_ID} + ) AS C + ON ${Manga.COL_ID} = C.${Chapter.COL_MANGA_ID} + WHERE C.unread > 0 + GROUP BY ${Manga.COL_ID} + ORDER BY ${Manga.COL_TITLE} + ) AS ${Manga.TABLE} + JOIN ${Chapter.TABLE} + ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} + JOIN ${History.TABLE} + ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID} + JOIN ( + SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID},${Chapter.TABLE}.${Chapter.COL_ID} as ${History.COL_CHAPTER_ID}, MAX(${History.TABLE}.${History.COL_LAST_READ}) as ${History.COL_LAST_READ} + FROM ${Chapter.TABLE} JOIN ${History.TABLE} + ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID} + GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS max_last_read + ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = max_last_read.${Chapter.COL_MANGA_ID} + WHERE ${History.TABLE}.${History.COL_LAST_READ} > ? + AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID} + AND lower(${Manga.TABLE}.${Manga.COL_TITLE}) LIKE '%$search%' + ORDER BY max_last_read.${History.COL_LAST_READ} DESC + LIMIT 8 +""" + fun getHistoryByMangaId() = """ SELECT ${History.TABLE}.* FROM ${History.TABLE} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterHistoryGetResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterHistoryGetResolver.kt index a87afe78c5..30cfa3703c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterHistoryGetResolver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterHistoryGetResolver.kt @@ -5,6 +5,7 @@ import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver import eu.kanade.tachiyomi.data.database.mappers.ChapterGetResolver import eu.kanade.tachiyomi.data.database.mappers.HistoryGetResolver import eu.kanade.tachiyomi.data.database.mappers.MangaGetResolver +import eu.kanade.tachiyomi.data.database.models.HistoryImpl import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory class MangaChapterHistoryGetResolver : DefaultGetResolver() { @@ -38,12 +39,13 @@ class MangaChapterHistoryGetResolver : DefaultGetResolver() val chapter = chapterResolver.mapFromCursor(cursor) // Get history object - val history = historyGetResolver.mapFromCursor(cursor) + val history = try { historyGetResolver.mapFromCursor(cursor) } catch (e: Exception) { HistoryImpl() } // Make certain column conflicts are dealt with manga.id = chapter.manga_id manga.url = cursor.getString(cursor.getColumnIndex("mangaUrl")) - chapter.id = history.chapter_id + if (history.id != null) + chapter.id = history.chapter_id // Return result return MangaChapterHistory(manga, chapter, history) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt index ca266d3f44..36f8ee9e41 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt @@ -99,6 +99,20 @@ class DownloadManager(val context: Context) { DownloadService.callListeners(false) } + fun startDownloadNow(chapter: Chapter) { + val download = downloader.queue.find { it.chapter.id == chapter.id } ?: return + val queue = downloader.queue.toMutableList() + queue.remove(download) + queue.add(0, download) + reorderQueue(queue) + if (isPaused()) { + if (DownloadService.isRunning(context)) + downloader.start() + else + DownloadService.start(context) + } + } + /** * Reorders the download queue. * diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index c7207c6c76..6bf71d0e5d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -263,6 +263,8 @@ class Downloader( // Start downloader if needed if (autoStart && wasEmpty) { DownloadService.start(this@Downloader.context) + } else if (!isRunning) { + notifier.onDownloadPaused() } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 5272a8ae2c..01bbb51b90 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -19,7 +19,6 @@ import android.view.ViewGroup import android.view.WindowInsets import android.view.WindowManager import android.webkit.WebView -import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.graphics.drawable.DrawerArrowDrawable import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils @@ -53,6 +52,7 @@ import eu.kanade.tachiyomi.ui.library.LibraryListController import eu.kanade.tachiyomi.ui.manga.MangaDetailsController import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController +import eu.kanade.tachiyomi.ui.recents.RecentsController import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.ui.setting.SettingsController import eu.kanade.tachiyomi.ui.setting.SettingsMainController @@ -61,6 +61,9 @@ import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets import eu.kanade.tachiyomi.util.view.updateLayoutParams import eu.kanade.tachiyomi.util.view.updatePadding +import java.util.Date +import java.util.concurrent.TimeUnit +import kotlin.math.abs import kotlinx.android.synthetic.main.main_activity.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope @@ -68,9 +71,6 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import timber.log.Timber import uy.kohesive.injekt.injectLazy -import java.util.Date -import java.util.concurrent.TimeUnit -import kotlin.math.abs open class MainActivity : BaseActivity(), DownloadServiceListener { @@ -151,24 +151,24 @@ open class MainActivity : BaseActivity(), DownloadServiceListener { else LibraryController(), id ) R.id.nav_recents -> { - if (preferences.showRecentUpdates().getOrDefault()) setRoot( - RecentChaptersController(), - id - ) - else setRoot(RecentlyReadController(), id) + setRoot(RecentsController(), id) +// if (preferences.showRecentUpdates().getOrDefault()) setRoot( +// RecentChaptersController(), id +// ) +// else setRoot(RecentlyReadController(), id) } R.id.nav_catalogues -> setRoot(CatalogueController(), id) } } else if (currentRoot.tag()?.toIntOrNull() == id) { if (router.backstackSize == 1) { when (id) { - R.id.nav_recents -> { + /*R.id.nav_recents -> { val showRecents = preferences.showRecentUpdates().getOrDefault() if (!showRecents) setRoot(RecentChaptersController(), id) else setRoot(RecentlyReadController(), id) preferences.showRecentUpdates().set(!showRecents) updateRecentsIcon() - } + }*/ R.id.nav_library -> { val controller = router.getControllerWithTag(id.toString()) as? LibraryController @@ -192,7 +192,7 @@ open class MainActivity : BaseActivity(), DownloadServiceListener { View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION container.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - updateRecentsIcon() + // updateRecentsIcon() supportActionBar?.setDisplayShowCustomEnabled(true) @@ -309,7 +309,7 @@ open class MainActivity : BaseActivity(), DownloadServiceListener { } } - fun updateRecentsIcon() { + /*fun updateRecentsIcon() { bottom_nav.menu.findItem(R.id.nav_recents).icon = AppCompatResources.getDrawable( this, if (preferences.showRecentUpdates() @@ -317,7 +317,7 @@ open class MainActivity : BaseActivity(), DownloadServiceListener { ) R.drawable.recent_updates_selector_24dp else R.drawable.recent_read_selector_24dp ) - } + }*/ override fun startSupportActionMode(callback: androidx.appcompat.view.ActionMode.Callback): androidx.appcompat.view.ActionMode? { window?.statusBarColor = getResourceColor(R.attr.colorPrimaryVariant) @@ -387,9 +387,14 @@ open class MainActivity : BaseActivity(), DownloadServiceListener { when (intent.action) { SHORTCUT_LIBRARY -> bottom_nav.selectedItemId = R.id.nav_library SHORTCUT_RECENTLY_UPDATED, SHORTCUT_RECENTLY_READ -> { - preferences.showRecentUpdates().set(intent.action == SHORTCUT_RECENTLY_UPDATED) + // preferences.showRecentUpdates().set(intent.action == SHORTCUT_RECENTLY_UPDATED) bottom_nav.selectedItemId = R.id.nav_recents - updateRecentsIcon() + val controller: Controller = when (intent.action) { + SHORTCUT_RECENTLY_UPDATED -> RecentChaptersController() + else -> RecentlyReadController() + } + router.pushController(controller.withFadeTransaction()) + // updateRecentsIcon() } SHORTCUT_CATALOGUES -> bottom_nav.selectedItemId = R.id.nav_catalogues SHORTCUT_EXTENSIONS -> { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsAdapter.kt index abd876b0c9..3e73e437a4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsAdapter.kt @@ -24,7 +24,7 @@ class MangaDetailsAdapter( var items: List = emptyList() - val coverListener: MangaHeaderInterface = controller + val delegate: MangaDetailsInterface = controller val readColor = context.getResourceColor(android.R.attr.textColorHint) @@ -67,13 +67,15 @@ class MangaDetailsAdapter( } } + interface MangaDetailsInterface : MangaHeaderInterface, DownloadInterface + interface MangaHeaderInterface { fun coverColor(): Int? fun mangaPresenter(): MangaDetailsPresenter fun prepareToShareManga() fun openInWebView() + fun startDownloadRange(position: Int) fun readNextChapter() - fun downloadChapter(position: Int) fun topCoverHeight(): Int fun tagClicked(text: String) fun showChapterFilter() @@ -81,6 +83,10 @@ class MangaDetailsAdapter( fun copyToClipboard(content: String, label: Int) fun zoomImageFromThumb(thumbView: View) fun showTrackingSheet() - fun startDownloadRange(position: Int) + } + + interface DownloadInterface { + fun downloadChapter(position: Int) + fun startDownloadNow(position: Int) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt index 045dafb061..8ea1f0ebc6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt @@ -115,7 +115,7 @@ class MangaDetailsController : BaseController, FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemLongClickListener, ActionMode.Callback, - MangaDetailsAdapter.MangaHeaderInterface, + MangaDetailsAdapter.MangaDetailsInterface, FlexibleAdapter.OnItemMoveListener, ChangeMangaCategoriesDialog.Listener { @@ -577,7 +577,7 @@ class MangaDetailsController : BaseController, setOnQueryTextChangeListener(searchView) { query = it ?: "" if (query.isNotEmpty()) { - (recycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder)?.collaspe() + (recycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder)?.collapse() } else (recycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder)?.expand() adapter?.setFilter(query) @@ -717,6 +717,11 @@ class MangaDetailsController : BaseController, } } + override fun startDownloadNow(position: Int) { + val chapter = (adapter?.getItem(position) as? ChapterItem) ?: return + presenter.startDownloadingNow(chapter) + } + private fun downloadChapters(chapters: List) { val view = view presenter.downloadChapters(chapters) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt index ce82769440..ebe8d67da2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt @@ -136,6 +136,9 @@ class MangaDetailsPresenter( for (chapter in chapters) { if (downloadManager.isChapterDownloaded(chapter, manga)) { chapter.status = Download.DOWNLOADED + } else if (downloadManager.hasQueue()) { + chapter.status = downloadManager.queue.find { it.chapter.id == chapter.id } + ?.status ?: 0 } } } @@ -276,6 +279,10 @@ class MangaDetailsPresenter( return chapters.maxBy { it.chapter_number }?.chapter_number } + fun startDownloadingNow(chapter: Chapter) { + downloadManager.startDownloadNow(chapter) + } + /** * Downloads the given list of chapters with the manager. * @param chapters the list of chapters to download. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt index 4165d002f1..fd021a7743 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt @@ -34,9 +34,9 @@ class MangaHeaderHolder( ) : BaseFlexibleViewHolder(view, adapter) { init { - start_reading_button.setOnClickListener { adapter.coverListener.readNextChapter() } + start_reading_button.setOnClickListener { adapter.delegate.readNextChapter() } top_view.updateLayoutParams { - height = adapter.coverListener.topCoverHeight() + height = adapter.delegate.topCoverHeight() } more_button.setOnClickListener { expandDesc() } manga_summary.setOnClickListener { expandDesc() } @@ -47,30 +47,30 @@ class MangaHeaderHolder( more_button_group.visible() } manga_genres_tags.setOnTagClickListener { - adapter.coverListener.tagClicked(it) + adapter.delegate.tagClicked(it) } - filter_button.setOnClickListener { adapter.coverListener.showChapterFilter() } - filters_text.setOnClickListener { adapter.coverListener.showChapterFilter() } - chapters_title.setOnClickListener { adapter.coverListener.showChapterFilter() } - webview_button.setOnClickListener { adapter.coverListener.openInWebView() } - share_button.setOnClickListener { adapter.coverListener.prepareToShareManga() } + filter_button.setOnClickListener { adapter.delegate.showChapterFilter() } + filters_text.setOnClickListener { adapter.delegate.showChapterFilter() } + chapters_title.setOnClickListener { adapter.delegate.showChapterFilter() } + webview_button.setOnClickListener { adapter.delegate.openInWebView() } + share_button.setOnClickListener { adapter.delegate.prepareToShareManga() } favorite_button.setOnClickListener { - adapter.coverListener.favoriteManga(false) + adapter.delegate.favoriteManga(false) } favorite_button.setOnLongClickListener { - adapter.coverListener.favoriteManga(true) + adapter.delegate.favoriteManga(true) true } manga_full_title.setOnLongClickListener { - adapter.coverListener.copyToClipboard(manga_full_title.text.toString(), R.string.manga_info_full_title_label) + adapter.delegate.copyToClipboard(manga_full_title.text.toString(), R.string.manga_info_full_title_label) true } manga_author.setOnLongClickListener { - adapter.coverListener.copyToClipboard(manga_author.text.toString(), R.string.manga_info_author_label) + adapter.delegate.copyToClipboard(manga_author.text.toString(), R.string.manga_info_author_label) true } - manga_cover.setOnClickListener { adapter.coverListener.zoomImageFromThumb(cover_card) } - track_button.setOnClickListener { adapter.coverListener.showTrackingSheet() } + manga_cover.setOnClickListener { adapter.delegate.zoomImageFromThumb(cover_card) } + track_button.setOnClickListener { adapter.delegate.showTrackingSheet() } if (startExpanded) expandDesc() } @@ -86,7 +86,7 @@ class MangaHeaderHolder( @SuppressLint("SetTextI18n") fun bind(item: MangaHeaderItem, manga: Manga) { - val presenter = adapter.coverListener.mangaPresenter() + val presenter = adapter.delegate.mangaPresenter() manga_full_title.text = manga.title if (manga.genre.isNullOrBlank().not()) @@ -109,7 +109,7 @@ class MangaHeaderHolder( more_button_group.gone() } else more_button_group.visible() } - if (adapter.hasFilter()) collaspe() + if (adapter.hasFilter()) collapse() else expand() } manga_summary_label.text = itemView.context.getString(R.string.about_this, @@ -139,7 +139,7 @@ class MangaHeaderHolder( ) checked(!item.isLocked && manga.favorite) } - true_backdrop.setBackgroundColor(adapter.coverListener.coverColor() + true_backdrop.setBackgroundColor(adapter.delegate.coverColor() ?: itemView.context.getResourceColor(android.R.attr.colorBackground)) val tracked = presenter.isTracked() && !item.isLocked @@ -180,7 +180,7 @@ class MangaHeaderHolder( chapters_title.text = itemView.resources.getQuantityString(R.plurals.chapters, count, count) top_view.updateLayoutParams { - height = adapter.coverListener.topCoverHeight() + height = adapter.delegate.topCoverHeight() } manga_status.text = (itemView.context.getString(when (manga.status) { @@ -243,7 +243,7 @@ class MangaHeaderHolder( } fun updateTracking() { - val presenter = adapter.coverListener.mangaPresenter() ?: return + val presenter = adapter.delegate.mangaPresenter() ?: return val tracked = presenter.isTracked() with(track_button) { text = itemView.context.getString(if (tracked) R.string.action_filter_tracked @@ -255,7 +255,7 @@ class MangaHeaderHolder( } } - fun collaspe() { + fun collapse() { sub_item_group.gone() if (more_button.visibility == View.VISIBLE || more_button.visibility == View.INVISIBLE) more_button_group.invisible() 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 febad68f32..1d37793820 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 @@ -25,7 +25,7 @@ class ChapterHolder( init { download_button.setOnClickListener { downloadOrRemoveMenu() } download_button.setOnLongClickListener { - adapter.coverListener.startDownloadRange(adapterPosition) + adapter.delegate.startDownloadRange(adapterPosition) true } } @@ -33,7 +33,7 @@ class ChapterHolder( private fun downloadOrRemoveMenu() { val chapter = adapter.getItem(adapterPosition) as? ChapterItem ?: return if (chapter.status == Download.NOT_DOWNLOADED || chapter.status == Download.ERROR) { - adapter.coverListener.downloadChapter(adapterPosition) + adapter.delegate.downloadChapter(adapterPosition) } else { download_button.post { // Create a PopupMenu, giving it the clicked view for an anchor @@ -42,14 +42,19 @@ class ChapterHolder( // Inflate our menu resource into the PopupMenu's Menu popup.menuInflater.inflate(R.menu.chapter_download, popup.menu) + popup.menu.findItem(R.id.action_start).isVisible = chapter.status == Download.QUEUE + // Hide download and show delete if the chapter is downloaded if (chapter.status != Download.DOWNLOADED) popup.menu.findItem(R.id.action_delete).title = download_button.context.getString( R.string.action_cancel ) // Set a listener so we are notified if a menu item is clicked - popup.setOnMenuItemClickListener { _ -> - adapter.coverListener.downloadChapter(adapterPosition) + popup.setOnMenuItemClickListener { item -> + when (item.itemId) { + R.id.action_delete -> adapter.delegate.downloadChapter(adapterPosition) + R.id.action_start -> adapter.delegate.startDownloadNow(adapterPosition) + } true } 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 50e24675ca..4602001187 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 @@ -8,7 +8,6 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode -import androidx.appcompat.widget.SearchView import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import com.jakewharton.rxbinding.support.v4.widget.refreshes @@ -19,24 +18,17 @@ import eu.kanade.tachiyomi.R 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.data.preference.PreferencesHelper import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.main.MainActivity -import eu.kanade.tachiyomi.ui.main.RootSearchInterface import eu.kanade.tachiyomi.ui.manga.MangaDetailsController import eu.kanade.tachiyomi.ui.reader.ReaderActivity -import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController import eu.kanade.tachiyomi.util.system.notificationManager -import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController import eu.kanade.tachiyomi.util.view.scrollViewWith -import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener import eu.kanade.tachiyomi.util.view.snack import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.recent_chapters_controller.* import timber.log.Timber -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get /** * Fragment that shows recent chapters. @@ -49,7 +41,6 @@ class RecentChaptersController : NucleusController(), FlexibleAdapter.OnItemLongClickListener, FlexibleAdapter.OnUpdateListener, ConfirmDeleteChaptersDialog.Listener, - RootSearchInterface, RecentChaptersAdapter.OnCoverClickListener { init { @@ -86,7 +77,7 @@ class RecentChaptersController : NucleusController(), */ override fun onViewCreated(view: View) { super.onViewCreated(view) - view.applyWindowInsetsForRootController(activity!!.bottom_nav) + // view.applyWindowInsetsForController() view.context.notificationManager.cancel(Notifications.ID_NEW_CHAPTERS) // Init RecyclerView and adapter @@ -347,43 +338,15 @@ class RecentChaptersController : NucleusController(), override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.recent_updates, menu) - val searchItem = menu.findItem(R.id.action_search) - val searchView = searchItem.actionView as SearchView - searchView.queryHint = resources?.getString(R.string.action_search) - if (query.isNotEmpty()) { - searchItem.expandActionView() - searchView.setQuery(query, true) - searchView.clearFocus() - } - setOnQueryTextChangeListener(searchView) { - if (query != it) { - query = it ?: return@setOnQueryTextChangeListener false - adapter?.setFilter(query) - adapter?.performFilter() - } - true - } - - // Fixes problem with the overflow icon showing up in lieu of search - searchItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener { - override fun onMenuItemActionExpand(item: MenuItem): Boolean { - return true - } - - override fun onMenuItemActionCollapse(item: MenuItem): Boolean { - activity?.invalidateOptionsMenu() - return true - } - }) } override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { - R.id.action_recents -> { - router.setRoot( + R.id.action_sort -> { + /*router.setRoot( RecentlyReadController().withFadeTransaction().tag(R.id.nav_recents.toString())) Injekt.get().showRecentUpdates().set(false) - (activity as? MainActivity)?.updateRecentsIcon() + (activity as? MainActivity)?.updateRecentsIcon()*/ } } return super.onOptionsItemSelected(item) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadAdapter.kt index e7feabbd32..e3f9d070b2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadAdapter.kt @@ -19,7 +19,7 @@ import uy.kohesive.injekt.injectLazy * @constructor creates an instance of the adapter. */ class RecentlyReadAdapter(controller: RecentlyReadController) : -FlexibleAdapter>(null, controller, true) { + FlexibleAdapter>(null, controller, true) { val sourceManager by injectLazy() 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 6f9a1620fb..d6b590d14f 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 @@ -15,25 +15,17 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.backup.BackupRestoreService import eu.kanade.tachiyomi.data.database.models.History import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.ui.base.controller.BaseController import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.catalogue.browse.ProgressItem -import eu.kanade.tachiyomi.ui.main.MainActivity -import eu.kanade.tachiyomi.ui.main.RootSearchInterface import eu.kanade.tachiyomi.ui.manga.MangaDetailsController import eu.kanade.tachiyomi.ui.reader.ReaderActivity -import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener -import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController import eu.kanade.tachiyomi.util.view.scrollViewWith import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener -import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.recently_read_controller.* -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get /** * Fragment that shows recently read manga. @@ -46,7 +38,6 @@ class RecentlyReadController(bundle: Bundle? = null) : BaseController(bundle), RecentlyReadAdapter.OnRemoveClickListener, RecentlyReadAdapter.OnResumeClickListener, RecentlyReadAdapter.OnCoverClickListener, - RootSearchInterface, RemoveHistoryDialog.Listener { init { @@ -69,7 +60,7 @@ class RecentlyReadController(bundle: Bundle? = null) : BaseController(bundle), private var recentItems: MutableList? = null override fun getTitle(): String? { - return resources?.getString(R.string.label_recent_manga) + return resources?.getString(R.string.history) } override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { @@ -83,7 +74,7 @@ class RecentlyReadController(bundle: Bundle? = null) : BaseController(bundle), */ override fun onViewCreated(view: View) { super.onViewCreated(view) - view.applyWindowInsetsForRootController(activity!!.bottom_nav) + // view.applyWindowInsetsForController() // Initialize adapter adapter = RecentlyReadAdapter(this) recycler.adapter = adapter @@ -232,7 +223,7 @@ class RecentlyReadController(bundle: Bundle? = null) : BaseController(bundle), }) } - override fun onOptionsItemSelected(item: MenuItem): Boolean { + /*override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.action_recents -> { router.setRoot( @@ -242,5 +233,5 @@ class RecentlyReadController(bundle: Bundle? = null) : BaseController(bundle), } } return super.onOptionsItemSelected(item) - } + }*/ } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadPresenter.kt index d828d6900e..c73a048684 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadPresenter.kt @@ -4,6 +4,7 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.History import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.util.system.executeOnIO import eu.kanade.tachiyomi.util.system.launchUI import java.util.Calendar import java.util.Comparator @@ -36,13 +37,13 @@ class RecentlyReadPresenter(private val view: RecentlyReadController) { * Get all recent manga up to a point * @return list of history */ - private fun getRecentMangaLimit(search: String = ""): List { + private suspend fun getRecentMangaLimit(search: String = ""): List { // Set date for recent manga val cal = Calendar.getInstance() cal.time = Date() cal.add(Calendar.YEAR, -50) - return db.getRecentMangaLimit(cal.time, lastCount, search).executeAsBlocking() + return db.getRecentMangaLimit(cal.time, lastCount, search).executeOnIO() .map(::RecentlyReadItem) } @@ -57,7 +58,7 @@ class RecentlyReadPresenter(private val view: RecentlyReadController) { } suspend fun refresh(search: String? = null): List { - val manga = withContext(Dispatchers.IO) { getRecentMangaLimit(search ?: "") } + val manga = getRecentMangaLimit(search ?: "") checkIfNew(manga.size, search) lastSearch = search ?: lastSearch lastCount = manga.size diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaAdapter.kt new file mode 100644 index 0000000000..721c93ade1 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaAdapter.kt @@ -0,0 +1,32 @@ +package eu.kanade.tachiyomi.ui.recents + +import android.widget.ImageView +import androidx.recyclerview.widget.ItemTouchHelper +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.IFlexible +import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.ui.manga.MangaDetailsAdapter +import java.text.DecimalFormat +import java.text.DecimalFormatSymbols + +class RecentMangaAdapter(val delegate: RecentsInterface) : + FlexibleAdapter>(null, delegate, true) { + + val decimalFormat = DecimalFormat("#.###", DecimalFormatSymbols() + .apply { decimalSeparator = '.' }) + + interface RecentsInterface : RecentMangaInterface, MangaDetailsAdapter.DownloadInterface + + interface RecentMangaInterface { + fun onCoverClick(position: Int) + fun markAsRead(position: Int) + fun setCover(manga: Manga, view: ImageView) + } + + override fun onItemSwiped(position: Int, direction: Int) { + super.onItemSwiped(position, direction) + when (direction) { + ItemTouchHelper.LEFT -> delegate.markAsRead(position) + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaHolder.kt new file mode 100644 index 0000000000..3154cef56c --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaHolder.kt @@ -0,0 +1,116 @@ +package eu.kanade.tachiyomi.ui.recents + +import android.text.format.DateUtils +import android.view.View +import androidx.appcompat.widget.PopupMenu +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.download.model.Download +import eu.kanade.tachiyomi.source.LocalSource +import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder +import eu.kanade.tachiyomi.util.view.visibleIf +import java.util.Date +import kotlinx.android.synthetic.main.download_button.* +import kotlinx.android.synthetic.main.recent_manga_item.* + +class RecentMangaHolder( + view: View, + val adapter: RecentMangaAdapter +) : BaseFlexibleViewHolder(view, adapter) { + + init { + cover_thumbnail.setOnClickListener { adapter.delegate.onCoverClick(adapterPosition) } + download_button.setOnClickListener { downloadOrRemoveMenu() } + } + + private fun downloadOrRemoveMenu() { + val chapter = adapter.getItem(adapterPosition) as? RecentMangaItem ?: return + if (chapter.status == Download.NOT_DOWNLOADED || chapter.status == Download.ERROR) { + adapter.delegate.downloadChapter(adapterPosition) + } else { + download_button.post { + // Create a PopupMenu, giving it the clicked view for an anchor + val popup = PopupMenu(download_button.context, download_button) + + // Inflate our menu resource into the PopupMenu's Menu + popup.menuInflater.inflate(R.menu.chapter_download, popup.menu) + + popup.menu.findItem(R.id.action_start).isVisible = chapter.status == Download.QUEUE + + // Hide download and show delete if the chapter is downloaded + if (chapter.status != Download.DOWNLOADED) popup.menu.findItem(R.id.action_delete).title = download_button.context.getString( + R.string.action_cancel + ) + + // Set a listener so we are notified if a menu item is clicked + popup.setOnMenuItemClickListener { item -> + when (item.itemId) { + R.id.action_delete -> adapter.delegate.downloadChapter(adapterPosition) + R.id.action_start -> adapter.delegate.startDownloadNow(adapterPosition) + } + true + } + + // Finally show the PopupMenu + popup.show() + } + } + } + + fun bind(item: RecentMangaItem) { + download_button.visibleIf(item.mch.manga.source != LocalSource.ID) + title.text = item.mch.manga.title + val holder = (adapter.delegate as RecentsHolder) + val isSearch = + (holder.adapter.getItem(holder.adapterPosition) as RecentsItem).recentType == RecentsItem.SEARCH + subtitle.text = item.chapter.name + body.text = if (isSearch) when { + item.chapter.id != item.mch.chapter.id -> body.context.getString( + R.string.last_read_chapter_x, adapter.decimalFormat.format( + item.mch.chapter.chapter_number + ) + " (${DateUtils.getRelativeTimeSpanString( + item.mch.history.last_read, Date().time, DateUtils.MINUTE_IN_MILLIS + )})" + ) + item.mch.history.id == null -> body.context.getString( + R.string.uploaded_x, DateUtils.getRelativeTimeSpanString( + item.chapter.date_upload, Date().time, DateUtils.HOUR_IN_MILLIS + ).toString() + ) + else -> body.context.getString( + R.string.last_read_x, DateUtils.getRelativeTimeSpanString( + item.mch.history.last_read, Date().time, DateUtils.MINUTE_IN_MILLIS + ).toString() + ) + } else when { + item.chapter.id != item.mch.chapter.id -> body.context.getString( + R.string.last_read_chapter_x, adapter.decimalFormat.format( + item.mch.chapter.chapter_number + ) + ) + item.mch.history.id == null -> DateUtils.getRelativeTimeSpanString( + item.chapter.date_upload, Date().time, DateUtils.HOUR_IN_MILLIS + ).toString() + item.chapter.pages_left > 0 -> itemView.resources.getQuantityString( + R.plurals.pages_left, item.chapter.pages_left, item.chapter.pages_left + ) + else -> "" + } + adapter.delegate.setCover(item.mch.manga, cover_thumbnail) + notifyStatus( + if (adapter.isSelected(adapterPosition)) Download.CHECKED else item.status, + item.progress + ) + } + + fun notifyStatus(status: Int, progress: Int) = with(download_button) { + setDownloadStatus(status, progress) + } + + override fun getFrontView(): View { + return front_view + } + + override fun getRearRightView(): View { + return right_view + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaItem.kt new file mode 100644 index 0000000000..a98085bd01 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaItem.kt @@ -0,0 +1,63 @@ +package eu.kanade.tachiyomi.ui.recents + +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +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.MangaChapterHistory +import eu.kanade.tachiyomi.data.download.model.Download +import eu.kanade.tachiyomi.source.model.Page + +class RecentMangaItem(val mch: MangaChapterHistory, val chapter: Chapter) : + AbstractFlexibleItem + () { + + private var _status: Int = 0 + + val progress: Int + get() { + val pages = download?.pages ?: return 0 + return pages.map(Page::progress).average().toInt() + } + + var status: Int + get() = download?.status ?: _status + set(value) { _status = value } + + @Transient var download: Download? = null + + override fun getLayoutRes(): Int { + return R.layout.recent_manga_item + } + + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): RecentMangaHolder { + return RecentMangaHolder(view, adapter as RecentMangaAdapter) + } + + override fun bindViewHolder( + adapter: FlexibleAdapter>, + holder: RecentMangaHolder, + position: Int, + payloads: MutableList? + ) { + + holder.bind(this) + } + + override fun equals(other: Any?): Boolean { + if (other is RecentMangaItem) { + 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/recents/RecentsAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsAdapter.kt new file mode 100644 index 0000000000..ce706e3c24 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsAdapter.kt @@ -0,0 +1,21 @@ +package eu.kanade.tachiyomi.ui.recents + +import android.widget.ImageView +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.IFlexible +import eu.kanade.tachiyomi.data.database.models.Chapter +import eu.kanade.tachiyomi.data.database.models.Manga + +class RecentsAdapter(val delegate: RecentsInterface) : + FlexibleAdapter>(null, delegate, true) { + + interface RecentsInterface { + fun resumeManga(manga: Manga, chapter: Chapter) + fun showManga(manga: Manga) + fun markAsRead(manga: Manga, chapter: Chapter) + fun downloadChapter(item: RecentMangaItem) + fun downloadChapterNow(chapter: Chapter) + fun setCover(manga: Manga, view: ImageView) + fun viewAll(position: Int) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt new file mode 100644 index 0000000000..9ea98efca9 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt @@ -0,0 +1,256 @@ +package eu.kanade.tachiyomi.ui.recents + +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 android.widget.ImageView +import androidx.appcompat.widget.SearchView +import androidx.recyclerview.widget.LinearLayoutManager +import com.bluelinelabs.conductor.Controller +import com.bluelinelabs.conductor.ControllerChangeHandler +import com.bluelinelabs.conductor.ControllerChangeType +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.signature.ObjectKey +import com.google.android.material.snackbar.BaseTransientBottomBar +import com.google.android.material.snackbar.Snackbar +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.database.models.MangaImpl +import eu.kanade.tachiyomi.data.download.DownloadService +import eu.kanade.tachiyomi.data.download.model.Download +import eu.kanade.tachiyomi.data.glide.GlideApp +import eu.kanade.tachiyomi.data.library.LibraryUpdateService +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.main.RootSearchInterface +import eu.kanade.tachiyomi.ui.manga.MangaDetailsController +import eu.kanade.tachiyomi.ui.reader.ReaderActivity +import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController +import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController +import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController +import eu.kanade.tachiyomi.util.view.scrollViewWith +import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener +import eu.kanade.tachiyomi.util.view.snack +import kotlinx.android.synthetic.main.main_activity.* +import kotlinx.android.synthetic.main.recently_read_controller.* + +/** + * Fragment that shows recently read manga. + * Uses R.layout.fragment_recently_read. + * UI related actions should be called from here. + */ +class RecentsController(bundle: Bundle? = null) : BaseController(bundle), + FlexibleAdapter.OnUpdateListener, + RecentsAdapter.RecentsInterface, + RootSearchInterface { + + init { + setHasOptionsMenu(true) + } + + /** + * Adapter containing the recent manga. + */ + private val adapter = RecentsAdapter(this) + + private var presenter = RecentsPresenter(this) + private var recentItems: List? = null + private var snack: Snackbar? = null + private var lastChapterId: Long? = null + + override fun getTitle(): String? { + return resources?.getString(R.string.short_recents) + } + + override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + return inflater.inflate(R.layout.recently_read_controller, container, false) + } + + /** + * Called when view is created + * + * @param view created view + */ + override fun onViewCreated(view: View) { + super.onViewCreated(view) + view.applyWindowInsetsForRootController(activity!!.bottom_nav) + // Initialize adapter + recycler.adapter = adapter + recycler.layoutManager = LinearLayoutManager(view.context) + recycler.setHasFixedSize(true) + + scrollViewWith(recycler, skipFirstSnap = true) + + if (recentItems != null) adapter.updateDataSet(recentItems!!.toList()) + presenter.onCreate() + } + + override fun onActivityResumed(activity: Activity) { + super.onActivityResumed(activity) + if (view != null) + refresh() + } + + override fun onDestroy() { + super.onDestroy() + snack?.dismiss() + presenter.onDestroy() + snack = null + } + + fun refresh() = presenter.getRecents() + + fun showLists(recents: List) { + recentItems = recents + adapter.updateDataSet(recents) + if (lastChapterId != null) { + refreshItem(lastChapterId ?: 0L) + lastChapterId = null + } + } + + override fun onUpdateEmptyView(size: Int) { + if (size > 0) { + empty_view?.hide() + } else { + empty_view?.show(R.drawable.ic_history_white_128dp, R.string + .information_no_recent_manga) + } + } + + fun updateChapterDownload(download: Download) { + if (view == null) return + for (i in 0 until adapter.itemCount) { + val holder = recycler.findViewHolderForAdapterPosition(i) as? RecentsHolder ?: continue + if (holder.updateChapterDownload(download)) break + } + } + + fun refreshItem(chapterId: Long) { + for (i in 0 until adapter.itemCount) { + val holder = recycler.findViewHolderForAdapterPosition(i) as? RecentsHolder ?: continue + holder.refreshChapter(chapterId) + } + } + + override fun downloadChapter(item: RecentMangaItem) { + val view = view ?: return + val chapter = item.chapter + val manga = item.mch.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.downloadChapter(manga, chapter) + } + } + + override fun downloadChapterNow(chapter: Chapter) { + presenter.startDownloadChapterNow(chapter) + } + + override fun showManga(manga: Manga) = router.pushController(MangaDetailsController(manga).withFadeTransaction()) + + override fun resumeManga(manga: Manga, chapter: Chapter) { + val activity = activity ?: return + val intent = ReaderActivity.newIntent(activity, manga, chapter) + startActivity(intent) + } + + override fun markAsRead(manga: Manga, chapter: Chapter) { + val lastRead = chapter.last_page_read + val pagesLeft = chapter.pages_left + lastChapterId = chapter.id + presenter.markChapterRead(chapter, true) + snack = + view?.snack(R.string.marked_as_read, Snackbar.LENGTH_INDEFINITE) { + anchorView = activity?.bottom_nav + var undoing = false + setAction(R.string.action_undo) { + presenter.markChapterRead(chapter, false, 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, manga) + } + } + }) + } + (activity as? MainActivity)?.setUndoSnackBar(snack) + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.recents, menu) + val searchItem = menu.findItem(R.id.action_search) + val searchView = searchItem.actionView as SearchView + searchView.queryHint = view?.context?.getString(R.string.search_recents) + if (presenter.query.isNotEmpty()) { + searchItem.expandActionView() + searchView.setQuery(presenter.query, true) + searchView.clearFocus() + } + setOnQueryTextChangeListener(searchView) { + if (presenter.query != it) { + presenter.query = it ?: return@setOnQueryTextChangeListener false + refresh() + } + true + } + } + + override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) { + super.onChangeStarted(handler, type) + if (type.isEnter) { + if (type == ControllerChangeType.POP_EXIT) { + presenter.onCreate() + } + setHasOptionsMenu(true) + } else { + snack?.dismiss() + setHasOptionsMenu(false) + } + } + + override fun setCover(manga: Manga, view: ImageView) { + val activity = activity ?: return + GlideApp.with(activity).load(manga).diskCacheStrategy(DiskCacheStrategy.AUTOMATIC) + .signature(ObjectKey(MangaImpl.getLastCoverFetch(manga.id!!).toString())).into(view) + } + + override fun viewAll(position: Int) { + val recentsType = (adapter.getItem(position) as? RecentsItem)?.recentType ?: return + val controller: Controller = when (recentsType) { + RecentsItem.NEW_CHAPTERS -> RecentChaptersController() + RecentsItem.CONTINUE_READING -> RecentlyReadController() + else -> return + } + router.pushController(controller.withFadeTransaction()) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_refresh -> { + if (!LibraryUpdateService.isRunning()) { + val view = view ?: return true + LibraryUpdateService.start(view.context) + snack = view.snack(R.string.updating_library) { + anchorView = (activity as? MainActivity)?.bottom_nav + } + } + } + } + return super.onOptionsItemSelected(item) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsHolder.kt new file mode 100644 index 0000000000..4da1f0adce --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsHolder.kt @@ -0,0 +1,132 @@ +package eu.kanade.tachiyomi.ui.recents + +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +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.holder.BaseFlexibleViewHolder +import eu.kanade.tachiyomi.util.system.dpToPx +import eu.kanade.tachiyomi.util.view.updateLayoutParams +import eu.kanade.tachiyomi.util.view.visibleIf +import kotlinx.android.synthetic.main.recents_item.* + +class RecentsHolder( + view: View, + val adapter: RecentsAdapter +) : BaseFlexibleViewHolder(view, adapter), + FlexibleAdapter.OnItemClickListener, + FlexibleAdapter.OnUpdateListener, + RecentMangaAdapter.RecentsInterface { + + private val subAdapter = RecentMangaAdapter(this) + + init { + recycler.adapter = subAdapter + subAdapter.isSwipeEnabled = true + val manager = LinearLayoutManager(view.context) + recycler.layoutManager = manager + recycler.addItemDecoration( + DividerItemDecoration( + recycler.context, DividerItemDecoration.VERTICAL + ) + ) + recycler.setHasFixedSize(true) + view_all.setOnClickListener { adapter.delegate.viewAll(adapterPosition) } + subAdapter.itemTouchHelperCallback.setSwipeFlags( + ItemTouchHelper.LEFT + ) + } + + fun bind(item: RecentsItem) { + when (item.recentType) { + RecentsItem.CONTINUE_READING -> { + title.setText(R.string.continue_reading) + view_all.setText(R.string.view_history) + } + RecentsItem.NEW_CHAPTERS -> { + title.setText(R.string.new_chapters) + view_all.setText(R.string.view_all_updates) + } + RecentsItem.NEWLY_ADDED -> { + title.setText(R.string.new_additions) + } + } + title.visibleIf(item.recentType != RecentsItem.SEARCH) + view_all.visibleIf(item.recentType == RecentsItem.CONTINUE_READING || item.recentType == RecentsItem.NEW_CHAPTERS) + recycler.updateLayoutParams { + bottomMargin = if (adapterPosition == adapter.itemCount - 1) 0 else 12.dpToPx + } + subAdapter.updateDataSet(item.mangaList) + } + + override fun downloadChapter(position: Int) { + val item = (subAdapter.getItem(position) as? RecentMangaItem) ?: return + adapter.delegate.downloadChapter(item) + } + + override fun startDownloadNow(position: Int) { + val chapter = (subAdapter.getItem(position) as? RecentMangaItem)?.chapter ?: return + adapter.delegate.downloadChapterNow(chapter) + } + + override fun onCoverClick(position: Int) { + val manga = (subAdapter.getItem(position) as? RecentMangaItem)?.mch?.manga ?: return + adapter.delegate.showManga(manga) + } + + override fun onItemClick(view: View?, position: Int): Boolean { + val item = (subAdapter.getItem(position) as? RecentMangaItem) ?: return false + adapter.delegate.resumeManga(item.mch.manga, item.chapter) + return true + } + + fun updateChapterDownload(download: Download): Boolean { + val holder = getHolder(download.chapter) ?: return false + holder.notifyStatus(download.status, download.progress) + return true + } + + fun refreshChapter(chapterId: Long) { + val item = (adapter.getItem(adapterPosition) as? RecentsItem) ?: return + val recentItemPos = item.mangaList.indexOfFirst { it.mch.chapter.id == chapterId } + if (recentItemPos > -1) subAdapter.notifyItemChanged(recentItemPos) + } + + override fun onUpdateEmptyView(size: Int) { + if (size > 0) { + empty_view?.hide() + } else { + val recentsType = (adapter.getItem(adapterPosition) as? RecentsItem)?.recentType ?: return + when (recentsType) { + RecentsItem.CONTINUE_READING -> + empty_view?.show(R.drawable.ic_history_white_128dp, R.string.information_no_recent_manga) + RecentsItem.NEW_CHAPTERS -> + empty_view?.show(R.drawable.ic_update_black_128dp, R.string.information_no_recent) + RecentsItem.NEWLY_ADDED -> + empty_view?.show(R.drawable.recent_read_outline_128dp, R.string.information_no_recent) + RecentsItem.SEARCH -> + empty_view?.show(R.drawable.search_128dp, R.string.no_search_result) + } + } + } + + private fun getHolder(chapter: Chapter): RecentMangaHolder? { + return recycler?.findViewHolderForItemId(chapter.id!!) as? RecentMangaHolder + } + + override fun setCover(manga: Manga, view: ImageView) { + adapter.delegate.setCover(manga, view) + } + + override fun markAsRead(position: Int) { + val item = (subAdapter.getItem(position) as RecentMangaItem) + adapter.delegate.markAsRead(item.mch.manga, item.chapter) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsItem.kt new file mode 100644 index 0000000000..224fac1dde --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsItem.kt @@ -0,0 +1,51 @@ +package eu.kanade.tachiyomi.ui.recents + +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import eu.davidea.flexibleadapter.items.IFlexible +import eu.kanade.tachiyomi.R + +class RecentsItem(val recentType: Int, val mangaList: List) : + AbstractFlexibleItem() { + + override fun getLayoutRes(): Int { + return R.layout.recents_item + } + + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): RecentsHolder { + return RecentsHolder(view, adapter as RecentsAdapter) + } + + override fun bindViewHolder( + adapter: FlexibleAdapter>, + holder: RecentsHolder, + position: Int, + payloads: MutableList? + ) { + + holder.bind(this) + } + + override fun equals(other: Any?): Boolean { + if (other is RecentsItem) { + return recentType == other.recentType + } + return false + } + + override fun hashCode(): Int { + return recentType.hashCode() + } + + companion object { + const val CONTINUE_READING = 0 + const val NEW_CHAPTERS = 1 + const val NEWLY_ADDED = 2 + const val SEARCH = 3 + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt new file mode 100644 index 0000000000..5c097c4684 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt @@ -0,0 +1,189 @@ +package eu.kanade.tachiyomi.ui.recents + +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.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.util.system.executeOnIO +import java.util.Calendar +import java.util.Date +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 + +class RecentsPresenter( + val controller: RecentsController, + val preferences: PreferencesHelper = Injekt.get(), + private val downloadManager: DownloadManager = Injekt.get(), + private val db: DatabaseHelper = Injekt.get() +) : DownloadQueue.DownloadListener, LibraryServiceListener { + + private var scope = CoroutineScope(Job() + Dispatchers.Default) + + private var recentItems = listOf() + var groupedRecentItems = listOf() + var query = "" + + fun onCreate() { + downloadManager.addListener(this) + LibraryUpdateService.setListener(this) + getRecents() + } + + fun getRecents() { + scope.launch { + val cal = Calendar.getInstance() + cal.time = Date() + if (query.isNotEmpty()) cal.add(Calendar.YEAR, -50) + else cal.add(Calendar.MONTH, -1) + val cReading = + if (query.isEmpty()) + db.getRecentsWithUnread(cal.time, query).executeOnIO() + else + db.getRecentMangaLimit(cal.time, 8, query).executeOnIO() + val rUpdates = db.getUpdatedManga(cal.time, query).executeOnIO() + rUpdates.forEach { + it.history.last_read = it.chapter.date_upload + } + val mangaList = (cReading + rUpdates).sortedByDescending { + it.history.last_read + }.distinctBy { + if (query.isEmpty()) it.manga.id else it.chapter.id } + recentItems = mangaList.mapNotNull { + val chapter = if (it.chapter.read) getNextChapter(it.manga) + else it.chapter + if (chapter == null) if (query.isNotEmpty()) RecentMangaItem(it, it.chapter) + else null + else RecentMangaItem(it, chapter) + } + setDownloadedChapters(recentItems) + if (query.isEmpty()) { + val nChaptersItems = RecentsItem( + RecentsItem.NEW_CHAPTERS, + recentItems.filter { it.mch.history.id == null }.take(4) + ) + val cReadingItems = RecentsItem( + RecentsItem.CONTINUE_READING, + recentItems.filter { it.mch.history.id != null }.take( + 8 - nChaptersItems.mangaList.size + ) + ) + // TODO: Add Date Added + groupedRecentItems = listOf(cReadingItems, nChaptersItems).sortedByDescending { + it.mangaList.firstOrNull()?.mch?.history?.last_read ?: 0 + } + } else { + groupedRecentItems = listOf(RecentsItem(RecentsItem.SEARCH, recentItems)) + } + withContext(Dispatchers.Main) { controller.showLists(groupedRecentItems) } + } + } + + private fun getNextChapter(manga: Manga): Chapter? { + val chapters = db.getChapters(manga).executeAsBlocking() + return chapters.sortedByDescending { it.source_order }.find { !it.read } + } + + fun onDestroy() { + downloadManager.removeListener(this) + LibraryUpdateService.removeListener(this) + } + + fun cancelScope() { + scope.cancel() + } + + /** + * Finds and assigns the list of downloaded chapters. + * + * @param chapters the list of chapter from the database. + */ + private fun setDownloadedChapters(chapters: List) { + for (item in chapters) { + if (downloadManager.isChapterDownloaded(item.chapter, item.mch.manga)) { + item.status = Download.DOWNLOADED + } else if (downloadManager.hasQueue()) { + item.status = downloadManager.queue.find { it.chapter.id == item.chapter.id } + ?.status ?: 0 + } + } + } + + override fun updateDownload(download: Download) { + recentItems.find { it.chapter.id == download.chapter.id }?.download = download + scope.launch(Dispatchers.Main) { + controller.updateChapterDownload(download) + } + } + + override fun onUpdateManga(manga: LibraryManga) { + getRecents() + } + + /** + * Deletes the given list of chapter. + * @param chapter the chapter to delete. + */ + 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 = recentItems.find { it.chapter.id == chapter.id } ?: return + item.apply { + status = Download.NOT_DOWNLOADED + download = null + } + + controller.showLists(groupedRecentItems) + } + } + + /** + * Downloads the given list of chapters with the manager. + * @param chapter the chapter to download. + */ + fun downloadChapter(manga: Manga, chapter: Chapter) { + downloadManager.downloadChapters(manga, listOf(chapter)) + } + + fun startDownloadChapterNow(chapter: Chapter) { + downloadManager.startDownloadNow(chapter) + } + + /** + * Mark the selected chapter list as read/unread. + * @param selectedChapters the list of selected chapters. + * @param read whether to mark chapters as read or unread. + */ + fun markChapterRead( + chapter: Chapter, + read: Boolean, + lastRead: Int? = null, + pagesLeft: Int? = null + ) { + scope.launch(Dispatchers.IO) { + chapter.apply { + this.read = read + if (!read) { + last_page_read = lastRead ?: 0 + pages_left = pagesLeft ?: 0 + } + } + db.updateChaptersProgress(listOf(chapter)).executeAsBlocking() + getRecents() + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/DatabaseExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/DatabaseExtensions.kt new file mode 100644 index 0000000000..2f5c04536f --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/DatabaseExtensions.kt @@ -0,0 +1,19 @@ +package eu.kanade.tachiyomi.util.system + +import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetListOfObjects +import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetObject +import com.pushtorefresh.storio.sqlite.operations.put.PreparedPutObject +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +suspend fun PreparedGetListOfObjects.executeOnIO(): List { + return withContext(Dispatchers.IO) { executeAsBlocking() } +} + +suspend fun PreparedGetObject.executeOnIO(): T? { + return withContext(Dispatchers.IO) { executeAsBlocking() } +} + +suspend fun PreparedPutObject.executeOnIO() { + withContext(Dispatchers.IO) { executeAsBlocking() } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt index e0f534daf6..e61d2307a9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt @@ -323,6 +323,7 @@ fun Controller.scrollViewWith( recycler: RecyclerView, padBottom: Boolean = false, customPadding: Boolean = false, + skipFirstSnap: Boolean = false, swipeRefreshLayout: SwipeRefreshLayout? = null, afterInsets: ((WindowInsets) -> Unit)? = null ) { @@ -370,7 +371,7 @@ fun Controller.scrollViewWith( val closerToTop = abs(activity!!.appbar.y) - halfWay > 0 val atTop = (!customPadding && (recycler.layoutManager as LinearLayoutManager) - .findFirstVisibleItemPosition() < 2) || + .findFirstVisibleItemPosition() < 2 && !skipFirstSnap) || !recycler.canScrollVertically(-1) activity!!.appbar.animate().y( if (closerToTop && !atTop) (-activity!!.appbar.height.toFloat()) diff --git a/app/src/main/res/drawable/recent_read_outline_128dp.xml b/app/src/main/res/drawable/recent_read_outline_128dp.xml new file mode 100644 index 0000000000..78392c3a45 --- /dev/null +++ b/app/src/main/res/drawable/recent_read_outline_128dp.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/round_play_background_large.xml b/app/src/main/res/drawable/round_play_background_large.xml new file mode 100644 index 0000000000..a6a5fbb27e --- /dev/null +++ b/app/src/main/res/drawable/round_play_background_large.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/search_128dp.xml b/app/src/main/res/drawable/search_128dp.xml new file mode 100644 index 0000000000..c823423642 --- /dev/null +++ b/app/src/main/res/drawable/search_128dp.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/recent_chapters_controller.xml b/app/src/main/res/layout/recent_chapters_controller.xml index 11fc342f17..6f90c617b5 100644 --- a/app/src/main/res/layout/recent_chapters_controller.xml +++ b/app/src/main/res/layout/recent_chapters_controller.xml @@ -1,35 +1,35 @@ - - - - + android:layout_height="match_parent"> - + - + - + + + \ No newline at end of file diff --git a/app/src/main/res/layout/recent_manga_item.xml b/app/src/main/res/layout/recent_manga_item.xml new file mode 100644 index 0000000000..e4ebed9ae7 --- /dev/null +++ b/app/src/main/res/layout/recent_manga_item.xml @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/recents_item.xml b/app/src/main/res/layout/recents_item.xml new file mode 100644 index 0000000000..83ddd4887d --- /dev/null +++ b/app/src/main/res/layout/recents_item.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/bottom_navigation.xml b/app/src/main/res/menu/bottom_navigation.xml index 00fc84c04f..b8bd8ba7e1 100644 --- a/app/src/main/res/menu/bottom_navigation.xml +++ b/app/src/main/res/menu/bottom_navigation.xml @@ -8,7 +8,7 @@ android:title="@string/label_library" /> + + diff --git a/app/src/main/res/menu/recent_updates.xml b/app/src/main/res/menu/recent_updates.xml index 2b4f9944a7..cba8c4893d 100644 --- a/app/src/main/res/menu/recent_updates.xml +++ b/app/src/main/res/menu/recent_updates.xml @@ -1,25 +1,9 @@ - - - - - + android:id="@+id/action_sort" + android:icon="@drawable/ic_sort_white_24dp" + android:title="@string/action_sort" + app:showAsAction="ifRoom" /> \ No newline at end of file diff --git a/app/src/main/res/menu/recently_read.xml b/app/src/main/res/menu/recently_read.xml index d5fa9e26ff..1a72493aaa 100644 --- a/app/src/main/res/menu/recently_read.xml +++ b/app/src/main/res/menu/recently_read.xml @@ -4,20 +4,6 @@ android:id="@+id/action_search" android:icon="@drawable/ic_search_white_24dp" android:title="@string/action_search" - android:visible="false" app:actionViewClass="androidx.appcompat.widget.SearchView" app:showAsAction="ifRoom|collapseActionView" /> - - - - \ No newline at end of file diff --git a/app/src/main/res/menu/recents.xml b/app/src/main/res/menu/recents.xml new file mode 100644 index 0000000000..26373bd57c --- /dev/null +++ b/app/src/main/res/menu/recents.xml @@ -0,0 +1,23 @@ + + + + + + \ 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 8a56968220..8162e5a10d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -19,7 +19,9 @@ Library Recently read Catalogues - Recently updated + Recent updates + New chapters + New additions Selected: %1$d Source migration Extensions @@ -84,6 +86,7 @@ Remove bookmark Delete Remove download + Start downloading now Update library Edit Edit cover @@ -459,7 +462,6 @@ New chapter %d unread - New 1 downloaded %d downloaded @@ -754,4 +756,14 @@ Sort by chapter number Newest to oldest Oldest to newest + View All + Previously read Chapter %1$s + Last read Chapter %1$s + Last read %1$s + Uploaded %1$s + View history + View all updates + Marked as read + No search results + Search recents…