Use Stable interface for History screen (#7586)

- Adds Stable interface
- Move last Dialog into Compose
- Make History screen be full Compose screen
This commit is contained in:
Andreas
2022-07-23 16:01:51 +02:00
committed by GitHub
parent 9f2ddaadde
commit c751851941
9 changed files with 416 additions and 349 deletions

View File

@@ -1,21 +0,0 @@
package eu.kanade.tachiyomi.ui.recent.history
import android.app.Dialog
import android.os.Bundle
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.controller.DialogController
class ClearHistoryDialogController : DialogController() {
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialAlertDialogBuilder(activity!!)
.setMessage(R.string.clear_history_confirmation)
.setPositiveButton(android.R.string.ok) { _, _ ->
(targetController as? HistoryController)
?.presenter
?.deleteAllHistory()
}
.setNegativeButton(android.R.string.cancel, null)
.create()
}
}

View File

@@ -1,37 +1,19 @@
package eu.kanade.tachiyomi.ui.recent.history
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.appcompat.widget.SearchView
import androidx.compose.runtime.Composable
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.presentation.history.HistoryScreen
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.controller.ComposeController
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
import eu.kanade.tachiyomi.ui.base.controller.RootController
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.appcompat.queryTextChanges
class HistoryController : ComposeController<HistoryPresenter>(), RootController {
private var query = ""
override fun getTitle() = resources?.getString(R.string.label_recent_manga)
class HistoryController : FullComposeController<HistoryPresenter>(), RootController {
override fun createPresenter() = HistoryPresenter()
@Composable
override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) {
override fun ComposeContent() {
HistoryScreen(
nestedScrollInterop = nestedScrollInterop,
presenter = presenter,
onClickCover = { history ->
router.pushController(MangaController(history.mangaId))
@@ -39,59 +21,9 @@ class HistoryController : ComposeController<HistoryPresenter>(), RootController
onClickResume = { history ->
presenter.getNextChapterForManga(history.mangaId, history.chapterId)
},
onClickDelete = { history, all ->
if (all) {
// Reset last read of chapter to 0L
presenter.removeAllFromHistory(history.mangaId)
} else {
// Remove all chapters belonging to manga from library
presenter.removeFromHistory(history)
}
},
)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.history, menu)
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView
searchView.maxWidth = Int.MAX_VALUE
if (query.isNotEmpty()) {
searchItem.expandActionView()
searchView.setQuery(query, true)
searchView.clearFocus()
}
searchView.queryTextChanges()
.filter { router.backstack.lastOrNull()?.controller == this }
.onEach {
query = it.toString()
presenter.search(query)
}
.launchIn(viewScope)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_clear_history -> {
val dialog = ClearHistoryDialogController()
dialog.targetController = this@HistoryController
dialog.showDialog(router)
true
}
else -> super.onOptionsItemSelected(item)
}
}
fun openChapter(chapter: Chapter?) {
val activity = activity ?: return
if (chapter != null) {
val intent = ReaderActivity.newIntent(activity, chapter.mangaId, chapter.id)
startActivity(intent)
} else {
activity.toast(R.string.no_next_chapter)
}
}
fun resumeLastChapterRead() {
presenter.resumeLastChapterRead()
}

View File

@@ -1,10 +1,19 @@
package eu.kanade.tachiyomi.ui.recent.history
import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.paging.PagingData
import androidx.paging.cachedIn
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.insertSeparators
import androidx.paging.map
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.history.interactor.DeleteHistoryTable
import eu.kanade.domain.history.interactor.GetHistory
import eu.kanade.domain.history.interactor.GetNextChapter
@@ -17,53 +26,46 @@ import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.lang.toDateKey
import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.receiveAsFlow
import logcat.LogPriority
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.Date
/**
* Presenter of HistoryFragment.
* Contains information and data for fragment.
* Observable updates should be called from here.
*/
class HistoryPresenter(
private val state: HistoryStateImpl = HistoryState() as HistoryStateImpl,
private val getHistory: GetHistory = Injekt.get(),
private val getNextChapter: GetNextChapter = Injekt.get(),
private val deleteHistoryTable: DeleteHistoryTable = Injekt.get(),
private val removeHistoryById: RemoveHistoryById = Injekt.get(),
private val removeHistoryByMangaId: RemoveHistoryByMangaId = Injekt.get(),
) : BasePresenter<HistoryController>() {
) : BasePresenter<HistoryController>(), HistoryState by state {
private val _query: MutableStateFlow<String> = MutableStateFlow("")
private val _state: MutableStateFlow<HistoryState> = MutableStateFlow(HistoryState.Loading)
val state: StateFlow<HistoryState> = _state.asStateFlow()
private val _events: Channel<Event> = Channel(Int.MAX_VALUE)
val events: Flow<Event> = _events.receiveAsFlow()
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
presenterScope.launchIO {
_query.collectLatest { query ->
getHistory.subscribe(query)
.catch { exception ->
_state.value = HistoryState.Error(exception)
}
.map { pagingData ->
pagingData.toHistoryUiModels()
}
.cachedIn(presenterScope)
.let { uiModelsPagingDataFlow ->
_state.value = HistoryState.Success(uiModelsPagingDataFlow)
}
}
@Composable
fun getLazyHistory(): LazyPagingItems<HistoryUiModel> {
val scope = rememberCoroutineScope()
val query = searchQuery ?: ""
val flow = remember(query) {
getHistory.subscribe(query)
.catch { error ->
logcat(LogPriority.ERROR, error)
_events.send(Event.InternalError)
}
.map { pagingData ->
pagingData.toHistoryUiModels()
}
.cachedIn(scope)
}
return flow.collectAsLazyPagingItems()
}
private fun PagingData<HistoryWithRelations>.toHistoryUiModels(): PagingData<HistoryUiModel> {
@@ -81,12 +83,6 @@ class HistoryPresenter(
}
}
fun search(query: String) {
presenterScope.launchIO {
_query.emit(query)
}
}
fun removeFromHistory(history: HistoryWithRelations) {
presenterScope.launchIO {
removeHistoryById.await(history)
@@ -102,9 +98,7 @@ class HistoryPresenter(
fun getNextChapterForManga(mangaId: Long, chapterId: Long) {
presenterScope.launchIO {
val chapter = getNextChapter.await(mangaId, chapterId)
launchUI {
view?.openChapter(chapter)
}
_events.send(if (chapter != null) Event.OpenChapter(chapter) else Event.NoNextChapterFound)
}
}
@@ -121,15 +115,33 @@ class HistoryPresenter(
fun resumeLastChapterRead() {
presenterScope.launchIO {
val chapter = getNextChapter.await()
launchUI {
view?.openChapter(chapter)
}
_events.send(if (chapter != null) Event.OpenChapter(chapter) else Event.NoNextChapterFound)
}
}
sealed class Dialog {
object DeleteAll : Dialog()
data class Delete(val history: HistoryWithRelations) : Dialog()
}
sealed class Event {
object InternalError : Event()
object NoNextChapterFound : Event()
data class OpenChapter(val chapter: Chapter) : Event()
}
}
sealed class HistoryState {
object Loading : HistoryState()
data class Error(val error: Throwable) : HistoryState()
data class Success(val uiModels: Flow<PagingData<HistoryUiModel>>) : HistoryState()
@Stable
interface HistoryState {
var searchQuery: String?
var dialog: HistoryPresenter.Dialog?
}
fun HistoryState(): HistoryState {
return HistoryStateImpl()
}
class HistoryStateImpl : HistoryState {
override var searchQuery: String? by mutableStateOf(null)
override var dialog: HistoryPresenter.Dialog? by mutableStateOf(null)
}