diff --git a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt index 260b1e1b3f..6bb2fca7ca 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt @@ -65,7 +65,7 @@ object Migrations { if (oldVersion < 54) DownloadProvider(context).renameChaapters() if (oldVersion < 62) - LibraryPresenter.resetCustomManga() + LibraryPresenter.updateDB() return true } return false diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt index 5ed7ed455c..224f6c53db 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt @@ -7,4 +7,8 @@ package eu.kanade.tachiyomi.data.database.models * @param chapter object containing chater * @param history object containing history */ -data class MangaChapterHistory(val manga: Manga, val chapter: Chapter, val history: History) +data class MangaChapterHistory(val manga: Manga, val chapter: Chapter, val history: History) { + companion object { + fun createBlank() = MangaChapterHistory(MangaImpl(), ChapterImpl(), HistoryImpl()) + } +} 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 621d9b0fc3..0de2f0cd61 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 @@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory import eu.kanade.tachiyomi.data.database.resolvers.HistoryLastReadPutResolver import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterHistoryGetResolver import eu.kanade.tachiyomi.data.database.tables.HistoryTable +import eu.kanade.tachiyomi.data.database.tables.MangaTable import java.util.Date interface HistoryQueries : DbProvider { @@ -33,6 +34,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 getRecentlyAdded(date: Date, search: String = "") = db.get() + .listOfObjects(MangaChapterHistory::class.java) + .withQuery(RawQuery.builder() + .query(getRecentAdditionsQuery(search)) + .args(date.time) + .observesTables(MangaTable.TABLE) + .build()) + .withGetResolver(MangaChapterHistoryGetResolver.INSTANCE) + .prepare() + /** * Returns history of recent manga containing last read chapter in 25s * @param date recent date range 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 01ebcff9ef..18d5c76f4a 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,18 @@ 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 getRecentAdditionsQuery(search: String) = """ + SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, * FROM ${Manga.TABLE} + WHERE ${Manga.COL_FAVORITE} = 1 + AND ${Manga.COL_DATE_ADDED} > ? + AND lower(${Manga.COL_TITLE}) LIKE '%$search%' + ORDER BY ${Manga.COL_DATE_ADDED} DESC + LIMIT 8 +""" + /** * Query to get the recent chapters of manga from the library up to a date. */ @@ -73,7 +85,7 @@ fun getRecentsQueryDistinct(search: String) = """ 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} > ? + WHERE ${Chapter.COL_DATE_UPLOAD} > ? 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} 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 30cfa3703c..9880fac7da 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.ChapterImpl import eu.kanade.tachiyomi.data.database.models.HistoryImpl import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory @@ -36,14 +37,17 @@ class MangaChapterHistoryGetResolver : DefaultGetResolver() val manga = mangaGetResolver.mapFromCursor(cursor) // Get chapter object - val chapter = chapterResolver.mapFromCursor(cursor) + val chapter = try { chapterResolver.mapFromCursor(cursor) } catch (e: Exception) { + ChapterImpl() } // Get history object 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")) + if (chapter.id != null) { + manga.id = chapter.manga_id + manga.url = cursor.getString(cursor.getColumnIndex("mangaUrl")) + } if (history.id != null) chapter.id = history.chapter_id diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListController.kt index 8b3acfa178..ffde9c0fdf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListController.kt @@ -55,7 +55,8 @@ import kotlin.math.pow import kotlin.math.roundToInt import kotlin.math.sign -class LibraryListController(bundle: Bundle? = null) : LibraryController(bundle), +class +LibraryListController(bundle: Bundle? = null) : LibraryController(bundle), FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemLongClickListener, FlexibleAdapter.OnItemMoveListener, LibraryCategoryAdapter.LibraryListener, SpinnerTitleInterface, OnTouchEventInterface, SwipeGestureInterface { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index bb459d39b9..905081ac06 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -927,11 +927,16 @@ class LibraryPresenter( companion object { private var currentLibrary: Library? = null - fun resetCustomManga() { + fun updateDB() { val db: DatabaseHelper = Injekt.get() db.inTransaction { val libraryManga = db.getLibraryMangas().executeAsBlocking() libraryManga.forEach { manga -> + if (manga.date_added == 0L) { + val chapters = db.getChapters(manga).executeAsBlocking() + manga.date_added = chapters.minBy { it.date_fetch }?.date_fetch ?: 0L + db.insertManga(manga).executeAsBlocking() + } db.resetMangaInfo(manga).executeAsBlocking() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt index 956e2f5816..3ad22861c8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt @@ -7,6 +7,6 @@ object LibrarySort { const val LATEST_CHAPTER = 2 const val UNREAD = 3 const val TOTAL = 4 + const val DATE_ADDED = 5 const val DRAG_AND_DROP = 6 - const val DATE_ADDED = 7 } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/BaseChapterHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/BaseChapterHolder.kt index 2fbba92d88..685ca4a4ce 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/BaseChapterHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/BaseChapterHolder.kt @@ -13,11 +13,11 @@ open class BaseChapterHolder( ) : BaseFlexibleViewHolder(view, adapter) { init { - download_button.setOnClickListener { downloadOrRemoveMenu() } + download_button?.setOnClickListener { downloadOrRemoveMenu() } } private fun downloadOrRemoveMenu() { - val chapter = adapter.getItem(adapterPosition) as? BaseChapterItem ?: return + val chapter = adapter.getItem(adapterPosition) as? BaseChapterItem<*, *> ?: return if (chapter.status == Download.NOT_DOWNLOADED || chapter.status == Download.ERROR) { adapter.baseDelegate.downloadChapter(adapterPosition) } else { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/BaseChapterItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/BaseChapterItem.kt index ffa4300cee..dece86fe00 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/BaseChapterItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/BaseChapterItem.kt @@ -1,12 +1,17 @@ package eu.kanade.tachiyomi.ui.manga.chapter -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import eu.davidea.flexibleadapter.items.AbstractHeaderItem +import eu.davidea.flexibleadapter.items.AbstractSectionableItem import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.source.model.Page -abstract class BaseChapterItem(val chapter: Chapter) : - AbstractFlexibleItem(), +abstract class BaseChapterItem>( + val chapter: +Chapter, + header: H? = null +) : + AbstractSectionableItem(header), Chapter by chapter { private var _status: Int = 0 @@ -28,13 +33,13 @@ abstract class BaseChapterItem(val chapter: Chapter) : override fun equals(other: Any?): Boolean { if (this === other) return true - if (other is BaseChapterItem<*>) { - return chapter.id!! == other.chapter.id!! + if (other is BaseChapterItem<*, *>) { + return chapter.id == other.chapter.id } return false } override fun hashCode(): Int { - return chapter.id!!.hashCode() + return (chapter.id ?: 0L).hashCode() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterItem.kt index 9d0c77f409..e71316ca95 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterItem.kt @@ -3,14 +3,16 @@ package eu.kanade.tachiyomi.ui.manga.chapter import android.view.View import androidx.recyclerview.widget.RecyclerView import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.AbstractHeaderItem import eu.davidea.flexibleadapter.items.IFlexible +import eu.davidea.viewholders.FlexibleViewHolder 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.ui.manga.MangaDetailsAdapter class ChapterItem(chapter: Chapter, val manga: Manga) : - BaseChapterItem(chapter) { + BaseChapterItem>(chapter) { var isLocked = false 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 index 4f97481751..d521718538 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaAdapter.kt @@ -9,7 +9,11 @@ import java.text.DecimalFormat import java.text.DecimalFormatSymbols class RecentMangaAdapter(val delegate: RecentsInterface) : - BaseChapterAdapter>(delegate) { + BaseChapterAdapter>(delegate) { + + init { + setDisplayHeadersAtStartUp(true) + } val decimalFormat = DecimalFormat("#.###", DecimalFormatSymbols() .apply { decimalSeparator = '.' }) @@ -17,9 +21,11 @@ class RecentMangaAdapter(val delegate: RecentsInterface) : interface RecentsInterface : RecentMangaInterface, DownloadInterface interface RecentMangaInterface { + fun onHeaderClick(position: Int) fun onCoverClick(position: Int) fun markAsRead(position: Int) fun setCover(manga: Manga, view: ImageView) + fun isSearching(): Boolean } override fun onItemSwiped(position: Int, direction: Int) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaHeaderItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaHeaderItem.kt new file mode 100644 index 0000000000..7c9790b19c --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaHeaderItem.kt @@ -0,0 +1,68 @@ +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.AbstractHeaderItem +import eu.davidea.flexibleadapter.items.IFlexible +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder +import eu.kanade.tachiyomi.ui.library.LibraryHeaderItem +import kotlinx.android.synthetic.main.recents_header_item.* + +class RecentMangaHeaderItem(val recentsType: Int) : + AbstractHeaderItem() { + + override fun getLayoutRes(): Int { + return R.layout.recents_header_item + } + + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): Holder { + return Holder(view, adapter as RecentMangaAdapter) + } + + override fun bindViewHolder( + adapter: FlexibleAdapter>, + holder: Holder, + position: Int, + payloads: MutableList? + ) { + holder.bind(recentsType) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other is LibraryHeaderItem) { + return recentsType == recentsType + } + return false + } + + override fun isDraggable(): Boolean { + return false + } + + override fun isSwipeable(): Boolean { + return false + } + + override fun hashCode(): Int { + return recentsType.hashCode() + } + + class Holder(val view: View, adapter: RecentMangaAdapter) : BaseFlexibleViewHolder(view, adapter, + true) { + + fun bind(recentsType: Int) { + title.setText(when (recentsType) { + RecentsItem.CONTINUE_READING -> R.string.continue_reading + RecentsItem.NEW_CHAPTERS -> R.string.new_chapters + RecentsItem.NEWLY_ADDED -> R.string.new_additions + else -> R.string.continue_reading + }) + } + } +} 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 index 8ab43b1a28..a940243030 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaHolder.kt @@ -17,46 +17,55 @@ class RecentMangaHolder( ) : BaseChapterHolder(view, adapter) { init { - cover_thumbnail.setOnClickListener { adapter.delegate.onCoverClick(adapterPosition) } + cover_thumbnail?.setOnClickListener { adapter.delegate.onCoverClick(adapterPosition) } + } + + fun bind(recentsType: Int) { + when (recentsType) { + RecentsItem.CONTINUE_READING -> { + title.setText(R.string.view_history) + } + RecentsItem.NEW_CHAPTERS -> { + title.setText(R.string.view_all_updates) + } + } } 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 + val isSearch = adapter.delegate.isSearching() subtitle.text = item.chapter.name - body.text = if (isSearch) when { + val notValidNum = item.mch.chapter.chapter_number <= 0 + body.text = when { + item.mch.chapter.id == null -> body.context.getString( + R.string.added_x, DateUtils.getRelativeTimeSpanString( + item.mch.manga.date_added, Date().time, DateUtils.MINUTE_IN_MILLIS + ).toString() + ) 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( + if (notValidNum) R.string.last_read_x else R.string.last_read_chapter_x, + if (notValidNum) item.mch.chapter.name else 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( + R.string.updated_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( + !isSearch && item.chapter.pages_left > 0 -> itemView.resources.getQuantityString( + R.plurals.pages_left, item.chapter.pages_left, item.chapter.pages_left + ) + + " (${DateUtils.getRelativeTimeSpanString( + item.mch.history.last_read, Date().time, DateUtils.MINUTE_IN_MILLIS + )})" + isSearch -> body.context.getString( + R.string.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) 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 index cfde9766d8..163a2836e9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaItem.kt @@ -6,14 +6,21 @@ import eu.davidea.flexibleadapter.FlexibleAdapter 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.ChapterImpl import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory import eu.kanade.tachiyomi.ui.manga.chapter.BaseChapterItem -class RecentMangaItem(val mch: MangaChapterHistory, chapter: Chapter) : - BaseChapterItem(chapter) { +class RecentMangaItem( + val mch: MangaChapterHistory = MangaChapterHistory.createBlank(), + chapter: Chapter = ChapterImpl(), + header: + RecentMangaHeaderItem? +) : + BaseChapterItem(chapter, header) { override fun getLayoutRes(): Int { - return R.layout.recent_manga_item + return if (mch.manga.id == null) R.layout.recents_footer_item + else R.layout.recent_manga_item } override fun createViewHolder( @@ -23,12 +30,31 @@ class RecentMangaItem(val mch: MangaChapterHistory, chapter: Chapter) : return RecentMangaHolder(view, adapter as RecentMangaAdapter) } + override fun isSwipeable(): Boolean { + return mch.manga.id != null + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other is RecentMangaItem) { + return if (mch.manga.id == null) header?.recentsType == other.header?.recentsType + else chapter.id == other.chapter.id + } + return false + } + + override fun hashCode(): Int { + return if (mch.manga.id == null) -(header?.recentsType ?: 0).hashCode() + else (chapter.id ?: 0L).hashCode() + } + override fun bindViewHolder( adapter: FlexibleAdapter>, holder: RecentMangaHolder, position: Int, payloads: MutableList? ) { - holder.bind(this) + if (mch.manga.id == null) holder.bind(header?.recentsType ?: 0) + else holder.bind(this) } } 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 index d1f56f362d..10f5ce8ed4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt @@ -10,6 +10,7 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import androidx.appcompat.widget.SearchView +import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import com.bluelinelabs.conductor.Controller import com.bluelinelabs.conductor.ControllerChangeHandler @@ -48,8 +49,9 @@ import kotlinx.android.synthetic.main.recently_read_controller.* * UI related actions should be called from here. */ class RecentsController(bundle: Bundle? = null) : BaseController(bundle), - FlexibleAdapter.OnUpdateListener, + RecentMangaAdapter.RecentsInterface, RecentsAdapter.RecentsInterface, + FlexibleAdapter.OnItemClickListener, RootSearchInterface { init { @@ -59,10 +61,11 @@ class RecentsController(bundle: Bundle? = null) : BaseController(bundle), /** * Adapter containing the recent manga. */ - private val adapter = RecentsAdapter(this) + // private val adapter = RecentsAdapter(this) + private var adapter = RecentMangaAdapter(this) private var presenter = RecentsPresenter(this) - private var recentItems: List? = null + private var recentItems: List? = null private var snack: Snackbar? = null private var lastChapterId: Long? = null @@ -83,10 +86,20 @@ class RecentsController(bundle: Bundle? = null) : BaseController(bundle), super.onViewCreated(view) view.applyWindowInsetsForRootController(activity!!.bottom_nav) // Initialize adapter + adapter = RecentMangaAdapter(this) recycler.adapter = adapter recycler.layoutManager = LinearLayoutManager(view.context) recycler.setHasFixedSize(true) - + recycler.recycledViewPool.setMaxRecycledViews(0, 0) + adapter.isSwipeEnabled = true + /*recycler.addItemDecoration( + DividerItemDecoration( + recycler.context, DividerItemDecoration.VERTICAL + ) + )*/ + adapter.itemTouchHelperCallback.setSwipeFlags( + ItemTouchHelper.LEFT + ) scrollViewWith(recycler, skipFirstSnap = true) if (recentItems != null) adapter.updateDataSet(recentItems!!.toList()) @@ -108,37 +121,42 @@ class RecentsController(bundle: Bundle? = null) : BaseController(bundle), fun refresh() = presenter.getRecents() - fun showLists(recents: List) { + 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) - } + // recycler.removeItemDecorationAt(0) } fun updateChapterDownload(download: Download) { if (view == null) return - for (i in 0 until adapter.itemCount) { + val id = download.chapter.id ?: return + val holder = recycler.findViewHolderForItemId(id) as? RecentMangaHolder ?: return + holder.notifyStatus(download.status, download.progress) + /* (i in 0 until adapter.itemCount) { val holder = recycler.findViewHolderForAdapterPosition(i) as? RecentsHolder ?: continue if (holder.updateChapterDownload(download)) break - } + }*/ } private fun refreshItem(chapterId: Long) { + val recentItemPos = adapter.currentItems.indexOfFirst { + it is RecentMangaItem && + it.mch.chapter.id == chapterId } + if (recentItemPos > -1) adapter.notifyItemChanged(recentItemPos) + /*holder.notifyStatus(download.status, download.progress) for (i in 0 until adapter.itemCount) { val holder = recycler.findViewHolderForAdapterPosition(i) as? RecentsHolder ?: continue holder.refreshChapter(chapterId) - } + }*/ + } + + override fun downloadChapter(position: Int) { + val item = adapter.getItem(position) as? RecentMangaItem ?: return + downloadChapter(item) } override fun downloadChapter(item: RecentMangaItem) { @@ -153,18 +171,49 @@ class RecentsController(bundle: Bundle? = null) : BaseController(bundle), } } + override fun startDownloadNow(position: Int) { + val chapter = (adapter.getItem(position) as? RecentMangaItem)?.chapter ?: return + presenter.startDownloadChapterNow(chapter) + } + override fun downloadChapterNow(chapter: Chapter) { presenter.startDownloadChapterNow(chapter) } + override fun onCoverClick(position: Int) { + val manga = (adapter.getItem(position) as? RecentMangaItem)?.mch?.manga ?: return + router.pushController(MangaDetailsController(manga).withFadeTransaction()) + } + override fun showManga(manga: Manga) = router.pushController(MangaDetailsController(manga).withFadeTransaction()) + override fun onItemClick(view: View?, position: Int): Boolean { + val item = adapter.getItem(position) ?: return false + if (item is RecentMangaItem) { + if (item.mch.manga.id == null) { + val headerItem = adapter.getHeaderOf(item) as? RecentMangaHeaderItem + val controller: Controller = when (headerItem?.recentsType) { + RecentsItem.NEW_CHAPTERS -> RecentChaptersController() + RecentsItem.CONTINUE_READING -> RecentlyReadController() + else -> return false + } + router.pushController(controller.withFadeTransaction()) + } else resumeManga(item.mch.manga, item.chapter) + } else if (item is RecentMangaHeaderItem) return false // onHeaderClick(position) + return true + } + override fun resumeManga(manga: Manga, chapter: Chapter) { val activity = activity ?: return val intent = ReaderActivity.newIntent(activity, manga, chapter) startActivity(intent) } + override fun markAsRead(position: Int) { + val item = adapter.getItem(position) as? RecentMangaItem ?: return + markAsRead(item.mch.manga, item.chapter) + } + override fun markAsRead(manga: Manga, chapter: Chapter) { val lastRead = chapter.last_page_read val pagesLeft = chapter.pages_left @@ -191,6 +240,8 @@ class RecentsController(bundle: Bundle? = null) : BaseController(bundle), (activity as? MainActivity)?.setUndoSnackBar(snack) } + override fun isSearching() = presenter.query.isNotEmpty() + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.recents, menu) val searchItem = menu.findItem(R.id.action_search) @@ -229,8 +280,9 @@ class RecentsController(bundle: Bundle? = null) : BaseController(bundle), .signature(ObjectKey(MangaImpl.getLastCoverFetch(manga.id!!).toString())).into(view) } - override fun viewAll(position: Int) { - val recentsType = (adapter.getItem(position) as? RecentsItem)?.recentType ?: return + override fun onHeaderClick(position: Int) { + val recentsType = (adapter.getItem(position) as? RecentMangaHeaderItem)?.recentsType + ?: return val controller: Controller = when (recentsType) { RecentsItem.NEW_CHAPTERS -> RecentChaptersController() RecentsItem.CONTINUE_READING -> RecentlyReadController() @@ -239,6 +291,16 @@ class RecentsController(bundle: Bundle? = null) : BaseController(bundle), router.pushController(controller.withFadeTransaction()) } + 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 -> { 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 index 4da1f0adce..b9f4e5c11f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsHolder.kt @@ -129,4 +129,8 @@ class RecentsHolder( val item = (subAdapter.getItem(position) as RecentMangaItem) adapter.delegate.markAsRead(item.mch.manga, item.chapter) } + + override fun onHeaderClick(position: Int) { + } + override fun isSearching() = false } 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 index ecf66122c4..2f1cfdd4fc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt @@ -32,9 +32,12 @@ class RecentsPresenter( private var scope = CoroutineScope(Job() + Dispatchers.Default) - private var recentItems = listOf() - var groupedRecentItems = listOf() + var recentItems = listOf() + // var groupedRecentItems = listOf() var query = "" + var newAdditionsHeader = RecentMangaHeaderItem(RecentsItem.NEWLY_ADDED) + var newChaptersHeader = RecentMangaHeaderItem(RecentsItem.NEW_CHAPTERS) + var continueReadingHeader = RecentMangaHeaderItem(RecentsItem.CONTINUE_READING) fun onCreate() { downloadManager.addListener(this) @@ -43,51 +46,75 @@ class RecentsPresenter( } fun getRecents() { + val oldQuery = query scope.launch { val cal = Calendar.getInstance() cal.time = Date() if (query.isNotEmpty()) cal.add(Calendar.YEAR, -50) else cal.add(Calendar.MONTH, -1) + + val calWeek = Calendar.getInstance() + calWeek.time = Date() + if (query.isNotEmpty()) calWeek.add(Calendar.YEAR, -50) + else calWeek.add(Calendar.DAY_OF_YEAR, -1) + val cReading = - if (query.isEmpty()) - db.getRecentsWithUnread(cal.time, query).executeOnIO() - else - db.getRecentMangaLimit(cal.time, 8, query).executeOnIO() + 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 + it.history.last_read = it.chapter.date_fetch + } + val nAdditions = db.getRecentlyAdded(calWeek.time, query).executeOnIO() + nAdditions.forEach { + it.history.last_read = it.manga.date_added } - val mangaList = (cReading + rUpdates).sortedByDescending { + if (query != oldQuery) return@launch + val mangaList = (cReading + rUpdates + nAdditions).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) + if (query.isEmpty()) it.manga.id else it.chapter.id + } + val pairs = mangaList.mapNotNull { + val chapter = if (it.chapter.read || it.chapter.id == null) getNextChapter(it.manga) else it.chapter - if (chapter == null) if (query.isNotEmpty()) RecentMangaItem(it, it.chapter) + if (chapter == null) if (query.isNotEmpty() && it.chapter.id != null) Pair( + it, it.chapter + ) else null - else RecentMangaItem(it, chapter) + else Pair(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 - } + val nChaptersItems = + pairs.filter { it.first.history.id == null && it.first.chapter.id != null } + .sortedByDescending { it.second.date_upload } + .take(4).map { + RecentMangaItem( + it.first, + it.second, + newChaptersHeader + ) + } + + RecentMangaItem(header = newChaptersHeader) + val cReadingItems = + pairs.filter { it.first.history.id != null }.take(9 - nChaptersItems.size).map { + RecentMangaItem( + it.first, + it.second, + continueReadingHeader + ) + } + RecentMangaItem(header = continueReadingHeader) + val nAdditionsItems = pairs.filter { it.first.chapter.id == null }.take(4) + .map { RecentMangaItem(it.first, it.second, newAdditionsHeader) } + recentItems = + listOf(nChaptersItems, cReadingItems, nAdditionsItems).sortedByDescending { + it.firstOrNull()?.mch?.history?.last_read ?: 0L + }.flatten() } else { - groupedRecentItems = listOf(RecentsItem(RecentsItem.SEARCH, recentItems)) + recentItems = pairs.map { RecentMangaItem(it.first, it.second, null) } } - withContext(Dispatchers.Main) { controller.showLists(groupedRecentItems) } + setDownloadedChapters(recentItems) + withContext(Dispatchers.Main) { controller.showLists(recentItems) } } } @@ -147,7 +174,7 @@ class RecentsPresenter( download = null } - controller.showLists(groupedRecentItems) + controller.showLists(recentItems) } } diff --git a/app/src/main/res/layout/recent_manga_item.xml b/app/src/main/res/layout/recent_manga_item.xml index e4ebed9ae7..cf6d9555c2 100644 --- a/app/src/main/res/layout/recent_manga_item.xml +++ b/app/src/main/res/layout/recent_manga_item.xml @@ -173,5 +173,14 @@ android:layout_height="6dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@+id/bottom_line" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout/recents_footer_item.xml b/app/src/main/res/layout/recents_footer_item.xml new file mode 100644 index 0000000000..9b610d74ec --- /dev/null +++ b/app/src/main/res/layout/recents_footer_item.xml @@ -0,0 +1,34 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/recents_header_item.xml b/app/src/main/res/layout/recents_header_item.xml new file mode 100644 index 0000000000..849193cc98 --- /dev/null +++ b/app/src/main/res/layout/recents_header_item.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 4806a59258..1283f87cbc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -21,7 +21,7 @@ Catalogues Recent updates New chapters - New additions + Newly added Selected: %1$d Source migration Extensions @@ -761,7 +761,9 @@ Previously read Chapter %1$s Last read Chapter %1$s Last read %1$s - Uploaded %1$s + Read %1$s + Updated %1$s + Added %1$s View history View all updates Marked as read