I have a LazyColumn
with an item {}
, a stickyHeader {}
and, depending on the Tab selected in the content of the stickyHeader, a list of either product items or picklist items.
Now, I have noticed that if the user taps on any of the tabs, scrolls a bit in the itemsIndexed {}
list and then switches the active tab and thus the active list of items, the scroll offset is preserved between the lists. For example, scrolling to product number 4 and then tapping on the picklists tab, will display the list in a scrolled state starting from picklist number 4.
I want to avoid this, but I am not sure how to go about it. I have tried using scrollTo()
and animateScrollToItem()
but don't know how to use these (if possible), to scroll to the first item in the currently active itemsIndexed list upon tab selection?
Here is my composable:
@OptIn(
ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class
)
@Composable
fun BatchDetailsScreen(
batchDetailsViewModel: BatchDetailsViewModel = koinInject(),
batchUUID: String,
batchRefId: String,
) {
val navViewModel = LocalNavViewModel.current
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
var shouldShowScanContainerBottomSheet by remember {
mutableStateOf(false)
}
var shouldShowPickProductBottomSheet by remember {
mutableStateOf(false)
}
var shouldShowPickProductsInPicklistBottomSheet by remember {
mutableStateOf(false)
}
var picklistToPickFrom: BatchPicklistDetails? by remember {
mutableStateOf(null)
}
val scanProductSheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
)
var productToPick: P2CProduct? by remember {
mutableStateOf(null)
}
val batchDetailsUiState by batchDetailsViewModel.batchDetailsUiStateFlow.collectAsState(
BatchDetailsUiState(isLoading = true)
)
val picklistsCompleted by remember {
derivedStateOf {
batchDetailsUiState.batchDetailsResponse?.batchDetails?.picklistsInfo?.picklists?.count {
it.getPicklistStatus() == PicklistStatus.COMPLETED
}
?: 0
}
}
val totalProductsInBatch by remember {
derivedStateOf {
batchDetailsUiState.productsAndPicksResponse?.products?.size ?: 0
}
}
val batchWorkflow by remember {
derivedStateOf {
batchDetailsUiState.batchDetailsResponse?.batchDetails?.getWorkflowType()
?: BatchWorkflow.NOT_AVAILABLE
}
}
LaunchedEffect(key1 = true) {
navViewModel.setAppBarState(
AppBarState(
"Batch - $batchRefId",
)
)
batchDetailsViewModel.fetchBatchDetails(batchUUID) {
Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
}
}
val dominantColor = LocalDominantColor.current
var selectedTabIndex by remember { mutableIntStateOf(0) }
if (batchDetailsUiState.isLoading) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator(
color = dominantColor
)
}
} else if (batchDetailsUiState.isError) {
Box(
Modifier
.fillMaxSize()
.padding(48.dp), contentAlignment = Alignment.Center
) {
Text(stringResource(R.string.failed_to_load_content_error_msg))
}
} else {
val scrollState = rememberLazyListState()
LazyColumn(
state = scrollState,
modifier = Modifier
.fillMaxSize()
.background(Color(0xfff5f5f5))
.padding(16.dp),
) {
item {
Spacer(Modifier.height(8.dp))
BatchInfoHeader(batchDetailsUiState, totalProductsInBatch, picklistsCompleted)
Spacer(Modifier.height(8.dp))
if (BuildConfig.DEBUG) {
//todo:sp pass picklist and product from scan
BottomSheetDebugOptions(
{ shouldShowScanContainerBottomSheet = it },
onPicklistToPickFromSelected = { picklistToPickFrom = it },
batchDetailsUiState,
onProductToPickSelected = {
productToPick = it
}
) { shouldShowPickProductBottomSheet = it }
}
Spacer(Modifier.height(16.dp))
}
stickyHeader {
Card(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
shape = RoundedCornerShape(
topStart = 6.dp,
topEnd = 6.dp,
bottomStart = 0.dp,
bottomEnd = 0.dp
),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 4.dp),
colors = CardDefaults.cardColors(
containerColor = Color.White,
contentColor = Color.Black
)
) {
PicklistsAndProductsTabRow(
Modifier.fillParentMaxWidth(),
selectedTabIndex,
onTabSelected = {
selectedTabIndex = it
// if user has scrolled enough to activate the sticky header,
// tab clicks should reset the scroll position to avoid preserving
// the scroll state of one tab list across other tab content
// coroutineScope.launch {
// scrollState.animateScrollToItem(1, 0)
// }
},
batchWorkflow
)
}
}
if (selectedTabIndex == 0 && batchWorkflow == BatchWorkflow.PICKLIST_TO_CONTAINER
|| selectedTabIndex == 1 && batchWorkflow == BatchWorkflow.PRODUCT_TO_CONTAINER
) {
itemsIndexed(
List(15) {
batchDetailsUiState.batchDetailsResponse?.batchDetails?.picklistsInfo?.picklists
?: emptyList()
}.flatten(),
key = { _, _ -> UUID.randomUUID() }) { index, picklist ->
Card(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
shape = RoundedCornerShape(
topStart = 0.dp,
topEnd = 0.dp,
bottomStart = if (index == 14) 6.dp else 0.dp,
bottomEnd = if (index == 14) 6.dp else 0.dp
),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 4.dp),
colors = CardDefaults.cardColors(
containerColor = Color.White,
contentColor = Color.Black
)
) {
PicklistListItem(index, picklist)
}
}
} else if (selectedTabIndex == 0 && batchWorkflow == BatchWorkflow.PRODUCT_TO_CONTAINER
|| selectedTabIndex == 1 && batchWorkflow == BatchWorkflow.PICKLIST_TO_CONTAINER
) {
itemsIndexed(
List(15) {
batchDetailsUiState.productsAndPicksResponse?.products
?: emptyList()
}.flatten(),
key = { _, _ -> UUID.randomUUID() }) { index, product ->
Card(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
shape = RoundedCornerShape(
topStart = 0.dp,
topEnd = 0.dp,
bottomStart = if (index == 14) 6.dp else 0.dp,
bottomEnd = if (index == 14) 6.dp else 0.dp
),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 4.dp),
colors = CardDefaults.cardColors(
containerColor = Color.White,
contentColor = Color.Black
)
) {
ProductListItem(index, product, context)
}
}
}
}
}
if (shouldShowScanContainerBottomSheet && picklistToPickFrom != null) {
ScanContainerModalBottomSheet(
onDismissRequest = { shouldShowScanContainerBottomSheet = false },
picklistReference = picklistToPickFrom?.reference!!,
onManualContainerCodeSubmission = {
//todo:sp call the assign container :)
//todo:sp add actual business logic
Toast.makeText(context, "Test submission of $it", Toast.LENGTH_SHORT).show()
}
)
}
if (shouldShowPickProductBottomSheet && picklistToPickFrom != null && productToPick != null) {
PickProductModalBottomSheet(
sheetState = scanProductSheetState,
onDismissRequest = { shouldShowPickProductBottomSheet = false },
selectedPicklistDetails = picklistToPickFrom!!,
selectedProduct = productToPick!!
) {
coroutineScope
.launch {
scanProductSheetState.hide()
}.invokeOnCompletion {
shouldShowPickProductBottomSheet = false
shouldShowPickProductsInPicklistBottomSheet = true
}
}
}
if (shouldShowPickProductsInPicklistBottomSheet) {
shouldShowPickProductBottomSheet = false
picklistToPickFrom?.let {
PickProductsInPicklistBottomSheet(
picklistDetails = it,
onProductScanned = { uuid ->
Toast.makeText(context, "Scanned $uuid", Toast.LENGTH_SHORT).show()
}) {
shouldShowPickProductsInPicklistBottomSheet = false
}
}
}
}