feat: add loading state management to ListDetailViewModel and display loading overlay in ListDetailScreen

This commit is contained in:
2025-11-11 23:25:20 +01:00
parent a7fff59f8c
commit 74d2bf60cf
2 changed files with 54 additions and 17 deletions
@@ -121,6 +121,7 @@ fun ListDetailScreen(
val list by viewModel.list.collectAsState() val list by viewModel.list.collectAsState()
val fields by viewModel.fields.collectAsState() val fields by viewModel.fields.collectAsState()
val items by viewModel.items.collectAsState() val items by viewModel.items.collectAsState()
val isLoading by viewModel.isLoading.collectAsState()
// Use derivedStateOf to create stable references // Use derivedStateOf to create stable references
val stableFields by remember { derivedStateOf { fields } } val stableFields by remember { derivedStateOf { fields } }
@@ -239,15 +240,18 @@ fun ListDetailScreen(
val processedItems = transformed.processed val processedItems = transformed.processed
val groupedItems = transformed.grouped val groupedItems = transformed.grouped
if (stableFields.isEmpty()) { // Root container to allow loading overlay
Box( Box(
modifier = modifier =
Modifier Modifier
.fillMaxSize() .fillMaxSize()
.padding(padding), .padding(padding),
contentAlignment = Alignment.Center, ) {
) { if (stableFields.isEmpty()) {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(
modifier = Modifier.align(Alignment.Center),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text( Text(
text = "No fields yet. Add fields to get started!", text = "No fields yet. Add fields to get started!",
style = MaterialTheme.typography.bodyLarge, style = MaterialTheme.typography.bodyLarge,
@@ -260,14 +264,12 @@ fun ListDetailScreen(
Text(stringResource(R.string.add_field)) Text(stringResource(R.string.add_field))
} }
} }
} } else {
} else { Column(
Column( modifier =
modifier = Modifier
Modifier .fillMaxSize(),
.fillMaxSize() ) {
.padding(padding),
) {
// Filter/Sort/Group Controls - Always visible above table // Filter/Sort/Group Controls - Always visible above table
Row( Row(
modifier = modifier =
@@ -560,6 +562,17 @@ fun ListDetailScreen(
) )
} }
} }
// Loading overlay during initial content load
if (isLoading) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
androidx.compose.material3.CircularProgressIndicator()
}
}
}
} }
// Items list below the fixed header // Items list below the fixed header
@@ -34,6 +34,9 @@ class ListDetailViewModel(
private val _items = MutableStateFlow<List<ItemWithValues>>(emptyList()) private val _items = MutableStateFlow<List<ItemWithValues>>(emptyList())
val items: StateFlow<List<ItemWithValues>> = _items.asStateFlow() val items: StateFlow<List<ItemWithValues>> = _items.asStateFlow()
private val _isLoading = MutableStateFlow(true)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
private val syncRepository = SyncRepository(context) private val syncRepository = SyncRepository(context)
init { init {
@@ -42,6 +45,15 @@ class ListDetailViewModel(
} }
private fun loadListData() { private fun loadListData() {
// Track first emissions to flip loading off after initial data is ready
var hasList = false
var hasFields = false
var hasItems = false
fun maybeLoaded() {
if (hasList && hasFields && hasItems) {
_isLoading.value = false
}
}
viewModelScope.launch { viewModelScope.launch {
database database
.listDao() .listDao()
@@ -49,6 +61,10 @@ class ListDetailViewModel(
.debounce(75) .debounce(75)
.collect { listWithFields -> .collect { listWithFields ->
_list.value = listWithFields?.list _list.value = listWithFields?.list
if (!hasList) {
hasList = true
maybeLoaded()
}
} }
} }
@@ -59,6 +75,10 @@ class ListDetailViewModel(
.debounce(75) .debounce(75)
.collect { itemsData -> .collect { itemsData ->
_items.value = itemsData _items.value = itemsData
if (!hasItems) {
hasItems = true
maybeLoaded()
}
} }
} }
@@ -69,6 +89,10 @@ class ListDetailViewModel(
.debounce(75) .debounce(75)
.collect { fieldsData -> .collect { fieldsData ->
_fields.value = fieldsData _fields.value = fieldsData
if (!hasFields) {
hasFields = true
maybeLoaded()
}
} }
} }
} }