Voyager on History tab (#8481)
This commit is contained in:
parent
ba00d9e5d2
commit
bc3bb82651
9 changed files with 233 additions and 186 deletions
|
@ -34,3 +34,5 @@ class PreferenceMutableState<T>(
|
|||
return { preference.set(it) }
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> Preference<T>.asState(scope: CoroutineScope) = PreferenceMutableState(this, scope)
|
||||
|
|
|
@ -8,9 +8,9 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
|
||||
@Composable
|
||||
fun LoadingScreen() {
|
||||
fun LoadingScreen(modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
modifier = modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
|
|
|
@ -1,110 +1,80 @@
|
|||
package eu.kanade.presentation.history
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.DeleteSweep
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ScaffoldDefaults
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.domain.history.model.HistoryWithRelations
|
||||
import eu.kanade.presentation.components.AppBarTitle
|
||||
import eu.kanade.presentation.components.EmptyScreen
|
||||
import eu.kanade.presentation.components.LoadingScreen
|
||||
import eu.kanade.presentation.components.Scaffold
|
||||
import eu.kanade.presentation.components.SearchToolbar
|
||||
import eu.kanade.presentation.history.components.HistoryContent
|
||||
import eu.kanade.presentation.history.components.HistoryDeleteAllDialog
|
||||
import eu.kanade.presentation.history.components.HistoryDeleteDialog
|
||||
import eu.kanade.presentation.history.components.HistoryToolbar
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.history.HistoryPresenter
|
||||
import eu.kanade.tachiyomi.ui.history.HistoryPresenter.Dialog
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
||||
import eu.kanade.tachiyomi.ui.history.HistoryState
|
||||
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import java.util.Date
|
||||
|
||||
@Composable
|
||||
fun HistoryScreen(
|
||||
presenter: HistoryPresenter,
|
||||
onClickCover: (HistoryWithRelations) -> Unit,
|
||||
onClickResume: (HistoryWithRelations) -> Unit,
|
||||
state: HistoryState,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
incognitoMode: Boolean,
|
||||
downloadedOnlyMode: Boolean,
|
||||
onSearchQueryChange: (String?) -> Unit,
|
||||
onClickCover: (mangaId: Long) -> Unit,
|
||||
onClickResume: (mangaId: Long, chapterId: Long) -> Unit,
|
||||
onDialogChange: (HistoryScreenModel.Dialog?) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
HistoryToolbar(
|
||||
state = presenter,
|
||||
incognitoMode = presenter.isIncognitoMode,
|
||||
downloadedOnlyMode = presenter.isDownloadOnly,
|
||||
SearchToolbar(
|
||||
titleContent = { AppBarTitle(stringResource(R.string.history)) },
|
||||
searchQuery = state.searchQuery,
|
||||
onChangeSearchQuery = onSearchQueryChange,
|
||||
actions = {
|
||||
IconButton(onClick = { onDialogChange(HistoryScreenModel.Dialog.DeleteAll) }) {
|
||||
Icon(
|
||||
Icons.Outlined.DeleteSweep,
|
||||
contentDescription = stringResource(R.string.pref_clear_history),
|
||||
)
|
||||
}
|
||||
},
|
||||
downloadedOnlyMode = downloadedOnlyMode,
|
||||
incognitoMode = incognitoMode,
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
},
|
||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||
contentWindowInsets = TachiyomiBottomNavigationView.withBottomNavInset(ScaffoldDefaults.contentWindowInsets),
|
||||
) { contentPadding ->
|
||||
val items by presenter.getHistory().collectAsState(initial = null)
|
||||
val contentPaddingWithNavBar = TachiyomiBottomNavigationView.withBottomNavPadding(contentPadding)
|
||||
items.let {
|
||||
state.list.let {
|
||||
if (it == null) {
|
||||
LoadingScreen()
|
||||
LoadingScreen(modifier = Modifier.padding(contentPadding))
|
||||
} else if (it.isEmpty()) {
|
||||
EmptyScreen(
|
||||
textResource = R.string.information_no_recent_manga,
|
||||
modifier = Modifier.padding(contentPaddingWithNavBar),
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
)
|
||||
} else {
|
||||
HistoryContent(
|
||||
history = it,
|
||||
contentPadding = contentPaddingWithNavBar,
|
||||
onClickCover = onClickCover,
|
||||
onClickResume = onClickResume,
|
||||
onClickDelete = { item -> presenter.dialog = Dialog.Delete(item) },
|
||||
contentPadding = contentPadding,
|
||||
onClickCover = { history -> onClickCover(history.mangaId) },
|
||||
onClickResume = { history -> onClickResume(history.mangaId, history.chapterId) },
|
||||
onClickDelete = { item -> onDialogChange(HistoryScreenModel.Dialog.Delete(item)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(items) {
|
||||
if (items != null) {
|
||||
(presenter.view?.activity as? MainActivity)?.ready = true
|
||||
}
|
||||
}
|
||||
}
|
||||
val onDismissRequest = { presenter.dialog = null }
|
||||
when (val dialog = presenter.dialog) {
|
||||
is Dialog.Delete -> {
|
||||
HistoryDeleteDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
onDelete = { all ->
|
||||
if (all) {
|
||||
presenter.removeAllFromHistory(dialog.history.mangaId)
|
||||
} else {
|
||||
presenter.removeFromHistory(dialog.history)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
is Dialog.DeleteAll -> {
|
||||
HistoryDeleteAllDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
onDelete = {
|
||||
presenter.removeAllHistory()
|
||||
},
|
||||
)
|
||||
}
|
||||
null -> {}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
presenter.events.collectLatest { event ->
|
||||
when (event) {
|
||||
HistoryPresenter.Event.InternalError -> context.toast(R.string.internal_error)
|
||||
HistoryPresenter.Event.NoNextChapterFound -> context.toast(R.string.no_next_chapter)
|
||||
is HistoryPresenter.Event.OpenChapter -> {
|
||||
val intent = ReaderActivity.newIntent(context, event.chapter.mangaId, event.chapter.id)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
package eu.kanade.presentation.history.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.DeleteSweep
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.presentation.components.AppBarTitle
|
||||
import eu.kanade.presentation.components.SearchToolbar
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.history.HistoryPresenter
|
||||
import eu.kanade.tachiyomi.ui.history.HistoryState
|
||||
|
||||
@Composable
|
||||
fun HistoryToolbar(
|
||||
state: HistoryState,
|
||||
scrollBehavior: TopAppBarScrollBehavior,
|
||||
incognitoMode: Boolean,
|
||||
downloadedOnlyMode: Boolean,
|
||||
) {
|
||||
SearchToolbar(
|
||||
titleContent = { AppBarTitle(stringResource(R.string.history)) },
|
||||
searchQuery = state.searchQuery,
|
||||
onChangeSearchQuery = { state.searchQuery = it },
|
||||
actions = {
|
||||
IconButton(onClick = { state.dialog = HistoryPresenter.Dialog.DeleteAll }) {
|
||||
Icon(Icons.Outlined.DeleteSweep, contentDescription = stringResource(R.string.pref_clear_history))
|
||||
}
|
||||
},
|
||||
downloadedOnlyMode = downloadedOnlyMode,
|
||||
incognitoMode = incognitoMode,
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
}
|
|
@ -5,6 +5,8 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import androidx.activity.OnBackPressedDispatcherOwner
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.tachiyomi.databinding.ComposeControllerBinding
|
||||
import eu.kanade.tachiyomi.util.view.setComposeContent
|
||||
import nucleus.presenter.Presenter
|
||||
|
@ -21,10 +23,12 @@ abstract class FullComposeController<P : Presenter<*>>(bundle: Bundle? = null) :
|
|||
|
||||
binding.root.apply {
|
||||
setComposeContent {
|
||||
CompositionLocalProvider(LocalRouter provides router) {
|
||||
ComposeContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleBack(): Boolean {
|
||||
val dispatcher = (activity as? OnBackPressedDispatcherOwner)?.onBackPressedDispatcher ?: return false
|
||||
|
@ -52,10 +56,12 @@ abstract class BasicFullComposeController(bundle: Bundle? = null) :
|
|||
|
||||
binding.root.apply {
|
||||
setComposeContent {
|
||||
CompositionLocalProvider(LocalRouter provides router) {
|
||||
ComposeContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Let Compose view handle this
|
||||
override fun handleBack(): Boolean {
|
||||
|
|
|
@ -1,30 +1,26 @@
|
|||
package eu.kanade.tachiyomi.ui.history
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import eu.kanade.presentation.history.HistoryScreen
|
||||
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import eu.kanade.domain.history.interactor.GetNextChapters
|
||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
||||
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.util.lang.launchIO
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class HistoryController : FullComposeController<HistoryPresenter>(), RootController {
|
||||
|
||||
override fun createPresenter() = HistoryPresenter()
|
||||
class HistoryController : BasicFullComposeController(), RootController {
|
||||
|
||||
@Composable
|
||||
override fun ComposeContent() {
|
||||
HistoryScreen(
|
||||
presenter = presenter,
|
||||
onClickCover = { history ->
|
||||
router.pushController(MangaController(history.mangaId))
|
||||
},
|
||||
onClickResume = { history ->
|
||||
presenter.getNextChapterForManga(history.mangaId, history.chapterId)
|
||||
},
|
||||
)
|
||||
Navigator(screen = HistoryScreen)
|
||||
}
|
||||
|
||||
fun resumeLastChapterRead() {
|
||||
presenter.resumeLastChapterRead()
|
||||
val context = activity ?: return
|
||||
viewScope.launchIO {
|
||||
val chapter = Injekt.get<GetNextChapters>().await(onlyUnread = false).firstOrNull()
|
||||
HistoryScreen.openChapter(context, chapter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
package eu.kanade.tachiyomi.ui.history
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
import eu.kanade.presentation.history.HistoryScreen
|
||||
import eu.kanade.presentation.history.components.HistoryDeleteAllDialog
|
||||
import eu.kanade.presentation.history.components.HistoryDeleteDialog
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
|
||||
object HistoryScreen : Screen {
|
||||
|
||||
private val snackbarHostState = SnackbarHostState()
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val context = LocalContext.current
|
||||
val screenModel = rememberScreenModel { HistoryScreenModel() }
|
||||
val state by screenModel.state.collectAsState()
|
||||
|
||||
HistoryScreen(
|
||||
state = state,
|
||||
snackbarHostState = snackbarHostState,
|
||||
incognitoMode = screenModel.isIncognitoMode,
|
||||
downloadedOnlyMode = screenModel.isDownloadOnly,
|
||||
onSearchQueryChange = screenModel::updateSearchQuery,
|
||||
onClickCover = { router.pushController(MangaController(it)) },
|
||||
onClickResume = screenModel::getNextChapterForManga,
|
||||
onDialogChange = screenModel::setDialog,
|
||||
)
|
||||
|
||||
val onDismissRequest = { screenModel.setDialog(null) }
|
||||
when (val dialog = state.dialog) {
|
||||
is HistoryScreenModel.Dialog.Delete -> {
|
||||
HistoryDeleteDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
onDelete = { all ->
|
||||
if (all) {
|
||||
screenModel.removeAllFromHistory(dialog.history.mangaId)
|
||||
} else {
|
||||
screenModel.removeFromHistory(dialog.history)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
is HistoryScreenModel.Dialog.DeleteAll -> {
|
||||
HistoryDeleteAllDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
onDelete = screenModel::removeAllHistory,
|
||||
)
|
||||
}
|
||||
null -> {}
|
||||
}
|
||||
|
||||
LaunchedEffect(state.list) {
|
||||
if (state.list != null) {
|
||||
(context as? MainActivity)?.ready = true
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
screenModel.events.collectLatest { e ->
|
||||
when (e) {
|
||||
HistoryScreenModel.Event.InternalError ->
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.internal_error))
|
||||
HistoryScreenModel.Event.HistoryCleared ->
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.clear_history_completed))
|
||||
is HistoryScreenModel.Event.OpenChapter -> openChapter(context, e.chapter)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun openChapter(context: Context, chapter: Chapter?) {
|
||||
if (chapter != null) {
|
||||
val intent = ReaderActivity.newIntent(context, chapter.mangaId, chapter.id)
|
||||
context.startActivity(intent)
|
||||
} else {
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.no_next_chapter))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
package eu.kanade.tachiyomi.ui.history
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import cafe.adriel.voyager.core.model.coroutineScope
|
||||
import eu.kanade.core.prefs.asState
|
||||
import eu.kanade.core.util.insertSeparators
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
|
@ -14,51 +13,53 @@ import eu.kanade.domain.history.interactor.GetNextChapters
|
|||
import eu.kanade.domain.history.interactor.RemoveHistory
|
||||
import eu.kanade.domain.history.model.HistoryWithRelations
|
||||
import eu.kanade.presentation.history.HistoryUiModel
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.toDateKey
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import logcat.LogPriority
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.Date
|
||||
|
||||
class HistoryPresenter(
|
||||
private val state: HistoryStateImpl = HistoryState() as HistoryStateImpl,
|
||||
class HistoryScreenModel(
|
||||
private val getHistory: GetHistory = Injekt.get(),
|
||||
private val getNextChapters: GetNextChapters = Injekt.get(),
|
||||
private val removeHistory: RemoveHistory = Injekt.get(),
|
||||
preferences: BasePreferences = Injekt.get(),
|
||||
) : BasePresenter<HistoryController>(), HistoryState by state {
|
||||
) : StateScreenModel<HistoryState>(HistoryState()) {
|
||||
|
||||
private val _events: Channel<Event> = Channel(Int.MAX_VALUE)
|
||||
private val _events: Channel<Event> = Channel(Channel.UNLIMITED)
|
||||
val events: Flow<Event> = _events.receiveAsFlow()
|
||||
|
||||
val isDownloadOnly: Boolean by preferences.downloadedOnly().asState()
|
||||
val isIncognitoMode: Boolean by preferences.incognitoMode().asState()
|
||||
val isDownloadOnly: Boolean by preferences.downloadedOnly().asState(coroutineScope)
|
||||
val isIncognitoMode: Boolean by preferences.incognitoMode().asState(coroutineScope)
|
||||
|
||||
@Composable
|
||||
fun getHistory(): Flow<List<HistoryUiModel>> {
|
||||
val query = searchQuery ?: ""
|
||||
return remember(query) {
|
||||
getHistory.subscribe(query)
|
||||
init {
|
||||
coroutineScope.launch {
|
||||
state.map { it.searchQuery }
|
||||
.distinctUntilChanged()
|
||||
.flatMapLatest { query ->
|
||||
getHistory.subscribe(query ?: "")
|
||||
.distinctUntilChanged()
|
||||
.catch { error ->
|
||||
logcat(LogPriority.ERROR, error)
|
||||
_events.send(Event.InternalError)
|
||||
}
|
||||
.map { pagingData ->
|
||||
pagingData.toHistoryUiModels()
|
||||
.map { it.toHistoryUiModels() }
|
||||
.flowOn(Dispatchers.IO)
|
||||
}
|
||||
.collect { newList -> mutableState.update { it.copy(list = newList) } }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,42 +77,42 @@ class HistoryPresenter(
|
|||
}
|
||||
|
||||
fun getNextChapterForManga(mangaId: Long, chapterId: Long) {
|
||||
presenterScope.launchIO {
|
||||
coroutineScope.launchIO {
|
||||
sendNextChapterEvent(getNextChapters.await(mangaId, chapterId, onlyUnread = false))
|
||||
}
|
||||
}
|
||||
|
||||
fun resumeLastChapterRead() {
|
||||
presenterScope.launchIO {
|
||||
sendNextChapterEvent(getNextChapters.await(onlyUnread = false))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun sendNextChapterEvent(chapters: List<Chapter>) {
|
||||
val chapter = chapters.firstOrNull()
|
||||
_events.send(if (chapter != null) Event.OpenChapter(chapter) else Event.NoNextChapterFound)
|
||||
_events.send(Event.OpenChapter(chapter))
|
||||
}
|
||||
|
||||
fun removeFromHistory(history: HistoryWithRelations) {
|
||||
presenterScope.launchIO {
|
||||
coroutineScope.launchIO {
|
||||
removeHistory.await(history)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeAllFromHistory(mangaId: Long) {
|
||||
presenterScope.launchIO {
|
||||
coroutineScope.launchIO {
|
||||
removeHistory.await(mangaId)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeAllHistory() {
|
||||
presenterScope.launchIO {
|
||||
coroutineScope.launchIO {
|
||||
val result = removeHistory.awaitAll()
|
||||
if (!result) return@launchIO
|
||||
withUIContext {
|
||||
view?.activity?.toast(R.string.clear_history_completed)
|
||||
_events.send(Event.HistoryCleared)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateSearchQuery(query: String?) {
|
||||
mutableState.update { it.copy(searchQuery = query) }
|
||||
}
|
||||
|
||||
fun setDialog(dialog: Dialog?) {
|
||||
mutableState.update { it.copy(dialog = dialog) }
|
||||
}
|
||||
|
||||
sealed class Dialog {
|
||||
|
@ -120,23 +121,15 @@ class HistoryPresenter(
|
|||
}
|
||||
|
||||
sealed class Event {
|
||||
data class OpenChapter(val chapter: Chapter?) : Event()
|
||||
object InternalError : Event()
|
||||
object NoNextChapterFound : Event()
|
||||
data class OpenChapter(val chapter: Chapter) : Event()
|
||||
object HistoryCleared : Event()
|
||||
}
|
||||
}
|
||||
|
||||
@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)
|
||||
}
|
||||
@Immutable
|
||||
data class HistoryState(
|
||||
val searchQuery: String? = null,
|
||||
val list: List<HistoryUiModel>? = null,
|
||||
val dialog: HistoryScreenModel.Dialog? = null,
|
||||
)
|
|
@ -9,6 +9,7 @@ import android.os.Parcelable
|
|||
import android.util.AttributeSet
|
||||
import android.view.ViewPropertyAnimator
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.calculateEndPadding
|
||||
import androidx.compose.foundation.layout.calculateStartPadding
|
||||
import androidx.compose.runtime.Composable
|
||||
|
@ -16,6 +17,7 @@ import androidx.compose.runtime.ReadOnlyComposable
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.max
|
||||
|
@ -26,6 +28,7 @@ import com.google.android.material.bottomnavigation.BottomNavigationView
|
|||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale
|
||||
import eu.kanade.tachiyomi.util.system.pxToDp
|
||||
import kotlin.math.max
|
||||
|
||||
class TachiyomiBottomNavigationView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
|
@ -173,5 +176,21 @@ class TachiyomiBottomNavigationView @JvmOverloads constructor(
|
|||
bottom = max(origin.calculateBottomPadding(), bottomNavPadding),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see withBottomNavPadding
|
||||
*/
|
||||
@ReadOnlyComposable
|
||||
@Composable
|
||||
fun withBottomNavInset(origin: WindowInsets): WindowInsets {
|
||||
val density = LocalDensity.current
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
return WindowInsets(
|
||||
left = origin.getLeft(density, layoutDirection),
|
||||
top = origin.getTop(density),
|
||||
right = origin.getRight(density, layoutDirection),
|
||||
bottom = max(origin.getBottom(density), with(density) { bottomNavPadding.roundToPx() }),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue