mirror of
https://github.com/rt-bishop/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
Added sticky header with a date and sunrise/sunset time
This commit is contained in:
1 parent
31d329a19b
commit
6e5a3cb0bb
5 files changed
+176
-35
No files matched your search
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M12.34,2.02C6.59,1.82 2,6.42 2,12c0,5.52 4.48,10 10,10c3.71,0 6.93,-2.02 8.66,-5.02C13.15,16.73 8.57,8.55 12.34,2.02z" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M11,4V2c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v2c0,0.55 -0.45,1 -1,1S11,4.55 11,4zM18.36,7.05l1.41,-1.42c0.39,-0.39 0.39,-1.02 0,-1.41c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.41,1.42c-0.39,0.39 -0.39,1.02 0,1.41C17.34,7.44 17.97,7.44 18.36,7.05zM22,11h-2c-0.55,0 -1,0.45 -1,1s0.45,1 1,1h2c0.55,0 1,-0.45 1,-1S22.55,11 22,11zM12,19c-0.55,0 -1,0.45 -1,1v2c0,0.55 0.45,1 1,1s1,-0.45 1,-1v-2C13,19.45 12.55,19 12,19zM5.64,7.05L4.22,5.64c-0.39,-0.39 -0.39,-1.03 0,-1.41s1.03,-0.39 1.41,0l1.41,1.41c0.39,0.39 0.39,1.03 0,1.41S6.02,7.44 5.64,7.05zM16.95,16.95c-0.39,0.39 -0.39,1.03 0,1.41l1.41,1.41c0.39,0.39 1.03,0.39 1.41,0c0.39,-0.39 0.39,-1.03 0,-1.41l-1.41,-1.41C17.98,16.56 17.34,16.56 16.95,16.95zM2,13h2c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1H2c-0.55,0 -1,0.45 -1,1S1.45,13 2,13zM5.64,19.78l1.41,-1.41c0.39,-0.39 0.39,-1.03 0,-1.41s-1.03,-0.39 -1.41,0l-1.41,1.41c-0.39,0.39 -0.39,1.03 0,1.41C4.61,20.17 5.25,20.17 5.64,19.78zM12,6c-3.31,0 -6,2.69 -6,6s2.69,6 6,6s6,-2.69 6,-6S15.31,6 12,6z" />
|
||||||
|
</vector>
|
||||||
+110
-20
@@ -29,6 +29,7 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
|
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||||
import androidx.compose.foundation.lazy.grid.LazyGridState
|
import androidx.compose.foundation.lazy.grid.LazyGridState
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
@@ -44,6 +45,8 @@ import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
|||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
|
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
|
||||||
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -144,7 +147,8 @@ private fun PassesScreen(
|
|||||||
passes = uiState.itemsList,
|
passes = uiState.itemsList,
|
||||||
navigateToRadar = navigateToRadar,
|
navigateToRadar = navigateToRadar,
|
||||||
refreshPasses = { onAction(PassesAction.RefreshPasses) },
|
refreshPasses = { onAction(PassesAction.RefreshPasses) },
|
||||||
gridState = gridState
|
gridState = gridState,
|
||||||
|
sunTimes = uiState.sunTimes
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,10 +161,43 @@ private fun PassesList(
|
|||||||
passes: List<OrbitalPass>,
|
passes: List<OrbitalPass>,
|
||||||
navigateToRadar: (Int, Long) -> Unit,
|
navigateToRadar: (Int, Long) -> Unit,
|
||||||
refreshPasses: () -> Unit,
|
refreshPasses: () -> Unit,
|
||||||
gridState: LazyGridState
|
gridState: LazyGridState,
|
||||||
|
sunTimes: Map<String, Pair<String, String>>
|
||||||
) {
|
) {
|
||||||
val isVerticalLayout = isVerticalLayout()
|
val isVerticalLayout = isVerticalLayout()
|
||||||
val refreshState = rememberPullToRefreshState()
|
val refreshState = rememberPullToRefreshState()
|
||||||
|
val timeZone = remember(isUtc) { if (isUtc) TimeZone.getTimeZone("UTC") else TimeZone.getDefault() }
|
||||||
|
val sdfDate = remember(isUtc) {
|
||||||
|
SimpleDateFormat("EEE, dd MMM yyyy", Locale.ENGLISH).also { it.timeZone = timeZone }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group passes by calendar day so we know where headers go
|
||||||
|
val groupedPasses = remember(passes, isUtc) {
|
||||||
|
passes.filter { !it.isDeepSpace }.groupBy { sdfDate.format(Date(it.aosTime)) } +
|
||||||
|
if (passes.any { it.isDeepSpace }) mapOf("Deep Space" to passes.filter { it.isDeepSpace }) else emptyMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive the sticky header label from the first visible item index
|
||||||
|
val stickyHeader by remember(gridState, groupedPasses) {
|
||||||
|
derivedStateOf {
|
||||||
|
val firstVisible = gridState.firstVisibleItemIndex
|
||||||
|
var itemIndex = 0
|
||||||
|
var header = groupedPasses.keys.firstOrNull() ?: ""
|
||||||
|
for ((key, items) in groupedPasses) {
|
||||||
|
header = key
|
||||||
|
itemIndex += 1 + items.size
|
||||||
|
if (itemIndex > firstVisible) break
|
||||||
|
}
|
||||||
|
header
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The sticky overlay should only show when the first in-list header has scrolled out of view.
|
||||||
|
// firstVisibleItemIndex == 0 means the first header is still visible — hide the overlay.
|
||||||
|
val showStickyOverlay by remember(gridState) {
|
||||||
|
derivedStateOf { gridState.firstVisibleItemIndex > 0 || gridState.firstVisibleItemScrollOffset > 0 }
|
||||||
|
}
|
||||||
|
|
||||||
ElevatedCard(modifier = Modifier.fillMaxSize()) {
|
ElevatedCard(modifier = Modifier.fillMaxSize()) {
|
||||||
PullToRefreshBox(
|
PullToRefreshBox(
|
||||||
isRefreshing = isRefreshing,
|
isRefreshing = isRefreshing,
|
||||||
@@ -179,19 +216,33 @@ private fun PassesList(
|
|||||||
if (passes.isEmpty()) {
|
if (passes.isEmpty()) {
|
||||||
EmptyListCard(message = stringResource(R.string.pass_empty_list_message))
|
EmptyListCard(message = stringResource(R.string.pass_empty_list_message))
|
||||||
} else {
|
} else {
|
||||||
LazyVerticalGrid(
|
Column {
|
||||||
state = gridState,
|
// Sticky header overlay — only visible once the in-list header scrolls away
|
||||||
columns = GridCells.Adaptive(320.dp),
|
if (showStickyOverlay) {
|
||||||
modifier = Modifier.fillMaxSize()
|
val (rise, set) = sunTimes[stickyHeader] ?: ("--:--" to "--:--")
|
||||||
) {
|
StickyDateHeader(label = stickyHeader, sunriseTime = rise, sunsetTime = set)
|
||||||
items(items = passes, key = { item -> item.catNum + item.aosTime }) { pass ->
|
}
|
||||||
PassItem(
|
LazyVerticalGrid(
|
||||||
pass = pass,
|
state = gridState,
|
||||||
navigateToRadar = navigateToRadar,
|
columns = GridCells.Adaptive(320.dp),
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.fillMaxSize()
|
||||||
isVerticalLayout = isVerticalLayout,
|
) {
|
||||||
isUtc = isUtc
|
for ((dateLabel, dayPasses) in groupedPasses) {
|
||||||
)
|
// In-list section header (scrolls away, drives the sticky overlay)
|
||||||
|
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||||
|
val (rise, set) = sunTimes[dateLabel] ?: ("--:--" to "--:--")
|
||||||
|
StickyDateHeader(label = dateLabel, sunriseTime = rise, sunsetTime = set)
|
||||||
|
}
|
||||||
|
items(items = dayPasses, key = { item -> item.catNum + item.aosTime }) { pass ->
|
||||||
|
PassItem(
|
||||||
|
pass = pass,
|
||||||
|
navigateToRadar = navigateToRadar,
|
||||||
|
modifier = Modifier.animateItem(),
|
||||||
|
isVerticalLayout = isVerticalLayout,
|
||||||
|
isUtc = isUtc
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,6 +250,45 @@ private fun PassesList(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun StickyDateHeader(label: String, sunriseTime: String, sunsetTime: String) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceContainerHighest)
|
||||||
|
.padding(horizontal = 12.dp, vertical = 4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = label,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_sun),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.size(16.dp)
|
||||||
|
)
|
||||||
|
Text(text = sunriseTime, fontSize = 14.sp, color = MaterialTheme.colorScheme.onSurface)
|
||||||
|
}
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_moon),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
|
modifier = Modifier.size(16.dp)
|
||||||
|
)
|
||||||
|
Text(text = sunsetTime, fontSize = 14.sp, color = MaterialTheme.colorScheme.onSurface)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
@Preview(showBackground = true)
|
||||||
@Composable
|
@Composable
|
||||||
private fun DeepSpacePassPreview() {
|
private fun DeepSpacePassPreview() {
|
||||||
@@ -230,15 +320,15 @@ private fun PassItem(
|
|||||||
val timeZone = remember(isUtc) {
|
val timeZone = remember(isUtc) {
|
||||||
if (isUtc) TimeZone.getTimeZone("UTC") else TimeZone.getDefault()
|
if (isUtc) TimeZone.getTimeZone("UTC") else TimeZone.getDefault()
|
||||||
}
|
}
|
||||||
val sdfDate = remember(isUtc) {
|
|
||||||
SimpleDateFormat("EEE dd MMM", Locale.ENGLISH).also { it.timeZone = timeZone }
|
|
||||||
}
|
|
||||||
val sdfTime = remember(isUtc) {
|
val sdfTime = remember(isUtc) {
|
||||||
SimpleDateFormat("HH:mm:ss", Locale.ENGLISH).also { it.timeZone = timeZone }
|
SimpleDateFormat("HH:mm:ss", Locale.ENGLISH).also { it.timeZone = timeZone }
|
||||||
}
|
}
|
||||||
val aosDateStr = remember(pass.aosTime, isUtc) { sdfDate.format(Date(pass.aosTime)) }
|
|
||||||
val aosTimeStr = remember(pass.aosTime, isUtc) { sdfTime.format(Date(pass.aosTime)) }
|
val aosTimeStr = remember(pass.aosTime, isUtc) { sdfTime.format(Date(pass.aosTime)) }
|
||||||
val losTimeStr = remember(pass.losTime, isUtc) { sdfTime.format(Date(pass.losTime)) }
|
val losTimeStr = remember(pass.losTime, isUtc) { sdfTime.format(Date(pass.losTime)) }
|
||||||
|
val durationStr = remember(pass.aosTime, pass.losTime) {
|
||||||
|
val seconds = (pass.losTime - pass.aosTime) / 1000
|
||||||
|
"${seconds / 60}m ${seconds % 60}s"
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier.clickable { navigateToRadar(pass.catNum, pass.aosTime) }
|
modifier = modifier.clickable { navigateToRadar(pass.catNum, pass.aosTime) }
|
||||||
@@ -295,7 +385,7 @@ private fun PassItem(
|
|||||||
color = MaterialTheme.colorScheme.onSurface
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Text(text = aosDateStr, fontSize = 15.sp, color = MaterialTheme.colorScheme.onSurface)
|
Text(text = durationStr, fontSize = 15.sp, color = MaterialTheme.colorScheme.onSurface)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ data class PassesState(
|
|||||||
val showDeepSpace: Boolean = true,
|
val showDeepSpace: Boolean = true,
|
||||||
val modes: List<String> = emptyList(),
|
val modes: List<String> = emptyList(),
|
||||||
val itemsList: List<OrbitalPass> = emptyList(),
|
val itemsList: List<OrbitalPass> = emptyList(),
|
||||||
val shouldSeeWhatsNew: Boolean = false
|
val shouldSeeWhatsNew: Boolean = false,
|
||||||
|
// Map of dateLabel -> Pair(sunriseTime, sunsetTime) for each day group
|
||||||
|
val sunTimes: Map<String, Pair<String, String>> = emptyMap()
|
||||||
)
|
)
|
||||||
|
|
||||||
sealed interface PassesAction {
|
sealed interface PassesAction {
|
||||||
|
|||||||
+45
-14
@@ -23,6 +23,7 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import androidx.lifecycle.viewmodel.initializer
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
import com.rtbishop.look4sat.core.domain.model.PassesSettings
|
import com.rtbishop.look4sat.core.domain.model.PassesSettings
|
||||||
|
import com.rtbishop.look4sat.core.domain.predict.CelestialComputer
|
||||||
import com.rtbishop.look4sat.core.domain.predict.OrbitalPass
|
import com.rtbishop.look4sat.core.domain.predict.OrbitalPass
|
||||||
import com.rtbishop.look4sat.core.domain.repository.IContainerProvider
|
import com.rtbishop.look4sat.core.domain.repository.IContainerProvider
|
||||||
import com.rtbishop.look4sat.core.domain.repository.ISatelliteRepo
|
import com.rtbishop.look4sat.core.domain.repository.ISatelliteRepo
|
||||||
@@ -37,6 +38,10 @@ import kotlinx.coroutines.flow.collectLatest
|
|||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.TimeZone
|
||||||
|
|
||||||
class PassesViewModel(
|
class PassesViewModel(
|
||||||
private val satelliteRepo: ISatelliteRepo,
|
private val satelliteRepo: ISatelliteRepo,
|
||||||
@@ -64,36 +69,45 @@ class PassesViewModel(
|
|||||||
_uiState.update { it.copy(isRefreshing = calculating) }
|
_uiState.update { it.copy(isRefreshing = calculating) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Local tick loop — computes pass progress and countdown timer every second
|
// React to settings changes: update UTC flag and whatsNew
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
settingsRepo.otherSettings.collectLatest { settings ->
|
||||||
|
_uiState.update { it.copy(isUtc = settings.stateOfUtc, shouldSeeWhatsNew = settings.shouldSeeWhatsNew) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Local tick loop — computes pass progress, countdown timer, and per-day sun times every second
|
||||||
|
viewModelScope.launch {
|
||||||
|
var lastSunTimesKey = "" // track when we need to recompute sun times
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
val timeNow = System.currentTimeMillis()
|
val timeNow = System.currentTimeMillis()
|
||||||
|
val isUtc = _uiState.value.isUtc
|
||||||
val showDeepSpace = _uiState.value.showDeepSpace
|
val showDeepSpace = _uiState.value.showDeepSpace
|
||||||
val allPasses = satelliteRepo.passes.value
|
val allPasses = satelliteRepo.passes.value
|
||||||
val filtered = if (showDeepSpace) allPasses else allPasses.filter { !it.isDeepSpace }
|
val filtered = if (showDeepSpace) allPasses else allPasses.filter { !it.isDeepSpace }
|
||||||
val processed = computePassProgress(filtered, timeNow)
|
val processed = computePassProgress(filtered, timeNow)
|
||||||
val (nextPass, nextTime, isAos) = resolveNextPass(processed, timeNow)
|
val (nextPass, nextTime, isAos) = resolveNextPass(processed, timeNow)
|
||||||
|
|
||||||
|
// Recompute per-day sun times only when passes list or UTC setting changes
|
||||||
|
val sunTimesKey = "${processed.firstOrNull()?.aosTime}-${processed.lastOrNull()?.aosTime}-$isUtc"
|
||||||
|
val sunTimes = if (sunTimesKey != lastSunTimesKey) {
|
||||||
|
lastSunTimesKey = sunTimesKey
|
||||||
|
computeSunTimes(processed, isUtc)
|
||||||
|
} else {
|
||||||
|
_uiState.value.sunTimes
|
||||||
|
}
|
||||||
|
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
itemsList = processed,
|
itemsList = processed,
|
||||||
nextPass = nextPass,
|
nextPass = nextPass,
|
||||||
nextTime = nextTime,
|
nextTime = nextTime,
|
||||||
isNextTimeAos = isAos
|
isNextTimeAos = isAos,
|
||||||
|
sunTimes = sunTimes
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
delay(1000)
|
delay(1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
viewModelScope.launch {
|
|
||||||
settingsRepo.otherSettings.collectLatest { settings ->
|
|
||||||
_uiState.update {
|
|
||||||
it.copy(
|
|
||||||
isUtc = settings.stateOfUtc,
|
|
||||||
shouldSeeWhatsNew = settings.shouldSeeWhatsNew
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onAction(action: PassesAction) {
|
fun onAction(action: PassesAction) {
|
||||||
@@ -101,18 +115,35 @@ class PassesViewModel(
|
|||||||
PassesAction.DismissWhatsNew -> settingsRepo.setWhatsNewDismissed()
|
PassesAction.DismissWhatsNew -> settingsRepo.setWhatsNewDismissed()
|
||||||
is PassesAction.FilterPasses ->
|
is PassesAction.FilterPasses ->
|
||||||
applyFilter(action.hoursAhead, action.minElevation, action.showDeepSpace, _uiState.value.modes)
|
applyFilter(action.hoursAhead, action.minElevation, action.showDeepSpace, _uiState.value.modes)
|
||||||
|
|
||||||
is PassesAction.FilterRadios ->
|
is PassesAction.FilterRadios ->
|
||||||
applyFilter(_uiState.value.hours, _uiState.value.elevation, _uiState.value.showDeepSpace, action.modes)
|
applyFilter(_uiState.value.hours, _uiState.value.elevation, _uiState.value.showDeepSpace, action.modes)
|
||||||
PassesAction.RefreshPasses -> refreshPasses()
|
PassesAction.RefreshPasses -> refreshPasses()
|
||||||
PassesAction.TogglePassesDialog ->
|
PassesAction.TogglePassesDialog ->
|
||||||
_uiState.update { it.copy(isPassesDialogShown = !it.isPassesDialogShown) }
|
_uiState.update { it.copy(isPassesDialogShown = !it.isPassesDialogShown) }
|
||||||
|
|
||||||
PassesAction.ToggleRadiosDialog ->
|
PassesAction.ToggleRadiosDialog ->
|
||||||
_uiState.update { it.copy(isRadiosDialogShown = !it.isRadiosDialogShown) }
|
_uiState.update { it.copy(isRadiosDialogShown = !it.isRadiosDialogShown) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Computes sunrise/sunset strings for each unique calendar day in the pass list. */
|
||||||
|
private fun computeSunTimes(passes: List<OrbitalPass>, isUtc: Boolean): Map<String, Pair<String, String>> {
|
||||||
|
val stationPos = settingsRepo.stationPosition.value
|
||||||
|
val tz = if (isUtc) TimeZone.getTimeZone("UTC") else TimeZone.getDefault()
|
||||||
|
val sdfDate = SimpleDateFormat("EEE, dd MMM yyyy", Locale.ENGLISH).also { it.timeZone = tz }
|
||||||
|
val sdfTime = SimpleDateFormat("HH:mm", Locale.ENGLISH).also { it.timeZone = tz }
|
||||||
|
val result = LinkedHashMap<String, Pair<String, String>>()
|
||||||
|
for (pass in passes) {
|
||||||
|
if (pass.isDeepSpace) continue
|
||||||
|
val label = sdfDate.format(Date(pass.aosTime))
|
||||||
|
if (label in result) continue
|
||||||
|
val riseSet = CelestialComputer.findSunRiseSet(stationPos, pass.aosTime)
|
||||||
|
val rise = if (riseSet.riseTimeMillis > 0) sdfTime.format(Date(riseSet.riseTimeMillis)) else "--:--"
|
||||||
|
val set = if (riseSet.setTimeMillis > 0) sdfTime.format(Date(riseSet.setTimeMillis)) else "--:--"
|
||||||
|
result[label] = rise to set
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
/** Computes live progress for each pass, filtering out expired ones. */
|
/** Computes live progress for each pass, filtering out expired ones. */
|
||||||
private fun computePassProgress(passList: List<OrbitalPass>, time: Long): List<OrbitalPass> {
|
private fun computePassProgress(passList: List<OrbitalPass>, time: Long): List<OrbitalPass> {
|
||||||
val result = ArrayList<OrbitalPass>(passList.size)
|
val result = ArrayList<OrbitalPass>(passList.size)
|
||||||
|
|||||||
Reference in new issue
Block a user