Extracted implementation classes into data module

This commit is contained in:
Arty Bishop committed 2025-12-27 15:01:45 +00:00
1 parent 5a449d4133
commit 28e926eb86
54 files changed
+247 -153

No files matched your search

@@ -6,23 +6,23 @@ import android.hardware.Sensor
import android.hardware.SensorManager
import android.location.LocationManager
import androidx.room.Room
import com.rtbishop.look4sat.domain.data.DataParser
import com.rtbishop.look4sat.domain.data.DatabaseRepo
import com.rtbishop.look4sat.domain.data.ISensorSource
import com.rtbishop.look4sat.domain.data.SatelliteRepo
import com.rtbishop.look4sat.domain.data.SelectionRepo
import com.rtbishop.look4sat.domain.predict.IDatabaseRepo
import com.rtbishop.look4sat.domain.predict.ISatelliteRepo
import com.rtbishop.look4sat.domain.predict.ISelectionRepo
import com.rtbishop.look4sat.domain.predict.ISettingsRepo
import com.rtbishop.look4sat.framework.BluetoothReporter
import com.rtbishop.look4sat.framework.NetworkReporter
import com.rtbishop.look4sat.framework.SettingsRepo
import com.rtbishop.look4sat.framework.data.DataSource
import com.rtbishop.look4sat.framework.data.LocalStorage
import com.rtbishop.look4sat.framework.data.MIGRATION_1_2
import com.rtbishop.look4sat.framework.data.MainDatabase
import com.rtbishop.look4sat.framework.data.SensorSource
import com.rtbishop.look4sat.data.database.MIGRATION_1_2
import com.rtbishop.look4sat.data.database.MainDatabase
import com.rtbishop.look4sat.data.framework.BluetoothReporter
import com.rtbishop.look4sat.data.framework.NetworkReporter
import com.rtbishop.look4sat.data.framework.SettingsRepo
import com.rtbishop.look4sat.data.repository.DataParser
import com.rtbishop.look4sat.data.repository.DatabaseRepo
import com.rtbishop.look4sat.data.repository.SatelliteRepo
import com.rtbishop.look4sat.data.repository.SelectionRepo
import com.rtbishop.look4sat.data.source.DataSource
import com.rtbishop.look4sat.data.source.LocalStorage
import com.rtbishop.look4sat.data.source.SensorSource
import com.rtbishop.look4sat.domain.repository.IDatabaseRepo
import com.rtbishop.look4sat.domain.repository.ISatelliteRepo
import com.rtbishop.look4sat.domain.repository.ISelectionRepo
import com.rtbishop.look4sat.domain.repository.ISettingsRepo
import com.rtbishop.look4sat.domain.source.ISensorSource
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -1,85 +0,0 @@
package com.rtbishop.look4sat.framework
import android.bluetooth.BluetoothManager
import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import java.io.OutputStream
import java.util.*
import kotlin.math.abs
class BluetoothReporter(
private val manager: BluetoothManager,
private val reporterScope: CoroutineScope
) {
private val tag = "BTReporter"
private val sppid: UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb")
private lateinit var outputStream: OutputStream
private var rotationConnectionJob: Job? = null
private var rotationReportingJob: Job? = null
private var connected = false
private var connecting = false
fun isConnected(): Boolean = connected
fun isConnecting(): Boolean = connecting
fun connectBTDevice(deviceId: String) {
if (!connected) {
rotationConnectionJob = reporterScope.launch {
try {
manager.adapter.getRemoteDevice(deviceId)?.let { device ->
device.createInsecureRfcommSocketToServiceRecord(sppid)?.let { socket ->
connecting = true
socket.connect()
outputStream = socket.outputStream
connected = true
connecting = false
Log.i(tag, "$tag: Connected!")
}
}
} catch (e: SecurityException) {
Log.e(tag, "$tag: ${e.message}")
} catch (e: Exception) {
Log.e(tag, "$tag: ${e.message}")
}
}
}
}
fun reportRotation(format: String, azimuth: Int, elevation: Int) {
if (connected) {
rotationReportingJob = reporterScope.launch {
val newElevation = if (elevation > 0) elevation else 0
try {
val azimuthString = intToStringWithLeadingZeroes(azimuth)
val elevationString = intToStringWithLeadingZeroes(newElevation)
val crChar = '\r'
val nlChar = '\n'
val tbChar = '\t'
var buffer = format.replace("\$AZ", azimuthString)
buffer = buffer.replace("\$EL", elevationString)
buffer = buffer.replace("\\r", crChar.toString())
buffer = buffer.replace("\\n", nlChar.toString())
buffer = buffer.replace("\\t", tbChar.toString())
Log.i(tag, "$tag: Sending $buffer")
if (connected) outputStream.write(buffer.toByteArray())
} catch (e: Exception) {
Log.e(tag, "$tag: ${e.message}")
connected = false
}
}
}
}
private fun intToStringWithLeadingZeroes(value: Int): String {
return if (value > 0) {
if (value < 10) "00$value" else if (value < 100) "0$value" else "$value"
} else {
val absValue = abs(value)
if (value > -10) "-00$absValue" else if (value > -100) "-0$absValue" else "-$absValue"
}
}
}
@@ -1,70 +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.framework
import java.net.InetSocketAddress
import java.nio.ByteBuffer
import java.nio.channels.SocketChannel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.launch
class NetworkReporter(private val reporterScope: CoroutineScope) {
private var rotationSocketChannel: SocketChannel? = null
private var rotationReporting: Job? = null
// private var frequencySocketChannel: SocketChannel? = null
// private var frequencyReporting: Job? = null
//
// fun reportFrequency(server: String, port: Int, frequency: Long) {
// frequencyReporting = reporterScope.launch {
// runCatching {
// if (frequencySocketChannel == null) {
// frequencySocketChannel = SocketChannel.open(InetSocketAddress(server, port))
// } else {
// val buffer = ByteBuffer.wrap("\\set_freq $frequency\n".toByteArray())
// frequencySocketChannel?.write(buffer)
// }
// }.onFailure { error ->
// println(error.localizedMessage)
// frequencySocketChannel = null
// frequencyReporting?.cancelAndJoin()
// }
// }
// }
fun reportRotation(server: String, port: Int, azimuth: Double, elevation: Double) {
rotationReporting = reporterScope.launch {
val newElevation = if (elevation > 0.0) elevation else 0.0
runCatching {
if (rotationSocketChannel == null) {
rotationSocketChannel = SocketChannel.open(InetSocketAddress(server, port))
} else {
val buffer = ByteBuffer.wrap("\\P $azimuth $newElevation\n".toByteArray())
rotationSocketChannel?.write(buffer)
}
}.onFailure { error ->
println(error.localizedMessage)
rotationSocketChannel = null
rotationReporting?.cancelAndJoin()
}
}
}
}
@@ -1,310 +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.framework
import android.content.SharedPreferences
import android.location.Criteria
import android.location.LocationManager
import androidx.core.content.edit
import androidx.core.location.LocationManagerCompat
import androidx.core.os.CancellationSignal
import com.rtbishop.look4sat.domain.model.DatabaseState
import com.rtbishop.look4sat.domain.model.GeoPos
import com.rtbishop.look4sat.domain.model.OtherSettings
import com.rtbishop.look4sat.domain.model.PassesSettings
import com.rtbishop.look4sat.domain.predict.ISettingsRepo
import com.rtbishop.look4sat.domain.utility.QthConverter
import com.rtbishop.look4sat.domain.utility.round
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import java.util.concurrent.Executors
class SettingsRepo(
private val manager: LocationManager, private val preferences: SharedPreferences
) : ISettingsRepo {
private val keyModes = "satModes"
private val keyUtcState = "utcState"
private val keyUpdateState = "updateState"
private val keySweepState = "sweepState"
private val keySensorState = "sensorState"
private val keyDataEntries = "dataEntries"
private val keyDataRadios = "dataRadios"
private val keyDataTimestamp = "dataTimestamp"
private val keyHoursAhead = "hoursAhead"
private val keyMinElevation = "minElevation"
private val keyRotator = "isRotatorEnabled"
private val keyRotatorAddress = "rotatorAddress"
private val keyRotatorPort = "rotatorPort"
private val keyBTEnabled = "isBTEnabled"
private val keyBTDeviceName = "BTDeviceName"
private val keyBTDeviceAddr = "BTDeviceAddr"
private val keyBTFormat = "BTFormat"
private val keyLatitude = "stationLat"
private val keyLongitude = "stationLon"
private val keyAltitude = "stationAlt"
private val keyLocator = "stationQTH"
private val keyLocTimestamp = "locTimestamp"
private val keySelection = "selection"
//region # Station position settings
private val _stationPosition = MutableStateFlow(loadStationPosition())
private val defaultProvider = LocationManager.PASSIVE_PROVIDER
private val executor = Executors.newSingleThreadExecutor()
private val timeoutSignal = CancellationSignal().apply {
setOnCancelListener { _stationPosition.value = loadStationPosition() }
}
override val stationPosition: StateFlow<GeoPos> = _stationPosition
override fun setGpsPosition(): Boolean {
if (!LocationManagerCompat.isLocationEnabled(manager)) return false
try {
val criteria = Criteria().apply { isCostAllowed = true }
val provider = manager.getBestProvider(criteria, true) ?: defaultProvider
println("Requesting location for $provider provider")
LocationManagerCompat.getCurrentLocation(manager, provider, timeoutSignal, executor) {
it?.let { setGeoPosition(it.latitude, it.longitude, it.altitude) }
}
} catch (exception: SecurityException) {
println("No permissions were given")
}
return true
}
override fun setGeoPosition(latitude: Double, longitude: Double, altitude: Double): Boolean {
val newLongitude = if (longitude > 180.0) longitude - 180 else longitude
val locator = QthConverter.positionToQth(latitude, newLongitude) ?: return false
saveGeoPos(latitude, newLongitude, altitude, locator)
return true
}
override fun setQthPosition(locator: String): Boolean {
val position = QthConverter.qthToPosition(locator) ?: return false
saveGeoPos(position.latitude, position.longitude, 0.0, locator)
return true
}
private fun saveStationPosition(stationPos: GeoPos) = preferences.edit {
putString(keyLatitude, stationPos.latitude.toString())
putString(keyLongitude, stationPos.longitude.toString())
putString(keyAltitude, stationPos.altitude.toString())
putString(keyLocator, stationPos.qthLocator)
putLong(keyLocTimestamp, stationPos.timestamp)
_stationPosition.value = stationPos
}
private fun saveGeoPos(latitude: Double, longitude: Double, altitude: Double, locator: String) {
val newLat = latitude.round(4)
val newLon = longitude.round(4)
val newAlt = altitude.round(1)
val timestamp = System.currentTimeMillis()
println("Received new Position($newLat, $newLon, $newAlt) & Locator $locator")
saveStationPosition(GeoPos(newLat, newLon, newAlt, locator, timestamp))
}
private fun loadStationPosition(): GeoPos {
val latitude = (preferences.getString(keyLatitude, null) ?: "0.0").toDouble()
val longitude = (preferences.getString(keyLongitude, null) ?: "0.0").toDouble()
val altitude = (preferences.getString(keyAltitude, null) ?: "0.0").toDouble()
val qthLocator = preferences.getString(keyLocator, null) ?: "null"
val timestamp = preferences.getLong(keyLocTimestamp, 0L)
return GeoPos(latitude, longitude, altitude, qthLocator, timestamp)
}
//endregion
//region # Database update settings
private val _databaseState = MutableStateFlow(loadDatabaseState())
override val databaseState: StateFlow<DatabaseState> = _databaseState
override fun saveDatabaseState(state: DatabaseState) = preferences.edit {
putInt(keyDataEntries, state.entriesTotal)
putInt(keyDataRadios, state.radiosTotal)
putLong(keyDataTimestamp, state.timestamp)
_databaseState.value = state
}
override fun saveSatType(type: String, catnums: List<Int>) {
val stringList = catnums.map { catnum -> catnum.toString() }
preferences.edit { putStringSet("type$type", stringList.toSet()) }
}
override fun loadSatType(type: String): List<Int> {
val catnums =
preferences.getStringSet("type$type", emptySet())?.map { catnum -> catnum.toInt() }
return catnums ?: emptyList()
}
private fun loadDatabaseState(): DatabaseState {
val entriesTotal = preferences.getInt(keyDataEntries, 0)
val radiosTotal = preferences.getInt(keyDataRadios, 0)
val timestamp = preferences.getLong(keyDataTimestamp, 0L)
return DatabaseState(entriesTotal, radiosTotal, timestamp)
}
//endregion
//region # Entries selection settings
private val _satelliteSelection = MutableStateFlow(loadEntriesSelection())
override val satelliteSelection: StateFlow<List<Int>> = _satelliteSelection
override fun saveEntriesSelection(catnums: List<Int>) = preferences.edit {
putStringSet(keySelection, catnums.map { catnum -> catnum.toString() }.toSet())
_satelliteSelection.value = catnums
}
private fun loadEntriesSelection(): List<Int> {
val catNums = preferences.getStringSet(keySelection, emptySet())
return catNums?.map { catnum -> catnum.toInt() }?.sorted() ?: emptyList()
}
//endregion
//region # Passes filter settings
private val _passesSettings = MutableStateFlow(loadPassesSettings())
override val passesSettings: StateFlow<PassesSettings> = _passesSettings
override fun savePassesSettings(settings: PassesSettings) = preferences.edit {
putInt(keyHoursAhead, settings.hoursAhead)
putDouble(keyMinElevation, settings.minElevation)
_passesSettings.value = settings
}
override fun saveModesSelection(modes: List<String>) {
preferences.edit { putStringSet(keyModes, modes.toSet()) }
}
override fun loadModesSelection(): List<String> {
return preferences.getStringSet(keyModes, null)?.toList()?.sorted() ?: emptyList()
}
private fun loadPassesSettings(): PassesSettings {
val hoursAhead = preferences.getInt(keyHoursAhead, 24)
val minElevation = preferences.getDouble(keyMinElevation, 16.0)
return PassesSettings(hoursAhead, minElevation)
}
//endregion
//region # Other settings
private val _otherSettings = MutableStateFlow(loadOtherSettings())
override val otherSettings: StateFlow<OtherSettings> = _otherSettings
override fun toggleUtc(value: Boolean) {
preferences.edit { putBoolean(keyUtcState, value) }
_otherSettings.value = otherSettings.value.copy(utcState = value)
}
override fun toggleUpdate(value: Boolean) {
preferences.edit { putBoolean(keyUpdateState, value) }
_otherSettings.value = otherSettings.value.copy(updateState = value)
}
override fun toggleSweep(value: Boolean) {
preferences.edit { putBoolean(keySweepState, value) }
_otherSettings.value = otherSettings.value.copy(sweepState = value)
}
override fun toggleSensor(value: Boolean) {
preferences.edit { putBoolean(keySensorState, value) }
_otherSettings.value = otherSettings.value.copy(sensorState = value)
}
private fun loadOtherSettings(): OtherSettings {
val utcState = preferences.getBoolean(keyUtcState, false)
val updateState = preferences.getBoolean(keyUpdateState, true)
val sweepState = preferences.getBoolean(keySweepState, true)
val sensorState = preferences.getBoolean(keySensorState, true)
return OtherSettings(utcState, updateState, sweepState, sensorState)
}
//endregion
//region # Undefined settings
override fun getRotatorEnabled(): Boolean {
return preferences.getBoolean(keyRotator, false)
}
override fun setRotatorEnabled(value: Boolean) {
preferences.edit { putBoolean(keyRotator, value) }
}
override fun getRotatorServer(): String {
return preferences.getString(keyRotatorAddress, null) ?: "127.0.0.1"
}
override fun setRotatorServer(value: String) {
preferences.edit { putString(keyRotatorAddress, value) }
}
override fun getRotatorPort(): String {
return preferences.getString(keyRotatorPort, null) ?: "4533"
}
override fun setRotatorPort(value: String) {
preferences.edit { putString(keyRotatorPort, value) }
}
override fun getBTEnabled(): Boolean {
return preferences.getBoolean(keyBTEnabled, false)
}
override fun setBTEnabled(value: Boolean) {
preferences.edit { putBoolean(keyBTEnabled, value) }
}
override fun getBTDeviceAddr(): String {
return preferences.getString(keyBTDeviceAddr, null) ?: "00:0C:BF:13:80:5D"
}
override fun setBTDeviceAddr(value: String) {
preferences.edit { putString(keyBTDeviceAddr, value) }
}
override fun getBTDeviceName(): String {
return preferences.getString(keyBTDeviceName, null) ?: "Default"
}
override fun setBTDeviceName(value: String) {
preferences.edit { putString(keyBTDeviceName, value) }
}
override fun getBTFormat(): String {
return preferences.getString(keyBTFormat, null) ?: "W\$AZ \$EL"
}
override fun setBTFormat(value: String) {
preferences.edit { putString(keyBTFormat, value) }
}
//endregion
private fun SharedPreferences.getDouble(key: String, default: Double): Double {
return Double.fromBits(getLong(key, default.toRawBits()))
}
private fun SharedPreferences.Editor.putDouble(key: String, double: Double) {
putLong(key, double.toRawBits())
}
}
@@ -1,47 +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.framework.data
import android.content.ContentResolver
import android.net.Uri
import com.rtbishop.look4sat.domain.data.IDataSource
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.InputStream
class DataSource(
private val contentResolver: ContentResolver,
private val httpClient: OkHttpClient,
private val dispatcher: CoroutineDispatcher
) : IDataSource {
override suspend fun getFileStream(uri: String): InputStream? {
return withContext(dispatcher) { contentResolver.openInputStream(Uri.parse(uri)) }
}
override suspend fun getNetworkStream(url: String): InputStream? = withContext(dispatcher) {
try {
val request = Request.Builder().url(url).build()
httpClient.newCall(request).execute().body?.byteStream()
} catch (exception: Exception) {
null
}
}
}
@@ -1,94 +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.framework.data
import com.rtbishop.look4sat.domain.data.ILocalStorage
import com.rtbishop.look4sat.domain.model.SatRadio
import com.rtbishop.look4sat.domain.predict.Satellite
import com.rtbishop.look4sat.domain.model.SatEntry as DomainEntry
import com.rtbishop.look4sat.domain.model.SatItem as DomainItem
import com.rtbishop.look4sat.domain.model.SatRadio as DomainRadio
import com.rtbishop.look4sat.framework.model.SatEntry as FrameworkEntry
import com.rtbishop.look4sat.framework.model.SatItem as FrameworkItem
import com.rtbishop.look4sat.framework.model.SatRadio as FrameworkRadio
class LocalStorage(private val storageDao: StorageDao) : ILocalStorage {
//region # Entries region
override suspend fun getEntriesTotal() = storageDao.getEntriesTotal()
override suspend fun getEntriesList(): List<DomainItem> {
return storageDao.getEntriesList().toDomainItems()
}
override suspend fun getEntriesWithIds(ids: List<Int>): List<Satellite> {
val selectedSatellites = mutableListOf<Satellite>()
ids.chunked(999).forEach { idsPart ->
val entries = storageDao.getEntriesWithIds(idsPart)
selectedSatellites.addAll(entries.map { entry -> entry.data.getSatellite() })
}
return selectedSatellites
}
override suspend fun insertEntries(entries: List<DomainEntry>) {
storageDao.insertEntries(entries.toFrameworkEntries())
}
override suspend fun deleteEntries() = storageDao.deleteEntries()
private fun DomainEntry.toFramework() = FrameworkEntry(this.data, this.comment)
private fun FrameworkItem.toDomain() = DomainItem(this.catnum, this.name, false)
private fun List<DomainEntry>.toFrameworkEntries() = this.map { entry -> entry.toFramework() }
private fun List<FrameworkItem>.toDomainItems() = this.map { item -> item.toDomain() }
//endregion
//region # Radios region
override suspend fun getRadiosTotal() = storageDao.getRadiosTotal()
override suspend fun getRadiosWithId(id: Int): List<SatRadio> {
return storageDao.getRadiosWithId(id).toDomainRadios()
}
override suspend fun insertRadios(radios: List<SatRadio>) {
storageDao.insertRadios(radios.toFrameworkRadios())
}
override suspend fun deleteRadios() = storageDao.deleteRadios()
private fun DomainRadio.toFramework() = FrameworkRadio(
this.uuid, this.info, this.isAlive, this.downlink, this.uplink,
this.mode, this.isInverted, this.catnum, this.comment
)
private fun FrameworkRadio.toDomain() = DomainRadio(
this.uuid, this.info, this.isAlive, this.downlink, this.uplink,
this.mode, this.isInverted, this.catnum, this.comment
)
private fun List<DomainRadio>.toFrameworkRadios() = this.map { radio -> radio.toFramework() }
private fun List<FrameworkRadio>.toDomainRadios() = this.map { radio -> radio.toDomain() }
//endregion
}
@@ -1,40 +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.framework.data
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.rtbishop.look4sat.framework.model.SatEntry
import com.rtbishop.look4sat.framework.model.SatRadio
@Database(entities = [SatEntry::class, SatRadio::class], version = 2, exportSchema = false)
abstract class MainDatabase : RoomDatabase() {
abstract fun storageDao(): StorageDao
}
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE entries_backup (name TEXT NOT NULL, epoch REAL NOT NULL, meanmo REAL NOT NULL, eccn REAL NOT NULL, incl REAL NOT NULL, raan REAL NOT NULL, argper REAL NOT NULL, meanan REAL NOT NULL, catnum INTEGER NOT NULL, bstar REAL NOT NULL, xincl REAL NOT NULL, xnodeo REAL NOT NULL, omegao REAL NOT NULL, xmo REAL NOT NULL, xno REAL NOT NULL, orbitalPeriod REAL NOT NULL, isDeepSpace INTEGER NOT NULL, comment TEXT, PRIMARY KEY(catnum))")
database.execSQL("INSERT INTO entries_backup (name, epoch, meanmo, eccn, incl, raan, argper, meanan, catnum, bstar, xincl, xnodeo, omegao, xmo, xno, orbitalPeriod, isDeepSpace, comment) SELECT name, epoch, meanmo, eccn, incl, raan, argper, meanan, catnum, bstar, xincl, xnodeo, omegao, xmo, xno, 1440 / meanmo, 1440 / meanmo >= 225.0, comment FROM entries")
database.execSQL("DROP TABLE entries")
database.execSQL("ALTER TABLE entries_backup RENAME TO entries")
}
}
@@ -1,66 +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.framework.data
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import com.rtbishop.look4sat.domain.data.ISensorSource
import com.rtbishop.look4sat.domain.predict.RAD2DEG
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlin.math.round
class SensorSource(private val sensorManager: SensorManager, private val sensor: Sensor?) :
SensorEventListener, ISensorSource {
private val _orientation = MutableStateFlow(Pair(0f, 0f))
private val rotationMatrix = FloatArray(9)
private val orientationValues = FloatArray(3)
private var sensorAccuracy = SensorManager.SENSOR_STATUS_UNRELIABLE
override val orientation: StateFlow<Pair<Float, Float>> = _orientation
override fun enableSensor() {
sensor?.let { sensorManager.registerListener(this, it, 8000) }
}
override fun disableSensor() = sensorManager.unregisterListener(this)
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
sensorAccuracy = accuracy
}
override fun onSensorChanged(event: SensorEvent) = when {
sensorAccuracy == SensorManager.SENSOR_STATUS_UNRELIABLE -> {}
event.sensor == sensor -> updateOrientation(event.values)
else -> {}
}
private fun updateOrientation(rotationVector: FloatArray) {
SensorManager.getRotationMatrixFromVector(rotationMatrix, rotationVector)
SensorManager.getOrientation(rotationMatrix, orientationValues)
val azimuth = (orientationValues[0] * RAD2DEG).toFloat()
val pitch = (orientationValues[1] * RAD2DEG).toFloat() // roll [2]
val magneticAzimuth = (azimuth + 360f) % 360f
val roundedAzimuth = round(magneticAzimuth * 10) / 10
val roundedPitch = round(pitch * 10) / 10
_orientation.value = Pair(roundedAzimuth, roundedPitch)
}
}
@@ -1,57 +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.framework.data
import androidx.room.*
import com.rtbishop.look4sat.framework.model.SatEntry
import com.rtbishop.look4sat.framework.model.SatItem
import com.rtbishop.look4sat.framework.model.SatRadio
@Dao
interface StorageDao {
@Query("SELECT COUNT(*) FROM entries")
suspend fun getEntriesTotal(): Int
//@Transaction
@Query("SELECT catnum, name FROM entries ORDER BY name ASC")
suspend fun getEntriesList(): List<SatItem>
@Transaction
@Query("SELECT * FROM entries WHERE catnum IN (:selectedIds)")
suspend fun getEntriesWithIds(selectedIds: List<Int>): List<SatEntry>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertEntries(entries: List<SatEntry>)
@Query("DELETE FROM entries")
suspend fun deleteEntries()
@Query("SELECT COUNT(*) FROM radios")
suspend fun getRadiosTotal(): Int
@Transaction
@Query("SELECT * FROM radios WHERE catnum = :id AND isAlive = 1")
suspend fun getRadiosWithId(id: Int): List<SatRadio>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertRadios(radios: List<SatRadio>)
@Query("DELETE FROM radios")
suspend fun deleteRadios()
}
@@ -1,25 +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.framework.model
import androidx.room.Embedded
import androidx.room.Entity
import com.rtbishop.look4sat.domain.model.OrbitalData
@Entity(tableName = "entries", primaryKeys = ["catnum"])
data class SatEntry(@Embedded val data: OrbitalData, var comment: String? = null)
@@ -1,30 +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.framework.model
data class SatItem(
val catnum: Int,
val name: String
// @Relation(
// parentColumn = "catnum",
// entity = SatRadio::class,
// entityColumn = "catnum",
// projection = ["mode"]
// )
// val modes: List<String>
)
@@ -1,34 +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.framework.model
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "radios")
data class SatRadio(
@PrimaryKey val uuid: String,
val info: String,
val isAlive: Boolean,
var downlink: Long?,
var uplink: Long?,
val mode: String?,
val isInverted: Boolean,
val catnum: Int?,
var comment: String? = null
)
@@ -24,7 +24,7 @@ import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.rtbishop.look4sat.MainApplication
import com.rtbishop.look4sat.domain.predict.ISelectionRepo
import com.rtbishop.look4sat.domain.repository.ISelectionRepo
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -17,7 +17,7 @@
*/
package com.rtbishop.look4sat.presentation.map
import com.rtbishop.look4sat.domain.model.GeoPos
import com.rtbishop.look4sat.domain.predict.GeoPos
data class MapData(
val catNum: Int,
@@ -28,8 +28,8 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.domain.model.GeoPos
import com.rtbishop.look4sat.domain.model.SatPos
import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.domain.predict.SatPos
import com.rtbishop.look4sat.domain.predict.Satellite
import org.osmdroid.tileprovider.tilesource.XYTileSource
import org.osmdroid.util.GeoPoint
@@ -23,11 +23,11 @@ import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.rtbishop.look4sat.MainApplication
import com.rtbishop.look4sat.domain.model.GeoPos
import com.rtbishop.look4sat.domain.model.SatPos
import com.rtbishop.look4sat.domain.predict.ISatelliteRepo
import com.rtbishop.look4sat.domain.predict.ISettingsRepo
import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.domain.predict.SatPos
import com.rtbishop.look4sat.domain.predict.Satellite
import com.rtbishop.look4sat.domain.repository.ISatelliteRepo
import com.rtbishop.look4sat.domain.repository.ISettingsRepo
import com.rtbishop.look4sat.domain.utility.QthConverter
import com.rtbishop.look4sat.domain.utility.toDegrees
import com.rtbishop.look4sat.domain.utility.toTimerString
@@ -44,9 +44,7 @@ import kotlin.collections.set
import kotlin.math.max
import kotlin.math.min
class MapViewModel(
private val satelliteRepo: ISatelliteRepo, private val settingsRepo: ISettingsRepo
) : ViewModel() {
class MapViewModel(private val satelliteRepo: ISatelliteRepo, settingsRepo: ISettingsRepo) : ViewModel() {
private val stationPos = settingsRepo.stationPosition.value
private var allPasses = satelliteRepo.passes.value
@@ -23,9 +23,9 @@ import androidx.compose.ui.unit.*
import androidx.lifecycle.viewmodel.compose.viewModel
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.domain.model.DataState
import com.rtbishop.look4sat.domain.model.OrbitalData
import com.rtbishop.look4sat.domain.model.SatPass
import com.rtbishop.look4sat.domain.predict.NearEarthSatellite
import com.rtbishop.look4sat.domain.predict.OrbitalData
import com.rtbishop.look4sat.domain.predict.SatPass
import com.rtbishop.look4sat.presentation.*
import com.rtbishop.look4sat.presentation.dialogs.FilterDialog
import com.rtbishop.look4sat.presentation.pullRefresh.PullRefreshIndicator
@@ -25,9 +25,9 @@ import androidx.lifecycle.viewmodel.viewModelFactory
import com.rtbishop.look4sat.MainApplication
import com.rtbishop.look4sat.domain.model.DataState
import com.rtbishop.look4sat.domain.model.PassesSettings
import com.rtbishop.look4sat.domain.model.SatPass
import com.rtbishop.look4sat.domain.predict.ISatelliteRepo
import com.rtbishop.look4sat.domain.predict.ISettingsRepo
import com.rtbishop.look4sat.domain.predict.SatPass
import com.rtbishop.look4sat.domain.repository.ISatelliteRepo
import com.rtbishop.look4sat.domain.repository.ISettingsRepo
import com.rtbishop.look4sat.domain.utility.toTimerString
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
@@ -17,6 +17,6 @@
*/
package com.rtbishop.look4sat.presentation.radar
import com.rtbishop.look4sat.domain.model.SatPos
import com.rtbishop.look4sat.domain.predict.SatPos
data class RadarData(val satPos: SatPos, val satTrack: List<SatPos> = emptyList())
@@ -14,8 +14,8 @@ import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.drawscope.*
import androidx.compose.ui.text.*
import androidx.compose.ui.unit.sp
import com.rtbishop.look4sat.domain.model.SatPos
import com.rtbishop.look4sat.domain.predict.PI_2
import com.rtbishop.look4sat.domain.predict.SatPos
import com.rtbishop.look4sat.domain.utility.toRadians
import kotlin.math.cos
import kotlin.math.sin
@@ -28,18 +28,18 @@ import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.rtbishop.look4sat.MainApplication
import com.rtbishop.look4sat.domain.data.ISensorSource
import com.rtbishop.look4sat.domain.model.GeoPos
import com.rtbishop.look4sat.domain.model.SatPass
import com.rtbishop.look4sat.domain.model.SatPos
import com.rtbishop.look4sat.data.framework.BluetoothReporter
import com.rtbishop.look4sat.data.framework.NetworkReporter
import com.rtbishop.look4sat.domain.model.SatRadio
import com.rtbishop.look4sat.domain.predict.ISatelliteRepo
import com.rtbishop.look4sat.domain.predict.ISettingsRepo
import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.domain.predict.SatPass
import com.rtbishop.look4sat.domain.predict.SatPos
import com.rtbishop.look4sat.domain.predict.Satellite
import com.rtbishop.look4sat.domain.repository.ISatelliteRepo
import com.rtbishop.look4sat.domain.repository.ISettingsRepo
import com.rtbishop.look4sat.domain.source.ISensorSource
import com.rtbishop.look4sat.domain.utility.round
import com.rtbishop.look4sat.domain.utility.toDegrees
import com.rtbishop.look4sat.framework.BluetoothReporter
import com.rtbishop.look4sat.framework.NetworkReporter
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.isActive
@@ -37,8 +37,8 @@ import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.rtbishop.look4sat.BuildConfig
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.domain.model.GeoPos
import com.rtbishop.look4sat.domain.model.OtherSettings
import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.presentation.CardButton
import com.rtbishop.look4sat.presentation.MainTheme
import com.rtbishop.look4sat.presentation.dialogs.LocatorDialog
@@ -1,6 +1,6 @@
package com.rtbishop.look4sat.presentation.settings
import com.rtbishop.look4sat.domain.model.GeoPos
import com.rtbishop.look4sat.domain.predict.GeoPos
data class PositionSettings(val isUpdating: Boolean, val stationPos: GeoPos, val messageResId: Int)
@@ -27,8 +27,8 @@ import androidx.lifecycle.viewmodel.viewModelFactory
import com.rtbishop.look4sat.MainApplication
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.domain.model.OtherSettings
import com.rtbishop.look4sat.domain.predict.IDatabaseRepo
import com.rtbishop.look4sat.domain.predict.ISettingsRepo
import com.rtbishop.look4sat.domain.repository.IDatabaseRepo
import com.rtbishop.look4sat.domain.repository.ISettingsRepo
import kotlinx.coroutines.launch
class SettingsViewModel(