Cleaned up EntriesScreen and ViewModel logic

This commit is contained in:
Arty Bishop committed 2025-12-27 15:01:45 +00:00
1 parent 1aefb04eb5
commit 49f713d82c
5 files changed
+89 -56

No files matched your search

@@ -11,6 +11,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
@@ -20,6 +21,7 @@ import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.presentation.entries.EntriesScreen
import com.rtbishop.look4sat.presentation.entries.EntriesViewModel
import com.rtbishop.look4sat.presentation.map.MapScreen
import com.rtbishop.look4sat.presentation.passes.PassesScreen
import com.rtbishop.look4sat.presentation.radar.RadarScreen
@@ -62,16 +64,20 @@ private fun MainNavBar(navController: NavController) {
@Composable
private fun MainNavGraph(navController: NavHostController) {
val navToPasses = { navController.navigate(Screen.Passes.route) }
val navToRadar = { catNum: Int, aosTime: Long ->
navController.navigate("${Screen.Radar.route}?catNum=${catNum}&aosTime=${aosTime}")
}
val radarRoute = "${Screen.Radar.route}?catNum={catNum}&aosTime={aosTime}"
val radarArgs = listOf(navArgument("catNum") { defaultValue = 0 },
navArgument("aosTime") { defaultValue = 0L })
val radarArgs = listOf(navArgument("catNum") { defaultValue = 0 }, navArgument("aosTime") { defaultValue = 0L })
NavHost(navController, startDestination = Screen.Passes.route) {
composable(Screen.Entries.route) { EntriesScreen(navToPasses) }
composable(Screen.Passes.route) { PassesScreen(navToRadar) }
composable(Screen.Entries.route) {
val viewModel = viewModel(EntriesViewModel::class.java, factory = EntriesViewModel.Factory)
val navToPasses = { navController.navigate(Screen.Passes.route) }
EntriesScreen(viewModel.uiState.value, navToPasses)
}
composable(Screen.Passes.route) {
val navToRadar = { catNum: Int, aosTime: Long ->
navController.navigate("${Screen.Radar.route}?catNum=${catNum}&aosTime=${aosTime}")
}
PassesScreen(navToRadar)
}
composable(radarRoute, radarArgs) { RadarScreen() }
composable(Screen.Map.route) { MapScreen() }
composable(Screen.Settings.route) { SettingsScreen() }
@@ -5,10 +5,9 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
@@ -33,31 +32,36 @@ private fun TypeDialogPreview() {
@Composable
fun TypesDialog(list: List<String>, selected: String, toggle: () -> Unit, click: (String) -> Unit) {
val clickAction = { type: String ->
click(type)
toggle()
}
Dialog(onDismissRequest = { toggle() }) {
ElevatedCard(modifier = Modifier.fillMaxHeight(0.9f)) {
ElevatedCard(modifier = Modifier.fillMaxHeight(0.75f)) {
LazyColumn(
modifier = Modifier.background(MaterialTheme.colorScheme.background),
verticalArrangement = Arrangement.spacedBy(1.dp)
) {
items(list) { type ->
itemsIndexed(list) { index, type ->
Surface {
Row(verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.background(MaterialTheme.colorScheme.surface)
.clickable {
click(type)
toggle()
}) {
RadioButton(selected = type == selected, onClick = {})
.clickable { clickAction(type) }) {
Text(
text = "$index).",
modifier = Modifier.padding(start = 12.dp, end = 6.dp),
fontWeight = FontWeight.Normal,
color = MaterialTheme.colorScheme.secondary
)
Text(
text = type,
modifier = Modifier
.padding(end = 6.dp)
.fillMaxWidth(),
fontWeight = FontWeight.Normal,
modifier = Modifier.weight(1f),
fontWeight = FontWeight.Medium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
RadioButton(selected = type == selected, onClick = { clickAction(type) })
}
}
}
@@ -38,42 +38,38 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.domain.model.SatItem
import com.rtbishop.look4sat.presentation.CardIcon
import com.rtbishop.look4sat.presentation.CardLoadingIndicator
import com.rtbishop.look4sat.presentation.MainTheme
import com.rtbishop.look4sat.presentation.dialogs.TypesDialog
import com.rtbishop.look4sat.presentation.onClick
@Composable
fun EntriesScreen(navToPasses: () -> Unit) {
val viewModel = viewModel(EntriesViewModel::class.java, factory = EntriesViewModel.Factory)
val uiState = viewModel.uiState.value
fun EntriesScreen(uiState: EntriesUiState, navToPasses: () -> Unit) {
val showDialog = rememberSaveable { mutableStateOf(false) }
val toggleDialog = { showDialog.value = showDialog.value.not() }
if (showDialog.value) {
TypesDialog(list = uiState.typesList, selected = uiState.currentType, toggleDialog) {
viewModel.setType(it)
uiState.takeAction(EntriesUiAction.SelectType(it))
}
}
val unselectAll = { viewModel.setSelection(false) }
val selectAll = { viewModel.setSelection(true) }
val unselectAll = { uiState.takeAction(EntriesUiAction.UnselectAll) }
val selectAll = { uiState.takeAction(EntriesUiAction.SelectAll) }
Column(modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
TopBar(setQuery = { newQuery: String -> viewModel.setQuery(newQuery) }, saveSelection = {
viewModel.saveSelection()
navToPasses()
})
TopBar(setQuery = { newQuery: String -> uiState.takeAction(EntriesUiAction.SearchFor(newQuery)) },
saveSelection = {
uiState.takeAction(EntriesUiAction.SaveSelection)
navToPasses()
})
MiddleBar(uiState.currentType, { toggleDialog() }, { unselectAll() }, { selectAll() })
ElevatedCard(modifier = Modifier.fillMaxSize()) {
if (uiState.isLoading) {
CardLoadingIndicator()
} else {
EntriesCard(uiState.itemsList) { list, value ->
viewModel.setSelection(list, value)
EntriesCard(uiState.itemsList) { id, isTicked ->
uiState.takeAction(EntriesUiAction.SelectSingle(id, isTicked))
}
}
}
@@ -188,19 +184,21 @@ private fun EntryTypeCard(type: String, onClick: () -> Unit, modifier: Modifier
@Preview(showBackground = true)
@Composable
private fun EntryPreview() {
val satItem = SatItem(45555, "Very long Satellite name", true)
val satItem = SatItem(44444, "Ultra Super Mega long satellite name", true)
MainTheme { Entry(item = satItem, onSelected = { _, _ -> run {} }, modifier = Modifier) }
}
@Composable
private fun Entry(item: SatItem, onSelected: (List<Int>, Boolean) -> Unit, modifier: Modifier) {
Surface(color = MaterialTheme.colorScheme.background, modifier = modifier) {
private fun Entry(item: SatItem, onSelected: (Int, Boolean) -> Unit, modifier: Modifier) {
Surface(color = MaterialTheme.colorScheme.background,
modifier = modifier.clickable { onSelected(item.catnum, item.isSelected) }) {
Surface(modifier = Modifier.padding(bottom = 1.dp)) {
Row(verticalAlignment = Alignment.CenterVertically,
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.background(MaterialTheme.colorScheme.surface)
.padding(start = 0.dp, top = 8.dp, end = 12.dp, bottom = 8.dp)
.onClick { onSelected(listOf(item.catnum), item.isSelected.not()) }) {
) {
Text(
text = "Id:${item.catnum} - ",
modifier = Modifier.width(104.dp),
@@ -209,14 +207,12 @@ private fun Entry(item: SatItem, onSelected: (List<Int>, Boolean) -> Unit, modif
)
Text(
text = item.name,
modifier = Modifier
.weight(1f)
.padding(end = 6.dp),
modifier = Modifier.weight(1f),
fontWeight = FontWeight.Medium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Checkbox(checked = item.isSelected, onCheckedChange = null)
Checkbox(checked = item.isSelected, onCheckedChange = null, modifier = Modifier.padding(start = 6.dp))
}
}
}
@@ -225,12 +221,17 @@ private fun Entry(item: SatItem, onSelected: (List<Int>, Boolean) -> Unit, modif
@Preview(showBackground = true)
@Composable
private fun EntriesPreview() {
MainTheme { EntriesCard(emptyList()) { _, _ -> run {} } }
val entries = listOf(
SatItem(8888, "Meteor", false),
SatItem(44444, "ISS", true),
SatItem(88888, "Starlink", false)
)
MainTheme { EntriesCard(entries) { _, _ -> run {} } }
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun EntriesCard(items: List<SatItem>, onSelected: (List<Int>, Boolean) -> Unit) {
fun EntriesCard(items: List<SatItem>, onSelected: (Int, Boolean) -> Unit) {
LazyColumn {
items(items = items, key = { item -> item.catnum }) { entry ->
Entry(entry, onSelected, Modifier.animateItemPlacement())
@@ -7,4 +7,14 @@ data class EntriesUiState(
val itemsList: List<SatItem>,
val currentType: String,
val typesList: List<String>,
val takeAction: (EntriesUiAction) -> Unit
)
sealed class EntriesUiAction {
data object SaveSelection : EntriesUiAction()
data class SearchFor(val query: String) : EntriesUiAction()
data object SelectAll : EntriesUiAction()
data class SelectSingle(val id: Int, val isTicked: Boolean) : EntriesUiAction()
data class SelectType(val type: String) : EntriesUiAction()
data object UnselectAll : EntriesUiAction()
}
@@ -34,7 +34,8 @@ class EntriesViewModel(private val selectionRepo: ISelectionRepo) : ViewModel()
isLoading = true,
itemsList = emptyList(),
currentType = selectionRepo.getCurrentType(),
typesList = selectionRepo.getTypesList()
typesList = selectionRepo.getTypesList(),
takeAction = ::handleAction
)
val uiState = mutableStateOf(defaultUiState)
@@ -47,22 +48,33 @@ class EntriesViewModel(private val selectionRepo: ISelectionRepo) : ViewModel()
}
}
fun setType(type: String) = viewModelScope.launch {
selectionRepo.setType(type)
uiState.value = uiState.value.copy(currentType = type)
private fun handleAction(action: EntriesUiAction) {
when (action) {
EntriesUiAction.SaveSelection -> saveSelection()
is EntriesUiAction.SearchFor -> searchFor(action.query)
EntriesUiAction.SelectAll -> selectAll(true)
is EntriesUiAction.SelectSingle -> selectSingle(action.id, action.isTicked)
is EntriesUiAction.SelectType -> selectType(action.type)
EntriesUiAction.UnselectAll -> selectAll(false)
}
}
fun setQuery(query: String) = viewModelScope.launch { selectionRepo.setQuery(query) }
private fun saveSelection() = viewModelScope.launch { selectionRepo.saveSelection() }
fun setSelection(selectAll: Boolean) = viewModelScope.launch {
private fun searchFor(query: String) = viewModelScope.launch { selectionRepo.setQuery(query) }
private fun selectAll(selectAll: Boolean) = viewModelScope.launch {
selectionRepo.setSelection(selectAll)
}
fun setSelection(ids: List<Int>, isTicked: Boolean) = viewModelScope.launch {
selectionRepo.setSelection(ids, isTicked)
private fun selectSingle(id: Int, isTicked: Boolean) = viewModelScope.launch {
selectionRepo.setSelection(listOf(id), isTicked.not())
}
fun saveSelection() = viewModelScope.launch { selectionRepo.saveSelection() }
private fun selectType(type: String) = viewModelScope.launch {
selectionRepo.setType(type)
uiState.value = uiState.value.copy(currentType = type)
}
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {