From bc3bb82651e85a8b0584a645e0fa07ab97ab1767 Mon Sep 17 00:00:00 2001 From: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com> Date: Wed, 9 Nov 2022 21:26:29 +0700 Subject: [PATCH] Voyager on History tab (#8481) --- .../core/prefs/PreferenceMutableState.kt | 2 + .../presentation/components/LoadingScreen.kt | 4 +- .../presentation/history/HistoryScreen.kt | 116 +++++++----------- .../history/components/HistoryToolbar.kt | 36 ------ .../ui/base/controller/ComposeController.kt | 10 +- .../tachiyomi/ui/history/HistoryController.kt | 30 ++--- .../tachiyomi/ui/history/HistoryScreen.kt | 97 +++++++++++++++ ...toryPresenter.kt => HistoryScreenModel.kt} | 105 ++++++++-------- .../widget/TachiyomiBottomNavigationView.kt | 19 +++ 9 files changed, 233 insertions(+), 186 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/presentation/history/components/HistoryToolbar.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreen.kt rename app/src/main/java/eu/kanade/tachiyomi/ui/history/{HistoryPresenter.kt => HistoryScreenModel.kt} (59%) diff --git a/app/src/main/java/eu/kanade/core/prefs/PreferenceMutableState.kt b/app/src/main/java/eu/kanade/core/prefs/PreferenceMutableState.kt index 4efd95539..b8c41b45f 100644 --- a/app/src/main/java/eu/kanade/core/prefs/PreferenceMutableState.kt +++ b/app/src/main/java/eu/kanade/core/prefs/PreferenceMutableState.kt @@ -34,3 +34,5 @@ class PreferenceMutableState( return { preference.set(it) } } } + +fun Preference.asState(scope: CoroutineScope) = PreferenceMutableState(this, scope) diff --git a/app/src/main/java/eu/kanade/presentation/components/LoadingScreen.kt b/app/src/main/java/eu/kanade/presentation/components/LoadingScreen.kt index 0afb5cfa8..357858938 100644 --- a/app/src/main/java/eu/kanade/presentation/components/LoadingScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/components/LoadingScreen.kt @@ -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() diff --git a/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt b/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt index 256f3dca2..5157e6f08 100644 --- a/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt @@ -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) - } - } - } } } diff --git a/app/src/main/java/eu/kanade/presentation/history/components/HistoryToolbar.kt b/app/src/main/java/eu/kanade/presentation/history/components/HistoryToolbar.kt deleted file mode 100644 index 19aef8214..000000000 --- a/app/src/main/java/eu/kanade/presentation/history/components/HistoryToolbar.kt +++ /dev/null @@ -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, - ) -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ComposeController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ComposeController.kt index 16b12725c..5c0883eee 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ComposeController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ComposeController.kt @@ -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,7 +23,9 @@ abstract class FullComposeController

>(bundle: Bundle? = null) : binding.root.apply { setComposeContent { - ComposeContent() + CompositionLocalProvider(LocalRouter provides router) { + ComposeContent() + } } } } @@ -52,7 +56,9 @@ abstract class BasicFullComposeController(bundle: Bundle? = null) : binding.root.apply { setComposeContent { - ComposeContent() + CompositionLocalProvider(LocalRouter provides router) { + ComposeContent() + } } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryController.kt index a7dbf63bb..c0410450e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryController.kt @@ -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(), 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().await(onlyUnread = false).firstOrNull() + HistoryScreen.openChapter(context, chapter) + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreen.kt new file mode 100644 index 000000000..0e3b943dd --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreen.kt @@ -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)) + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt similarity index 59% rename from app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryPresenter.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt index e252b0026..4991c89c9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt @@ -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(), HistoryState by state { +) : StateScreenModel(HistoryState()) { - private val _events: Channel = Channel(Int.MAX_VALUE) + private val _events: Channel = Channel(Channel.UNLIMITED) val events: Flow = _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> { - val query = searchQuery ?: "" - return remember(query) { - getHistory.subscribe(query) + init { + coroutineScope.launch { + state.map { it.searchQuery } .distinctUntilChanged() - .catch { error -> - logcat(LogPriority.ERROR, error) - _events.send(Event.InternalError) - } - .map { pagingData -> - pagingData.toHistoryUiModels() + .flatMapLatest { query -> + getHistory.subscribe(query ?: "") + .distinctUntilChanged() + .catch { error -> + logcat(LogPriority.ERROR, error) + _events.send(Event.InternalError) + } + .map { it.toHistoryUiModels() } + .flowOn(Dispatchers.IO) } + .collect { newList -> mutableState.update { it.copy(list = newList) } } } } @@ -76,67 +77,59 @@ 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) { 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 { object DeleteAll : Dialog() data class Delete(val history: HistoryWithRelations) : Dialog() } 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? = null, + val dialog: HistoryScreenModel.Dialog? = null, +) diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiBottomNavigationView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiBottomNavigationView.kt index 002a5f029..0bea9f8a0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiBottomNavigationView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiBottomNavigationView.kt @@ -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() }), + ) + } } }