Fixed behaviour of passes recycler ticking and refreshing

This commit is contained in:
Arty Bishop committed 2021-04-03 13:47:02 +01:00
1 parent cfd1a2f2b8
commit c2216e4917
9 files changed
+84 -71

No files matched your search

@@ -35,10 +35,10 @@ data class SatPass(
val predictor: PassPredictor, val predictor: PassPredictor,
var progress: Int = 0 var progress: Int = 0
) { ) {
val startDate: Date val aosDate: Date
get() = Date(aosTime) get() = Date(aosTime)
val endDate: Date val losDate: Date
get() = Date(losTime) get() = Date(losTime)
val tcaDate: Date val tcaDate: Date
@@ -41,21 +41,28 @@ class PassesRepo @Inject constructor(
val passes: SharedFlow<Result<List<SatPass>>> = _passes val passes: SharedFlow<Result<List<SatPass>>> = _passes
suspend fun triggerCalculation(satellites: List<Satellite>, refDate: Date = Date()) { suspend fun triggerCalculation(satellites: List<Satellite>, refDate: Date = Date()) {
if (satellites.isEmpty()) _passes.emit(Result.Error(Exception())) if (satellites.isEmpty()) {
val oldCatNums = selectedSatellites.map { it.tle.catnum } _passes.emit(Result.Error(Exception()))
val newCatNums = satellites.map { it.tle.catnum } } else {
if (oldCatNums != newCatNums) forceCalculation(satellites, refDate) val oldCatNums = selectedSatellites.map { it.tle.catnum }
val newCatNums = satellites.map { it.tle.catnum }
if (oldCatNums != newCatNums) forceCalculation(satellites, refDate)
}
} }
suspend fun forceCalculation(satellites: List<Satellite>, refDate: Date = Date()) { suspend fun forceCalculation(satellites: List<Satellite>, refDate: Date = Date()) {
_passes.emit(Result.InProgress) if (satellites.isEmpty()) {
withContext(defaultDispatcher) { _passes.emit(Result.Error(Exception()))
val allPasses = mutableListOf<SatPass>() } else {
selectedSatellites = satellites _passes.emit(Result.InProgress)
satellites.forEach { satellite -> withContext(defaultDispatcher) {
allPasses.addAll(getPasses(satellite, refDate)) val allPasses = mutableListOf<SatPass>()
selectedSatellites = satellites
satellites.forEach { satellite ->
allPasses.addAll(getPasses(satellite, refDate))
}
_passes.emit(Result.Success(filterPasses(allPasses, refDate)))
} }
_passes.emit(Result.Success(filterPasses(allPasses, refDate)))
} }
} }
@@ -66,9 +73,9 @@ class PassesRepo @Inject constructor(
private fun filterPasses(passes: List<SatPass>, refDate: Date): List<SatPass> { private fun filterPasses(passes: List<SatPass>, refDate: Date): List<SatPass> {
val timeFuture = Date(refDate.time + (prefsManager.getHoursAhead() * 3600 * 1000)) val timeFuture = Date(refDate.time + (prefsManager.getHoursAhead() * 3600 * 1000))
return passes.filter { it.endDate.after(refDate) } return passes.filter { it.losDate.after(refDate) }
.filter { it.startDate.before(timeFuture) } .filter { it.aosDate.before(timeFuture) }
.filter { it.maxElevation > prefsManager.getMinElevation() } .filter { it.maxElevation > prefsManager.getMinElevation() }
.sortedBy { it.startDate } .sortedBy { it.aosDate }
} }
} }
@@ -19,8 +19,6 @@ package com.rtbishop.look4sat.ui.passesScreen
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.navigation.findNavController
import androidx.recyclerview.widget.AsyncListDiffer import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@@ -28,15 +26,15 @@ import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.data.model.SatPass import com.rtbishop.look4sat.data.model.SatPass
import com.rtbishop.look4sat.databinding.ItemPassGeoBinding import com.rtbishop.look4sat.databinding.ItemPassGeoBinding
import com.rtbishop.look4sat.databinding.ItemPassLeoBinding import com.rtbishop.look4sat.databinding.ItemPassLeoBinding
import com.rtbishop.look4sat.utility.navigateSafe
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
class PassesAdapter(private val isUTC: Boolean) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { class PassesAdapter(private val isUTC: Boolean, private val clickListener: PassesClickListener) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val diffCallback = object : DiffUtil.ItemCallback<SatPass>() { private val diffCallback = object : DiffUtil.ItemCallback<SatPass>() {
override fun areItemsTheSame(oldItem: SatPass, newItem: SatPass): Boolean { override fun areItemsTheSame(oldItem: SatPass, newItem: SatPass): Boolean {
return oldItem.catNum == newItem.catNum && oldItem.startDate == newItem.startDate return oldItem.catNum == newItem.catNum && oldItem.aosDate == newItem.aosDate
} }
override fun areContentsTheSame(oldItem: SatPass, newItem: SatPass): Boolean { override fun areContentsTheSame(oldItem: SatPass, newItem: SatPass): Boolean {
@@ -44,30 +42,30 @@ class PassesAdapter(private val isUTC: Boolean) : RecyclerView.Adapter<RecyclerV
} }
} }
private val differ = AsyncListDiffer(this, diffCallback) private val differ = AsyncListDiffer(this, diffCallback)
private var allPasses = emptyList<SatPass>()
private var currentPasses = emptyList<SatPass>() private var currentPasses = emptyList<SatPass>()
interface PassesClickListener {
fun navigateToPass(satPass: SatPass)
}
fun submitList(passes: List<SatPass>) { fun submitList(passes: List<SatPass>) {
allPasses = passes.map { it.copy() } currentPasses = passes
currentPasses = passes.map { it.copy() }
tickDiffer(System.currentTimeMillis()) tickDiffer(System.currentTimeMillis())
} }
fun tickDiffer(timeNow: Long) { fun tickDiffer(timeNow: Long) {
val list = currentPasses.map { it.copy() } val copiedPasses = currentPasses.map { pass -> pass.copy() }
list.forEach { pass -> copiedPasses.forEach { pass ->
if (!pass.isDeepSpace) { if (!pass.isDeepSpace) {
val timeStart = pass.startDate.time val timeStart = pass.aosDate.time
if (timeNow > timeStart) { if (timeNow > timeStart) {
val timeEnd = pass.endDate.time
val deltaNow = timeNow.minus(timeStart).toFloat() val deltaNow = timeNow.minus(timeStart).toFloat()
val deltaTotal = timeEnd.minus(timeStart).toFloat() val deltaTotal = pass.losDate.time.minus(timeStart).toFloat()
pass.progress = ((deltaNow / deltaTotal) * 100).toInt() pass.progress = ((deltaNow / deltaTotal) * 100).toInt()
} }
} }
} }
currentPasses = list.filter { pass -> pass.progress < 100 } differ.submitList(copiedPasses.filter { pass -> pass.progress < 100 })
differ.submitList(currentPasses)
} }
override fun getItemCount() = differ.currentList.size override fun getItemCount() = differ.currentList.size
@@ -86,11 +84,10 @@ class PassesAdapter(private val isUTC: Boolean) : RecyclerView.Adapter<RecyclerV
} }
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val pass = differ.currentList[position]
if (holder.itemViewType == 0) { if (holder.itemViewType == 0) {
(holder as SatPassLeoHolder).bind(pass, differ.currentList.indexOf(pass), isUTC) (holder as SatPassLeoHolder).bind(differ.currentList[position], clickListener, isUTC)
} else { } else {
(holder as SatPassGeoHolder).bind(pass, differ.currentList.indexOf(pass)) (holder as SatPassGeoHolder).bind(differ.currentList[position], clickListener)
} }
} }
@@ -108,7 +105,7 @@ class PassesAdapter(private val isUTC: Boolean) : RecyclerView.Adapter<RecyclerV
private val startFormat = SimpleDateFormat(startTimeFormat, Locale.getDefault()) private val startFormat = SimpleDateFormat(startTimeFormat, Locale.getDefault())
private val endFormat = SimpleDateFormat(endTimeFormat, Locale.getDefault()) private val endFormat = SimpleDateFormat(endTimeFormat, Locale.getDefault())
fun bind(satPass: SatPass, passIndex: Int, shouldUseUTC: Boolean) { fun bind(satPass: SatPass, listener: PassesClickListener, shouldUseUTC: Boolean) {
binding.apply { binding.apply {
if (shouldUseUTC) { if (shouldUseUTC) {
startFormat.timeZone = timeZoneUTC startFormat.timeZone = timeZoneUTC
@@ -119,17 +116,14 @@ class PassesAdapter(private val isUTC: Boolean) : RecyclerView.Adapter<RecyclerV
passLeoAosAz.text = String.format(aosAzFormat, satPass.aosAzimuth) passLeoAosAz.text = String.format(aosAzFormat, satPass.aosAzimuth)
passLeoMaxEl.text = String.format(maxElFormat, satPass.maxElevation) passLeoMaxEl.text = String.format(maxElFormat, satPass.maxElevation)
passLeoLosAz.text = String.format(losAzFormat, satPass.losAzimuth) passLeoLosAz.text = String.format(losAzFormat, satPass.losAzimuth)
passLeoStart.text = startFormat.format(satPass.startDate) passLeoStart.text = startFormat.format(satPass.aosDate)
passLeoAlt.text = String.format(altFormat, satPass.altitude) passLeoAlt.text = String.format(altFormat, satPass.altitude)
passLeoEnd.text = endFormat.format(satPass.endDate) passLeoEnd.text = endFormat.format(satPass.losDate)
passLeoProgress.progress = satPass.progress passLeoProgress.progress = satPass.progress
} }
itemView.setOnClickListener { itemView.setOnClickListener {
if (satPass.progress < 100) { listener.navigateToPass(satPass)
val bundle = bundleOf("index" to passIndex)
itemView.findNavController().navigateSafe(R.id.action_passes_to_polar, bundle)
}
} }
} }
@@ -149,7 +143,7 @@ class PassesAdapter(private val isUTC: Boolean) : RecyclerView.Adapter<RecyclerV
private val altFormat = itemView.context.getString(R.string.pat_altitude) private val altFormat = itemView.context.getString(R.string.pat_altitude)
private val elevFormat = itemView.context.getString(R.string.pat_elevation) private val elevFormat = itemView.context.getString(R.string.pat_elevation)
fun bind(satPass: SatPass, passIndex: Int) { fun bind(satPass: SatPass, listener: PassesClickListener) {
binding.apply { binding.apply {
passGeoSatName.text = satPass.name passGeoSatName.text = satPass.name
passGeoSatId.text = String.format(satIdFormat, satPass.catNum) passGeoSatId.text = String.format(satIdFormat, satPass.catNum)
@@ -159,8 +153,7 @@ class PassesAdapter(private val isUTC: Boolean) : RecyclerView.Adapter<RecyclerV
} }
itemView.setOnClickListener { itemView.setOnClickListener {
val bundle = bundleOf("index" to passIndex) listener.navigateToPass(satPass)
itemView.findNavController().navigateSafe(R.id.action_passes_to_polar, bundle)
} }
} }
@@ -19,8 +19,10 @@ package com.rtbishop.look4sat.ui.passesScreen
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.SimpleItemAnimator import androidx.recyclerview.widget.SimpleItemAnimator
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
@@ -29,14 +31,15 @@ import com.rtbishop.look4sat.data.model.SatPass
import com.rtbishop.look4sat.databinding.FragmentPassesBinding import com.rtbishop.look4sat.databinding.FragmentPassesBinding
import com.rtbishop.look4sat.utility.RecyclerDivider import com.rtbishop.look4sat.utility.RecyclerDivider
import com.rtbishop.look4sat.utility.formatForTimer import com.rtbishop.look4sat.utility.formatForTimer
import com.rtbishop.look4sat.utility.navigateSafe
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import java.util.* import java.util.*
@AndroidEntryPoint @AndroidEntryPoint
class PassesFragment : Fragment(R.layout.fragment_passes) { class PassesFragment : Fragment(R.layout.fragment_passes), PassesAdapter.PassesClickListener {
private val viewModel: PassesViewModel by viewModels() private val viewModel: PassesViewModel by viewModels()
private var passes = mutableListOf<SatPass>() private var passes = listOf<SatPass>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@@ -44,20 +47,20 @@ class PassesFragment : Fragment(R.layout.fragment_passes) {
} }
private fun setupComponents(view: View) { private fun setupComponents(view: View) {
val passesAdapter = PassesAdapter(viewModel.shouldUseUTC()) val passesAdapter = PassesAdapter(viewModel.shouldUseUTC(), this)
val binding = FragmentPassesBinding.bind(view).apply { val binding = FragmentPassesBinding.bind(view).apply {
passesRecycler.apply { passesRecycler.apply {
setHasFixedSize(true) setHasFixedSize(true)
adapter = passesAdapter adapter = passesAdapter
isVerticalScrollBarEnabled = false isVerticalScrollBarEnabled = false
layoutManager = LinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
// (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
addItemDecoration(RecyclerDivider(R.drawable.rec_divider_dark)) addItemDecoration(RecyclerDivider(R.drawable.rec_divider_dark))
} }
passesRefresh.setOnClickListener { viewModel.forceCalculation() } passesRefresh.setOnClickListener { viewModel.forceCalculation() }
} }
viewModel.passes.observe(viewLifecycleOwner, { result -> viewModel.passes.observe(viewLifecycleOwner, { passesResult ->
handleNewPasses(result, passesAdapter, binding) handleNewPasses(passesResult, passesAdapter, binding)
}) })
viewModel.getAppTimer().observe(viewLifecycleOwner, { timeNow -> viewModel.getAppTimer().observe(viewLifecycleOwner, { timeNow ->
tickMainTimer(timeNow, passesAdapter, binding) tickMainTimer(timeNow, passesAdapter, binding)
@@ -71,7 +74,7 @@ class PassesFragment : Fragment(R.layout.fragment_passes) {
) { ) {
when (result) { when (result) {
is Result.Success -> { is Result.Success -> {
passes = result.data.toMutableList() passes = result.data
passesAdapter.submitList(passes) passesAdapter.submitList(passes)
binding.apply { binding.apply {
passesError.visibility = View.INVISIBLE passesError.visibility = View.INVISIBLE
@@ -80,7 +83,7 @@ class PassesFragment : Fragment(R.layout.fragment_passes) {
} }
} }
is Result.InProgress -> { is Result.InProgress -> {
passes.clear() passes = emptyList()
binding.apply { binding.apply {
passesTimer.text = 0L.formatForTimer() passesTimer.text = 0L.formatForTimer()
passesError.visibility = View.INVISIBLE passesError.visibility = View.INVISIBLE
@@ -89,7 +92,7 @@ class PassesFragment : Fragment(R.layout.fragment_passes) {
} }
} }
is Result.Error -> { is Result.Error -> {
passes.clear() passes = emptyList()
binding.apply { binding.apply {
passesTimer.text = 0L.formatForTimer() passesTimer.text = 0L.formatForTimer()
passesProgress.visibility = View.INVISIBLE passesProgress.visibility = View.INVISIBLE
@@ -107,18 +110,24 @@ class PassesFragment : Fragment(R.layout.fragment_passes) {
) { ) {
if (passes.isNotEmpty()) { if (passes.isNotEmpty()) {
try { try {
val nextPass = passes.first { it.startDate.time.minus(timeNow) > 0 } val nextPass = passes.first { it.aosDate.time.minus(timeNow) > 0 }
val millisBeforeStart = nextPass.startDate.time.minus(timeNow) val millisBeforeStart = nextPass.aosDate.time.minus(timeNow)
binding.passesTimer.text = millisBeforeStart.formatForTimer() binding.passesTimer.text = millisBeforeStart.formatForTimer()
} catch (e: NoSuchElementException) { } catch (e: NoSuchElementException) {
val lastPass = passes.last() val lastPass = passes.last()
val millisBeforeEnd = lastPass.endDate.time.minus(timeNow) val millisBeforeEnd = lastPass.losDate.time.minus(timeNow)
binding.passesTimer.text = millisBeforeEnd.formatForTimer() binding.passesTimer.text = millisBeforeEnd.formatForTimer()
} }
// passesAdapter.tickPasses(timeNow)
passesAdapter.tickDiffer(timeNow) passesAdapter.tickDiffer(timeNow)
} else { } else {
binding.passesTimer.text = 0L.formatForTimer() binding.passesTimer.text = 0L.formatForTimer()
} }
} }
override fun navigateToPass(satPass: SatPass) {
if (satPass.progress < 100) {
val bundle = bundleOf("catNum" to satPass.catNum, "aosTime" to satPass.aosDate.time)
findNavController().navigateSafe(R.id.action_passes_to_polar, bundle)
}
}
} }
@@ -117,8 +117,9 @@ class PolarFragment : Fragment(R.layout.fragment_polar), SensorEventListener {
} }
private fun observePass() { private fun observePass() {
val passId = requireArguments().getInt("index") val catNum = requireArguments().getInt("catNum")
viewModel.getPass(passId).observe(viewLifecycleOwner) { pass -> val aosTime = requireArguments().getLong("aosTime")
viewModel.getPass(catNum, aosTime).observe(viewLifecycleOwner) { pass ->
satPass = pass satPass = pass
polarView = PolarView(requireContext()).apply { setPass(pass) } polarView = PolarView(requireContext()).apply { setPass(pass) }
binding.frame.addView(polarView) binding.frame.addView(polarView)
@@ -171,13 +172,13 @@ class PolarFragment : Fragment(R.layout.fragment_polar), SensorEventListener {
binding.altitude.text = String.format(polarAlt, satPos.altitude) binding.altitude.text = String.format(polarAlt, satPos.altitude)
if (!satPass.isDeepSpace) { if (!satPass.isDeepSpace) {
if (dateNow.before(satPass.startDate)) { if (dateNow.before(satPass.aosDate)) {
val millisBeforeStart = satPass.startDate.time.minus(timeNow) val millisBeforeStart = satPass.aosDate.time.minus(timeNow)
binding.polarTimer.text = millisBeforeStart.formatForTimer() binding.polarTimer.text = millisBeforeStart.formatForTimer()
} else { } else {
val millisBeforeEnd = satPass.endDate.time.minus(timeNow) val millisBeforeEnd = satPass.losDate.time.minus(timeNow)
binding.polarTimer.text = millisBeforeEnd.formatForTimer() binding.polarTimer.text = millisBeforeEnd.formatForTimer()
if (dateNow.after(satPass.endDate)) { if (dateNow.after(satPass.losDate)) {
binding.polarTimer.text = 0L.formatForTimer() binding.polarTimer.text = 0L.formatForTimer()
findNavController().popBackStack() findNavController().popBackStack()
} }
@@ -92,13 +92,13 @@ class PolarView(context: Context) : View(context) {
} }
private fun drawPassTrajectory(cvs: Canvas, satPass: SatPass) { private fun drawPassTrajectory(cvs: Canvas, satPass: SatPass) {
val startTime = satPass.startDate val startTime = satPass.aosDate
val endTime = satPass.endDate val endTime = satPass.losDate
while (startTime.before(endTime)) { while (startTime.before(endTime)) {
val satPos = satPass.predictor.getSatPos(startTime) val satPos = satPass.predictor.getSatPos(startTime)
val x = sph2CartX(satPos.azimuth, satPos.elevation, radius.toDouble()) val x = sph2CartX(satPos.azimuth, satPos.elevation, radius.toDouble())
val y = sph2CartY(satPos.azimuth, satPos.elevation, radius.toDouble()) val y = sph2CartY(satPos.azimuth, satPos.elevation, radius.toDouble())
if (startTime.compareTo(satPass.startDate) == 0) { if (startTime.compareTo(satPass.aosDate) == 0) {
path.moveTo(x, -y) path.moveTo(x, -y)
} else { } else {
path.lineTo(x, -y) path.lineTo(x, -y)
@@ -44,9 +44,12 @@ class PolarViewModel @Inject constructor(
} }
} }
fun getPass(passId: Int) = liveData { fun getPass(catNum: Int, aosTime: Long) = liveData {
passesRepo.passes.collect { passes -> passesRepo.passes.collect { passes ->
if (passes is Result.Success) emit(passes.data[passId]) if (passes is Result.Success) {
val pass = passes.data.find { it.catNum == catNum && it.aosDate.time == aosTime }
pass?.let { emit(it) }
}
} }
} }
@@ -69,10 +69,10 @@ class PassPredictor(private val satellite: Satellite, private val qth: GroundSta
do { do {
if (count > 0) shouldWindBack = false if (count > 0) shouldWindBack = false
val pass = nextNearEarthPass(startDate, shouldWindBack) val pass = nextNearEarthPass(startDate, shouldWindBack)
lastAosDate = pass.startDate lastAosDate = pass.aosDate
passes.add(pass) passes.add(pass)
startDate = startDate =
Date(pass.endDate.time + (oneQuarterOrbitMin * 3) * 60L * 1000L) Date(pass.losDate.time + (oneQuarterOrbitMin * 3) * 60L * 1000L)
count++ count++
} while (lastAosDate < endDate) } while (lastAosDate < endDate)
} }
+1 -1
View File
@@ -4,7 +4,7 @@ buildscript {
gradle_plugin_version = '1.4.32' gradle_plugin_version = '1.4.32'
material_version = '1.3.0' material_version = '1.3.0'
constraint_layout_version = '2.0.4' constraint_layout_version = '2.0.4'
lifecycle_version = '2.3.0' lifecycle_version = '2.3.1'
navigation_version = '2.3.4' navigation_version = '2.3.4'
preference_version = '1.1.1' preference_version = '1.1.1'
room_version = '2.2.6' room_version = '2.2.6'