mirror of
https://github.com/rt-bishop/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
Added PassesViewModel.kt
This commit is contained in:
1 parent
d32e5a5ce3
commit
4edf316b5e
21 files changed
+106
-99
No files matched your search
@@ -23,4 +23,4 @@
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
@@ -29,4 +29,4 @@ class Look4SatApp : Application() {
|
||||
Timber.plant(Timber.DebugTree())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,8 +41,10 @@ class PassesRepo @Inject constructor(
|
||||
private var selectedSatellites = emptyList<Satellite>()
|
||||
|
||||
suspend fun calculatePasses(satellites: List<Satellite>) {
|
||||
if (satellites !== selectedSatellites) {
|
||||
withContext(defaultDispatcher) {
|
||||
withContext(defaultDispatcher) {
|
||||
val newCatNums = satellites.map { it.tle.catnum }
|
||||
val oldCatNums = selectedSatellites.map { it.tle.catnum }
|
||||
if (newCatNums != oldCatNums) {
|
||||
_passes.value = Result.InProgress
|
||||
selectedSatellites = satellites
|
||||
val refDate = Date(System.currentTimeMillis())
|
||||
@@ -75,4 +77,4 @@ class PassesRepo @Inject constructor(
|
||||
passes.sortBy { it.pass.startTime }
|
||||
return passes
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,4 +43,4 @@ class MainActivity : AppCompatActivity() {
|
||||
if (prefsRepo.isFirstLaunch()) navHost.navController.navigate(R.id.nav_dialog_splash)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,26 +17,24 @@
|
||||
*/
|
||||
package com.rtbishop.look4sat.ui
|
||||
|
||||
import androidx.lifecycle.*
|
||||
import com.github.amsacode.predict4java.Satellite
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.liveData
|
||||
import com.rtbishop.look4sat.data.model.Result
|
||||
import com.rtbishop.look4sat.data.model.SatPass
|
||||
import com.rtbishop.look4sat.data.repository.PrefsRepo
|
||||
import com.rtbishop.look4sat.data.repository.SatelliteRepo
|
||||
import com.rtbishop.look4sat.utility.getPredictor
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@FlowPreview
|
||||
@HiltViewModel
|
||||
class SharedViewModel @Inject constructor(
|
||||
private val prefsRepo: PrefsRepo,
|
||||
prefsRepo: PrefsRepo,
|
||||
private val satelliteRepo: SatelliteRepo,
|
||||
) : ViewModel() {
|
||||
|
||||
@@ -47,7 +45,6 @@ class SharedViewModel @Inject constructor(
|
||||
if (prefsRepo.isFirstLaunch()) {
|
||||
prefsRepo.setFirstLaunchDone()
|
||||
}
|
||||
calculatePasses()
|
||||
}
|
||||
|
||||
fun getAppTimer() = liveData {
|
||||
@@ -58,36 +55,4 @@ class SharedViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun getTransmittersForSat(satId: Int) = satelliteRepo.getSatTransmitters(satId).asLiveData()
|
||||
|
||||
fun calculatePasses(dateNow: Date = Date(System.currentTimeMillis())) {
|
||||
_passes.value = Result.InProgress
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
val passes = mutableListOf<SatPass>()
|
||||
satelliteRepo.getSelectedSatellites().forEach { satellite ->
|
||||
passes.addAll(getPasses(satellite, dateNow))
|
||||
}
|
||||
val filteredPasses = sortList(passes, dateNow)
|
||||
_passes.value = Result.Success(filteredPasses)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPasses(satellite: Satellite, dateNow: Date): MutableList<SatPass> {
|
||||
val predictor = satellite.getPredictor(prefsRepo.getStationPosition())
|
||||
val passes = predictor.getPasses(dateNow, prefsRepo.getHoursAhead(), true)
|
||||
val passList = passes.map { SatPass(satellite.tle, predictor, it) }
|
||||
return passList as MutableList<SatPass>
|
||||
}
|
||||
|
||||
private fun sortList(passes: MutableList<SatPass>, dateNow: Date): MutableList<SatPass> {
|
||||
val hoursAhead = prefsRepo.getHoursAhead()
|
||||
val dateFuture = Calendar.getInstance().apply {
|
||||
this.time = dateNow
|
||||
this.add(Calendar.HOUR, hoursAhead)
|
||||
}.time
|
||||
passes.removeAll { it.pass.startTime.after(dateFuture) }
|
||||
passes.removeAll { it.pass.endTime.before(dateNow) }
|
||||
passes.removeAll { it.pass.maxEl < prefsRepo.getMinElevation() }
|
||||
passes.sortBy { it.pass.startTime }
|
||||
return passes
|
||||
}
|
||||
}
|
||||
@@ -145,4 +145,4 @@ class EntriesFragment : Fragment(R.layout.fragment_entries), EntriesAdapter.Entr
|
||||
binding = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,4 +79,4 @@ class EntriesViewModel @Inject constructor(
|
||||
satelliteRepo.updateEntriesSelection(items, isSelected)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,4 +204,4 @@ class MapFragment : Fragment(R.layout.fragment_map) {
|
||||
)
|
||||
return XYTileSource("wikimedia", 0, 6, 256, ".png", sources, copyright, policy)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,4 +196,4 @@ class MapViewModel @Inject constructor(
|
||||
Position(osmLat, osmLon)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,32 +20,26 @@ package com.rtbishop.look4sat.ui.passesScreen
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||
import com.rtbishop.look4sat.R
|
||||
import com.rtbishop.look4sat.data.model.Result
|
||||
import com.rtbishop.look4sat.data.model.SatPass
|
||||
import com.rtbishop.look4sat.databinding.FragmentPassesBinding
|
||||
import com.rtbishop.look4sat.ui.SharedViewModel
|
||||
import com.rtbishop.look4sat.data.repository.PrefsRepo
|
||||
import com.rtbishop.look4sat.utility.RecyclerDivider
|
||||
import com.rtbishop.look4sat.utility.formatForTimer
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@FlowPreview
|
||||
@AndroidEntryPoint
|
||||
class PassesFragment : Fragment(R.layout.fragment_passes) {
|
||||
|
||||
@Inject
|
||||
lateinit var prefsRepo: PrefsRepo
|
||||
|
||||
private var binding: FragmentPassesBinding? = null
|
||||
private var passesAdapter: PassesAdapter? = null
|
||||
private val viewModel: SharedViewModel by activityViewModels()
|
||||
private val viewModel: PassesViewModel by viewModels()
|
||||
private var passes = mutableListOf<SatPass>()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
@@ -90,7 +84,7 @@ class PassesFragment : Fragment(R.layout.fragment_passes) {
|
||||
}
|
||||
|
||||
private fun setupComponents() {
|
||||
passesAdapter = PassesAdapter(requireContext(), prefsRepo.shouldUseUTC())
|
||||
passesAdapter = PassesAdapter(requireContext(), viewModel.shouldUseUTC())
|
||||
binding?.apply {
|
||||
passesRecycler.apply {
|
||||
setHasFixedSize(true)
|
||||
@@ -100,7 +94,6 @@ class PassesFragment : Fragment(R.layout.fragment_passes) {
|
||||
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||
addItemDecoration(RecyclerDivider(R.drawable.rec_divider_dark))
|
||||
}
|
||||
passesFab.setOnClickListener { viewModel.calculatePasses() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.rtbishop.look4sat.ui.passesScreen
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.liveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.rtbishop.look4sat.data.repository.PassesRepo
|
||||
import com.rtbishop.look4sat.data.repository.PrefsRepo
|
||||
import com.rtbishop.look4sat.data.repository.SatelliteRepo
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class PassesViewModel @Inject constructor(
|
||||
private val prefsRepo: PrefsRepo,
|
||||
private val satelliteRepo: SatelliteRepo,
|
||||
private val passesRepo: PassesRepo
|
||||
) : ViewModel() {
|
||||
|
||||
val passes = passesRepo.passes.asLiveData()
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
satelliteRepo.getSelectedSatellitesFlow().collect { selectedSatellites ->
|
||||
passesRepo.calculatePasses(selectedSatellites)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getAppTimer() = liveData {
|
||||
while (true) {
|
||||
emit(System.currentTimeMillis())
|
||||
delay(1000)
|
||||
}
|
||||
}
|
||||
|
||||
fun shouldUseUTC(): Boolean {
|
||||
return prefsRepo.shouldUseUTC()
|
||||
}
|
||||
}
|
||||
@@ -127,4 +127,4 @@ class PolarView(context: Context) : View(context) {
|
||||
val radius = r * (piDiv2 - elevation) / piDiv2
|
||||
return (radius * sin(piDiv2 - azimuth)).toFloat()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,4 +71,4 @@ class SourcesAdapter : RecyclerView.Adapter<SourcesAdapter.TleSourceHolder>() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,4 +63,4 @@ class SourcesDialog : AppCompatDialogFragment() {
|
||||
tleSourcesBtnNeg.setOnClickListener { dismiss() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,4 +42,4 @@ class SplashDialog : AppCompatDialogFragment() {
|
||||
splashButton.setOnClickListener { dismiss() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,7 @@ import java.util.*
|
||||
class PassPredictor(private val satellite: Satellite, private val qth: GroundStationPosition) {
|
||||
private val oneQuarterOrbitMin = (24.0 * 60.0 / satellite.tle.meanmo / 4.0).toInt()
|
||||
private val speedOfLight = 2.99792458E8
|
||||
private val polePassed = "none"
|
||||
|
||||
|
||||
fun getDownlinkFreq(freq: Long, date: Date): Long {
|
||||
val rangeRate = getSatPos(date).rangeRate
|
||||
return (freq.toDouble() * (speedOfLight - rangeRate * 1000.0) / speedOfLight).toLong()
|
||||
@@ -47,7 +46,6 @@ class PassPredictor(private val satellite: Satellite, private val qth: GroundSta
|
||||
val endDate = Date(refDate.time + minAfter * 60L * 1000L)
|
||||
val startDate = Date(refDate.time - minBefore * 60L * 1000L)
|
||||
var currentDate = startDate
|
||||
|
||||
while (currentDate.before(endDate)) {
|
||||
positions.add(getSatPos(currentDate))
|
||||
currentDate = Date(currentDate.time + stepSeconds * 1000)
|
||||
@@ -62,7 +60,6 @@ class PassPredictor(private val satellite: Satellite, private val qth: GroundSta
|
||||
var shouldWindBack = windBack
|
||||
var lastAosDate: Date
|
||||
var count = 0
|
||||
|
||||
if (satellite.willBeSeen(qth)) {
|
||||
if (satellite.tle.isDeepspace) {
|
||||
passes.add(nextDeepSpacePass(refDate))
|
||||
@@ -87,7 +84,7 @@ class PassPredictor(private val satellite: Satellite, private val qth: GroundSta
|
||||
val aosAzimuth = (satPos.azimuth / (2.0 * Math.PI) * 360.0).toInt()
|
||||
val losAzimuth = (satPos.azimuth / (2.0 * Math.PI) * 360.0).toInt()
|
||||
val maxEl = satPos.elevation / (2.0 * Math.PI) * 360.0
|
||||
return SatPassTime(startDate, endDate, polePassed, aosAzimuth, losAzimuth, maxEl)
|
||||
return SatPassTime(startDate, endDate, "pole", aosAzimuth, losAzimuth, maxEl)
|
||||
}
|
||||
|
||||
private fun nextNearEarthPass(refDate: Date, windBack: Boolean = false): SatPassTime {
|
||||
@@ -101,27 +98,26 @@ class PassPredictor(private val satellite: Satellite, private val qth: GroundSta
|
||||
// wind back time 1/4 of an orbit
|
||||
if (windBack) calendar.add(Calendar.MINUTE, -oneQuarterOrbitMin)
|
||||
var satPos = getSatPos(calendar.time)
|
||||
|
||||
// test for the elevation being above the horizon
|
||||
|
||||
if (satPos.elevation > 0.0) {
|
||||
// move time forward in 30 second intervals until the sat goes below the horizon
|
||||
// move forward in 30 second intervals until the sat goes below the horizon
|
||||
do {
|
||||
calendar.add(Calendar.SECOND, 30)
|
||||
satPos = getSatPos(calendar.time)
|
||||
} while (satPos.elevation > 0.0)
|
||||
// move time forward 3/4 orbit
|
||||
// move forward 3/4 of an orbit
|
||||
calendar.add(Calendar.MINUTE, oneQuarterOrbitMin * 3)
|
||||
}
|
||||
|
||||
// find the next time it comes above the horizon
|
||||
|
||||
// find the next time sat comes above the horizon
|
||||
do {
|
||||
calendar.add(Calendar.SECOND, 60)
|
||||
satPos = getSatPos(calendar.time)
|
||||
elevation = satPos.elevation
|
||||
if (elevation > maxElevation) maxElevation = elevation
|
||||
} while (satPos.elevation < 0.0)
|
||||
|
||||
// refine it to 3 seconds
|
||||
|
||||
// refine to 3 seconds
|
||||
calendar.add(Calendar.SECOND, -60)
|
||||
do {
|
||||
calendar.add(Calendar.SECOND, 3)
|
||||
@@ -129,19 +125,19 @@ class PassPredictor(private val satellite: Satellite, private val qth: GroundSta
|
||||
elevation = satPos.elevation
|
||||
if (elevation > maxElevation) maxElevation = elevation
|
||||
} while (satPos.elevation < 0.0)
|
||||
|
||||
|
||||
val startDate = satPos.time
|
||||
val aosAzimuth = (satPos.azimuth / (2.0 * Math.PI) * 360.0).toInt()
|
||||
|
||||
// now find when it goes below
|
||||
|
||||
// find when sat goes below
|
||||
do {
|
||||
calendar.add(Calendar.SECOND, 30)
|
||||
satPos = getSatPos(calendar.time)
|
||||
elevation = satPos.elevation
|
||||
if (elevation > maxElevation) maxElevation = elevation
|
||||
} while (satPos.elevation > 0.0)
|
||||
|
||||
// refine it to 3 seconds
|
||||
|
||||
// refine to 3 seconds
|
||||
calendar.add(Calendar.SECOND, -30)
|
||||
do {
|
||||
calendar.add(Calendar.SECOND, 3)
|
||||
@@ -149,10 +145,10 @@ class PassPredictor(private val satellite: Satellite, private val qth: GroundSta
|
||||
elevation = satPos.elevation
|
||||
if (elevation > maxElevation) maxElevation = elevation
|
||||
} while (satPos.elevation > 0.0)
|
||||
|
||||
|
||||
val endDate = satPos.time
|
||||
val losAzimuth = (satPos.azimuth / (2.0 * Math.PI) * 360.0).toInt()
|
||||
val maxEl = maxElevation / (2.0 * Math.PI) * 360.0
|
||||
return SatPassTime(startDate, endDate, polePassed, aosAzimuth, losAzimuth, maxEl)
|
||||
return SatPassTime(startDate, endDate, "pole", aosAzimuth, losAzimuth, maxEl)
|
||||
}
|
||||
}
|
||||
@@ -60,4 +60,4 @@ class QthConverter @Inject constructor() {
|
||||
private fun isValidLocation(lat: Double, lon: Double): Boolean {
|
||||
return (lat > -90.0 && lat < 90.0) && (lon > -180.0 && lon < 360.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
class RecyclerDivider(private val resId: Int) : RecyclerView.ItemDecoration() {
|
||||
|
||||
override fun onDrawOver(c: Canvas, parent: RecyclerView) {
|
||||
override fun onDrawOver(canvas: Canvas, parent: RecyclerView) {
|
||||
ContextCompat.getDrawable(parent.context, resId)?.let { drawable ->
|
||||
val leftPos = parent.paddingLeft
|
||||
val rightPos = parent.width - parent.paddingRight
|
||||
@@ -35,10 +35,10 @@ class RecyclerDivider(private val resId: Int) : RecyclerView.ItemDecoration() {
|
||||
val topPos = view.bottom + params.bottomMargin
|
||||
val bottomPos = topPos + drawable.intrinsicHeight
|
||||
drawable.setBounds(leftPos, topPos, rightPos, bottomPos)
|
||||
drawable.draw(c)
|
||||
drawable.draw(canvas)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,13 +65,4 @@
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/passes_fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|center"
|
||||
android:layout_marginBottom="@dimen/fab_padding"
|
||||
android:contentDescription="@string/placeholder"
|
||||
android:src="@drawable/ic_refresh" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -39,4 +39,4 @@ class CoroutineTestRule(private val dispatcher: TestCoroutineDispatcher = TestCo
|
||||
Dispatchers.resetMain()
|
||||
dispatcher.cleanupTestCoroutines()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,4 +57,4 @@ class QthConverterTest {
|
||||
assert(converter.locationToQTH(91.0542, -170.1142) == null)
|
||||
assert(converter.locationToQTH(89.0542, -240.1142) == null)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user