d97eab0328
This moves the banners to the root composable and so eliminates the need to track the app states in every screen.
101 lines
3.7 KiB
Kotlin
101 lines
3.7 KiB
Kotlin
package eu.kanade.presentation.components
|
|
|
|
import androidx.annotation.StringRes
|
|
import androidx.compose.foundation.layout.Column
|
|
import androidx.compose.foundation.layout.PaddingValues
|
|
import androidx.compose.foundation.layout.calculateEndPadding
|
|
import androidx.compose.foundation.layout.calculateStartPadding
|
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
import androidx.compose.foundation.layout.padding
|
|
import androidx.compose.material3.MaterialTheme
|
|
import androidx.compose.material3.SnackbarHost
|
|
import androidx.compose.material3.SnackbarHostState
|
|
import androidx.compose.material3.Tab
|
|
import androidx.compose.material3.TabRow
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.compose.runtime.LaunchedEffect
|
|
import androidx.compose.runtime.remember
|
|
import androidx.compose.runtime.rememberCoroutineScope
|
|
import androidx.compose.ui.Alignment
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
|
import androidx.compose.ui.res.stringResource
|
|
import kotlinx.coroutines.launch
|
|
|
|
@Composable
|
|
fun TabbedScreen(
|
|
@StringRes titleRes: Int,
|
|
tabs: List<TabContent>,
|
|
startIndex: Int? = null,
|
|
searchQuery: String? = null,
|
|
onChangeSearchQuery: (String?) -> Unit = {},
|
|
) {
|
|
val scope = rememberCoroutineScope()
|
|
val state = rememberPagerState()
|
|
val snackbarHostState = remember { SnackbarHostState() }
|
|
|
|
LaunchedEffect(startIndex) {
|
|
if (startIndex != null) {
|
|
state.scrollToPage(startIndex)
|
|
}
|
|
}
|
|
|
|
Scaffold(
|
|
topBar = {
|
|
val tab = tabs[state.currentPage]
|
|
val searchEnabled = tab.searchEnabled
|
|
|
|
SearchToolbar(
|
|
titleContent = { AppBarTitle(stringResource(titleRes)) },
|
|
searchEnabled = searchEnabled,
|
|
searchQuery = if (searchEnabled) searchQuery else null,
|
|
onChangeSearchQuery = onChangeSearchQuery,
|
|
actions = { AppBarActions(tab.actions) },
|
|
)
|
|
},
|
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
|
) { contentPadding ->
|
|
Column(
|
|
modifier = Modifier.padding(
|
|
top = contentPadding.calculateTopPadding(),
|
|
start = contentPadding.calculateStartPadding(LocalLayoutDirection.current),
|
|
end = contentPadding.calculateEndPadding(LocalLayoutDirection.current),
|
|
),
|
|
) {
|
|
TabRow(
|
|
selectedTabIndex = state.currentPage,
|
|
indicator = { TabIndicator(it[state.currentPage]) },
|
|
) {
|
|
tabs.forEachIndexed { index, tab ->
|
|
Tab(
|
|
selected = state.currentPage == index,
|
|
onClick = { scope.launch { state.animateScrollToPage(index) } },
|
|
text = { TabText(text = stringResource(tab.titleRes), badgeCount = tab.badgeNumber) },
|
|
unselectedContentColor = MaterialTheme.colorScheme.onSurface,
|
|
)
|
|
}
|
|
}
|
|
|
|
HorizontalPager(
|
|
count = tabs.size,
|
|
modifier = Modifier.fillMaxSize(),
|
|
state = state,
|
|
verticalAlignment = Alignment.Top,
|
|
) { page ->
|
|
tabs[page].content(
|
|
PaddingValues(bottom = contentPadding.calculateBottomPadding()),
|
|
snackbarHostState,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
data class TabContent(
|
|
@StringRes val titleRes: Int,
|
|
val badgeNumber: Int? = null,
|
|
val searchEnabled: Boolean = false,
|
|
val actions: List<AppBar.Action> = emptyList(),
|
|
val content: @Composable (contentPadding: PaddingValues, snackbarHostState: SnackbarHostState) -> Unit,
|
|
)
|