mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-13 20:48:56 +01:00
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:
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user