Voyager on History tab (#8481)

This commit is contained in:
Ivan Iskandar 2022-11-09 21:26:29 +07:00 committed by GitHub
parent ba00d9e5d2
commit bc3bb82651
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 233 additions and 186 deletions

View File

@ -34,3 +34,5 @@ class PreferenceMutableState<T>(
return { preference.set(it) } return { preference.set(it) }
} }
} }
fun <T> Preference<T>.asState(scope: CoroutineScope) = PreferenceMutableState(this, scope)

View File

@ -8,9 +8,9 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@Composable @Composable
fun LoadingScreen() { fun LoadingScreen(modifier: Modifier = Modifier) {
Box( Box(
modifier = Modifier.fillMaxSize(), modifier = modifier.fillMaxSize(),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
CircularProgressIndicator() CircularProgressIndicator()

View File

@ -1,110 +1,80 @@
package eu.kanade.presentation.history package eu.kanade.presentation.history
import androidx.compose.foundation.layout.padding 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.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.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource
import eu.kanade.domain.history.model.HistoryWithRelations import eu.kanade.domain.history.model.HistoryWithRelations
import eu.kanade.presentation.components.AppBarTitle
import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.Scaffold 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.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.R
import eu.kanade.tachiyomi.ui.history.HistoryPresenter import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
import eu.kanade.tachiyomi.ui.history.HistoryPresenter.Dialog import eu.kanade.tachiyomi.ui.history.HistoryState
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.widget.TachiyomiBottomNavigationView import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
import kotlinx.coroutines.flow.collectLatest
import java.util.Date import java.util.Date
@Composable @Composable
fun HistoryScreen( fun HistoryScreen(
presenter: HistoryPresenter, state: HistoryState,
onClickCover: (HistoryWithRelations) -> Unit, snackbarHostState: SnackbarHostState,
onClickResume: (HistoryWithRelations) -> Unit, 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( Scaffold(
topBar = { scrollBehavior -> topBar = { scrollBehavior ->
HistoryToolbar( SearchToolbar(
state = presenter, titleContent = { AppBarTitle(stringResource(R.string.history)) },
incognitoMode = presenter.isIncognitoMode, searchQuery = state.searchQuery,
downloadedOnlyMode = presenter.isDownloadOnly, 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, scrollBehavior = scrollBehavior,
) )
}, },
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
contentWindowInsets = TachiyomiBottomNavigationView.withBottomNavInset(ScaffoldDefaults.contentWindowInsets),
) { contentPadding -> ) { contentPadding ->
val items by presenter.getHistory().collectAsState(initial = null) state.list.let {
val contentPaddingWithNavBar = TachiyomiBottomNavigationView.withBottomNavPadding(contentPadding)
items.let {
if (it == null) { if (it == null) {
LoadingScreen() LoadingScreen(modifier = Modifier.padding(contentPadding))
} else if (it.isEmpty()) { } else if (it.isEmpty()) {
EmptyScreen( EmptyScreen(
textResource = R.string.information_no_recent_manga, textResource = R.string.information_no_recent_manga,
modifier = Modifier.padding(contentPaddingWithNavBar), modifier = Modifier.padding(contentPadding),
) )
} else { } else {
HistoryContent( HistoryContent(
history = it, history = it,
contentPadding = contentPaddingWithNavBar, contentPadding = contentPadding,
onClickCover = onClickCover, onClickCover = { history -> onClickCover(history.mangaId) },
onClickResume = onClickResume, onClickResume = { history -> onClickResume(history.mangaId, history.chapterId) },
onClickDelete = { item -> presenter.dialog = Dialog.Delete(item) }, 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)
}
}
}
} }
} }

View File

@ -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,
)
}

View File

@ -5,6 +5,8 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import androidx.activity.OnBackPressedDispatcherOwner import androidx.activity.OnBackPressedDispatcherOwner
import androidx.compose.runtime.Composable 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.databinding.ComposeControllerBinding
import eu.kanade.tachiyomi.util.view.setComposeContent import eu.kanade.tachiyomi.util.view.setComposeContent
import nucleus.presenter.Presenter import nucleus.presenter.Presenter
@ -21,10 +23,12 @@ abstract class FullComposeController<P : Presenter<*>>(bundle: Bundle? = null) :
binding.root.apply { binding.root.apply {
setComposeContent { setComposeContent {
CompositionLocalProvider(LocalRouter provides router) {
ComposeContent() ComposeContent()
} }
} }
} }
}
override fun handleBack(): Boolean { override fun handleBack(): Boolean {
val dispatcher = (activity as? OnBackPressedDispatcherOwner)?.onBackPressedDispatcher ?: return false val dispatcher = (activity as? OnBackPressedDispatcherOwner)?.onBackPressedDispatcher ?: return false
@ -52,10 +56,12 @@ abstract class BasicFullComposeController(bundle: Bundle? = null) :
binding.root.apply { binding.root.apply {
setComposeContent { setComposeContent {
CompositionLocalProvider(LocalRouter provides router) {
ComposeContent() ComposeContent()
} }
} }
} }
}
// Let Compose view handle this // Let Compose view handle this
override fun handleBack(): Boolean { override fun handleBack(): Boolean {

View File

@ -1,30 +1,26 @@
package eu.kanade.tachiyomi.ui.history package eu.kanade.tachiyomi.ui.history
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import eu.kanade.presentation.history.HistoryScreen import cafe.adriel.voyager.navigator.Navigator
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController 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.RootController
import eu.kanade.tachiyomi.ui.base.controller.pushController import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.ui.manga.MangaController import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class HistoryController : FullComposeController<HistoryPresenter>(), RootController { class HistoryController : BasicFullComposeController(), RootController {
override fun createPresenter() = HistoryPresenter()
@Composable @Composable
override fun ComposeContent() { override fun ComposeContent() {
HistoryScreen( Navigator(screen = HistoryScreen)
presenter = presenter,
onClickCover = { history ->
router.pushController(MangaController(history.mangaId))
},
onClickResume = { history ->
presenter.getNextChapterForManga(history.mangaId, history.chapterId)
},
)
} }
fun resumeLastChapterRead() { fun resumeLastChapterRead() {
presenter.resumeLastChapterRead() val context = activity ?: return
viewScope.launchIO {
val chapter = Injekt.get<GetNextChapters>().await(onlyUnread = false).firstOrNull()
HistoryScreen.openChapter(context, chapter)
}
} }
} }

View File

@ -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))
}
}
}

View File

@ -1,11 +1,10 @@
package eu.kanade.tachiyomi.ui.history package eu.kanade.tachiyomi.ui.history
import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import cafe.adriel.voyager.core.model.StateScreenModel
import androidx.compose.runtime.remember import cafe.adriel.voyager.core.model.coroutineScope
import androidx.compose.runtime.setValue import eu.kanade.core.prefs.asState
import eu.kanade.core.util.insertSeparators import eu.kanade.core.util.insertSeparators
import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.chapter.model.Chapter 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.interactor.RemoveHistory
import eu.kanade.domain.history.model.HistoryWithRelations import eu.kanade.domain.history.model.HistoryWithRelations
import eu.kanade.presentation.history.HistoryUiModel 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.launchIO
import eu.kanade.tachiyomi.util.lang.toDateKey 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.logcat
import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import logcat.LogPriority import logcat.LogPriority
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Date import java.util.Date
class HistoryPresenter( class HistoryScreenModel(
private val state: HistoryStateImpl = HistoryState() as HistoryStateImpl,
private val getHistory: GetHistory = Injekt.get(), private val getHistory: GetHistory = Injekt.get(),
private val getNextChapters: GetNextChapters = Injekt.get(), private val getNextChapters: GetNextChapters = Injekt.get(),
private val removeHistory: RemoveHistory = Injekt.get(), private val removeHistory: RemoveHistory = Injekt.get(),
preferences: BasePreferences = 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 events: Flow<Event> = _events.receiveAsFlow()
val isDownloadOnly: Boolean by preferences.downloadedOnly().asState() val isDownloadOnly: Boolean by preferences.downloadedOnly().asState(coroutineScope)
val isIncognitoMode: Boolean by preferences.incognitoMode().asState() val isIncognitoMode: Boolean by preferences.incognitoMode().asState(coroutineScope)
@Composable init {
fun getHistory(): Flow<List<HistoryUiModel>> { coroutineScope.launch {
val query = searchQuery ?: "" state.map { it.searchQuery }
return remember(query) { .distinctUntilChanged()
getHistory.subscribe(query) .flatMapLatest { query ->
getHistory.subscribe(query ?: "")
.distinctUntilChanged() .distinctUntilChanged()
.catch { error -> .catch { error ->
logcat(LogPriority.ERROR, error) logcat(LogPriority.ERROR, error)
_events.send(Event.InternalError) _events.send(Event.InternalError)
} }
.map { pagingData -> .map { it.toHistoryUiModels() }
pagingData.toHistoryUiModels() .flowOn(Dispatchers.IO)
} }
.collect { newList -> mutableState.update { it.copy(list = newList) } }
} }
} }
@ -76,42 +77,42 @@ class HistoryPresenter(
} }
fun getNextChapterForManga(mangaId: Long, chapterId: Long) { fun getNextChapterForManga(mangaId: Long, chapterId: Long) {
presenterScope.launchIO { coroutineScope.launchIO {
sendNextChapterEvent(getNextChapters.await(mangaId, chapterId, onlyUnread = false)) sendNextChapterEvent(getNextChapters.await(mangaId, chapterId, onlyUnread = false))
} }
} }
fun resumeLastChapterRead() {
presenterScope.launchIO {
sendNextChapterEvent(getNextChapters.await(onlyUnread = false))
}
}
private suspend fun sendNextChapterEvent(chapters: List<Chapter>) { private suspend fun sendNextChapterEvent(chapters: List<Chapter>) {
val chapter = chapters.firstOrNull() val chapter = chapters.firstOrNull()
_events.send(if (chapter != null) Event.OpenChapter(chapter) else Event.NoNextChapterFound) _events.send(Event.OpenChapter(chapter))
} }
fun removeFromHistory(history: HistoryWithRelations) { fun removeFromHistory(history: HistoryWithRelations) {
presenterScope.launchIO { coroutineScope.launchIO {
removeHistory.await(history) removeHistory.await(history)
} }
} }
fun removeAllFromHistory(mangaId: Long) { fun removeAllFromHistory(mangaId: Long) {
presenterScope.launchIO { coroutineScope.launchIO {
removeHistory.await(mangaId) removeHistory.await(mangaId)
} }
} }
fun removeAllHistory() { fun removeAllHistory() {
presenterScope.launchIO { coroutineScope.launchIO {
val result = removeHistory.awaitAll() val result = removeHistory.awaitAll()
if (!result) return@launchIO if (!result) return@launchIO
withUIContext { _events.send(Event.HistoryCleared)
view?.activity?.toast(R.string.clear_history_completed)
} }
} }
fun updateSearchQuery(query: String?) {
mutableState.update { it.copy(searchQuery = query) }
}
fun setDialog(dialog: Dialog?) {
mutableState.update { it.copy(dialog = dialog) }
} }
sealed class Dialog { sealed class Dialog {
@ -120,23 +121,15 @@ class HistoryPresenter(
} }
sealed class Event { sealed class Event {
data class OpenChapter(val chapter: Chapter?) : Event()
object InternalError : Event() object InternalError : Event()
object NoNextChapterFound : Event() object HistoryCleared : Event()
data class OpenChapter(val chapter: Chapter) : Event()
} }
} }
@Stable @Immutable
interface HistoryState { data class HistoryState(
var searchQuery: String? val searchQuery: String? = null,
var dialog: HistoryPresenter.Dialog? val list: List<HistoryUiModel>? = null,
} val dialog: HistoryScreenModel.Dialog? = null,
)
fun HistoryState(): HistoryState {
return HistoryStateImpl()
}
class HistoryStateImpl : HistoryState {
override var searchQuery: String? by mutableStateOf(null)
override var dialog: HistoryPresenter.Dialog? by mutableStateOf(null)
}

View File

@ -9,6 +9,7 @@ import android.os.Parcelable
import android.util.AttributeSet import android.util.AttributeSet
import android.view.ViewPropertyAnimator import android.view.ViewPropertyAnimator
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -16,6 +17,7 @@ import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.max 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.R
import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale
import eu.kanade.tachiyomi.util.system.pxToDp import eu.kanade.tachiyomi.util.system.pxToDp
import kotlin.math.max
class TachiyomiBottomNavigationView @JvmOverloads constructor( class TachiyomiBottomNavigationView @JvmOverloads constructor(
context: Context, context: Context,
@ -173,5 +176,21 @@ class TachiyomiBottomNavigationView @JvmOverloads constructor(
bottom = max(origin.calculateBottomPadding(), bottomNavPadding), 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() }),
)
}
} }
} }