AsyncListDiffer try for satellite passes

This commit is contained in:
Arty Bishop committed 2021-04-02 20:29:57 +01:00
1 parent 6b3a3b31d6
commit 7e97cbea1b
17 files changed
+309 -286

No files matched your search

@@ -27,14 +27,12 @@ import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface SatelliteDao { interface SatelliteDao {
// Query
@Query("SELECT * FROM transmitters WHERE catNum = :catNum")
fun getTransmittersByCatNum(catNum: Int): Flow<List<SatTrans>>
@Transaction @Transaction
@Query("SELECT catNum, name, isSelected FROM entries ORDER BY name ASC") @Query("SELECT catNum, name, isSelected FROM entries ORDER BY name ASC")
fun getItemsFlow(): Flow<List<SatItem>> fun getSatItems(): Flow<List<SatItem>>
@Query("SELECT * FROM transmitters WHERE catNum = :catNum")
fun getSatTransmitters(catNum: Int): Flow<List<SatTrans>>
@Query("SELECT tle FROM entries WHERE isSelected = 1") @Query("SELECT tle FROM entries WHERE isSelected = 1")
suspend fun getSelectedSatellites(): List<Satellite> suspend fun getSelectedSatellites(): List<Satellite>
@@ -42,8 +40,6 @@ interface SatelliteDao {
@Query("SELECT catNum FROM entries WHERE isSelected = 1") @Query("SELECT catNum FROM entries WHERE isSelected = 1")
suspend fun getSelectedCatNums(): List<Int> suspend fun getSelectedCatNums(): List<Int>
// Insert
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertEntries(entries: List<SatEntry>) suspend fun insertEntries(entries: List<SatEntry>)
@@ -53,24 +49,21 @@ interface SatelliteDao {
insertTransmitters(transmitters) insertTransmitters(transmitters)
} }
@Query("DELETE from transmitters")
suspend fun deleteTransmitters()
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertTransmitters(transmitters: List<SatTrans>) suspend fun insertTransmitters(transmitters: List<SatTrans>)
// Update
@Transaction @Transaction
suspend fun restoreEntriesSelection(catNums: List<Int>, isSelected: Boolean) { suspend fun restoreEntriesSelection(catNums: List<Int>, isSelected: Boolean) {
clearEntriesSelection() clearEntriesSelection()
catNums.forEach { catNum -> catNums.forEach { catNum -> updateItemSelection(catNum, isSelected) }
updateItemSelection(catNum, isSelected)
}
} }
@Transaction @Transaction
suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean) { suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean) {
catNums.forEach { catNum -> catNums.forEach { catNum -> updateItemSelection(catNum, isSelected) }
updateItemSelection(catNum, isSelected)
}
} }
@Query("UPDATE entries SET isSelected = 0") @Query("UPDATE entries SET isSelected = 0")
@@ -78,9 +71,4 @@ interface SatelliteDao {
@Query("UPDATE entries SET isSelected = :isSelected WHERE catNum = :catNum") @Query("UPDATE entries SET isSelected = :isSelected WHERE catNum = :catNum")
suspend fun updateItemSelection(catNum: Int, isSelected: Boolean) suspend fun updateItemSelection(catNum: Int, isSelected: Boolean)
// Delete
@Query("DELETE from transmitters")
suspend fun deleteTransmitters()
} }
@@ -30,7 +30,6 @@ class SelectedSat(
val velocity: Double, val velocity: Double,
val qthLoc: String, val qthLoc: String,
val osmPos: Position, val osmPos: Position,
val coverage: Double,
val footprint: Overlay, val footprint: Overlay,
val groundTrack: Overlay val groundTrack: Overlay
) )
@@ -18,13 +18,16 @@
package com.rtbishop.look4sat.data.repository package com.rtbishop.look4sat.data.repository
import com.github.amsacode.predict4java.Satellite import com.github.amsacode.predict4java.Satellite
import com.rtbishop.look4sat.data.model.Result
import com.rtbishop.look4sat.data.model.SatPass import com.rtbishop.look4sat.data.model.SatPass
import com.rtbishop.look4sat.di.DefaultDispatcher import com.rtbishop.look4sat.di.DefaultDispatcher
import com.rtbishop.look4sat.di.ExternalScope
import com.rtbishop.look4sat.utility.PrefsManager
import com.rtbishop.look4sat.utility.getPredictor import com.rtbishop.look4sat.utility.getPredictor
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@@ -32,49 +35,38 @@ import javax.inject.Singleton
@Singleton @Singleton
class PassesRepo @Inject constructor( class PassesRepo @Inject constructor(
private val prefsRepo: PrefsRepo, private val prefsManager: PrefsManager,
private val satelliteRepo: SatelliteRepo,
@ExternalScope private val externalScope: CoroutineScope,
@DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher @DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher
) { ) {
private val _passes = private val _passes = MutableSharedFlow<List<SatPass>>()
MutableStateFlow<Result<MutableList<SatPass>>>(Result.Success(mutableListOf())) val passes: SharedFlow<List<SatPass>> = _passes
val passes: StateFlow<Result<MutableList<SatPass>>> = _passes
private var selectedSatellites = emptyList<Satellite>() init {
externalScope.launch { calculatePasses() }
}
suspend fun calculatePasses(satellites: List<Satellite>) { suspend fun calculatePasses(refDate: Date = Date()) {
withContext(defaultDispatcher) { withContext(defaultDispatcher) {
val newCatNums = satellites.map { it.tle.catnum } val allPasses = mutableListOf<SatPass>()
val oldCatNums = selectedSatellites.map { it.tle.catnum } satelliteRepo.getSelectedSatellites().forEach { satellite ->
if (newCatNums != oldCatNums) { allPasses.addAll(getPasses(satellite, refDate))
_passes.value = Result.InProgress
selectedSatellites = satellites
val refDate = Date(System.currentTimeMillis())
val allPasses = mutableListOf<SatPass>()
selectedSatellites.forEach { satellite ->
allPasses.addAll(getPasses(satellite, refDate))
}
val filteredPasses = filterPasses(allPasses, refDate)
_passes.value = Result.Success(filteredPasses)
} }
_passes.emit(filterPasses(allPasses, refDate))
} }
} }
private fun getPasses(satellite: Satellite, refDate: Date): MutableList<SatPass> { private fun getPasses(satellite: Satellite, refDate: Date): List<SatPass> {
val predictor = satellite.getPredictor(prefsRepo.getStationPosition()) val predictor = satellite.getPredictor(prefsManager.getStationPosition())
val passes = predictor.getPasses(refDate, prefsRepo.getHoursAhead(), true) return predictor.getPasses(refDate, prefsManager.getHoursAhead(), true)
return passes as MutableList<SatPass>
} }
private fun filterPasses(passes: MutableList<SatPass>, refDate: Date): MutableList<SatPass> { private fun filterPasses(passes: List<SatPass>, refDate: Date): List<SatPass> {
val hoursAhead = prefsRepo.getHoursAhead() val timeFuture = Date(refDate.time + (prefsManager.getHoursAhead() * 3600 * 1000))
val dateFuture = Calendar.getInstance().apply { return passes.filter { it.endDate.after(refDate) }
this.time = refDate .filter { it.startDate.before(timeFuture) }
this.add(Calendar.HOUR, hoursAhead) .filter { it.maxElevation > prefsManager.getMinElevation() }
}.time .sortedBy { it.startDate }
passes.removeAll { it.startDate.after(dateFuture) }
passes.removeAll { it.endDate.before(refDate) }
passes.removeAll { it.maxElevation < prefsRepo.getMinElevation() }
passes.sortBy { it.startDate }
return passes
} }
} }
@@ -46,22 +46,18 @@ class SatelliteRepo @Inject constructor(
@IoDispatcher private val ioDispatcher: CoroutineDispatcher @IoDispatcher private val ioDispatcher: CoroutineDispatcher
) { ) {
// Query fun getSatItems(): Flow<List<SatItem>> {
return satelliteDao.getSatItems()
fun getSatTransmitters(catNum: Int): Flow<List<SatTrans>> {
return satelliteDao.getTransmittersByCatNum(catNum)
} }
fun getItemsFlow(): Flow<List<SatItem>> { fun getSatTransmitters(catNum: Int): Flow<List<SatTrans>> {
return satelliteDao.getItemsFlow() return satelliteDao.getSatTransmitters(catNum)
} }
suspend fun getSelectedSatellites(): List<Satellite> { suspend fun getSelectedSatellites(): List<Satellite> {
return satelliteDao.getSelectedSatellites() return satelliteDao.getSelectedSatellites()
} }
// Insert
suspend fun importSatDataFromFile(uri: Uri) { suspend fun importSatDataFromFile(uri: Uri) {
runCatching { runCatching {
resolver.openInputStream(uri)?.use { stream -> resolver.openInputStream(uri)?.use { stream ->
@@ -80,6 +76,10 @@ class SatelliteRepo @Inject constructor(
} }
} }
suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean) {
satelliteDao.updateEntriesSelection(catNums, isSelected)
}
private suspend fun importEntriesFromSources(sources: List<TleSource>) { private suspend fun importEntriesFromSources(sources: List<TleSource>) {
val streams = getStreamsForSources(sources) val streams = getStreamsForSources(sources)
val entries = importEntriesFromStreams(streams) val entries = importEntriesFromStreams(streams)
@@ -133,10 +133,4 @@ class SatelliteRepo @Inject constructor(
satelliteDao.insertEntries(entries) satelliteDao.insertEntries(entries)
satelliteDao.restoreEntriesSelection(selectedCatNums, true) satelliteDao.restoreEntriesSelection(selectedCatNums, true)
} }
// Update
suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean) {
satelliteDao.updateEntriesSelection(catNums, isSelected)
}
} }
@@ -22,13 +22,21 @@ import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import javax.inject.Qualifier import javax.inject.Qualifier
import javax.inject.Singleton
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
object CoroutinesModule { object CoroutinesModule {
@Provides
@Singleton
@ExternalScope
fun providesExternalScope(): CoroutineScope = CoroutineScope(SupervisorJob())
@Provides @Provides
@DefaultDispatcher @DefaultDispatcher
fun provideDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default fun provideDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
@@ -42,6 +50,10 @@ object CoroutinesModule {
fun provideMainDispatcher(): CoroutineDispatcher = Dispatchers.Main fun provideMainDispatcher(): CoroutineDispatcher = Dispatchers.Main
} }
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class ExternalScope
@Qualifier @Qualifier
@Retention(AnnotationRetention.BINARY) @Retention(AnnotationRetention.BINARY)
annotation class DefaultDispatcher annotation class DefaultDispatcher
@@ -22,7 +22,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.data.repository.PrefsRepo import com.rtbishop.look4sat.utility.PrefsManager
import com.rtbishop.look4sat.databinding.ActivityMainBinding import com.rtbishop.look4sat.databinding.ActivityMainBinding
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject import javax.inject.Inject
@@ -31,7 +31,7 @@ import javax.inject.Inject
class Look4SatActivity : AppCompatActivity() { class Look4SatActivity : AppCompatActivity() {
@Inject @Inject
lateinit var prefsRepo: PrefsRepo lateinit var prefsManager: PrefsManager
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.AppTheme) setTheme(R.style.AppTheme)
@@ -40,9 +40,8 @@ class Look4SatActivity : AppCompatActivity() {
setContentView(root) setContentView(root)
val navHost = supportFragmentManager.findFragmentById(R.id.nav_host) as NavHostFragment val navHost = supportFragmentManager.findFragmentById(R.id.nav_host) as NavHostFragment
navBottom.setupWithNavController(navHost.navController) navBottom.setupWithNavController(navHost.navController)
if (prefsManager.isFirstLaunch()) {
if (prefsRepo.isFirstLaunch()) { prefsManager.setFirstLaunchDone()
prefsRepo.setFirstLaunchDone()
navHost.navController.navigate(R.id.nav_dialog_splash) navHost.navController.navigate(R.id.nav_dialog_splash)
} }
} }
@@ -27,7 +27,7 @@ import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.data.model.Result import com.rtbishop.look4sat.data.model.Result
import com.rtbishop.look4sat.data.model.SatItem import com.rtbishop.look4sat.data.model.SatItem
import com.rtbishop.look4sat.data.model.TleSource import com.rtbishop.look4sat.data.model.TleSource
import com.rtbishop.look4sat.data.repository.PrefsRepo import com.rtbishop.look4sat.utility.PrefsManager
import com.rtbishop.look4sat.data.repository.SatelliteRepo import com.rtbishop.look4sat.data.repository.SatelliteRepo
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
@@ -39,14 +39,14 @@ import kotlin.system.measureTimeMillis
@HiltViewModel @HiltViewModel
class EntriesViewModel @Inject constructor( class EntriesViewModel @Inject constructor(
private val prefsRepo: PrefsRepo, private val prefsManager: PrefsManager,
private val satelliteRepo: SatelliteRepo private val satelliteRepo: SatelliteRepo
) : ViewModel(), EntriesAdapter.EntriesClickListener, SearchView.OnQueryTextListener { ) : ViewModel(), EntriesAdapter.EntriesClickListener, SearchView.OnQueryTextListener {
private val transModes = MutableLiveData(prefsRepo.loadModesSelection()) private val transModes = MutableLiveData(prefsManager.loadModesSelection())
private val currentQuery = MutableLiveData(String()) private val currentQuery = MutableLiveData(String())
private val itemsWithModes = transModes.switchMap { modes -> private val itemsWithModes = transModes.switchMap { modes ->
liveData { satelliteRepo.getItemsFlow().collect { emit(filterByModes(it, modes)) } } liveData { satelliteRepo.getSatItems().collect { emit(filterByModes(it, modes)) } }
} }
private val itemsWithQuery = currentQuery.switchMap { query -> private val itemsWithQuery = currentQuery.switchMap { query ->
itemsWithModes.map { items -> Result.Success(filterByQuery(items, query)) } itemsWithModes.map { items -> Result.Success(filterByQuery(items, query)) }
@@ -72,10 +72,10 @@ class EntriesViewModel @Inject constructor(
viewModelScope.launch { viewModelScope.launch {
_satData.value = Result.InProgress _satData.value = Result.InProgress
val satSources = if (sources.isNotEmpty()) sources val satSources = if (sources.isNotEmpty()) sources
else prefsRepo.loadDefaultSources() else prefsManager.loadDefaultSources()
val updateMillis = measureTimeMillis { val updateMillis = measureTimeMillis {
try { try {
prefsRepo.saveTleSources(satSources) prefsManager.saveTleSources(satSources)
satelliteRepo.importSatDataFromWeb(satSources) satelliteRepo.importSatDataFromWeb(satSources)
} catch (exception: Exception) { } catch (exception: Exception) {
_satData.value = Result.Error(exception) _satData.value = Result.Error(exception)
@@ -108,7 +108,7 @@ class EntriesViewModel @Inject constructor(
"PSK", "PSK31", "PSK63", "QPSK", "QPSK31", "QPSK63", "SSTV", "USB", "WSJT" "PSK", "PSK31", "PSK63", "QPSK", "QPSK31", "QPSK63", "SSTV", "USB", "WSJT"
) )
val savedModes = BooleanArray(modes.size) val savedModes = BooleanArray(modes.size)
val selectedModes = prefsRepo.loadModesSelection().toMutableList() val selectedModes = prefsManager.loadModesSelection().toMutableList()
selectedModes.forEach { savedModes[modes.indexOf(it)] = true } selectedModes.forEach { savedModes[modes.indexOf(it)] = true }
val dialogBuilder = MaterialAlertDialogBuilder(context).apply { val dialogBuilder = MaterialAlertDialogBuilder(context).apply {
setTitle(context.getString(R.string.modes_title)) setTitle(context.getString(R.string.modes_title))
@@ -120,7 +120,7 @@ class EntriesViewModel @Inject constructor(
} }
setPositiveButton(context.getString(android.R.string.ok)) { _, _ -> setPositiveButton(context.getString(android.R.string.ok)) { _, _ ->
transModes.value = selectedModes transModes.value = selectedModes
prefsRepo.saveModesSelection(selectedModes) prefsManager.saveModesSelection(selectedModes)
} }
setNeutralButton(context.getString(android.R.string.cancel)) { dialog, _ -> setNeutralButton(context.getString(android.R.string.cancel)) { dialog, _ ->
dialog.dismiss() dialog.dismiss()
@@ -29,7 +29,7 @@ import com.github.amsacode.predict4java.Position
import com.github.amsacode.predict4java.Satellite import com.github.amsacode.predict4java.Satellite
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.data.model.SelectedSat import com.rtbishop.look4sat.data.model.SelectedSat
import com.rtbishop.look4sat.data.repository.PrefsRepo import com.rtbishop.look4sat.utility.PrefsManager
import com.rtbishop.look4sat.databinding.FragmentMapBinding import com.rtbishop.look4sat.databinding.FragmentMapBinding
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
@@ -50,7 +50,7 @@ import javax.inject.Inject
class MapFragment : Fragment(R.layout.fragment_map) { class MapFragment : Fragment(R.layout.fragment_map) {
@Inject @Inject
lateinit var prefsRepo: PrefsRepo lateinit var prefsManager: PrefsManager
private lateinit var binding: FragmentMapBinding private lateinit var binding: FragmentMapBinding
private val mapViewModel: MapViewModel by viewModels() private val mapViewModel: MapViewModel by viewModels()
private val minLat = MapView.getTileSystem().minLatitude private val minLat = MapView.getTileSystem().minLatitude
@@ -58,7 +58,7 @@ class MapFragment : Fragment(R.layout.fragment_map) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
Configuration.getInstance().load(requireContext(), prefsRepo.preferences) Configuration.getInstance().load(requireContext(), prefsManager.preferences)
binding = FragmentMapBinding.bind(view).apply { binding = FragmentMapBinding.bind(view).apply {
mapView.apply { mapView.apply {
setMultiTouchControls(true) setMultiTouchControls(true)
@@ -119,7 +119,7 @@ class MapFragment : Fragment(R.layout.fragment_map) {
binding.apply { binding.apply {
val markers = FolderOverlay() val markers = FolderOverlay()
map.entries.forEach { map.entries.forEach {
if (prefsRepo.shouldUseTextLabels()) { if (prefsManager.shouldUseTextLabels()) {
Marker(mapView).apply { Marker(mapView).apply {
setInfoWindow(null) setInfoWindow(null)
textLabelFontSize = 24 textLabelFontSize = 24
@@ -24,7 +24,7 @@ import com.github.amsacode.predict4java.Position
import com.github.amsacode.predict4java.SatPos import com.github.amsacode.predict4java.SatPos
import com.github.amsacode.predict4java.Satellite import com.github.amsacode.predict4java.Satellite
import com.rtbishop.look4sat.data.model.SelectedSat import com.rtbishop.look4sat.data.model.SelectedSat
import com.rtbishop.look4sat.data.repository.PrefsRepo import com.rtbishop.look4sat.utility.PrefsManager
import com.rtbishop.look4sat.data.repository.SatelliteRepo import com.rtbishop.look4sat.data.repository.SatelliteRepo
import com.rtbishop.look4sat.utility.* import com.rtbishop.look4sat.utility.*
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@@ -39,7 +39,7 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class MapViewModel @Inject constructor( class MapViewModel @Inject constructor(
prefsRepo: PrefsRepo, prefsManager: PrefsManager,
private val satelliteRepo: SatelliteRepo, private val satelliteRepo: SatelliteRepo,
private val qthConverter: QthConverter private val qthConverter: QthConverter
) : ViewModel() { ) : ViewModel() {
@@ -59,7 +59,7 @@ class MapViewModel @Inject constructor(
isAntiAlias = true isAntiAlias = true
} }
private val gsp = prefsRepo.getStationPosition() private val gsp = prefsManager.getStationPosition()
private var filteredSats = listOf<Satellite>() private var filteredSats = listOf<Satellite>()
private lateinit var selectedSat: Satellite private lateinit var selectedSat: Satellite
@@ -118,12 +118,11 @@ class MapViewModel @Inject constructor(
) )
val qthLoc = qthConverter.locationToQTH(osmPos.lat, osmPos.lon) ?: "-- --" val qthLoc = qthConverter.locationToQTH(osmPos.lat, osmPos.lon) ?: "-- --"
val velocity = satPos.altitude.getOrbitalVelocity() val velocity = satPos.altitude.getOrbitalVelocity()
val coverage = satPos.rangeCircleRadiusKm * 2
val footprint = getSatFootprint(satPos) val footprint = getSatFootprint(satPos)
val track = getSatTrack(satellite) val track = getSatTrack(satellite)
return@withContext SelectedSat( return@withContext SelectedSat(
satellite, satellite.tle.catnum, satellite.tle.name, satPos.range, satellite, satellite.tle.catnum, satellite.tle.name, satPos.range,
satPos.altitude, velocity, qthLoc, osmPos, coverage, footprint, track satPos.altitude, velocity, qthLoc, osmPos, footprint, track
) )
} }
@@ -19,57 +19,47 @@ 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.recyclerview.widget.AsyncListDiffer
import androidx.navigation.findNavController import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.rtbishop.look4sat.R 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 shouldUseUTC: Boolean = false) : class PassesAdapter(private val shouldUseUTC: Boolean = false) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() { RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var satPassList: MutableList<SatPass> = mutableListOf() private val diffCallback = object : DiffUtil.ItemCallback<SatPass>() {
override fun areItemsTheSame(oldItem: SatPass, newItem: SatPass): Boolean {
return oldItem.catNum == newItem.catNum && oldItem.startDate == newItem.startDate
}
fun setList(list: MutableList<SatPass>) { override fun areContentsTheSame(oldItem: SatPass, newItem: SatPass): Boolean {
satPassList = list return false
notifyDataSetChanged()
}
fun tickPasses(timeNow: Long) {
val iterator = satPassList.listIterator()
while (iterator.hasNext()) {
val satPass = iterator.next()
if (!satPass.isDeepSpace) {
if (satPass.progress < 100) {
val timeStart = satPass.startDate.time
if (timeNow > timeStart) {
val timeEnd = satPass.endDate.time
val index = satPassList.indexOf(satPass)
val deltaNow = timeNow.minus(timeStart).toFloat()
val deltaTotal = timeEnd.minus(timeStart).toFloat()
satPass.progress = ((deltaNow / deltaTotal) * 100).toInt()
notifyItemChanged(index)
}
} else {
val index = satPassList.indexOf(satPass)
iterator.remove()
notifyItemRemoved(index)
}
}
} }
} }
private val listDiffer = AsyncListDiffer(this, diffCallback)
private lateinit var passesClickListener: PassesClickListener
override fun getItemCount(): Int { interface PassesClickListener {
return satPassList.size fun navigateToPass(satPass: SatPass)
} }
fun setPassesClickListener(listener: PassesClickListener) {
passesClickListener = listener
}
fun submitPasses(items: List<SatPass>) {
listDiffer.submitList(items)
}
override fun getItemCount() = listDiffer.currentList.size
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
return if (satPassList[position].isDeepSpace) 1 return if (listDiffer.currentList[position].isDeepSpace) 1
else 0 else 0
} }
@@ -83,9 +73,11 @@ class PassesAdapter(private val shouldUseUTC: Boolean = false) :
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder.itemViewType == 0) { if (holder.itemViewType == 0) {
(holder as SatPassLeoHolder).bind(satPassList[position], position, shouldUseUTC) (holder as SatPassLeoHolder)
.bind(listDiffer.currentList[position], shouldUseUTC, passesClickListener)
} else { } else {
(holder as SatPassGeoHolder).bind(satPassList[position], position) (holder as SatPassGeoHolder)
.bind(listDiffer.currentList[position], passesClickListener)
} }
} }
@@ -103,7 +95,7 @@ class PassesAdapter(private val shouldUseUTC: Boolean = false) :
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, position: Int, shouldUseUTC: Boolean) { fun bind(satPass: SatPass, shouldUseUTC: Boolean, listener: PassesClickListener) {
binding.apply { binding.apply {
if (shouldUseUTC) { if (shouldUseUTC) {
startFormat.timeZone = timeZoneUTC startFormat.timeZone = timeZoneUTC
@@ -119,12 +111,8 @@ class PassesAdapter(private val shouldUseUTC: Boolean = false) :
passLeoEnd.text = endFormat.format(satPass.endDate) passLeoEnd.text = endFormat.format(satPass.endDate)
passLeoProgress.progress = satPass.progress passLeoProgress.progress = satPass.progress
} }
itemView.setOnClickListener { itemView.setOnClickListener {
if (satPass.progress < 100) { listener.navigateToPass(satPass)
val bundle = bundleOf("index" to position)
itemView.findNavController().navigateSafe(R.id.action_passes_to_polar, bundle)
}
} }
} }
@@ -144,7 +132,7 @@ class PassesAdapter(private val shouldUseUTC: Boolean = false) :
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, position: 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)
@@ -152,10 +140,8 @@ class PassesAdapter(private val shouldUseUTC: Boolean = false) :
passGeoAlt.text = String.format(altFormat, satPass.altitude) passGeoAlt.text = String.format(altFormat, satPass.altitude)
passGeoEl.text = String.format(elevFormat, satPass.maxElevation) passGeoEl.text = String.format(elevFormat, satPass.maxElevation)
} }
itemView.setOnClickListener { itemView.setOnClickListener {
val bundle = bundleOf("index" to position) 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,63 +31,26 @@ 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 kotlinx.coroutines.FlowPreview
import java.util.* import java.util.*
@FlowPreview
@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>()
private var binding: FragmentPassesBinding? = null
private var passesAdapter: PassesAdapter? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding = FragmentPassesBinding.bind(view) setupComponents(view)
setupComponents()
setupObservers()
} }
private fun setupObservers() { private fun setupComponents(view: View) {
viewModel.passes.observe(viewLifecycleOwner, { result -> val passesAdapter = PassesAdapter(viewModel.shouldUseUTC()).apply {
if (result is Result.Success) { setPassesClickListener(this@PassesFragment)
if (result.data.isEmpty()) { }
binding?.apply { val binding = FragmentPassesBinding.bind(view).apply {
passesProgress.visibility = View.INVISIBLE
passesRecycler.visibility = View.INVISIBLE
passesError.visibility = View.VISIBLE
}
} else {
passes = result.data
passesAdapter?.setList(passes)
binding?.apply {
passesError.visibility = View.INVISIBLE
passesProgress.visibility = View.INVISIBLE
passesRecycler.visibility = View.VISIBLE
}
}
} else if (result is Result.InProgress) {
passes.clear()
binding?.apply {
passesTimer.text = 0L.formatForTimer()
passesError.visibility = View.INVISIBLE
passesRecycler.visibility = View.INVISIBLE
passesProgress.visibility = View.VISIBLE
}
}
})
viewModel.getAppTimer().observe(viewLifecycleOwner, { currentTime ->
tickMainTimer(currentTime)
passesAdapter?.tickPasses(currentTime)
})
}
private fun setupComponents() {
passesAdapter = PassesAdapter(viewModel.shouldUseUTC())
binding?.apply {
passesRecycler.apply { passesRecycler.apply {
setHasFixedSize(true) setHasFixedSize(true)
adapter = passesAdapter adapter = passesAdapter
@@ -94,28 +59,68 @@ class PassesFragment : Fragment(R.layout.fragment_passes) {
(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() }
}
viewModel.passes.observe(viewLifecycleOwner, { result ->
handlePassesResult(result, binding, passesAdapter)
})
viewModel.getAppTimer().observe(viewLifecycleOwner, { currentTime ->
tickMainTimer(currentTime, binding)
})
}
private fun handlePassesResult(
result: Result<List<SatPass>>,
binding: FragmentPassesBinding,
passesAdapter: PassesAdapter
) {
if (result is Result.Success) {
if (result.data.isEmpty()) {
binding.apply {
passesProgress.visibility = View.INVISIBLE
passesRecycler.visibility = View.INVISIBLE
passesError.visibility = View.VISIBLE
passesTimer.text = 0L.formatForTimer()
}
} else {
passes = result.data
passesAdapter.submitPasses(result.data)
binding.apply {
passesError.visibility = View.INVISIBLE
passesProgress.visibility = View.INVISIBLE
passesRecycler.visibility = View.VISIBLE
}
}
} else if (result is Result.InProgress) {
binding.apply {
passesTimer.text = 0L.formatForTimer()
passesError.visibility = View.INVISIBLE
passesRecycler.visibility = View.INVISIBLE
passesProgress.visibility = View.VISIBLE
}
} }
} }
private fun tickMainTimer(timeNow: Long) { private fun tickMainTimer(timeNow: Long, binding: FragmentPassesBinding) {
if (passes.isNotEmpty()) { if (passes.isNotEmpty()) {
try { try {
val nextPass = passes.first { it.startDate.time.minus(timeNow) > 0 } val nextPass = passes.first { it.startDate.time.minus(timeNow) > 0 }
val millisBeforeStart = nextPass.startDate.time.minus(timeNow) val millisBeforeStart = nextPass.startDate.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.endDate.time.minus(timeNow)
binding?.passesTimer?.text = millisBeforeEnd.formatForTimer() binding.passesTimer.text = millisBeforeEnd.formatForTimer()
} }
} else { } else {
binding?.passesTimer?.text = 0L.formatForTimer() binding.passesTimer.text = 0L.formatForTimer()
} }
} }
override fun onDestroyView() { override fun navigateToPass(satPass: SatPass) {
passesAdapter = null if (satPass.progress <= 100) {
binding = null val bundle = bundleOf("index" to passes.indexOf(satPass))
super.onDestroyView() findNavController().navigateSafe(R.id.action_passes_to_polar, bundle)
}
} }
} }
@@ -21,28 +21,29 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData import androidx.lifecycle.asLiveData
import androidx.lifecycle.liveData import androidx.lifecycle.liveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.rtbishop.look4sat.data.model.Result
import com.rtbishop.look4sat.data.model.SatPass
import com.rtbishop.look4sat.data.repository.PassesRepo import com.rtbishop.look4sat.data.repository.PassesRepo
import com.rtbishop.look4sat.data.repository.PrefsRepo import com.rtbishop.look4sat.di.DefaultDispatcher
import com.rtbishop.look4sat.data.repository.SatelliteRepo import com.rtbishop.look4sat.utility.PrefsManager
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay import kotlinx.coroutines.*
import kotlinx.coroutines.launch import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class PassesViewModel @Inject constructor( class PassesViewModel @Inject constructor(
private val prefsRepo: PrefsRepo, private val passesRepo: PassesRepo,
private val satelliteRepo: SatelliteRepo, private val prefsManager: PrefsManager,
private val passesRepo: PassesRepo @DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher
) : ViewModel() { ) : ViewModel() {
val passes = passesRepo.passes.asLiveData(viewModelScope.coroutineContext) private val _passes = MutableStateFlow<Result<List<SatPass>>>(Result.InProgress)
private var passesCache = emptyList<SatPass>()
init { private var passesCalculation: Job? = null
viewModelScope.launch { val passes = _passes.asLiveData(viewModelScope.coroutineContext)
passesRepo.calculatePasses(satelliteRepo.getSelectedSatellites())
}
}
fun getAppTimer() = liveData { fun getAppTimer() = liveData {
while (true) { while (true) {
@@ -52,6 +53,53 @@ class PassesViewModel @Inject constructor(
} }
fun shouldUseUTC(): Boolean { fun shouldUseUTC(): Boolean {
return prefsRepo.shouldUseUTC() return prefsManager.shouldUseUTC()
}
init {
viewModelScope.launch {
passesRepo.passes.collect { newPasses ->
passesCalculation?.cancelAndJoin()
passesCalculation = launch { handlePasses(newPasses) }
}
}
}
fun forceCalculation() {
viewModelScope.launch {
_passes.value = Result.InProgress
passesRepo.calculatePasses()
}
}
private suspend fun handlePasses(newPasses: List<SatPass>) = withContext(defaultDispatcher) {
if (newPasses.isEmpty()) {
_passes.value = Result.Success(newPasses)
} else {
while (isActive) {
val timeNow = System.currentTimeMillis()
passesCache = newPasses.toMutableList().apply {
val iterator = listIterator()
while (iterator.hasNext()) {
val satPass = iterator.next()
if (!satPass.isDeepSpace) {
if (satPass.progress <= 100) {
val timeStart = satPass.startDate.time
if (timeNow > timeStart) {
val deltaNow = timeNow.minus(timeStart).toFloat()
val deltaTotal = satPass.endDate.time.minus(timeStart).toFloat()
satPass.progress = ((deltaNow / deltaTotal) * 100).toInt()
}
} else {
iterator.remove()
}
}
}
}
_passes.value = Result.Success(passesCache)
Timber.d("$passesCache")
delay(1000)
}
}
} }
} }
@@ -21,10 +21,9 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData import androidx.lifecycle.asLiveData
import androidx.lifecycle.liveData import androidx.lifecycle.liveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.rtbishop.look4sat.data.model.Result
import com.rtbishop.look4sat.data.repository.PassesRepo import com.rtbishop.look4sat.data.repository.PassesRepo
import com.rtbishop.look4sat.data.repository.PrefsRepo
import com.rtbishop.look4sat.data.repository.SatelliteRepo import com.rtbishop.look4sat.data.repository.SatelliteRepo
import com.rtbishop.look4sat.utility.PrefsManager
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
@@ -32,7 +31,7 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class PolarViewModel @Inject constructor( class PolarViewModel @Inject constructor(
private val prefsRepo: PrefsRepo, private val prefsManager: PrefsManager,
private val passesRepo: PassesRepo, private val passesRepo: PassesRepo,
private val satelliteRepo: SatelliteRepo private val satelliteRepo: SatelliteRepo
) : ViewModel() { ) : ViewModel() {
@@ -45,19 +44,15 @@ class PolarViewModel @Inject constructor(
} }
fun getPass(passId: Int) = liveData { fun getPass(passId: Int) = liveData {
passesRepo.passes.collect { passes -> passesRepo.passes.collect { passes -> emit(passes[passId]) }
if (passes is Result.Success) {
emit(passes.data[passId])
}
}
} }
fun shouldUseCompass(): Boolean { fun shouldUseCompass(): Boolean {
return prefsRepo.shouldUseCompass() return prefsManager.shouldUseCompass()
} }
fun getMagDeclination(): Float { fun getMagDeclination(): Float {
return prefsRepo.getMagDeclination() return prefsManager.getMagDeclination()
} }
fun getSatTransmitters(satId: Int) = fun getSatTransmitters(satId: Int) =
@@ -28,7 +28,7 @@ import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.data.repository.PrefsRepo import com.rtbishop.look4sat.utility.PrefsManager
import com.rtbishop.look4sat.utility.QthConverter import com.rtbishop.look4sat.utility.QthConverter
import com.rtbishop.look4sat.utility.round import com.rtbishop.look4sat.utility.round
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@@ -41,7 +41,7 @@ class PrefsFragment : PreferenceFragmentCompat() {
lateinit var locationManager: LocationManager lateinit var locationManager: LocationManager
@Inject @Inject
lateinit var prefsRepo: PrefsRepo lateinit var prefsManager: PrefsManager
@Inject @Inject
lateinit var qthConverter: QthConverter lateinit var qthConverter: QthConverter
@@ -62,14 +62,14 @@ class PrefsFragment : PreferenceFragmentCompat() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
findPreference<Preference>(PrefsRepo.keyPositionGPS)?.apply { findPreference<Preference>(PrefsManager.keyPositionGPS)?.apply {
setOnPreferenceClickListener { setOnPreferenceClickListener {
setPositionFromGPS() setPositionFromGPS()
return@setOnPreferenceClickListener true return@setOnPreferenceClickListener true
} }
} }
findPreference<Preference>(PrefsRepo.keyPositionQTH)?.apply { findPreference<Preference>(PrefsManager.keyPositionQTH)?.apply {
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
setPositionFromQth(newValue.toString()) setPositionFromQth(newValue.toString())
} }
@@ -78,7 +78,7 @@ class PrefsFragment : PreferenceFragmentCompat() {
private fun setPositionFromQth(qthString: String): Boolean { private fun setPositionFromQth(qthString: String): Boolean {
qthConverter.qthToLocation(qthString)?.let { gsp -> qthConverter.qthToLocation(qthString)?.let { gsp ->
prefsRepo.setStationPosition(gsp.latitude, gsp.longitude, gsp.heightAMSL) prefsManager.setStationPosition(gsp.latitude, gsp.longitude, gsp.heightAMSL)
showSnack(getString(R.string.pref_pos_success)) showSnack(getString(R.string.pref_pos_success))
return true return true
} }
@@ -95,7 +95,7 @@ class PrefsFragment : PreferenceFragmentCompat() {
val latitude = location.latitude.round(4) val latitude = location.latitude.round(4)
val longitude = location.longitude.round(4) val longitude = location.longitude.round(4)
val altitude = location.altitude.round(1) val altitude = location.altitude.round(1)
prefsRepo.setStationPosition(latitude, longitude, altitude) prefsManager.setStationPosition(latitude, longitude, altitude)
showSnack(getString(R.string.pref_pos_success)) showSnack(getString(R.string.pref_pos_success))
} else showSnack(getString(R.string.pref_pos_gps_null)) } else showSnack(getString(R.string.pref_pos_gps_null))
} else requestPermissionLauncher.launch(locPermString) } else requestPermissionLauncher.launch(locPermString)
@@ -25,7 +25,7 @@ import android.view.WindowManager
import androidx.appcompat.app.AppCompatDialogFragment import androidx.appcompat.app.AppCompatDialogFragment
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.data.repository.PrefsRepo import com.rtbishop.look4sat.utility.PrefsManager
import com.rtbishop.look4sat.databinding.DialogSourcesBinding import com.rtbishop.look4sat.databinding.DialogSourcesBinding
import com.rtbishop.look4sat.utility.setNavResult import com.rtbishop.look4sat.utility.setNavResult
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@@ -35,7 +35,7 @@ import javax.inject.Inject
class SourcesDialog : AppCompatDialogFragment() { class SourcesDialog : AppCompatDialogFragment() {
@Inject @Inject
lateinit var prefsRepo: PrefsRepo lateinit var prefsManager: PrefsManager
override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? {
return inflater.inflate(R.layout.dialog_sources, group, false) return inflater.inflate(R.layout.dialog_sources, group, false)
@@ -43,7 +43,7 @@ class SourcesDialog : AppCompatDialogFragment() {
override fun onViewCreated(view: View, state: Bundle?) { override fun onViewCreated(view: View, state: Bundle?) {
super.onViewCreated(view, state) super.onViewCreated(view, state)
val sourcesAdapter = SourcesAdapter().apply { setSources(prefsRepo.loadTleSources()) } val sourcesAdapter = SourcesAdapter().apply { setSources(prefsManager.loadTleSources()) }
DialogSourcesBinding.bind(view).apply { DialogSourcesBinding.bind(view).apply {
dialog?.window?.setLayout( dialog?.window?.setLayout(
WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT,
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.rtbishop.look4sat.data.repository package com.rtbishop.look4sat.utility
import android.content.SharedPreferences import android.content.SharedPreferences
import android.hardware.GeomagneticField import android.hardware.GeomagneticField
@@ -28,7 +28,7 @@ import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class PrefsRepo @Inject constructor(val preferences: SharedPreferences, moshi: Moshi) { class PrefsManager @Inject constructor(val preferences: SharedPreferences, moshi: Moshi) {
private val sourcesType = Types.newParameterizedType(List::class.java, TleSource::class.java) private val sourcesType = Types.newParameterizedType(List::class.java, TleSource::class.java)
private val sourcesAdapter = moshi.adapter<List<TleSource>>(sourcesType) private val sourcesAdapter = moshi.adapter<List<TleSource>>(sourcesType)
+62 -56
View File
@@ -1,68 +1,74 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout <com.google.android.material.card.MaterialCardView
android:layout_width="match_parent" android:id="@+id/passes_bar"
android:layout_height="match_parent"> style="@style/TopBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.card.MaterialCardView <com.google.android.material.textview.MaterialTextView
android:id="@+id/passes_bar" android:id="@+id/passes_timer"
style="@style/TopBar" style="@style/TimerText"
app:layout_constraintEnd_toEndOf="parent" android:layout_width="wrap_content"
app:layout_constraintStart_toStartOf="parent" android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" /> android:elevation="4dp"
android:text="@string/timer_zero"
android:textSize="40sp"
app:layout_constraintBottom_toBottomOf="@+id/passes_bar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/passes_bar"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView <ImageButton
android:id="@+id/passes_timer" android:id="@+id/passes_refresh"
style="@style/TimerText" android:layout_width="64dp"
android:layout_width="wrap_content" android:layout_height="48dp"
android:layout_height="wrap_content" android:layout_marginEnd="4dp"
android:elevation="4dp" android:backgroundTint="@color/greyDark"
android:text="@string/timer_zero" android:contentDescription="@string/placeholder"
android:textSize="40sp" android:elevation="4dp"
app:layout_constraintBottom_toBottomOf="@+id/passes_bar" android:src="@drawable/ic_refresh"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="@+id/passes_bar"
app:layout_constraintStart_toStartOf="@id/passes_bar" app:layout_constraintTop_toTopOf="@+id/passes_bar" />
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/passes_recycler" android:id="@+id/passes_recycler"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:padding="2dp" android:padding="2dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/passes_bar"> app:layout_constraintTop_toBottomOf="@+id/passes_bar">
</androidx.recyclerview.widget.RecyclerView> </androidx.recyclerview.widget.RecyclerView>
<ProgressBar <ProgressBar
android:id="@+id/passes_progress" android:id="@+id/passes_progress"
style="@android:style/Widget.DeviceDefault.Light.ProgressBar.Large" style="@android:style/Widget.DeviceDefault.Light.ProgressBar.Large"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="invisible" android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@+id/passes_recycler" app:layout_constraintBottom_toBottomOf="@+id/passes_recycler"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/passes_bar" /> app:layout_constraintTop_toBottomOf="@+id/passes_bar" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/passes_error" android:id="@+id/passes_error"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/passes_error" android:text="@string/passes_error"
android:textSize="20sp" android:textSize="20sp"
android:visibility="invisible" android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/passes_bar" /> app:layout_constraintTop_toBottomOf="@+id/passes_bar" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>