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