The problem is that the pager is inside LazyColumn, and inside the pager is the list. When changing pages, if the dimensions of one do not match the other, then a white screen is visible for a long time.
fun CustomTabRow(
tabs: List<Pair<String, @Composable () -> Unit>>,
modifier: Modifier = Modifier,
spaceBetweenTabAndPager: Dp = 0.dp,
isBeyondBoundsPageCount: Boolean = true,
horizontalSpaceBetweenContent: Dp = 0.dp,
selectedTabIndex: Int = 0,
maxLinesInTitle: Int = 1
) {
val scope = rememberCoroutineScope()
val pagerState = rememberPagerState(
initialPage = selectedTabIndex,
initialPageOffsetFraction = 0f,
) {
tabs.size
}
val dimensions = LocalDimensions.current
val indicatorHeight = 3.dp
pagerState.currentPageOffsetFraction
Column(modifier = modifier) {
TabRow(
modifier = Modifier.padding(horizontal = horizontalSpaceBetweenContent),
selectedTabIndex = pagerState.currentPage,
indicator = { tabPositions ->
TabRowDefaults.SecondaryIndicator(
color = PrimaryMain,
modifier = Modifier
.tabIndicatorOffset(tabPositions[pagerState.currentPage])
.height(indicatorHeight)
.clip(shape = RoundedCornerShape(size = dimensions.pagerIndicatorCornerRadius))
)
}
) {
tabs.forEachIndexed { index, tabItem ->
Tab(
modifier = Modifier.background(color = BackgroundBase),
selected = pagerState.currentPage == index,
onClick = {
scope.launch {
pagerState.animateScrollToPage(index)
}
},
selectedContentColor = PrimaryMain,
unselectedContentColor = SecondaryText,
text = {
Text(
text = tabItem.first,
style = BoldMontserrat16,
maxLines = maxLinesInTitle
)
}
)
}
}
HorizontalPager(
state = pagerState,
beyondBoundsPageCount = if (isBeyondBoundsPageCount) tabs.size else 0,
verticalAlignment = Alignment.Top
) { index ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = horizontalSpaceBetweenContent)
.padding(top = spaceBetweenTabAndPager)
) {
tabs[index].second()
}
}
}
}
class TimeTrackingScreen : TimeTracking {
@OptIn(ExperimentalFoundationApi::class)
@Composable
override fun Content() {
val dimensions = LocalDimensions.current
val navigator = LocalNavigator.currentOrThrow
val snackbarHostState = LocalSnackbarHostState.current
val horizontalPadding = Modifier.padding(horizontal = dimensions.horizontalMedium)
val screenModel = getScreenModel<TimeTrackingScreenModel>()
val screenState by screenModel.state.collectAsState()
val state by screenModel.timeTrackingState.collectAsState()
val loginScreen = rememberScreen(SharedScreen.LoginScreen)
when (screenState) {
is TimeTrackingScreenModel.State.EditLeavingDetailsDialog -> {
EditLeavingHoursDialog(
leavingDetails =
(screenState as TimeTrackingScreenModel.State.EditLeavingDetailsDialog)
.selectedLeavingDetails,
rateMultiplier = state.rate,
onSaveHoursClick = rememberSafe { leavingDetails ->
screenModel.changeLeavingDetail(leavingDetails = leavingDetails)
},
onDismiss = rememberSafe { ->
screenModel.clearErrors()
}
)
}
is TimeTrackingScreenModel.State.EditProjectDetailsDialog -> {
EditProjectHoursDialog(
projectDetails =
(screenState as TimeTrackingScreenModel.State.EditProjectDetailsDialog)
.selectedProjectDetails,
onSaveHoursClick = rememberSafe { projectDetails ->
screenModel.changeProjectDetail(projectDetails = projectDetails)
},
onDismiss = rememberSafe { ->
screenModel.clearErrors()
}
)
}
else -> { /* Do nothing */ }
}
LaunchedEffect(screenState) {
when (screenState) {
is TimeTrackingScreenModel.State.Error -> {
(screenState as TimeTrackingScreenModel.State.Error).message?.also { message ->
snackbarHostState.showMessage(message = message)
screenModel.clearErrors()
}
}
is TimeTrackingScreenModel.State.CriticalError -> {
(screenState as TimeTrackingScreenModel.State.CriticalError).message?.also { message ->
snackbarHostState.showMessage(message = message)
screenModel.clearErrors()
}
navigator.replaceAll(loginScreen)
}
else -> { /* Do nothing */
}
}
}
Localization(locale = DateUtils.DEFAULT_LOCALE) {
val localization = Vocabulary.localization
if (screenState is TimeTrackingScreenModel.State.Delete) {
DeletionDialog(
title = localization.removalOfAbsenceStr(),
description = localization.descriptionOfRemovalStr(),
positiveButtonText = localization.deleteStr(),
negativeButtonText = localization.canselStr(),
state = screenState,
dimensions = dimensions,
onNegativeButtonClick = {
screenModel.clearErrors()
},
onPositiveButtonClick = rememberSafe { ->
screenModel.removeLeavingDetail(
(screenState as TimeTrackingScreenModel.State.Delete).leavingDetails
)
}
)
}
LazyColumn(
modifier = Modifier
.fillMaxSize()
.background(color = BackgroundBase)
) {
item(key = TimeTrackingScreenItems.HEADER) {
Row(
modifier = horizontalPadding.padding(top = dimensions.verticalMedium),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = localization.timeTrackingStr(),
style = BoldMontserrat22.copy(color = BlackDefaultText)
)
BetaDie(modifier = Modifier.padding(dimensions.horizontalSmall))
}
SpacerHeight(height = dimensions.verticalMedium)
}
item(key = TimeTrackingScreenItems.ENTER_HOURS) {
Text(
modifier = horizontalPadding,
text = localization.enterHoursStr(),
style = SemiBoldMontserrat22
)
SpacerHeight(height = dimensions.verticalSmall)
}
item(key = TimeTrackingScreenItems.ENTERING_AND_MISSING_HOURS_CONTENT) {
CustomTabRowWithEnteringAndMissingHoursContent(
projectList = state.projectDetails,
currentMonth = state.currentMonth,
typesAbsence = listOf(
LeaveType.SickLeave,
LeaveType.Idle,
LeaveType.Training,
LeaveType.Vacation
),
state = screenState,
localization = localization,
onSaveEnteringHoursSpentOnWork = rememberSafe { projectDetails ->
screenModel.saveProjectDetails(
projectDetails = projectDetails
)
},
onSaveMissingHours = rememberSafe { leavingDetails ->
screenModel.saveLeavingDetails(
leavingDetails = leavingDetails
)
},
onMonthChange = rememberSafe { selectedMonth ->
screenModel.changeHoursContext(selectedMonth, state.currentYear)
},
rateMultiplier = state.rate
)
SpacerHeight(height = dimensions.verticalXXlarge)
}
item(key = TimeTrackingScreenItems.MONTH_YEAR_FOR_HOURS_SPENT) {
MonthYearForHoursSpent(
modifier = horizontalPadding.animateItemPlacement(),
text = localization.hoursForStr(),
onClick = rememberSafe { monthId, year ->
screenModel.changeHoursContext(monthId = monthId, year = year)
},
currentYearId = state.currentYear,
currentMonthId = state.currentMonth
)
SpacerHeight(height = dimensions.verticalLarge)
}
item(key = TimeTrackingScreenItems.CIRCULAR_DIAGRAM) {
CustomCircularDiagram(
modifier = horizontalPadding.animateItemPlacement(),
projectList = state.projectsForDiagram,
onBackClick = rememberSafe { -> screenModel.backPressedDiagram() },
onNextClick = rememberSafe { -> screenModel.nextPressedDiagram() },
isBackEnabled = state.isBackDiagramEnabled,
isNextEnabled = state.isNextDiagramEnabled,
totalTime = state.normHours
)
SpacerHeight(height = dimensions.verticalLarge)
}
item(key = TimeTrackingScreenItems.PROJECTS_AND_LEAVING_LIST) {
CustomTabRow(
spaceBetweenTabAndPager = dimensions.verticalMedium,
horizontalSpaceBetweenContent = dimensions.horizontalMedium,
isBeyondBoundsPageCount = false,
tabs = listOf(
localization.hoursForStr() to {
ProjectsInfoList(
onIconClick = rememberSafe { selectedProjectDetails ->
screenModel.showEditProjectDetailsDialog(selectedProjectDetails)
},
projectsInfoList = state.projectDetails
)
SpacerHeight(height = dimensions.verticalLarge)
},
localization.absenceStr() to {
LeavingDetailsList(
leavingDetailsList = state.leavingDetails,
onEditIconClick = rememberSafe { selectedLeavingDetails ->
screenModel.showEditLeavingDetailsDialog(selectedLeavingDetails)
},
onDeleteIconClick = rememberSafe { selectedLeavingDetails ->
screenModel.showDeletionDialog(selectedLeavingDetails)
}
)
SpacerHeight(height = dimensions.verticalLarge)
}
)
)
}
}
}
}
}
@Composable
fun DeletionDialog(
state: TimeTrackingScreenModel.State,
title: String,
dimensions: Dimensions,
description: String,
positiveButtonText: String,
negativeButtonText: String,
modifier: Modifier = Modifier,
onNegativeButtonClick: () -> Unit,
onPositiveButtonClick: () -> Unit
) {
DialogWrapper(
modifier = modifier.padding(horizontal = dimensions.horizontalMedium),
showDialog = state is TimeTrackingScreenModel.State.Delete,
onDismissRequest = { /* Do nothing */ }
) {
WarningDialog(
modifier = Modifier.padding(dimensions.horizontalMedium),
title = title,
description = description,
isLoading = state is TimeTrackingScreenModel.State.Loading,
confirmButtonText = positiveButtonText,
cancelButtonText = negativeButtonText,
onConfirmButtonClick = onPositiveButtonClick,
onCanselButtonRequest = onNegativeButtonClick
)
}
}
@Composable
fun CustomTabRowWithEnteringAndMissingHoursContent(
onSaveEnteringHoursSpentOnWork: (projectDetails: UiProjectDetails) -> Unit,
onSaveMissingHours: (leavingDetails: UiLeavingDetails) -> Unit,
currentMonth: Int,
state: TimeTrackingScreenModel.State,
localization: Localization,
onMonthChange: (selectedMonth: Int) -> Unit,
projectList: List<UiProjectDetails>,
typesAbsence: List<LeaveType>,
rateMultiplier: Double
) {
val dimensions = LocalDimensions.current
CustomTabRow(
spaceBetweenTabAndPager = dimensions.verticalMedium,
horizontalSpaceBetweenContent = dimensions.horizontalMedium,
tabs = listOf(
localization.hoursForStr() to {
EnteringHoursSpentOnWork(
projectList = projectList,
onSaveProjectDetails = onSaveEnteringHoursSpentOnWork,
onMonthChange = onMonthChange,
isLoading = state is TimeTrackingScreenModel.State.Loading,
currentMonth = currentMonth
)
},
localization.absenceStr() to {
MissingHours(
modifier = Modifier.fillMaxSize(),
typesAbsence = typesAbsence,
isLoading = state is TimeTrackingScreenModel.State.Loading,
onSaveLeavingDetails = onSaveMissingHours,
rateMultiplier = rateMultiplier
)
}
),
isBeyondBoundsPageCount = false
)
}
https://www.veed.io/view/85492348-dd0a-4f5b-955b-4e949b31b3bb?panel=share