Added PassesViewModel.kt

This commit is contained in:
Arty Bishop committed 2021-03-20 16:44:40 +00:00
1 parent d32e5a5ce3
commit 4edf316b5e
7 files changed
+82 -75

No files matched your search

@@ -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())
@@ -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
}
}
@@ -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()
}
}
@@ -26,7 +26,6 @@ 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
@@ -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 {
@@ -102,18 +99,17 @@ class PassPredictor(private val satellite: Satellite, private val qth: GroundSta
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)
@@ -121,7 +117,7 @@ class PassPredictor(private val satellite: Satellite, private val qth: GroundSta
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)
@@ -133,7 +129,7 @@ class PassPredictor(private val satellite: Satellite, private val qth: GroundSta
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)
@@ -141,7 +137,7 @@ class PassPredictor(private val satellite: Satellite, private val qth: GroundSta
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)
@@ -153,6 +149,6 @@ class PassPredictor(private val satellite: Satellite, private val qth: GroundSta
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)
}
}
@@ -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,7 +35,7 @@ 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>