Cleanup History/Updates Controller

This commit is contained in:
Jays2Kings 2021-04-06 22:20:08 -04:00
parent f264a69626
commit 53c04c7084
14 changed files with 3 additions and 1180 deletions

View File

@ -1,110 +0,0 @@
package eu.kanade.tachiyomi.ui.recent_updates
import android.app.Activity
import android.view.View
import androidx.core.content.ContextCompat
import coil.api.clear
import coil.transform.CircleCropTransformation
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.image.coil.loadLibraryManga
import eu.kanade.tachiyomi.databinding.RecentChaptersItemBinding
import eu.kanade.tachiyomi.ui.manga.chapter.BaseChapterHolder
import eu.kanade.tachiyomi.util.chapter.ChapterUtil
import eu.kanade.tachiyomi.util.system.getResourceColor
/**
* Holder that contains chapter item
* Uses R.layout.item_recent_chapters.
* UI related actions should be called from here.
*
* @param view the inflated view for this holder.
* @param adapter the adapter handling this holder.
* @param listener a listener to react to single tap and long tap events.
* @constructor creates a new recent chapter holder.
*/
class RecentChapterHolder(private val view: View, private val adapter: RecentChaptersAdapter) :
BaseChapterHolder(view, adapter) {
/**
* Color of read chapter
*/
private var readColor = view.context.getResourceColor(android.R.attr.textColorHint)
/**
* Color of unread chapter
*/
private var unreadColor = view.context.getResourceColor(android.R.attr.textColorPrimary)
/**
* Currently bound item.
*/
private var item: RecentChapterItem? = null
private val binding = RecentChaptersItemBinding.bind(view)
init {
binding.mangaCover.setOnClickListener {
adapter.coverClickListener.onCoverClick(flexibleAdapterPosition)
}
}
/**
* Set values of view
*
* @param item item containing chapter information
*/
fun bind(item: RecentChapterItem) {
this.item = item
// Set chapter binding.title
binding.chapterTitle.text = item.chapter.name
// Set manga binding.title
binding.title.text = item.manga.title
if (binding.frontView.translationX == 0f) {
binding.read.setImageDrawable(
ContextCompat.getDrawable(
binding.root.context,
if (item.read) R.drawable.ic_eye_off_24dp
else R.drawable.ic_eye_24dp
)
)
}
// Set cover
if ((view.context as? Activity)?.isDestroyed != true) {
binding.mangaCover.clear()
binding.mangaCover.loadLibraryManga(item.manga) {
transformations(CircleCropTransformation())
}
}
val chapterColor = ChapterUtil.chapterColor(itemView.context, item)
binding.chapterTitle.setTextColor(chapterColor)
binding.title.setTextColor(chapterColor)
// Set chapter status
notifyStatus(item.status, item.progress)
resetFrontView()
}
private fun resetFrontView() {
if (binding.frontView.translationX != 0f) itemView.post { adapter.notifyItemChanged(flexibleAdapterPosition) }
}
override fun getFrontView(): View {
return binding.frontView
}
override fun getRearRightView(): View {
return binding.rightView
}
/**
* Updates chapter status in view.
*
* @param status download status
*/
fun notifyStatus(status: Int, progress: Int) =
binding.downloadButton.root.setDownloadStatus(status, progress)
}

View File

@ -1,36 +0,0 @@
package eu.kanade.tachiyomi.ui.recent_updates
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.ui.manga.chapter.BaseChapterItem
class RecentChapterItem(chapter: Chapter, val manga: Manga, header: DateItem) :
BaseChapterItem<RecentChapterHolder, DateItem>(chapter, header) {
override fun getLayoutRes(): Int {
return R.layout.recent_chapters_item
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): RecentChapterHolder {
return RecentChapterHolder(view, adapter as RecentChaptersAdapter)
}
override fun bindViewHolder(
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
holder: RecentChapterHolder,
position: Int,
payloads: MutableList<Any?>?
) {
holder.bind(this)
}
fun filter(text: String): Boolean {
return chapter.name.contains(text, false) ||
manga.title.contains(text, false)
}
}

View File

@ -1,42 +0,0 @@
package eu.kanade.tachiyomi.ui.recent_updates
import androidx.recyclerview.widget.ItemTouchHelper
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.ui.manga.chapter.BaseChapterAdapter
class RecentChaptersAdapter(val controller: RecentChaptersController) :
BaseChapterAdapter<IFlexible<*>>(controller) {
val coverClickListener: OnCoverClickListener = controller
var recents = emptyList<RecentChapterItem>()
init {
setDisplayHeadersAtStartUp(true)
// setStickyHeaders(true)
}
fun setItems(recents: List<RecentChapterItem>) {
this.recents = recents
performFilter()
}
fun performFilter() {
val s = getFilter(String::class.java)
if (s.isNullOrBlank()) {
updateDataSet(recents)
} else {
updateDataSet(recents.filter { it.filter(s) })
}
}
interface OnCoverClickListener {
fun onCoverClick(position: Int)
}
override fun onItemSwiped(position: Int, direction: Int) {
super.onItemSwiped(position, direction)
when (direction) {
ItemTouchHelper.LEFT -> controller.toggleMarkAsRead(position)
}
}
}

View File

@ -1,265 +0,0 @@
package eu.kanade.tachiyomi.ui.recent_updates
import android.app.Activity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.databinding.RecentChaptersControllerBinding
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
import eu.kanade.tachiyomi.ui.manga.chapter.BaseChapterAdapter
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.system.notificationManager
import eu.kanade.tachiyomi.util.view.scrollViewWith
import eu.kanade.tachiyomi.util.view.setStyle
import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.withFadeTransaction
import timber.log.Timber
/**
* Fragment that shows recent chapters.
* Uses [R.layout.recent_chapters_controller].
* UI related actions should be called from here.
*/
class RecentChaptersController(bundle: Bundle? = null) :
BaseController<RecentChaptersControllerBinding>(bundle),
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnUpdateListener,
FlexibleAdapter.OnItemMoveListener,
RecentChaptersAdapter.OnCoverClickListener,
BaseChapterAdapter.DownloadInterface {
/**
* Adapter containing the recent chapters.
*/
var adapter: RecentChaptersAdapter? = null
private set
private var presenter = RecentChaptersPresenter(this)
private var snack: Snackbar? = null
private var lastChapterId: Long? = null
override fun getTitle(): String? {
return resources?.getString(R.string.recent_updates)
}
override fun createBinding(inflater: LayoutInflater) = RecentChaptersControllerBinding.inflate(inflater)
/**
* Called when view is created
* @param view created view
*/
override fun onViewCreated(view: View) {
super.onViewCreated(view)
// view.applyWindowInsetsForController()
view.context.notificationManager.cancel(Notifications.ID_NEW_CHAPTERS)
// Init RecyclerView and adapter
val layoutManager = LinearLayoutManager(view.context)
binding.recycler.layoutManager = layoutManager
binding.recycler.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL))
binding.recycler.setHasFixedSize(true)
adapter = RecentChaptersAdapter(this@RecentChaptersController)
binding.recycler.adapter = adapter
adapter?.isSwipeEnabled = true
adapter?.itemTouchHelperCallback?.setSwipeFlags(
ItemTouchHelper.LEFT
)
if (presenter.chapters.isNotEmpty()) adapter?.updateDataSet(presenter.chapters.toList())
binding.swipeRefresh.setStyle()
binding.swipeRefresh.setDistanceToTriggerSync((2 * 64 * view.resources.displayMetrics.density).toInt())
binding.swipeRefresh.setOnRefreshListener {
if (!LibraryUpdateService.isRunning()) {
LibraryUpdateService.start(view.context)
snack = view.snack(R.string.updating_library)
}
// It can be a very long operation, so we disable swipe refresh and show a snackbar.
binding.swipeRefresh.isRefreshing = false
}
scrollViewWith(binding.recycler, swipeRefreshLayout = binding.swipeRefresh, padBottom = true)
presenter.onCreate()
}
override fun onDestroy() {
super.onDestroy()
presenter.onDestroy()
}
override fun onDestroyView(view: View) {
adapter = null
snack = null
super.onDestroyView(view)
}
override fun onActivityResumed(activity: Activity) {
super.onActivityResumed(activity)
if (view != null) {
refresh()
}
}
fun refresh() = presenter.getUpdates()
/**
* Called when item in list is clicked
* @param position position of clicked item
*/
override fun onItemClick(view: View?, position: Int): Boolean {
val adapter = adapter ?: return false
// Get item from position
val item = adapter.getItem(position) as? RecentChapterItem ?: return false
openChapter(item)
return false
}
/**
* Open chapter in reader
* @param chapter selected chapter
*/
private fun openChapter(item: RecentChapterItem) {
val activity = activity ?: return
val intent = ReaderActivity.newIntent(activity, item.manga, item.chapter)
startActivity(intent)
}
/**
* Populate adapter with chapters
* @param chapters list of [Any]
*/
fun onNextRecentChapters(chapters: List<RecentChapterItem>) {
adapter?.setItems(chapters)
}
fun updateChapterDownload(download: Download) {
if (view == null) return
val id = download.chapter.id ?: return
val holder = binding.recycler.findViewHolderForItemId(id) as? RecentChapterHolder ?: return
holder.notifyStatus(download.status, download.progress)
}
override fun onUpdateEmptyView(size: Int) {
if (size > 0) {
binding.emptyView.hide()
} else {
binding.emptyView.show(R.drawable.ic_update_24dp, R.string.no_recent_chapters)
}
}
override fun onItemMove(fromPosition: Int, toPosition: Int) { }
override fun shouldMoveItem(fromPosition: Int, toPosition: Int) = true
override fun onActionStateChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
binding.swipeRefresh.isEnabled = actionState != ItemTouchHelper.ACTION_STATE_SWIPE
}
/**
* Update download status of chapter
* @param download [Download] object containing download progress.
*/
fun onChapterStatusChange(download: Download) {
getHolder(download)?.notifyStatus(download.status, download.progress)
}
/**
* Returns holder belonging to chapter
* @param download [Download] object containing download progress.
*/
private fun getHolder(download: Download): RecentChapterHolder? {
return binding.recycler.findViewHolderForItemId(download.chapter.id!!) as? RecentChapterHolder
}
/**
* Mark chapter as read
* @param position position of chapter item
*/
fun toggleMarkAsRead(position: Int) {
val item = adapter?.getItem(position) as? RecentChapterItem ?: return
val chapter = item.chapter
val lastRead = chapter.last_page_read
val pagesLeft = chapter.pages_left
val read = item.chapter.read
lastChapterId = chapter.id
presenter.markChapterRead(item, !read)
if (!read) {
snack = view?.snack(R.string.marked_as_read, Snackbar.LENGTH_INDEFINITE) {
var undoing = false
setAction(R.string.undo) {
presenter.markChapterRead(item, read, lastRead, pagesLeft)
undoing = true
}
addCallback(
object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
super.onDismissed(transientBottomBar, event)
if (!undoing && presenter.preferences.removeAfterMarkedAsRead()) {
lastChapterId = chapter.id
presenter.deleteChapter(chapter, item.manga)
}
}
}
)
}
(activity as? MainActivity)?.setUndoSnackBar(snack)
}
// presenter.markChapterRead(item, !item.chapter.read)
}
override fun downloadChapter(position: Int) {
val view = view ?: return
val item = adapter?.getItem(position) as? RecentChapterItem ?: return
val chapter = item.chapter
val manga = item.manga
if (item.status != Download.NOT_DOWNLOADED && item.status != Download.ERROR) {
presenter.deleteChapter(chapter, manga)
} else {
if (item.status == Download.ERROR) DownloadService.start(view.context)
else presenter.downloadChapters(listOf(item))
}
}
override fun startDownloadNow(position: Int) {
val chapter = (adapter?.getItem(position) as? RecentChapterItem)?.chapter ?: return
presenter.startDownloadChapterNow(chapter)
}
override fun onCoverClick(position: Int) {
val chapterClicked = adapter?.getItem(position) as? RecentChapterItem ?: return
openManga(chapterClicked)
}
fun openManga(chapter: RecentChapterItem) {
router.pushController(MangaDetailsController(chapter.manga).withFadeTransaction())
}
/**
* Called when chapters are deleted
*/
fun onChaptersDeleted() {
adapter?.notifyDataSetChanged()
}
/**
* Called when error while deleting
* @param error error message
*/
fun onChaptersDeletedError(error: Throwable) {
Timber.e(error)
}
}

View File

@ -1,183 +0,0 @@
package eu.kanade.tachiyomi.ui.recent_updates
import eu.kanade.tachiyomi.data.database.DatabaseHelper
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.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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.Calendar
import java.util.Date
import java.util.TreeMap
class RecentChaptersPresenter(
private val controller: RecentChaptersController,
val preferences: PreferencesHelper = Injekt.get(),
private val db: DatabaseHelper = Injekt.get(),
private val downloadManager: DownloadManager = Injekt.get(),
private val sourceManager: SourceManager = Injekt.get()
) : DownloadQueue.DownloadListener, LibraryServiceListener {
/**
* List containing chapter and manga information
*/
var chapters: List<RecentChapterItem> = emptyList()
private var scope = CoroutineScope(Job() + Dispatchers.Default)
fun onCreate() {
downloadManager.addListener(this)
LibraryUpdateService.setListener(this)
getUpdates()
}
fun getUpdates() {
scope.launch {
val cal = Calendar.getInstance().apply {
time = Date()
add(Calendar.MONTH, -1)
}
val mangaChapters = db.getRecentChapters(cal.time).executeOnIO()
val map = TreeMap<Date, MutableList<MangaChapter>> { d1, d2 -> d2.compareTo(d1) }
val byDay = mangaChapters.groupByTo(map, { getMapKey(it.chapter.date_fetch) })
val items = byDay.flatMap {
val dateItem = DateItem(it.key)
it.value.map { mc ->
RecentChapterItem(mc.chapter, mc.manga, dateItem)
}
}
setDownloadedChapters(items)
chapters = items
withContext(Dispatchers.Main) { controller.onNextRecentChapters(chapters) }
}
}
fun onDestroy() {
downloadManager.removeListener(this)
LibraryUpdateService.removeListener(this)
}
fun cancelScope() {
scope.cancel()
}
override fun updateDownload(download: Download) {
chapters.find { it.chapter.id == download.chapter.id }?.download = download
scope.launch(Dispatchers.Main) {
controller.updateChapterDownload(download)
}
}
override fun updateDownloads() {
scope.launch {
setDownloadedChapters(chapters)
withContext(Dispatchers.Main) {
controller.onNextRecentChapters(chapters)
}
}
}
override fun onUpdateManga(manga: Manga?) {
getUpdates()
}
/**
* Get date as time key
*
* @param date desired date
* @return date as time key
*/
private fun getMapKey(date: Long): Date {
val cal = Calendar.getInstance()
cal.time = Date(date)
cal[Calendar.HOUR_OF_DAY] = 0
cal[Calendar.MINUTE] = 0
cal[Calendar.SECOND] = 0
cal[Calendar.MILLISECOND] = 0
return cal.time
}
/**
* Finds and assigns the list of downloaded chapters.
*
* @param chapters the list of chapter from the database.
*/
private fun setDownloadedChapters(chapters: List<RecentChapterItem>) {
for (item in chapters) {
if (downloadManager.isChapterDownloaded(item.chapter, item.manga)) {
item.status = Download.DOWNLOADED
} else if (downloadManager.hasQueue()) {
item.status = downloadManager.queue.find { it.chapter.id == item.chapter.id }
?.status ?: 0
}
}
}
/**
* Mark selected chapter as read
*
* @param items list of selected chapters
* @param read read status
*/
fun markChapterRead(
item: RecentChapterItem,
read: Boolean,
lastRead: Int? = null,
pagesLeft: Int? = null
) {
item.chapter.apply {
this.read = read
if (!read) {
last_page_read = lastRead ?: 0
pages_left = pagesLeft ?: 0
}
}
db.updateChapterProgress(item.chapter).executeAsBlocking()
controller.onNextRecentChapters(this.chapters)
}
fun startDownloadChapterNow(chapter: Chapter) {
downloadManager.startDownloadNow(chapter)
}
/**
* 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<SourceManager>().getOrStub(manga.source)
downloadManager.deleteChapters(listOf(chapter), manga, source)
if (update) {
val item = chapters.find { it.chapter.id == chapter.id } ?: return
item.apply {
status = Download.NOT_DOWNLOADED
download = null
}
controller.onNextRecentChapters(chapters)
}
}
/**
* Download selected chapters
* @param items list of recent chapters seleted.
*/
fun downloadChapters(items: List<RecentChapterItem>) {
items.forEach { downloadManager.downloadChapters(it.manga, listOf(it.chapter)) }
}
}

View File

@ -1,57 +0,0 @@
package eu.kanade.tachiyomi.ui.recently_read
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.SourceManager
import uy.kohesive.injekt.injectLazy
import java.text.DateFormat
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
/**
* Adapter of RecentlyReadHolder.
* Connection between Fragment and Holder
* Holder updates should be called from here.
*
* @param controller a RecentlyReadController object
* @constructor creates an instance of the adapter.
*/
class RecentlyReadAdapter(controller: RecentlyReadController) :
FlexibleAdapter<IFlexible<*>>(null, controller, true) {
val sourceManager by injectLazy<SourceManager>()
val resumeClickListener: OnResumeClickListener = controller
val removeClickListener: OnRemoveClickListener = controller
val coverClickListener: OnCoverClickListener = controller
/**
* DecimalFormat used to display correct chapter number
*/
val decimalFormat = DecimalFormat(
"#.###",
DecimalFormatSymbols()
.apply { decimalSeparator = '.' }
)
private val preferences: PreferencesHelper by injectLazy()
val dateFormat: DateFormat by lazy {
preferences.dateFormat()
}
interface OnResumeClickListener {
fun onResumeClick(position: Int)
}
interface OnRemoveClickListener {
fun onRemoveClick(position: Int)
}
interface OnCoverClickListener {
fun onCoverClick(position: Int)
}
}

View File

@ -1,244 +0,0 @@
package eu.kanade.tachiyomi.ui.recently_read
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 androidx.appcompat.widget.SearchView
import androidx.recyclerview.widget.LinearLayoutManager
import eu.davidea.flexibleadapter.FlexibleAdapter
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.databinding.RecentlyReadControllerBinding
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.source.browse.ProgressItem
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.scrollViewWith
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
import eu.kanade.tachiyomi.util.view.withFadeTransaction
/**
* Fragment that shows recently read manga.
* Uses R.layout.fragment_recently_read.
* UI related actions should be called from here.
*/
class RecentlyReadController(bundle: Bundle? = null) :
BaseController<RecentlyReadControllerBinding>(bundle),
FlexibleAdapter.OnUpdateListener,
FlexibleAdapter.EndlessScrollListener,
RecentlyReadAdapter.OnRemoveClickListener,
RecentlyReadAdapter.OnResumeClickListener,
RecentlyReadAdapter.OnCoverClickListener,
RemoveHistoryDialog.Listener {
init {
setHasOptionsMenu(true)
}
/**
* Adapter containing the recent manga.
*/
var adapter: RecentlyReadAdapter? = null
private set
/**
* Endless loading item.
*/
private var progressItem: ProgressItem? = null
private var observeLater: Boolean = false
private var query = ""
private var presenter = RecentlyReadPresenter(this)
private var recentItems: MutableList<RecentlyReadItem>? = null
override fun getTitle(): String? {
return resources?.getString(R.string.history)
}
override fun createBinding(inflater: LayoutInflater) = RecentlyReadControllerBinding.inflate(inflater)
/**
* Called when view is created
*
* @param view created view
*/
override fun onViewCreated(view: View) {
super.onViewCreated(view)
// view.applyWindowInsetsForController()
// Initialize adapter
adapter = RecentlyReadAdapter(this)
binding.recycler.adapter = adapter
binding.recycler.layoutManager = LinearLayoutManager(view.context)
binding.recycler.setHasFixedSize(true)
binding.recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener)
resetProgressItem()
scrollViewWith(binding.recycler, padBottom = true)
if (recentItems != null) {
adapter?.updateDataSet(recentItems!!.toList())
}
launchUI {
val manga = presenter.refresh(query)
recentItems = manga.toMutableList()
adapter?.updateDataSet(manga)
}
}
override fun onActivityResumed(activity: Activity) {
super.onActivityResumed(activity)
if (observeLater) {
launchUI {
val manga = presenter.refresh(query)
recentItems = manga.toMutableList()
adapter?.updateDataSet(manga)
}
observeLater = false
}
}
/**
* Populate adapter with chapters
*
* @param mangaHistory list of manga history
*/
fun onNextManga(mangaHistory: List<RecentlyReadItem>) {
val adapter = adapter ?: return
adapter.updateDataSet(mangaHistory)
adapter.onLoadMoreComplete(null)
if (recentItems == null) {
resetProgressItem()
}
recentItems = mangaHistory.toMutableList()
}
fun onAddPageError() {
adapter?.onLoadMoreComplete(null)
adapter?.endlessTargetCount = 1
}
override fun onUpdateEmptyView(size: Int) {
if (size > 0) {
binding.emptyView.hide()
} else {
binding.emptyView.show(
R.drawable.ic_history_24dp,
R.string
.no_recently_read_manga
)
}
}
/**
* Sets a new progress item and reenables the scroll listener.
*/
private fun resetProgressItem() {
progressItem = ProgressItem()
adapter?.endlessTargetCount = 0
adapter?.setEndlessScrollListener(this, progressItem!!)
}
override fun onLoadMore(lastPosition: Int, currentPage: Int) {
val view = view ?: return
if (BackupRestoreService.isRunning(view.context.applicationContext)) {
onAddPageError()
return
}
presenter.requestNext(query)
}
override fun noMoreLoad(newItemsSize: Int) { }
override fun onResumeClick(position: Int) {
val activity = activity ?: return
observeLater = true
val (manga, chapter, _) = (adapter?.getItem(position) as? RecentlyReadItem)?.mch ?: return
val nextChapter = presenter.getNextChapter(chapter, manga)
if (nextChapter != null) {
val intent = ReaderActivity.newIntent(activity, manga, nextChapter)
startActivity(intent)
} else {
activity.toast(R.string.next_chapter_not_found)
}
}
override fun onRemoveClick(position: Int) {
val (manga, _, history) = (adapter?.getItem(position) as? RecentlyReadItem)?.mch ?: return
RemoveHistoryDialog(this, manga, history).showDialog(router)
}
override fun onCoverClick(position: Int) {
val manga = (adapter?.getItem(position) as? RecentlyReadItem)?.mch?.manga ?: return
router.pushController(MangaDetailsController(manga).withFadeTransaction())
}
override fun removeHistory(manga: Manga, history: History, all: Boolean) {
presenter.lastCount = adapter?.itemCount ?: 25
if (all) {
// Reset last read of chapter to 0L
presenter.removeAllFromHistory(manga.id!!)
} else {
// Remove all chapters belonging to manga from library
presenter.removeFromHistory(history)
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.recently_read, menu)
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView
if (query.isNotEmpty()) {
searchItem.expandActionView()
searchView.setQuery(query, true)
searchView.clearFocus()
}
setOnQueryTextChangeListener(searchView) {
if (query != it) {
query = it ?: return@setOnQueryTextChangeListener false
launchUI {
resetProgressItem()
presenter.lastCount = 25
val manga = presenter.refresh(query)
recentItems = manga.toMutableList()
adapter?.updateDataSet(manga)
}
}
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(
RecentChaptersController().withFadeTransaction().tag(R.id.nav_recents.toString()))
Injekt.get<PreferencesHelper>().showRecentUpdates().set(true)
(activity as? MainActivity)?.updateRecentsIcon()
}
}
return super.onOptionsItemSelected(item)
}*/
}

View File

@ -1,66 +0,0 @@
package eu.kanade.tachiyomi.ui.recently_read
import android.view.View
import coil.api.clear
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
import eu.kanade.tachiyomi.data.image.coil.loadLibraryManga
import eu.kanade.tachiyomi.databinding.RecentlyReadItemBinding
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import eu.kanade.tachiyomi.util.lang.toTimestampString
import java.util.Date
/**
* Holder that contains recent manga item
* Uses R.layout.item_recently_read.
* UI related actions should be called from here.
*
* @param view the inflated view for this holder.
* @param adapter the adapter handling this holder.
* @constructor creates a new recent chapter holder.
*/
class RecentlyReadHolder(
view: View,
val adapter: RecentlyReadAdapter
) : BaseFlexibleViewHolder(view, adapter) {
private val binding = RecentlyReadItemBinding.bind(view)
init {
binding.remove.setOnClickListener {
adapter.removeClickListener.onRemoveClick(flexibleAdapterPosition)
}
binding.resume.setOnClickListener {
adapter.resumeClickListener.onResumeClick(flexibleAdapterPosition)
}
binding.cover.setOnClickListener {
adapter.coverClickListener.onCoverClick(flexibleAdapterPosition)
}
}
/**
* Set values of view
*
* @param item item containing history information
*/
fun bind(item: MangaChapterHistory) {
// Retrieve objects
val (manga, chapter, history) = item
// Set manga title
binding.title.text = manga.title
// Set source + chapter title
val formattedNumber = adapter.decimalFormat.format(chapter.chapter_number.toDouble())
binding.mangaSource.text = itemView.context.getString(R.string.source_dash_chapter_)
.format(adapter.sourceManager.getOrStub(manga.source).toString(), formattedNumber)
// Set last read timestamp title
binding.lastRead.text = Date(history.last_read).toTimestampString(adapter.dateFormat)
// Set binding.cover
binding.cover.clear()
binding.cover.loadLibraryManga(manga)
}
}

View File

@ -1,40 +0,0 @@
package eu.kanade.tachiyomi.ui.recently_read
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.MangaChapterHistory
class RecentlyReadItem(val mch: MangaChapterHistory) : AbstractFlexibleItem<RecentlyReadHolder>() {
override fun getLayoutRes(): Int {
return R.layout.recently_read_item
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): RecentlyReadHolder {
return RecentlyReadHolder(view, adapter as RecentlyReadAdapter)
}
override fun bindViewHolder(
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
holder: RecentlyReadHolder,
position: Int,
payloads: MutableList<Any?>?
) {
holder.bind(mch)
}
override fun equals(other: Any?): Boolean {
if (other is RecentlyReadItem) {
return mch.manga.id == other.mch.manga.id
}
return false
}
override fun hashCode(): Int {
return mch.manga.id!!.hashCode()
}
}

View File

@ -1,131 +0,0 @@
package eu.kanade.tachiyomi.ui.recently_read
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 kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import uy.kohesive.injekt.injectLazy
import java.util.Calendar
import java.util.Comparator
import java.util.Date
/**
* Presenter of RecentlyReadFragment.
* Contains information and data for fragment.
* Observable updates should be called from here.
*/
class RecentlyReadPresenter(private val view: RecentlyReadController) {
/**
* Used to connect to database
*/
val db: DatabaseHelper by injectLazy()
var lastCount = 25
var lastSearch = ""
fun requestNext(search: String = "") {
lastCount += 25
lastSearch = search
updateList(search)
}
/**
* Get all recent manga up to a point
* @return list of history
*/
private suspend fun getRecentMangaLimit(search: String = ""): List<RecentlyReadItem> {
// Set date for recent manga
val cal = Calendar.getInstance()
cal.time = Date()
cal.add(Calendar.YEAR, -50)
return db.getRecentMangaLimit(cal.time, lastCount, search).executeOnIO()
.map(::RecentlyReadItem)
}
/**
* Reset last read of chapter to 0L
* @param history history belonging to chapter
*/
fun removeFromHistory(history: History) {
history.last_read = 0L
db.updateHistoryLastRead(history).executeAsBlocking()
updateList()
}
suspend fun refresh(search: String? = null): List<RecentlyReadItem> {
val manga = getRecentMangaLimit(search ?: "")
checkIfNew(manga.size, search)
lastSearch = search ?: lastSearch
lastCount = manga.size
return manga
}
private fun updateList(search: String? = null) {
launchUI {
val manga = withContext(Dispatchers.IO) { getRecentMangaLimit(search ?: "") }
checkIfNew(manga.size, search)
lastSearch = search ?: lastSearch
lastCount = manga.size
view.onNextManga(manga)
}
}
private fun checkIfNew(newCount: Int, newSearch: String?) {
if (lastCount > newCount && newSearch == lastSearch) {
view.onAddPageError()
}
}
/**
* Removes all chapters belonging to manga from history.
* @param mangaId id of manga
*/
fun removeAllFromHistory(mangaId: Long) {
val history = db.getHistoryByMangaId(mangaId).executeAsBlocking()
history.forEach { it.last_read = 0L }
db.updateHistoryLastRead(history).executeAsBlocking()
updateList()
}
/**
* Retrieves the next chapter of the given one.
*
* @param chapter the chapter of the history object.
* @param manga the manga of the chapter.
*/
fun getNextChapter(chapter: Chapter, manga: Manga): Chapter? {
if (!chapter.read) {
return chapter
}
val sortFunction: (Chapter, Chapter) -> Int = when (manga.sorting) {
Manga.SORTING_SOURCE -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) }
Manga.SORTING_NUMBER -> { c1, c2 -> c1.chapter_number.compareTo(c2.chapter_number) }
else -> throw NotImplementedError("Unknown sorting method")
}
val chapters = db.getChapters(manga).executeAsBlocking()
.sortedWith(Comparator<Chapter> { c1, c2 -> sortFunction(c1, c2) })
val currChapterIndex = chapters.indexOfFirst { chapter.id == it.id }
return when (manga.sorting) {
Manga.SORTING_SOURCE -> chapters.getOrNull(currChapterIndex + 1)
Manga.SORTING_NUMBER -> {
val chapterNumber = chapter.chapter_number
((currChapterIndex + 1) until chapters.size)
.map { chapters[it] }
.firstOrNull {
it.chapter_number > chapterNumber &&
it.chapter_number <= chapterNumber + 1
}
}
else -> throw NotImplementedError("Unknown sorting method")
}
}
}

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.ui.recent_updates
package eu.kanade.tachiyomi.ui.recents
import android.text.format.DateUtils
import android.view.View
@ -11,8 +11,7 @@ import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R
import java.util.Date
class DateItem(val date: Date, val addedString: Boolean = false) : AbstractHeaderItem<DateItem
.Holder>() {
class DateItem(val date: Date, val addedString: Boolean = false) : AbstractHeaderItem<DateItem.Holder>() {
override fun getLayoutRes(): Int {
return R.layout.recent_chapters_section_item

View File

@ -33,7 +33,6 @@ 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.RemoveHistoryDialog
import eu.kanade.tachiyomi.ui.source.browse.ProgressItem
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.spToPx

View File

@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.data.library.LibraryServiceListener
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.recent_updates.DateItem
import eu.kanade.tachiyomi.util.system.executeOnIO
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.ui.recently_read
package eu.kanade.tachiyomi.ui.recents
import android.app.Dialog
import android.os.Bundle