mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
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
|
@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,
|
||||||
|
|||||||
+2
-2
@@ -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)
|
||||||
@@ -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>
|
|
||||||
Reference in new issue
Block a user