mirror of
https://github.com/rt-bishop/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
Added normalized time for AOS/LOS, fixed passes refresh
This commit is contained in:
1 parent
b59f9a6c17
commit
be96602c76
6 files changed
+116
-89
No files matched your search
+10
-39
@@ -54,8 +54,7 @@ class SatelliteRepo(
|
|||||||
settingsRepo.selectedIds.collect { selectedIds ->
|
settingsRepo.selectedIds.collect { selectedIds ->
|
||||||
_satellites.update { localStorage.getEntriesWithIds(selectedIds) }
|
_satellites.update { localStorage.getEntriesWithIds(selectedIds) }
|
||||||
val (hoursAhead, minElevation, modes) = settingsRepo.passesSettings.value
|
val (hoursAhead, minElevation, modes) = settingsRepo.passesSettings.value
|
||||||
val timeNow = 1000 * ((System.currentTimeMillis() + 500) / 1000)
|
calculatePasses(System.currentTimeMillis(), hoursAhead, minElevation, modes)
|
||||||
calculatePasses(timeNow, hoursAhead, minElevation, modes)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,27 +94,10 @@ class SatelliteRepo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun processPasses(passList: List<OrbitalPass>, time: Long): List<OrbitalPass> {
|
|
||||||
return withContext(dispatcher) {
|
|
||||||
val result = ArrayList<OrbitalPass>(passList.size)
|
|
||||||
for (pass in passList) {
|
|
||||||
if (!pass.isDeepSpace) {
|
|
||||||
val timeStart = pass.aosTime
|
|
||||||
if (time > timeStart) {
|
|
||||||
val deltaNow = time.minus(timeStart).toFloat()
|
|
||||||
val deltaTotal = pass.losTime.minus(timeStart).toFloat()
|
|
||||||
pass.progress = (deltaNow / deltaTotal).round(2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (pass.progress < 1.0f) {
|
|
||||||
result.add(pass.copy())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun calculatePasses(time: Long, hoursAhead: Int, minElevation: Double, modes: List<String>) {
|
override suspend fun calculatePasses(time: Long, hoursAhead: Int, minElevation: Double, modes: List<String>) {
|
||||||
|
// Normalize to the start of the current minute so that coarse 60-second stepping
|
||||||
|
// in getLeoPass always begins from the same phase, producing stable AOS/LOS times
|
||||||
|
val normalizedTime = time / 60_000L * 60_000L
|
||||||
val currentSatellites = _satellites.value
|
val currentSatellites = _satellites.value
|
||||||
if (currentSatellites.isNotEmpty()) {
|
if (currentSatellites.isNotEmpty()) {
|
||||||
withContext(dispatcher) {
|
withContext(dispatcher) {
|
||||||
@@ -129,13 +111,11 @@ class SatelliteRepo(
|
|||||||
// Compute passes for each satellite in parallel
|
// Compute passes for each satellite in parallel
|
||||||
val passLists = coroutineScope {
|
val passLists = coroutineScope {
|
||||||
filteredSatellites.map { satellite ->
|
filteredSatellites.map { satellite ->
|
||||||
async {
|
async { satellite.getPasses(stationPos, normalizedTime, hoursAhead) }
|
||||||
satellite.getPasses(stationPos, time, hoursAhead)
|
|
||||||
}
|
|
||||||
}.awaitAll()
|
}.awaitAll()
|
||||||
}
|
}
|
||||||
// Flatten and filter in a single pass
|
// Flatten and filter in a single pass
|
||||||
val timeFuture = time + (hoursAhead * 60L * 60L * 1000L)
|
val timeFuture = normalizedTime + (hoursAhead * 60L * 60L * 1000L)
|
||||||
val newPasses = ArrayList<OrbitalPass>()
|
val newPasses = ArrayList<OrbitalPass>()
|
||||||
for (list in passLists) {
|
for (list in passLists) {
|
||||||
for (pass in list) {
|
for (pass in list) {
|
||||||
@@ -177,7 +157,6 @@ class SatelliteRepo(
|
|||||||
return passes
|
return passes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun getGeoPass(sat: OrbitalObject, pos: GeoPos, time: Long): OrbitalPass {
|
private fun getGeoPass(sat: OrbitalObject, pos: GeoPos, time: Long): OrbitalPass {
|
||||||
val satPos = sat.getPosition(pos, time)
|
val satPos = sat.getPosition(pos, time)
|
||||||
val aos = time - 24 * 60L * 60L * 1000L
|
val aos = time - 24 * 60L * 60L * 1000L
|
||||||
@@ -210,9 +189,7 @@ class SatelliteRepo(
|
|||||||
do {
|
do {
|
||||||
calendarTimeMillis += 60L * 1000L
|
calendarTimeMillis += 60L * 1000L
|
||||||
elevation = sat.getElevation(pos, calendarTimeMillis)
|
elevation = sat.getElevation(pos, calendarTimeMillis)
|
||||||
if (elevation > maxElevation) {
|
if (elevation > maxElevation) maxElevation = elevation
|
||||||
maxElevation = elevation
|
|
||||||
}
|
|
||||||
} while (elevation < 0.0)
|
} while (elevation < 0.0)
|
||||||
|
|
||||||
// refine AOS to ~500ms precision
|
// refine AOS to ~500ms precision
|
||||||
@@ -220,9 +197,7 @@ class SatelliteRepo(
|
|||||||
do {
|
do {
|
||||||
calendarTimeMillis += 500L
|
calendarTimeMillis += 500L
|
||||||
elevation = sat.getElevation(pos, calendarTimeMillis)
|
elevation = sat.getElevation(pos, calendarTimeMillis)
|
||||||
if (elevation > maxElevation) {
|
if (elevation > maxElevation) maxElevation = elevation
|
||||||
maxElevation = elevation
|
|
||||||
}
|
|
||||||
} while (elevation < 0.0)
|
} while (elevation < 0.0)
|
||||||
|
|
||||||
// Get full position for AOS data (azimuth, altitude)
|
// Get full position for AOS data (azimuth, altitude)
|
||||||
@@ -234,9 +209,7 @@ class SatelliteRepo(
|
|||||||
do {
|
do {
|
||||||
calendarTimeMillis += 30L * 1000L
|
calendarTimeMillis += 30L * 1000L
|
||||||
elevation = sat.getElevation(pos, calendarTimeMillis)
|
elevation = sat.getElevation(pos, calendarTimeMillis)
|
||||||
if (elevation > maxElevation) {
|
if (elevation > maxElevation) maxElevation = elevation
|
||||||
maxElevation = elevation
|
|
||||||
}
|
|
||||||
} while (elevation > 0.0)
|
} while (elevation > 0.0)
|
||||||
|
|
||||||
// refine LOS to ~500ms precision
|
// refine LOS to ~500ms precision
|
||||||
@@ -244,9 +217,7 @@ class SatelliteRepo(
|
|||||||
do {
|
do {
|
||||||
calendarTimeMillis += 500L
|
calendarTimeMillis += 500L
|
||||||
elevation = sat.getElevation(pos, calendarTimeMillis)
|
elevation = sat.getElevation(pos, calendarTimeMillis)
|
||||||
if (elevation > maxElevation) {
|
if (elevation > maxElevation) maxElevation = elevation
|
||||||
maxElevation = elevation
|
|
||||||
}
|
|
||||||
} while (elevation > 0.0)
|
} while (elevation > 0.0)
|
||||||
|
|
||||||
// Get full position for LOS data (azimuth, altitude)
|
// Get full position for LOS data (azimuth, altitude)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ data class OrbitalPass(
|
|||||||
val altitude: Int = 1000,
|
val altitude: Int = 1000,
|
||||||
val maxElevation: Double = 75.0,
|
val maxElevation: Double = 75.0,
|
||||||
val orbitalObject: OrbitalObject,
|
val orbitalObject: OrbitalObject,
|
||||||
var progress: Float = 0.0f
|
val progress: Float = 0.0f
|
||||||
) {
|
) {
|
||||||
val catNum: Int = orbitalObject.data.catnum
|
val catNum: Int = orbitalObject.data.catnum
|
||||||
val name: String = orbitalObject.data.name
|
val name: String = orbitalObject.data.name
|
||||||
|
|||||||
+20
-6
@@ -25,13 +25,27 @@ import com.rtbishop.look4sat.core.domain.predict.OrbitalPos
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
interface ISatelliteRepo {
|
interface ISatelliteRepo {
|
||||||
val passes: StateFlow<List<OrbitalPass>>
|
/** All selected OrbitalObjects, updated when the selection changes. */
|
||||||
val satellites: StateFlow<List<OrbitalObject>>
|
val satellites: StateFlow<List<OrbitalObject>>
|
||||||
suspend fun getRadiosWithId(id: Int): List<SatRadio>
|
|
||||||
|
/** Raw calculated passes (without live progress). Updated on selection/filter change. */
|
||||||
|
val passes: StateFlow<List<OrbitalPass>>
|
||||||
|
|
||||||
|
/** Load satellite objects from DB based on the current selection. */
|
||||||
suspend fun initRepository()
|
suspend fun initRepository()
|
||||||
suspend fun getPosition(sat: OrbitalObject, pos: GeoPos, time: Long): OrbitalPos
|
|
||||||
suspend fun getTrack(sat: OrbitalObject, pos: GeoPos, start: Long, end: Long): List<OrbitalPos>
|
/** Recalculate passes with the given filter parameters. */
|
||||||
suspend fun getRadios(sat: OrbitalObject, pos: GeoPos, radios: List<SatRadio>, time: Long): List<SatRadio>
|
|
||||||
suspend fun processPasses(passList: List<OrbitalPass>, time: Long): List<OrbitalPass>
|
|
||||||
suspend fun calculatePasses(time: Long, hoursAhead: Int, minElevation: Double, modes: List<String>)
|
suspend fun calculatePasses(time: Long, hoursAhead: Int, minElevation: Double, modes: List<String>)
|
||||||
|
|
||||||
|
/** Get the current position of a single satellite. */
|
||||||
|
suspend fun getPosition(sat: OrbitalObject, pos: GeoPos, time: Long): OrbitalPos
|
||||||
|
|
||||||
|
/** Get the ground track for a satellite over a time range. */
|
||||||
|
suspend fun getTrack(sat: OrbitalObject, pos: GeoPos, start: Long, end: Long): List<OrbitalPos>
|
||||||
|
|
||||||
|
/** Get Doppler-shifted radio frequencies for a satellite at the given time. */
|
||||||
|
suspend fun getRadios(sat: OrbitalObject, pos: GeoPos, radios: List<SatRadio>, time: Long): List<SatRadio>
|
||||||
|
|
||||||
|
/** Fetch radio transceivers for a satellite by its catalog number. */
|
||||||
|
suspend fun getRadiosWithId(id: Int): List<SatRadio>
|
||||||
}
|
}
|
||||||
@@ -44,6 +44,7 @@ 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.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
@@ -223,6 +224,10 @@ private fun NearEarthPass(
|
|||||||
) {
|
) {
|
||||||
val passSatId = stringResource(id = R.string.pass_satId, pass.catNum)
|
val passSatId = stringResource(id = R.string.pass_satId, pass.catNum)
|
||||||
val horizontalPadding = if (isVerticalLayout) 6.dp else 10.dp
|
val horizontalPadding = if (isVerticalLayout) 6.dp else 10.dp
|
||||||
|
// Cache formatted date/time strings — aosTime/losTime never change for a given pass
|
||||||
|
val aosDateStr = remember(pass.aosTime) { sdfDate.format(Date(pass.aosTime)) }
|
||||||
|
val aosTimeStr = remember(pass.aosTime) { sdfTime.format(Date(pass.aosTime)) }
|
||||||
|
val losTimeStr = remember(pass.losTime) { sdfTime.format(Date(pass.losTime)) }
|
||||||
Surface(color = MaterialTheme.colorScheme.background, modifier = modifier) {
|
Surface(color = MaterialTheme.colorScheme.background, modifier = modifier) {
|
||||||
Surface(modifier = Modifier
|
Surface(modifier = Modifier
|
||||||
.padding(bottom = 2.dp)
|
.padding(bottom = 2.dp)
|
||||||
@@ -274,7 +279,7 @@ private fun NearEarthPass(
|
|||||||
Text(text = stringResource(R.string.pass_deep_space), fontSize = 15.sp)
|
Text(text = stringResource(R.string.pass_deep_space), fontSize = 15.sp)
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
text = sdfDate.format(Date(pass.aosTime)),
|
text = aosDateStr,
|
||||||
fontSize = 15.sp
|
fontSize = 15.sp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -317,7 +322,7 @@ private fun NearEarthPass(
|
|||||||
) {
|
) {
|
||||||
val defaultTime = " - - : - - "
|
val defaultTime = " - - : - - "
|
||||||
Text(
|
Text(
|
||||||
text = if (pass.isDeepSpace) defaultTime else sdfTime.format(Date(pass.aosTime)),
|
text = if (pass.isDeepSpace) defaultTime else aosTimeStr,
|
||||||
fontSize = 15.sp
|
fontSize = 15.sp
|
||||||
)
|
)
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
@@ -326,7 +331,7 @@ private fun NearEarthPass(
|
|||||||
modifier = modifier.fillMaxWidth(0.75f)
|
modifier = modifier.fillMaxWidth(0.75f)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = if (pass.isDeepSpace) defaultTime else sdfTime.format(Date(pass.losTime)),
|
text = if (pass.isDeepSpace) defaultTime else losTimeStr,
|
||||||
fontSize = 15.sp
|
fontSize = 15.sp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+62
-27
@@ -27,10 +27,9 @@ 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
|
||||||
import com.rtbishop.look4sat.core.domain.repository.ISettingsRepo
|
import com.rtbishop.look4sat.core.domain.repository.ISettingsRepo
|
||||||
|
import com.rtbishop.look4sat.core.domain.utility.round
|
||||||
import com.rtbishop.look4sat.core.domain.utility.toTimerString
|
import com.rtbishop.look4sat.core.domain.utility.toTimerString
|
||||||
import com.rtbishop.look4sat.core.presentation.getDefaultPass
|
import com.rtbishop.look4sat.core.presentation.getDefaultPass
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.cancelAndJoin
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
@@ -44,6 +43,7 @@ class PassesViewModel(
|
|||||||
private val settingsRepo: ISettingsRepo
|
private val settingsRepo: ISettingsRepo
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val defaultPass = getDefaultPass()
|
||||||
private val _uiState = MutableStateFlow(
|
private val _uiState = MutableStateFlow(
|
||||||
PassesState(
|
PassesState(
|
||||||
isPassesDialogShown = false,
|
isPassesDialogShown = false,
|
||||||
@@ -52,7 +52,7 @@ class PassesViewModel(
|
|||||||
isUtc = settingsRepo.otherSettings.value.stateOfUtc,
|
isUtc = settingsRepo.otherSettings.value.stateOfUtc,
|
||||||
nextTime = "00:00:00",
|
nextTime = "00:00:00",
|
||||||
isNextTimeAos = true,
|
isNextTimeAos = true,
|
||||||
nextPass = getDefaultPass(),
|
nextPass = defaultPass,
|
||||||
hours = settingsRepo.passesSettings.value.hoursAhead,
|
hours = settingsRepo.passesSettings.value.hoursAhead,
|
||||||
elevation = settingsRepo.passesSettings.value.minElevation,
|
elevation = settingsRepo.passesSettings.value.minElevation,
|
||||||
modes = settingsRepo.passesSettings.value.selectedModes,
|
modes = settingsRepo.passesSettings.value.selectedModes,
|
||||||
@@ -61,25 +61,36 @@ class PassesViewModel(
|
|||||||
takeAction = ::handleAction
|
takeAction = ::handleAction
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
private var processing: Job? = null
|
|
||||||
val uiState: StateFlow<PassesState> = _uiState
|
val uiState: StateFlow<PassesState> = _uiState
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
// React to raw pass list changes (initial load, recalculation, filter change)
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
delay(1000)
|
var initialLoadDone = false
|
||||||
satelliteRepo.passes.collectLatest { passes ->
|
satelliteRepo.passes.collectLatest { passes ->
|
||||||
processing?.cancelAndJoin()
|
if (!initialLoadDone && passes.isNotEmpty()) {
|
||||||
processing = viewModelScope.launch {
|
initialLoadDone = true
|
||||||
|
_uiState.update { it.copy(isRefreshing = false) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Local tick loop — computes pass progress and countdown timer every second
|
||||||
|
viewModelScope.launch {
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
val timeNow = System.currentTimeMillis()
|
val timeNow = System.currentTimeMillis()
|
||||||
val newPasses = satelliteRepo.processPasses(passes, timeNow)
|
val processed = computePassProgress(satelliteRepo.passes.value, timeNow)
|
||||||
setPassInfo(newPasses, timeNow)
|
val nextInfo = resolveNextPass(processed, timeNow)
|
||||||
_uiState.update { it.copy(isRefreshing = false, itemsList = newPasses) }
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
itemsList = processed,
|
||||||
|
nextPass = nextInfo.first,
|
||||||
|
nextTime = nextInfo.second,
|
||||||
|
isNextTimeAos = nextInfo.third
|
||||||
|
)
|
||||||
|
}
|
||||||
delay(1000)
|
delay(1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
settingsRepo.otherSettings.collectLatest { settings ->
|
settingsRepo.otherSettings.collectLatest { settings ->
|
||||||
_uiState.update { it.copy(shouldSeeWhatsNew = settings.shouldSeeWhatsNew) }
|
_uiState.update { it.copy(shouldSeeWhatsNew = settings.shouldSeeWhatsNew) }
|
||||||
@@ -87,6 +98,43 @@ class PassesViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Computes live progress for each pass, filtering out expired ones. */
|
||||||
|
private fun computePassProgress(passList: List<OrbitalPass>, time: Long): List<OrbitalPass> {
|
||||||
|
val result = ArrayList<OrbitalPass>(passList.size)
|
||||||
|
for (pass in passList) {
|
||||||
|
if (!pass.isDeepSpace && time > pass.aosTime) {
|
||||||
|
val deltaNow = time.minus(pass.aosTime).toFloat()
|
||||||
|
val deltaTotal = pass.losTime.minus(pass.aosTime).toFloat()
|
||||||
|
val newProgress = (deltaNow / deltaTotal).round(2)
|
||||||
|
if (newProgress >= 1.0f) continue
|
||||||
|
if (newProgress != pass.progress) {
|
||||||
|
result.add(pass.copy(progress = newProgress))
|
||||||
|
} else {
|
||||||
|
result.add(pass)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.add(pass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Resolves the next upcoming or active pass and its countdown timer. */
|
||||||
|
private fun resolveNextPass(
|
||||||
|
passes: List<OrbitalPass>,
|
||||||
|
timeNow: Long
|
||||||
|
): Triple<OrbitalPass, String, Boolean> {
|
||||||
|
val upcoming = passes.firstOrNull { it.aosTime.minus(timeNow) > 0 }
|
||||||
|
if (upcoming != null) {
|
||||||
|
return Triple(upcoming, upcoming.aosTime.minus(timeNow).toTimerString(), true)
|
||||||
|
}
|
||||||
|
if (passes.isNotEmpty()) {
|
||||||
|
val lastPass = passes.last()
|
||||||
|
return Triple(lastPass, lastPass.losTime.minus(timeNow).toTimerString(), false)
|
||||||
|
}
|
||||||
|
return Triple(defaultPass, "00:00:00", true)
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleAction(action: PassesAction) {
|
private fun handleAction(action: PassesAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
PassesAction.DismissWhatsNew -> settingsRepo.setWhatsNewDismissed()
|
PassesAction.DismissWhatsNew -> settingsRepo.setWhatsNewDismissed()
|
||||||
@@ -100,30 +148,17 @@ class PassesViewModel(
|
|||||||
|
|
||||||
private fun applyFilter(hoursAhead: Int, minElevation: Double, modes: List<String>) = viewModelScope.launch {
|
private fun applyFilter(hoursAhead: Int, minElevation: Double, modes: List<String>) = viewModelScope.launch {
|
||||||
_uiState.update { it.copy(isRefreshing = true) }
|
_uiState.update { it.copy(isRefreshing = true) }
|
||||||
processing?.cancelAndJoin()
|
|
||||||
settingsRepo.setPassesSettings(PassesSettings(hoursAhead, minElevation, modes))
|
settingsRepo.setPassesSettings(PassesSettings(hoursAhead, minElevation, modes))
|
||||||
_uiState.update { it.copy(hours = hoursAhead, elevation = minElevation, modes = modes) }
|
_uiState.update { it.copy(hours = hoursAhead, elevation = minElevation, modes = modes) }
|
||||||
satelliteRepo.calculatePasses(System.currentTimeMillis(), hoursAhead, minElevation, modes)
|
satelliteRepo.calculatePasses(System.currentTimeMillis(), hoursAhead, minElevation, modes)
|
||||||
|
_uiState.update { it.copy(isRefreshing = false) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshPasses() = viewModelScope.launch {
|
private fun refreshPasses() = viewModelScope.launch {
|
||||||
_uiState.update { it.copy(isRefreshing = true) }
|
_uiState.update { it.copy(isRefreshing = true) }
|
||||||
processing?.cancelAndJoin()
|
|
||||||
val (hoursAhead, minElevation, modes) = settingsRepo.passesSettings.value
|
val (hoursAhead, minElevation, modes) = settingsRepo.passesSettings.value
|
||||||
satelliteRepo.calculatePasses(System.currentTimeMillis(), hoursAhead, minElevation, modes)
|
satelliteRepo.calculatePasses(System.currentTimeMillis(), hoursAhead, minElevation, modes)
|
||||||
}
|
_uiState.update { it.copy(isRefreshing = false) }
|
||||||
|
|
||||||
private fun setPassInfo(passes: List<OrbitalPass>, timeNow: Long) {
|
|
||||||
if (passes.isEmpty()) return
|
|
||||||
try {
|
|
||||||
val nextPass = passes.first { it.aosTime.minus(timeNow) > 0 }
|
|
||||||
val time = nextPass.aosTime.minus(timeNow).toTimerString()
|
|
||||||
_uiState.update { it.copy(nextPass = nextPass, nextTime = time, isNextTimeAos = true) }
|
|
||||||
} catch (_: NoSuchElementException) {
|
|
||||||
val lastPass = passes.last()
|
|
||||||
val time = lastPass.losTime.minus(timeNow).toTimerString()
|
|
||||||
_uiState.update { it.copy(nextPass = lastPass, nextTime = time, isNextTimeAos = false) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toggleFilterDialog() {
|
private fun toggleFilterDialog() {
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import androidx.lifecycle.viewmodel.initializer
|
|||||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
import com.rtbishop.look4sat.core.domain.model.SatRadio
|
import com.rtbishop.look4sat.core.domain.model.SatRadio
|
||||||
import com.rtbishop.look4sat.core.domain.predict.OrbitalObject
|
import com.rtbishop.look4sat.core.domain.predict.OrbitalObject
|
||||||
import com.rtbishop.look4sat.core.domain.predict.OrbitalPass
|
|
||||||
import com.rtbishop.look4sat.core.domain.predict.OrbitalPos
|
import com.rtbishop.look4sat.core.domain.predict.OrbitalPos
|
||||||
import com.rtbishop.look4sat.core.domain.repository.IContainerProvider
|
import com.rtbishop.look4sat.core.domain.repository.IContainerProvider
|
||||||
import com.rtbishop.look4sat.core.domain.repository.IReporter
|
import com.rtbishop.look4sat.core.domain.repository.IReporter
|
||||||
@@ -75,6 +74,7 @@ class RadarViewModel(
|
|||||||
val uiState: StateFlow<RadarState> = _uiState
|
val uiState: StateFlow<RadarState> = _uiState
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
// Compass sensor collection
|
||||||
if (settingsRepo.otherSettings.value.stateOfSensors) {
|
if (settingsRepo.otherSettings.value.stateOfSensors) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
sensorsRepo.enableSensor()
|
sensorsRepo.enableSensor()
|
||||||
@@ -84,6 +84,7 @@ class RadarViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Resolve which pass we're tracking
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val catNum = savedStateHandle.get<Int>("catNum") ?: 0
|
val catNum = savedStateHandle.get<Int>("catNum") ?: 0
|
||||||
val aosTime = savedStateHandle.get<Long>("aosTime") ?: 0L
|
val aosTime = savedStateHandle.get<Long>("aosTime") ?: 0L
|
||||||
@@ -93,25 +94,33 @@ class RadarViewModel(
|
|||||||
currentPass?.let { satPass ->
|
currentPass?.let { satPass ->
|
||||||
_uiState.update { it.copy(currentPass = satPass) }
|
_uiState.update { it.copy(currentPass = satPass) }
|
||||||
val transmitters = satelliteRepo.getRadiosWithId(satPass.catNum)
|
val transmitters = satelliteRepo.getRadiosWithId(satPass.catNum)
|
||||||
|
// Compute track once (it doesn't change)
|
||||||
|
if (!satPass.isDeepSpace) {
|
||||||
|
val track = satelliteRepo.getTrack(
|
||||||
|
satPass.orbitalObject, stationPos, satPass.aosTime, satPass.losTime
|
||||||
|
)
|
||||||
|
_uiState.update { it.copy(satTrack = track) }
|
||||||
|
}
|
||||||
|
// Local tick loop — computes position only while the radar screen is alive
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
val timeNow = System.currentTimeMillis()
|
val timeNow = System.currentTimeMillis()
|
||||||
val pos = satelliteRepo.getPosition(satPass.orbitalObject, stationPos, timeNow)
|
val pos = satelliteRepo.getPosition(satPass.orbitalObject, stationPos, timeNow)
|
||||||
when {
|
when {
|
||||||
satPass.isDeepSpace -> {
|
satPass.isDeepSpace -> {
|
||||||
val time = 0L.toTimerString()
|
val time = 0L.toTimerString()
|
||||||
_uiState.update { it.copy(currentTime = time, isCurrentTimeAos = false) }
|
_uiState.update { it.copy(currentTime = time, isCurrentTimeAos = false, orbitalPos = pos) }
|
||||||
}
|
}
|
||||||
satPass.aosTime > timeNow -> {
|
satPass.aosTime > timeNow -> {
|
||||||
val time = satPass.aosTime.minus(timeNow).toTimerString()
|
val time = satPass.aosTime.minus(timeNow).toTimerString()
|
||||||
_uiState.update { it.copy(currentTime = time, isCurrentTimeAos = true) }
|
_uiState.update { it.copy(currentTime = time, isCurrentTimeAos = true, orbitalPos = pos) }
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val time = satPass.losTime.minus(timeNow).toTimerString()
|
val time = satPass.losTime.minus(timeNow).toTimerString()
|
||||||
_uiState.update { it.copy(currentTime = time, isCurrentTimeAos = false) }
|
_uiState.update { it.copy(currentTime = time, isCurrentTimeAos = false, orbitalPos = pos) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
processRadios(transmitters, satPass.orbitalObject, timeNow)
|
processRadios(transmitters, satPass.orbitalObject, timeNow)
|
||||||
sendPassData(satPass, pos, satPass.orbitalObject)
|
sendPassData(pos)
|
||||||
sendPassDataBT(pos)
|
sendPassDataBT(pos)
|
||||||
delay(1000)
|
delay(1000)
|
||||||
}
|
}
|
||||||
@@ -131,14 +140,7 @@ class RadarViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun sendPassData(orbitalPass: OrbitalPass, orbitalPos: OrbitalPos, orbitalObject: OrbitalObject) {
|
private fun sendPassData(orbitalPos: OrbitalPos) {
|
||||||
var track: List<OrbitalPos> = emptyList()
|
|
||||||
if (!orbitalPass.isDeepSpace) {
|
|
||||||
track = satelliteRepo.getTrack(
|
|
||||||
orbitalObject, stationPos, orbitalPass.aosTime, orbitalPass.losTime
|
|
||||||
)
|
|
||||||
}
|
|
||||||
_uiState.update { it.copy(orbitalPos = orbitalPos, satTrack = track) }
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val rc = settingsRepo.rcSettings.value
|
val rc = settingsRepo.rcSettings.value
|
||||||
if (rc.rotatorState) {
|
if (rc.rotatorState) {
|
||||||
|
|||||||
Reference in new issue
Block a user