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…