Simplified packages and classes structure

This commit is contained in:
Arty Bishop committed 2025-12-27 15:01:33 +00:00
1 parent 208c587ddf
commit f6fcd21404
34 files changed
+412 -564

No files matched your search

@@ -17,11 +17,10 @@
*/
package com.rtbishop.look4sat.data
import com.rtbishop.look4sat.domain.IDataRepository
import com.rtbishop.look4sat.domain.ISettingsRepository
import com.rtbishop.look4sat.domain.IDatabaseRepo
import com.rtbishop.look4sat.domain.ISettingsRepo
import com.rtbishop.look4sat.model.DataState
import com.rtbishop.look4sat.model.SatEntry
import com.rtbishop.look4sat.model.SatItem
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
@@ -32,15 +31,13 @@ import kotlinx.coroutines.launch
import java.io.InputStream
import java.util.zip.ZipInputStream
class DataRepository(
class DatabaseRepo(
private val dataParser: DataParser,
private val fileSource: IFileSource,
private val entrySource: IEntriesStorage,
private val radioSource: IRadiosStorage,
private val remoteSource: INetworkSource,
private val localStorage: ILocalStorage,
private val dataSource: IDataSource,
private val repositoryScope: CoroutineScope,
private val settingsRepository: ISettingsRepository
) : IDataRepository {
private val settingsRepository: ISettingsRepo
) : IDatabaseRepo {
private val exceptionHandler = CoroutineExceptionHandler { _, exception ->
_updateState.value = DataState.Exception(exception.message)
@@ -49,27 +46,16 @@ class DataRepository(
private val _updateState = MutableStateFlow<DataState<Long>>(DataState.Handled)
override val updateState: StateFlow<DataState<Long>> = _updateState
override fun getEntriesTotal() = entrySource.getEntriesTotal()
override fun getEntriesTotal() = localStorage.getEntriesTotal()
override fun getRadiosTotal() = radioSource.getRadiosTotal()
override fun getSatelliteTypes() = remoteSource.satelliteSourcesMap.keys.sorted()
override suspend fun getEntriesList(): List<SatItem> {
val selectedIds = settingsRepository.satelliteSelection.value
return entrySource.getEntriesList().map { it.copy(isSelected = it.catnum in selectedIds) }
}
override suspend fun getEntriesWithIds(ids: List<Int>) = entrySource.getEntriesWithIds(ids)
override suspend fun getRadiosWithId(id: Int) = radioSource.getRadiosWithId(id)
override fun getRadiosTotal() = localStorage.getRadiosTotal()
override fun updateFromFile(uri: String) {
repositoryScope.launch(exceptionHandler) {
_updateState.value = DataState.Loading
fileSource.getDataStream(uri)?.let { stream ->
dataSource.getFileStream(uri)?.let { stream ->
delay(updateStateDelay)
entrySource.insertEntries(importSatellites(stream))
localStorage.insertEntries(importSatellites(stream))
}
setUpdateSuccessful()
}
@@ -79,8 +65,8 @@ class DataRepository(
_updateState.value = DataState.Loading
repositoryScope.launch(exceptionHandler) {
val importedEntries = mutableListOf<SatEntry>()
val sourcesMap = remoteSource.satelliteSourcesMap
val jobsMap = sourcesMap.mapValues { async { remoteSource.getDataStream(it.value) } }
val sourcesMap = settingsRepository.satelliteSourcesMap
val jobsMap = sourcesMap.mapValues { async { dataSource.getNetworkStream(it.value) } }
jobsMap.mapValues { job -> job.value.await() }.forEach { entry ->
entry.value?.let { stream ->
when (val type = entry.key) {
@@ -112,12 +98,12 @@ class DataRepository(
}
}
}
entrySource.insertEntries(importedEntries)
localStorage.insertEntries(importedEntries)
setUpdateSuccessful()
}
repositoryScope.launch(exceptionHandler) {
remoteSource.getDataStream(remoteSource.radioSourceUrl)?.let { stream ->
radioSource.insertRadios(dataParser.parseJSONStream(stream))
dataSource.getNetworkStream(settingsRepository.radioSourceUrl)?.let { stream ->
localStorage.insertRadios(dataParser.parseJSONStream(stream))
}
}
}
@@ -130,8 +116,8 @@ class DataRepository(
repositoryScope.launch {
_updateState.value = DataState.Loading
delay(updateStateDelay)
entrySource.deleteEntries()
radioSource.deleteRadios()
localStorage.deleteEntries()
localStorage.deleteRadios()
setUpdateSuccessful(0L)
}
}
@@ -19,7 +19,9 @@ package com.rtbishop.look4sat.data
import java.io.InputStream
interface IFileSource {
interface IDataSource {
suspend fun getDataStream(uri: String): InputStream?
suspend fun getFileStream(uri: String): InputStream?
suspend fun getNetworkStream(url: String): InputStream?
}
@@ -20,9 +20,10 @@ package com.rtbishop.look4sat.data
import com.rtbishop.look4sat.domain.Satellite
import com.rtbishop.look4sat.model.SatEntry
import com.rtbishop.look4sat.model.SatItem
import com.rtbishop.look4sat.model.SatRadio
import kotlinx.coroutines.flow.Flow
interface IEntriesStorage {
interface ILocalStorage {
fun getEntriesTotal(): Flow<Int>
@@ -33,4 +34,12 @@ interface IEntriesStorage {
suspend fun insertEntries(entries: List<SatEntry>)
suspend fun deleteEntries()
fun getRadiosTotal(): Flow<Int>
suspend fun getRadiosWithId(id: Int): List<SatRadio>
suspend fun insertRadios(radios: List<SatRadio>)
suspend fun deleteRadios()
}
@@ -1,32 +0,0 @@
/*
* Look4Sat. Amateur radio satellite tracker and pass predictor.
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rtbishop.look4sat.data
import com.rtbishop.look4sat.model.SatRadio
import kotlinx.coroutines.flow.Flow
interface IRadiosStorage {
fun getRadiosTotal(): Flow<Int>
suspend fun getRadiosWithId(id: Int): List<SatRadio>
suspend fun insertRadios(radios: List<SatRadio>)
suspend fun deleteRadios()
}
@@ -17,7 +17,7 @@
*/
package com.rtbishop.look4sat.data
import com.rtbishop.look4sat.domain.ISatelliteRepository
import com.rtbishop.look4sat.domain.ISatelliteRepo
import com.rtbishop.look4sat.domain.Satellite
import com.rtbishop.look4sat.model.GeoPos
import com.rtbishop.look4sat.model.SatPass
@@ -30,7 +30,10 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.withContext
class SatelliteRepository(private val dispatcher: CoroutineDispatcher) : ISatelliteRepository {
class SatelliteRepo(
private val dispatcher: CoroutineDispatcher,
private val localStorage: ILocalStorage
) : ISatelliteRepo {
private var passes = listOf<SatPass>()
private val _calculatedPasses = MutableSharedFlow<List<SatPass>>(replay = 1)
@@ -38,6 +41,10 @@ class SatelliteRepository(private val dispatcher: CoroutineDispatcher) : ISatell
override fun getPasses(): List<SatPass> = passes
override suspend fun getEntriesWithIds(ids: List<Int>) = localStorage.getEntriesWithIds(ids)
override suspend fun getRadiosWithId(id: Int) = localStorage.getRadiosWithId(id)
override suspend fun getPosition(sat: Satellite, pos: GeoPos, time: Long): SatPos {
return withContext(dispatcher) { sat.getPosition(pos, time) }
}
@@ -1,7 +1,7 @@
package com.rtbishop.look4sat.data
import com.rtbishop.look4sat.domain.IDataRepository
import com.rtbishop.look4sat.domain.ISettingsRepository
import com.rtbishop.look4sat.domain.ISelectionRepo
import com.rtbishop.look4sat.domain.ISettingsRepo
import com.rtbishop.look4sat.model.SatItem
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -12,11 +12,11 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
@OptIn(ExperimentalCoroutinesApi::class)
class SelectionRepository(
class SelectionRepo(
private val dispatcher: CoroutineDispatcher,
private val dataRepository: IDataRepository,
private val settingsRepository: ISettingsRepository
) {
private val localStorage: ILocalStorage,
private val settingsRepo: ISettingsRepo
) : ISelectionRepo {
private val currentItems = MutableStateFlow<List<SatItem>>(emptyList())
private val currentType = MutableStateFlow("All")
private val currentQuery = MutableStateFlow("")
@@ -27,37 +27,44 @@ class SelectionRepository(
itemsWithType.map { items -> items.filterByQuery(query) }
}
fun getCurrentType() = currentType.value
override fun getCurrentType() = currentType.value
fun getTypesList() = dataRepository.getSatelliteTypes()
override fun getTypesList() = settingsRepo.satelliteSourcesMap.keys.sorted()
suspend fun getEntriesFlow() = withContext(dispatcher) {
currentItems.value = dataRepository.getEntriesList()
override suspend fun getEntriesFlow() = withContext(dispatcher) {
val selectedIds = settingsRepo.satelliteSelection.value
currentItems.value = localStorage.getEntriesList().map { item ->
item.copy(isSelected = item.catnum in selectedIds)
}
return@withContext itemsWithQuery
}
suspend fun setType(type: String) = withContext(dispatcher) { currentType.value = type }
suspend fun setQuery(query: String) = withContext(dispatcher) { currentQuery.value = query }
suspend fun updateSelection(selectAll: Boolean) = withContext(dispatcher) {
updateSelection(itemsWithQuery.first().map { item -> item.catnum }, selectAll)
override suspend fun setType(type: String) = withContext(dispatcher) {
currentType.value = type
}
suspend fun updateSelection(catNums: List<Int>, isSelected: Boolean) = withContext(dispatcher) {
override suspend fun setQuery(query: String) = withContext(dispatcher) {
currentQuery.value = query
}
override suspend fun setSelection(selectAll: Boolean) = withContext(dispatcher) {
setSelection(itemsWithQuery.first().map { item -> item.catnum }, selectAll)
}
override suspend fun setSelection(ids: List<Int>, isTicked: Boolean) = withContext(dispatcher) {
currentItems.value = currentItems.value.map { item ->
if (item.catnum in catNums) item.copy(isSelected = isSelected) else item
if (item.catnum in ids) item.copy(isSelected = isTicked) else item
}
}
suspend fun saveSelection() = withContext(dispatcher) {
override suspend fun saveSelection() = withContext(dispatcher) {
val currentSelection = currentItems.value.filter { it.isSelected }.map { it.catnum }
settingsRepository.saveEntriesSelection(currentSelection)
settingsRepo.saveEntriesSelection(currentSelection)
}
private suspend fun List<SatItem>.filterByType(type: String) = withContext(dispatcher) {
if (type == "All") return@withContext this@filterByType
val catnums = settingsRepository.loadSatType(type)
val catnums = settingsRepo.loadSatType(type)
if (catnums.isEmpty()) return@withContext this@filterByType
return@withContext this@filterByType.filter { item -> item.catnum in catnums }
}
@@ -18,12 +18,10 @@
package com.rtbishop.look4sat.domain
import com.rtbishop.look4sat.model.DataState
import com.rtbishop.look4sat.model.SatItem
import com.rtbishop.look4sat.model.SatRadio
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
interface IDataRepository {
interface IDatabaseRepo {
val updateState: StateFlow<DataState<Long>>
@@ -31,14 +29,6 @@ interface IDataRepository {
fun getRadiosTotal(): Flow<Int>
fun getSatelliteTypes(): List<String>
suspend fun getEntriesList(): List<SatItem>
suspend fun getEntriesWithIds(ids: List<Int>): List<Satellite>
suspend fun getRadiosWithId(id: Int): List<SatRadio>
fun updateFromFile(uri: String)
fun updateFromWeb()
@@ -6,12 +6,16 @@ import com.rtbishop.look4sat.model.SatPos
import com.rtbishop.look4sat.model.SatRadio
import kotlinx.coroutines.flow.SharedFlow
interface ISatelliteRepository {
interface ISatelliteRepo {
val calculatedPasses: SharedFlow<List<SatPass>>
fun getPasses(): List<SatPass>
suspend fun getEntriesWithIds(ids: List<Int>): List<Satellite>
suspend fun getRadiosWithId(id: Int): List<SatRadio>
suspend fun getPosition(sat: Satellite, pos: GeoPos, time: Long): SatPos
suspend fun getTrack(sat: Satellite, pos: GeoPos, start: Long, end: Long): List<SatPos>
@@ -0,0 +1,23 @@
package com.rtbishop.look4sat.domain
import com.rtbishop.look4sat.model.SatItem
import kotlinx.coroutines.flow.Flow
interface ISelectionRepo {
fun getCurrentType(): String
fun getTypesList(): List<String>
suspend fun getEntriesFlow(): Flow<List<SatItem>>
suspend fun setType(type: String)
suspend fun setQuery(query: String)
suspend fun setSelection(selectAll: Boolean)
suspend fun setSelection(ids: List<Int>, isTicked: Boolean)
suspend fun saveSelection()
}
@@ -2,7 +2,7 @@ package com.rtbishop.look4sat.domain
import kotlinx.coroutines.flow.StateFlow
interface ISensorRepository {
interface ISensorsRepo {
val orientation: StateFlow<Pair<Float, Float>>
@@ -15,13 +15,15 @@
* 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
package com.rtbishop.look4sat.domain
import java.io.InputStream
import com.rtbishop.look4sat.model.GeoPos
import kotlinx.coroutines.flow.StateFlow
interface INetworkSource {
interface ISettingsRepo {
val radioSourceUrl: String get() = "https://db.satnogs.org/api/transmitters/?format=json"
val satelliteSourcesMap: Map<String, String>
get() = mapOf(
"All" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=active&FORMAT=csv",
@@ -52,5 +54,79 @@ interface INetworkSource {
"X-Comm" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=x-comm&FORMAT=csv"
)
suspend fun getDataStream(url: String): InputStream?
val stationPosition: StateFlow<GeoPos>
val satelliteSelection: StateFlow<List<Int>>
fun setGpsPosition(): Boolean
fun setGeoPosition(latitude: Double, longitude: Double, altitude: Double = 0.0): Boolean
fun setQthPosition(locator: String): Boolean
fun getHoursAhead(): Int
fun setHoursAhead(hoursAhead: Int)
fun getMinElevation(): Double
fun setMinElevation(minElevation: Double)
fun isUtcEnabled(): Boolean
fun setUtcState(value: Boolean)
fun getLastUpdateTime(): Long
fun setLastUpdateTime(updateTime: Long)
fun isUpdateEnabled(): Boolean
fun setUpdateState(value: Boolean)
fun isSensorEnabled(): Boolean
fun setSensorState(value: Boolean)
fun isSweepEnabled(): Boolean
fun setSweepState(value: Boolean)
fun saveModesSelection(modes: List<String>)
fun loadModesSelection(): List<String>
fun saveEntriesSelection(catnums: List<Int>)
fun saveSatType(type: String, catnums: List<Int>)
fun loadSatType(type: String): List<Int>
fun getRotatorEnabled(): Boolean
fun setRotatorEnabled(value: Boolean)
fun getRotatorServer(): String
fun setRotatorServer(value: String)
fun getRotatorPort(): String
fun setRotatorPort(value: String)
fun getBTEnabled(): Boolean
fun setBTEnabled(value: Boolean)
fun getBTDeviceAddr(): String
fun setBTDeviceAddr(value: String)
fun getBTDeviceName(): String
fun setBTDeviceName(value: String)
fun getBTFormat(): String
fun setBTFormat(value: String)
}
@@ -1,100 +0,0 @@
/*
* Look4Sat. Amateur radio satellite tracker and pass predictor.
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rtbishop.look4sat.domain
import com.rtbishop.look4sat.model.GeoPos
import kotlinx.coroutines.flow.StateFlow
interface ISettingsRepository {
val stationPosition: StateFlow<GeoPos>
val satelliteSelection: StateFlow<List<Int>>
fun setGpsPosition(): Boolean
fun setGeoPosition(latitude: Double, longitude: Double, altitude: Double = 0.0): Boolean
fun setQthPosition(locator: String): Boolean
fun getHoursAhead(): Int
fun setHoursAhead(hoursAhead: Int)
fun getMinElevation(): Double
fun setMinElevation(minElevation: Double)
fun isUtcEnabled(): Boolean
fun setUtcState(value: Boolean)
fun getLastUpdateTime(): Long
fun setLastUpdateTime(updateTime: Long)
fun isUpdateEnabled(): Boolean
fun setUpdateState(value: Boolean)
fun isSensorEnabled(): Boolean
fun setSensorState(value: Boolean)
fun isSweepEnabled(): Boolean
fun setSweepState(value: Boolean)
fun saveModesSelection(modes: List<String>)
fun loadModesSelection(): List<String>
fun saveEntriesSelection(catnums: List<Int>)
fun saveSatType(type: String, catnums: List<Int>)
fun loadSatType(type: String): List<Int>
fun getRotatorEnabled(): Boolean
fun setRotatorEnabled(value: Boolean)
fun getRotatorServer(): String
fun setRotatorServer(value: String)
fun getRotatorPort(): String
fun setRotatorPort(value: String)
fun getBTEnabled(): Boolean
fun setBTEnabled(value: Boolean)
fun getBTDeviceAddr(): String
fun setBTDeviceAddr(value: String)
fun getBTDeviceName(): String
fun setBTDeviceName(value: String)
fun getBTFormat(): String
fun setBTFormat(value: String)
}
@@ -72,17 +72,3 @@ fun String.isValidPort(): Boolean {
val port = "([1-9]|[1-9]\\d{1,3}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])"
return this.matches(port.toRegex()) && this.toInt() in 1024..65535
}
//fun ping(hostname: String, port: Int): Int {
// val start = System.currentTimeMillis()
// val socket = Socket()
// try {
// socket.connect(InetSocketAddress(hostname, port), 5000)
// socket.close()
// } catch (exception: Exception) {
// exception.printStackTrace()
// println("Failed to ping: $hostname")
// return Int.MAX_VALUE
// }
// return (System.currentTimeMillis() - start).toInt()
//}