Added several tweaks to PassesDialog, RadiosDialog

This commit is contained in:
Arty Bishop committed 2025-12-27 15:01:46 +00:00
1 parent 68d814d996
commit ad0b611bcb
6 files changed
+186 -145

No files matched your search

@@ -23,7 +23,6 @@ import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ElevatedButton
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.Icon
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -45,7 +44,6 @@ import com.rtbishop.look4sat.domain.predict.OrbitalData
import com.rtbishop.look4sat.domain.predict.OrbitalPass
import com.rtbishop.look4sat.presentation.MainTheme
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
private val sdf = SimpleDateFormat("HH:mm:ss", Locale.ENGLISH)
@@ -99,12 +97,13 @@ private fun NextPassRowPreview() = MainTheme {
@Composable
fun NextPassRow(pass: OrbitalPass) {
ElevatedCard {
ElevatedCard(modifier = Modifier.height(48.dp)) {
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
verticalArrangement = Arrangement.spacedBy((-2).dp),
modifier = Modifier
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.surface)
.padding(horizontal = 6.dp, vertical = 4.dp)
.padding(start = 6.dp, top = 1.dp, end = 6.dp, bottom = 0.dp)
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
@@ -158,22 +157,6 @@ fun NextPassRow(pass: OrbitalPass) {
modifier = Modifier.weight(1f)
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth()
) {
val defaultTime = " - - : - - "
Text(text = if (pass.isDeepSpace) defaultTime else sdf.format(Date(pass.aosTime)), fontSize = 15.sp)
LinearProgressIndicator(
progress = { if (pass.isDeepSpace) 100f else pass.progress },
modifier = Modifier
.fillMaxWidth(0.75f)
.padding(top = 2.dp),
trackColor = MaterialTheme.colorScheme.inverseSurface
)
Text(text = if (pass.isDeepSpace) defaultTime else sdf.format(Date(pass.losTime)), fontSize = 15.sp)
}
}
}
}
@@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.itemsIndexed
import androidx.compose.material3.BottomSheetDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
@@ -39,11 +38,10 @@ fun TypesDialog(items: List<String>, selected: String, dismiss: () -> Unit, sele
ModalBottomSheet(
onDismissRequest = { dismiss() },
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
dragHandle = { BottomSheetDefaults.DragHandle() },
modifier = Modifier.fillMaxHeight(0.80f)
) {
LazyVerticalGrid(
columns = GridCells.Adaptive(240.dp),
columns = GridCells.Fixed(1),
modifier = Modifier.background(MaterialTheme.colorScheme.background),
horizontalArrangement = Arrangement.spacedBy(1.dp),
verticalArrangement = Arrangement.spacedBy(1.dp)
@@ -22,21 +22,26 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.itemsIndexed
import androidx.compose.material3.Checkbox
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf
@@ -50,7 +55,6 @@ 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.compose.ui.window.Dialog
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.presentation.MainTheme
import com.rtbishop.look4sat.presentation.components.CardButton
@@ -65,122 +69,161 @@ private val allModes = listOf(
@Preview(showBackground = true)
@Composable
private fun FilterDialogPreview() {
MainTheme { FilterDialog(24, 16.0, emptyList(), {}) { _, _, _ -> } }
private fun PassesDialogPreview() {
MainTheme { PassesDialog(24, 16.0, {}) { _, _ -> } }
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FilterDialog(
hours: Int,
elev: Double,
modes: List<String>,
dismiss: () -> Unit,
save: (Int, Double, List<String>) -> Unit
) {
val maxWidthModifier = Modifier.fillMaxWidth(1f)
fun PassesDialog(hours: Int, elev: Double, dismiss: () -> Unit, save: (Int, Double) -> Unit) {
val maxWidthModifier = Modifier.fillMaxWidth()
val hoursValue = remember { mutableIntStateOf(hours) }
val elevationValue = remember { mutableIntStateOf(elev.toInt()) }
ModalBottomSheet(onDismissRequest = { dismiss() }) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(6.dp),
modifier = maxWidthModifier.padding(start = 16.dp, top = 0.dp, end = 16.dp, bottom = 32.dp)
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = maxWidthModifier
) {
CardButton(onClick = { dismiss() }, text = stringResource(id = R.string.btn_cancel))
Text(text = "Passes", fontSize = 18.sp, color = MaterialTheme.colorScheme.primary)
CardButton(
onClick = {
save(hoursValue.intValue, elevationValue.intValue.toDouble())
dismiss()
}, text = stringResource(id = R.string.btn_accept)
)
}
Spacer(modifier = Modifier.height(8.dp))
Row(horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.Bottom) {
Text(text = "Show passes above", fontSize = 16.sp, modifier = Modifier.weight(1f))
Icon(
painter = painterResource(id = R.drawable.ic_elevation),
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier
.size(20.dp)
.padding(bottom = 4.dp)
)
Text(
text = "${elevationValue.intValue}°",
fontSize = 18.sp,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.primary
)
}
Slider(
value = elevationValue.intValue.toFloat(),
onValueChange = { elevationValue.intValue = it.toInt() },
valueRange = 0f..60f,
colors = SliderDefaults.colors(inactiveTrackColor = MaterialTheme.colorScheme.onSurfaceVariant)
)
Spacer(modifier = Modifier.height(8.dp))
Row(horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.Bottom) {
Text(text = "Show passes within", fontSize = 16.sp, modifier = Modifier.weight(1f))
Icon(
painter = painterResource(id = R.drawable.ic_time),
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier
.size(20.dp)
.padding(bottom = 4.dp)
)
Text(
text = "${hoursValue.intValue}h",
fontSize = 18.sp,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.primary
)
}
Slider(
value = hoursValue.intValue.toFloat(),
onValueChange = { hoursValue.intValue = it.toInt() },
valueRange = 1f..96f,
colors = SliderDefaults.colors(inactiveTrackColor = MaterialTheme.colorScheme.onSurfaceVariant)
)
}
}
}
@Preview(showBackground = true)
@Composable
private fun RadiosDialogPreview() {
MainTheme { RadiosDialog(emptyList(), {}) { _ -> } }
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RadiosDialog(modes: List<String>, dismiss: () -> Unit, save: (List<String>) -> Unit) {
val maxWidthModifier = Modifier.fillMaxWidth(1f)
val selected = remember { mutableStateListOf<String>().apply { addAll(modes) } }
val select = { mode: String -> if (selected.contains(mode)) selected.remove(mode) else selected.add(mode) }
Dialog(onDismissRequest = { dismiss() }) {
ElevatedCard(modifier = Modifier.fillMaxHeight(0.80f)) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(6.dp),
modifier = maxWidthModifier.padding(12.dp)
ModalBottomSheet(
onDismissRequest = { dismiss() },
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
modifier = Modifier.fillMaxHeight(0.80f)
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(6.dp),
modifier = maxWidthModifier.padding(horizontal = 12.dp)
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = maxWidthModifier
) {
Text(text = "Filter passes", fontSize = 18.sp, color = MaterialTheme.colorScheme.primary)
Row(horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.Bottom) {
Text(text = "Show passes within", fontSize = 16.sp, modifier = Modifier.weight(1f))
Icon(
painter = painterResource(id = R.drawable.ic_time),
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier
.size(20.dp)
.padding(bottom = 4.dp)
)
Text(
text = "${hoursValue.intValue}h",
fontSize = 18.sp,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.primary
)
}
Slider(
value = hoursValue.intValue.toFloat(),
onValueChange = { hoursValue.intValue = it.toInt() },
valueRange = 1f..96f,
colors = SliderDefaults.colors(inactiveTrackColor = MaterialTheme.colorScheme.onSurfaceVariant)
CardButton(onClick = { dismiss() }, text = stringResource(id = R.string.btn_cancel))
Text(text = "Radios", fontSize = 18.sp, color = MaterialTheme.colorScheme.primary)
CardButton(
onClick = {
save(selected.toList())
dismiss()
}, text = stringResource(id = R.string.btn_accept)
)
Row(horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.Bottom) {
Text(text = "Show passes above", fontSize = 16.sp, modifier = Modifier.weight(1f))
Icon(
painter = painterResource(id = R.drawable.ic_elevation),
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier
.size(20.dp)
.padding(bottom = 4.dp)
)
Text(
text = "${elevationValue.intValue}°",
fontSize = 18.sp,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.primary
)
}
Slider(
value = elevationValue.intValue.toFloat(),
onValueChange = { elevationValue.intValue = it.toInt() },
valueRange = 0f..60f,
colors = SliderDefaults.colors(inactiveTrackColor = MaterialTheme.colorScheme.onSurfaceVariant)
)
LazyVerticalGrid(
columns = GridCells.Adaptive(160.dp),
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.weight(1f),
horizontalArrangement = Arrangement.spacedBy(1.dp),
verticalArrangement = Arrangement.spacedBy(1.dp)
) {
itemsIndexed(allModes) { index, item ->
Surface {
Row(verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.background(MaterialTheme.colorScheme.surface)
.clickable { select(item) }) {
Text(
text = "$index).",
modifier = Modifier.padding(start = 8.dp, end = 6.dp),
fontWeight = FontWeight.Normal,
color = MaterialTheme.colorScheme.secondary
)
Text(
text = item,
modifier = Modifier.weight(1f),
fontWeight = FontWeight.Medium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Checkbox(
checked = selected.contains(item),
onCheckedChange = null,
modifier = Modifier.padding(8.dp)
)
}
}
LazyVerticalGrid(
columns = GridCells.Fixed(1),
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.weight(1f),
horizontalArrangement = Arrangement.spacedBy(1.dp),
verticalArrangement = Arrangement.spacedBy(1.dp)
) {
item { HorizontalDivider(thickness = 0.dp, color = MaterialTheme.colorScheme.surface) }
itemsIndexed(allModes) { index, item ->
Surface {
Row(verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.background(MaterialTheme.colorScheme.surface)
.clickable { select(item) }) {
Text(
text = "$index).",
modifier = Modifier.padding(start = 8.dp, end = 6.dp),
fontWeight = FontWeight.Normal,
color = MaterialTheme.colorScheme.secondary
)
Text(
text = item,
modifier = Modifier.weight(1f),
fontWeight = FontWeight.Medium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Checkbox(
checked = selected.contains(item),
onCheckedChange = null,
modifier = Modifier.padding(8.dp)
)
}
}
}
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
CardButton(onClick = { dismiss() }, text = stringResource(id = R.string.btn_cancel))
CardButton(
onClick = {
save(hoursValue.intValue, elevationValue.intValue.toDouble(), selected)
dismiss()
}, text = stringResource(id = R.string.btn_accept)
)
}
item { HorizontalDivider(thickness = 24.dp, color = MaterialTheme.colorScheme.surface) }
}
}
}
@@ -55,17 +55,23 @@ private val sdf = SimpleDateFormat("HH:mm:ss", Locale.ENGLISH)
fun PassesScreen(uiState: PassesState, navToRadar: (Int, Long) -> Unit) {
val refreshPasses = { uiState.takeAction(PassesAction.RefreshPasses) }
val refreshState = rememberPullRefreshState(refreshing = uiState.isRefreshing, onRefresh = refreshPasses)
val toggleDialog = { uiState.takeAction(PassesAction.ToggleFilterDialog) }
if (uiState.isDialogShown) {
FilterDialog(uiState.hours, uiState.elevation, uiState.modes, toggleDialog) { hours, elevation, modes ->
uiState.takeAction(PassesAction.ApplyFilter(hours, elevation, modes))
val showPassesDialog = { uiState.takeAction(PassesAction.TogglePassesDialog) }
val showRadiosDialog = { uiState.takeAction(PassesAction.ToggleRadiosDialog) }
if (uiState.isPassesDialogShown) {
PassesDialog(hours = uiState.hours, elev = uiState.elevation, dismiss = showPassesDialog) { hours, elevation ->
uiState.takeAction(PassesAction.FilterPasses(hours, elevation))
}
}
if (uiState.isRadiosDialogShown) {
RadiosDialog(modes = uiState.modes, dismiss = showRadiosDialog) { modes ->
uiState.takeAction(PassesAction.FilterRadios(modes))
}
}
Column(modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
TimerRow {
CardIcon(onClick = { toggleDialog() }, iconId = R.drawable.ic_filter)
CardIcon(onClick = { showPassesDialog() }, iconId = R.drawable.ic_filter)
TimerBar(timeString = uiState.nextTime, isTimeAos = uiState.isNextTimeAos)
CardIcon(onClick = { toggleDialog() }, iconId = R.drawable.ic_satellite)
CardIcon(onClick = { showRadiosDialog() }, iconId = R.drawable.ic_satellite)
}
NextPassRow(pass = uiState.nextPass)
PassesList(refreshState, uiState.isRefreshing, uiState.itemsList, navToRadar)
@@ -82,7 +88,7 @@ private fun PassesList(
) {
val backgroundColor = MaterialTheme.colorScheme.primary
ElevatedCard(modifier = Modifier.fillMaxSize()) {
Box(Modifier.pullRefresh(refreshState), contentAlignment = Alignment.TopCenter) {
Box(modifier = Modifier.pullRefresh(refreshState), contentAlignment = Alignment.TopCenter) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(items = passes, key = { item -> item.catNum + item.aosTime }) { pass ->
PassItem(pass = pass, navToRadar = navToRadar, modifier = Modifier.animateItemPlacement())
@@ -3,7 +3,8 @@ package com.rtbishop.look4sat.presentation.passes
import com.rtbishop.look4sat.domain.predict.OrbitalPass
data class PassesState(
val isDialogShown: Boolean,
val isPassesDialogShown: Boolean,
val isRadiosDialogShown: Boolean,
val isRefreshing: Boolean,
val isUtc: Boolean,
val nextPass: OrbitalPass,
@@ -17,7 +18,9 @@ data class PassesState(
)
sealed class PassesAction {
data class ApplyFilter(val hoursAhead: Int, val minElevation: Double, val modes: List<String>) : PassesAction()
data class FilterPasses(val hoursAhead: Int, val minElevation: Double) : PassesAction()
data class FilterRadios(val modes: List<String>) : PassesAction()
data object RefreshPasses : PassesAction()
data object ToggleFilterDialog : PassesAction()
data object TogglePassesDialog : PassesAction()
data object ToggleRadiosDialog : PassesAction()
}
@@ -43,7 +43,8 @@ class PassesViewModel(
private val _uiState = mutableStateOf(
PassesState(
isDialogShown = false,
isPassesDialogShown = false,
isRadiosDialogShown = false,
isRefreshing = true,
isUtc = settingsRepo.otherSettings.value.stateOfUtc,
nextTime = "00:00:00",
@@ -78,9 +79,11 @@ class PassesViewModel(
private fun handleAction(action: PassesAction) {
when (action) {
is PassesAction.ApplyFilter -> applyFilter(action.hoursAhead, action.minElevation, action.modes)
is PassesAction.FilterPasses -> applyFilter(action.hoursAhead, action.minElevation, uiState.value.modes)
is PassesAction.FilterRadios -> applyFilter(uiState.value.hours, uiState.value.elevation, action.modes)
PassesAction.RefreshPasses -> refreshPasses()
PassesAction.ToggleFilterDialog -> toggleFilterDialog()
PassesAction.TogglePassesDialog -> toggleFilterDialog()
PassesAction.ToggleRadiosDialog -> toggleRadiosDialog()
}
}
@@ -113,8 +116,13 @@ class PassesViewModel(
}
private fun toggleFilterDialog() {
val currentDialogState = _uiState.value.isDialogShown
_uiState.value = _uiState.value.copy(isDialogShown = currentDialogState.not())
val currentDialogState = _uiState.value.isPassesDialogShown
_uiState.value = _uiState.value.copy(isPassesDialogShown = currentDialogState.not())
}
private fun toggleRadiosDialog() {
val currentDialogState = _uiState.value.isRadiosDialogShown
_uiState.value = _uiState.value.copy(isRadiosDialogShown = currentDialogState.not())
}
companion object {