diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/components/Common.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/components/Common.kt index c5c6bb85..54a469bb 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/components/Common.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/components/Common.kt @@ -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) - } } } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesDialog.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesDialog.kt index fc9793d4..cbd32aff 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesDialog.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesDialog.kt @@ -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, 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) diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesDialog.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesDialog.kt index 4855e7c1..48f2adf4 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesDialog.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesDialog.kt @@ -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, - dismiss: () -> Unit, - save: (Int, Double, List) -> 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, dismiss: () -> Unit, save: (List) -> Unit) { + val maxWidthModifier = Modifier.fillMaxWidth(1f) val selected = remember { mutableStateListOf().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) } } } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt index 03f035b8..e1e3432c 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt @@ -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()) diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesState.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesState.kt index bedc1d09..2ba70937 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesState.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesState.kt @@ -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) : PassesAction() + data class FilterPasses(val hoursAhead: Int, val minElevation: Double) : PassesAction() + data class FilterRadios(val modes: List) : PassesAction() data object RefreshPasses : PassesAction() - data object ToggleFilterDialog : PassesAction() + data object TogglePassesDialog : PassesAction() + data object ToggleRadiosDialog : PassesAction() } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesViewModel.kt index 2ea4e8ca..73863558 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesViewModel.kt @@ -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 {