TabbedScreen.kt 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. package eu.kanade.presentation.components
  2. import androidx.annotation.StringRes
  3. import androidx.compose.foundation.layout.Column
  4. import androidx.compose.foundation.layout.PaddingValues
  5. import androidx.compose.foundation.layout.calculateEndPadding
  6. import androidx.compose.foundation.layout.calculateStartPadding
  7. import androidx.compose.foundation.layout.fillMaxSize
  8. import androidx.compose.foundation.layout.padding
  9. import androidx.compose.foundation.pager.rememberPagerState
  10. import androidx.compose.material3.MaterialTheme
  11. import androidx.compose.material3.PrimaryTabRow
  12. import androidx.compose.material3.SnackbarHost
  13. import androidx.compose.material3.SnackbarHostState
  14. import androidx.compose.material3.Tab
  15. import androidx.compose.runtime.Composable
  16. import androidx.compose.runtime.LaunchedEffect
  17. import androidx.compose.runtime.remember
  18. import androidx.compose.runtime.rememberCoroutineScope
  19. import androidx.compose.ui.Alignment
  20. import androidx.compose.ui.Modifier
  21. import androidx.compose.ui.platform.LocalLayoutDirection
  22. import androidx.compose.ui.res.stringResource
  23. import kotlinx.coroutines.launch
  24. import tachiyomi.presentation.core.components.HorizontalPager
  25. import tachiyomi.presentation.core.components.material.Scaffold
  26. import tachiyomi.presentation.core.components.material.TabText
  27. @Composable
  28. fun TabbedScreen(
  29. @StringRes titleRes: Int,
  30. tabs: List<TabContent>,
  31. startIndex: Int? = null,
  32. searchQuery: String? = null,
  33. onChangeSearchQuery: (String?) -> Unit = {},
  34. ) {
  35. val scope = rememberCoroutineScope()
  36. val state = rememberPagerState { tabs.size }
  37. val snackbarHostState = remember { SnackbarHostState() }
  38. LaunchedEffect(startIndex) {
  39. if (startIndex != null) {
  40. state.scrollToPage(startIndex)
  41. }
  42. }
  43. Scaffold(
  44. topBar = {
  45. val tab = tabs[state.currentPage]
  46. val searchEnabled = tab.searchEnabled
  47. SearchToolbar(
  48. titleContent = { AppBarTitle(stringResource(titleRes)) },
  49. searchEnabled = searchEnabled,
  50. searchQuery = if (searchEnabled) searchQuery else null,
  51. onChangeSearchQuery = onChangeSearchQuery,
  52. actions = { AppBarActions(tab.actions) },
  53. )
  54. },
  55. snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
  56. ) { contentPadding ->
  57. Column(
  58. modifier = Modifier.padding(
  59. top = contentPadding.calculateTopPadding(),
  60. start = contentPadding.calculateStartPadding(LocalLayoutDirection.current),
  61. end = contentPadding.calculateEndPadding(LocalLayoutDirection.current),
  62. ),
  63. ) {
  64. PrimaryTabRow(
  65. selectedTabIndex = state.currentPage,
  66. ) {
  67. tabs.forEachIndexed { index, tab ->
  68. Tab(
  69. selected = state.currentPage == index,
  70. onClick = { scope.launch { state.animateScrollToPage(index) } },
  71. text = { TabText(text = stringResource(tab.titleRes), badgeCount = tab.badgeNumber) },
  72. unselectedContentColor = MaterialTheme.colorScheme.onSurface,
  73. )
  74. }
  75. }
  76. HorizontalPager(
  77. modifier = Modifier.fillMaxSize(),
  78. state = state,
  79. verticalAlignment = Alignment.Top,
  80. ) { page ->
  81. tabs[page].content(
  82. PaddingValues(bottom = contentPadding.calculateBottomPadding()),
  83. snackbarHostState,
  84. )
  85. }
  86. }
  87. }
  88. }
  89. data class TabContent(
  90. @StringRes val titleRes: Int,
  91. val badgeNumber: Int? = null,
  92. val searchEnabled: Boolean = false,
  93. val actions: List<AppBar.Action> = emptyList(),
  94. val content: @Composable (contentPadding: PaddingValues, snackbarHostState: SnackbarHostState) -> Unit,
  95. )