Recent Read Controller is no longer Rx

Unfortunately to fix #72, an rx observer is needed since the reader adds to the db after the recents is back in view
This commit is contained in:
Jay 2020-02-10 01:02:09 -08:00
parent f91e66269f
commit fb2ab7d765
6 changed files with 130 additions and 85 deletions

View File

@ -96,7 +96,7 @@ class MigrationListController(bundle: Bundle? = null) : BaseController(bundle),
new new
} }
adapter = MigrationProcessAdapter(this, view.context) adapter = MigrationProcessAdapter(this)
recycler.adapter = adapter recycler.adapter = adapter
recycler.layoutManager = LinearLayoutManager(view.context) recycler.layoutManager = LinearLayoutManager(view.context)

View File

@ -16,8 +16,7 @@ import kotlinx.coroutines.withContext
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class MigrationProcessAdapter( class MigrationProcessAdapter(
val controller: MigrationListController, val controller: MigrationListController
context: Context
) : FlexibleAdapter<MigrationProcessItem>(null, controller, true) { ) : FlexibleAdapter<MigrationProcessItem>(null, controller, true) {

View File

@ -1,35 +1,37 @@
package eu.kanade.tachiyomi.ui.recently_read package eu.kanade.tachiyomi.ui.recently_read
import androidx.recyclerview.widget.LinearLayoutManager import android.app.Activity
import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import com.jakewharton.rxbinding.support.v7.widget.queryTextChanges import androidx.recyclerview.widget.LinearLayoutManager
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.BackupRestoreService import eu.kanade.tachiyomi.data.backup.BackupRestoreService
import eu.kanade.tachiyomi.data.database.models.History import eu.kanade.tachiyomi.data.database.models.History
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.catalogue.browse.ProgressItem import eu.kanade.tachiyomi.ui.catalogue.browse.ProgressItem
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import kotlinx.android.synthetic.main.recently_read_controller.empty_view import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener
import kotlinx.android.synthetic.main.recently_read_controller.recycler import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
import kotlinx.android.synthetic.main.recently_read_controller.*
/** /**
* Fragment that shows recently read manga. * Fragment that shows recently read manga.
* Uses R.layout.fragment_recently_read. * Uses R.layout.fragment_recently_read.
* UI related actions should be called from here. * UI related actions should be called from here.
*/ */
class RecentlyReadController : NucleusController<RecentlyReadPresenter>(), class RecentlyReadController(bundle: Bundle? = null) : BaseController(bundle),
FlexibleAdapter.OnUpdateListener, FlexibleAdapter.OnUpdateListener,
FlexibleAdapter.EndlessScrollListener, FlexibleAdapter.EndlessScrollListener,
RecentlyReadAdapter.OnRemoveClickListener, RecentlyReadAdapter.OnRemoveClickListener,
@ -50,16 +52,17 @@ class RecentlyReadController : NucleusController<RecentlyReadPresenter>(),
* Endless loading item. * Endless loading item.
*/ */
private var progressItem: ProgressItem? = null private var progressItem: ProgressItem? = null
private var observeLater:Boolean = false
private var query = "" private var query = ""
private var presenter = RecentlyReadPresenter(this)
private var recentItems: MutableList<RecentlyReadItem>? = null
override fun getTitle(): String? { override fun getTitle(): String? {
return resources?.getString(R.string.label_recent_manga) return resources?.getString(R.string.label_recent_manga)
} }
override fun createPresenter(): RecentlyReadPresenter {
return RecentlyReadPresenter()
}
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
return inflater.inflate(R.layout.recently_read_controller, container, false) return inflater.inflate(R.layout.recently_read_controller, container, false)
} }
@ -73,38 +76,52 @@ class RecentlyReadController : NucleusController<RecentlyReadPresenter>(),
super.onViewCreated(view) super.onViewCreated(view)
// Initialize adapter // Initialize adapter
recycler.layoutManager = LinearLayoutManager(view.context) adapter = RecentlyReadAdapter(this)
adapter = RecentlyReadAdapter(this@RecentlyReadController)
recycler.setHasFixedSize(true)
recycler.adapter = adapter recycler.adapter = adapter
recycler.layoutManager = LinearLayoutManager(view.context)
recycler.setHasFixedSize(true)
recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener)
resetProgressItem()
if (recentItems != null)
adapter?.updateDataSet(recentItems!!.toList())
launchUI {
val manga = presenter.refresh(query)
recentItems = manga.toMutableList()
adapter?.updateDataSet(manga)
}
} }
override fun onDestroyView(view: View) { override fun onActivityResumed(activity: Activity) {
adapter = null super.onActivityResumed(activity)
super.onDestroyView(view) if (observeLater) {
presenter.observe()
observeLater = false
}
} }
/** /**
* Populate adapter with chapters * Populate adapter with chapters
* *
* @param mangaHistory list of manga history * @param mangaHistory list of manga history
*/ */
fun onNextManga(mangaHistory: List<RecentlyReadItem>, cleanBatch: Boolean = false) { fun onNextManga(mangaHistory: List<RecentlyReadItem>) {
if (adapter?.itemCount ?: 0 == 0 || cleanBatch) val adapter = adapter ?: return
adapter.updateDataSet(mangaHistory)
adapter.onLoadMoreComplete(null)
if (recentItems == null)
resetProgressItem() resetProgressItem()
if (cleanBatch) adapter?.updateDataSet(mangaHistory) recentItems = mangaHistory.toMutableList()
else adapter?.onLoadMoreComplete(mangaHistory)
} }
fun onAddPageError(error: Throwable) { fun onAddPageError() {
adapter?.onLoadMoreComplete(null) adapter?.onLoadMoreComplete(null)
adapter?.endlessTargetCount = 1 adapter?.endlessTargetCount = 1
} }
override fun onUpdateEmptyView(size: Int) { override fun onUpdateEmptyView(size: Int) {
if (size > 0) { if (size > 0) {
empty_view.hide() empty_view?.hide()
} else { } else {
empty_view.show(R.drawable.ic_glasses_black_128dp, R.string.information_no_recent_manga) empty_view.show(R.drawable.ic_glasses_black_128dp, R.string.information_no_recent_manga)
} }
@ -122,17 +139,17 @@ class RecentlyReadController : NucleusController<RecentlyReadPresenter>(),
override fun onLoadMore(lastPosition: Int, currentPage: Int) { override fun onLoadMore(lastPosition: Int, currentPage: Int) {
val view = view ?: return val view = view ?: return
if (BackupRestoreService.isRunning(view.context.applicationContext)) { if (BackupRestoreService.isRunning(view.context.applicationContext)) {
onAddPageError(Throwable()) onAddPageError()
return return
} }
val adapter = adapter ?: return presenter.requestNext(query)
presenter.requestNext(adapter.itemCount, query)
} }
override fun noMoreLoad(newItemsSize: Int) { } override fun noMoreLoad(newItemsSize: Int) { }
override fun onResumeClick(position: Int) { override fun onResumeClick(position: Int) {
val activity = activity ?: return val activity = activity ?: return
observeLater = true
val (manga, chapter, _) = (adapter?.getItem(position) as? RecentlyReadItem)?.mch ?: return val (manga, chapter, _) = (adapter?.getItem(position) as? RecentlyReadItem)?.mch ?: return
val nextChapter = presenter.getNextChapter(chapter, manga) val nextChapter = presenter.getNextChapter(chapter, manga)
@ -168,16 +185,23 @@ class RecentlyReadController : NucleusController<RecentlyReadPresenter>(),
inflater.inflate(R.menu.recently_read, menu) inflater.inflate(R.menu.recently_read, menu)
val searchItem = menu.findItem(R.id.action_search) val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView val searchView = searchItem.actionView as SearchView
searchView.maxWidth = Int.MAX_VALUE
if (query.isNotEmpty()) { if (query.isNotEmpty()) {
searchItem.expandActionView() searchItem.expandActionView()
searchView.setQuery(query, true) searchView.setQuery(query, true)
searchView.clearFocus() searchView.clearFocus()
} }
searchView.queryTextChanges().filter { router.backstack.lastOrNull()?.controller() == this } setOnQueryTextChangeListener(searchView) {
.subscribeUntilDestroy { if (query != it) {
query = it.toString() query = it ?: return@setOnQueryTextChangeListener false
presenter.updateList(query) 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 // Fixes problem with the overflow icon showing up in lieu of search

View File

@ -6,6 +6,10 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.History import eu.kanade.tachiyomi.data.database.models.History
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.system.launchUI
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.Dispatcher
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
@ -20,60 +24,47 @@ import java.util.Date
* Contains information and data for fragment. * Contains information and data for fragment.
* Observable updates should be called from here. * Observable updates should be called from here.
*/ */
class RecentlyReadPresenter : BasePresenter<RecentlyReadController>() { class RecentlyReadPresenter(private val view: RecentlyReadController) {
/** /**
* Used to connect to database * Used to connect to database
*/ */
val db: DatabaseHelper by injectLazy() val db: DatabaseHelper by injectLazy()
private var readerSubscription:Subscription? = null
var lastCount = 25 var lastCount = 25
var lastSearch = "" var lastSearch = ""
override fun onCreate(savedState: Bundle?) { fun requestNext(search: String = "") {
super.onCreate(savedState) lastCount += 25
//pageSubscription?.let { remove(it) }
// Used to get a list of recently read manga
updateList()
}
fun requestNext(offset: Int, search: String = "") {
lastCount = offset
lastSearch = search lastSearch = search
getRecentMangaObservable((offset), search) updateList(search)
.subscribeLatestCache({ view, mangas ->
view.onNextManga(mangas)
}, RecentlyReadController::onAddPageError)
} }
/** /**
* Get recent manga observable * Get all recent manga up to a point
* @return list of history * @return list of history
*/ */
private fun getRecentMangaObservable(offset: Int = 0, search: String = ""): Observable<List<RecentlyReadItem>> { private fun getRecentMangaLimit(search: String = ""): List<RecentlyReadItem> {
// Set date for recent manga // Set date for recent manga
val cal = Calendar.getInstance() val cal = Calendar.getInstance()
cal.time = Date() cal.time = Date()
cal.add(Calendar.YEAR, -50) cal.add(Calendar.YEAR, -50)
return db.getRecentManga(cal.time, offset, search).asRxObservable() return db.getRecentMangaLimit(cal.time, lastCount, search).executeAsBlocking()
.map { recents -> recents.map(::RecentlyReadItem) } .map(::RecentlyReadItem)
.observeOn(AndroidSchedulers.mainThread())
} }
/** fun observe() {
* Get recent manga observable readerSubscription?.unsubscribe()
* @return list of history
*/
private fun getRecentMangaLimitObservable(offset: Int = 0, search: String = ""): Observable<List<RecentlyReadItem>> {
// Set date for recent manga
val cal = Calendar.getInstance() val cal = Calendar.getInstance()
cal.time = Date() cal.time = Date()
cal.add(Calendar.YEAR, -50) cal.add(Calendar.YEAR, -50)
readerSubscription = db.getRecentMangaLimit(cal.time, lastCount, "").asRxObservable().map {
return db.getRecentMangaLimit(cal.time, lastCount, search).asRxObservable() val items = it.map(::RecentlyReadItem)
.map { recents -> recents.map(::RecentlyReadItem) } launchUI {
.observeOn(AndroidSchedulers.mainThread()) view.onNextManga(items)
}
}.observeOn(Schedulers.io()).skip(1).take(1).subscribe()
} }
/** /**
@ -86,12 +77,28 @@ class RecentlyReadPresenter : BasePresenter<RecentlyReadController>() {
updateList() updateList()
} }
fun updateList(search: String? = null) { suspend fun refresh(search: String? = null): List<RecentlyReadItem> {
val manga = withContext(Dispatchers.IO) { getRecentMangaLimit(search ?: "") }
checkIfNew(manga.size, search)
lastSearch = search ?: lastSearch lastSearch = search ?: lastSearch
getRecentMangaLimitObservable(lastCount, lastSearch).take(1) lastCount = manga.size
.subscribeLatestCache({ view, mangas -> return manga
view.onNextManga(mangas, true) }
}, RecentlyReadController::onAddPageError)
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()
}
} }
/** /**

View File

@ -13,14 +13,12 @@ import android.widget.TextView
import androidx.annotation.Px import androidx.annotation.Px
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.appcompat.widget.SearchView
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.WhichButton
import com.afollestad.materialdialogs.actions.getActionButton
import com.afollestad.materialdialogs.actions.hasActionButton
import com.amulyakhare.textdrawable.TextDrawable import com.amulyakhare.textdrawable.TextDrawable
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import com.amulyakhare.textdrawable.util.ColorGenerator import com.amulyakhare.textdrawable.util.ColorGenerator
import com.bluelinelabs.conductor.Controller
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import kotlin.math.min import kotlin.math.min
@ -199,6 +197,22 @@ data class ViewPaddingState(
val end: Int val end: Int
) )
fun Controller.setOnQueryTextChangeListener(searchView: SearchView, f: (text: String?) -> Boolean) {
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextChange(newText: String?): Boolean {
if (router.backstack.lastOrNull()?.controller() == this@setOnQueryTextChangeListener) {
return f(newText)
}
return true
}
override fun onQueryTextSubmit(query: String?): Boolean {
return true
}
})
}
@RequiresApi(17) @RequiresApi(17)
inline fun View.updatePaddingRelative( inline fun View.updatePaddingRelative(
@Px start: Int = paddingStart, @Px start: Int = paddingStart,

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/frame_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
@ -9,18 +10,18 @@
android:id="@+id/recycler" android:id="@+id/recycler"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:paddingBottom="4dp"
android:paddingTop="4dp"
android:clipToPadding="false" android:clipToPadding="false"
android:paddingTop="4dp"
android:paddingBottom="4dp"
tools:listitem="@layout/recently_read_item"> tools:listitem="@layout/recently_read_item">
</androidx.recyclerview.widget.RecyclerView> </androidx.recyclerview.widget.RecyclerView>
<eu.kanade.tachiyomi.widget.EmptyView <eu.kanade.tachiyomi.widget.EmptyView
android:id="@+id/empty_view" android:id="@+id/empty_view"
android:visibility="gone"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_height="wrap_content"/> android:visibility="gone" />
</FrameLayout> </FrameLayout>