mirror of
https://github.com/rt-bishop/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
Added various minor fixes, cleaned up classes
This commit is contained in:
1 parent
5a8108384f
commit
fc0b995bac
35 files changed
+295
-310
No files matched your search
@@ -1,11 +1,13 @@
|
|||||||
@file: Suppress("UnstableApiUsage")
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("com.google.devtools.ksp")
|
id("com.google.devtools.ksp")
|
||||||
id("org.jetbrains.kotlin.android")
|
id("org.jetbrains.kotlin.android")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvmToolchain(17)
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.rtbishop.look4sat"
|
namespace = "com.rtbishop.look4sat"
|
||||||
compileSdk = 33
|
compileSdk = 33
|
||||||
@@ -29,12 +31,7 @@ android {
|
|||||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
|
||||||
}
|
|
||||||
composeOptions { kotlinCompilerExtensionVersion = "1.5.1" }
|
composeOptions { kotlinCompilerExtensionVersion = "1.5.1" }
|
||||||
packaging { resources { excludes += listOf("META-INF/{AL2.0,LGPL2.1}") } }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class MainApplication : Application() {
|
|||||||
if (timeDelta > 172800000) { // 48 hours in ms
|
if (timeDelta > 172800000) { // 48 hours in ms
|
||||||
val sdf = SimpleDateFormat("d MMM yyyy - HH:mm:ss", Locale.getDefault())
|
val sdf = SimpleDateFormat("d MMM yyyy - HH:mm:ss", Locale.getDefault())
|
||||||
println("Started periodic data update on ${sdf.format(Date())}")
|
println("Started periodic data update on ${sdf.format(Date())}")
|
||||||
container.databaseRepo.updateFromWeb()
|
container.databaseRepo.updateFromRemote()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,18 +10,21 @@ import com.rtbishop.look4sat.data.database.MIGRATION_1_2
|
|||||||
import com.rtbishop.look4sat.data.database.MainDatabase
|
import com.rtbishop.look4sat.data.database.MainDatabase
|
||||||
import com.rtbishop.look4sat.data.framework.BluetoothReporter
|
import com.rtbishop.look4sat.data.framework.BluetoothReporter
|
||||||
import com.rtbishop.look4sat.data.framework.NetworkReporter
|
import com.rtbishop.look4sat.data.framework.NetworkReporter
|
||||||
import com.rtbishop.look4sat.data.framework.SettingsRepo
|
|
||||||
import com.rtbishop.look4sat.data.repository.DatabaseRepo
|
import com.rtbishop.look4sat.data.repository.DatabaseRepo
|
||||||
import com.rtbishop.look4sat.data.repository.SatelliteRepo
|
import com.rtbishop.look4sat.data.repository.SatelliteRepo
|
||||||
import com.rtbishop.look4sat.data.repository.SelectionRepo
|
import com.rtbishop.look4sat.data.repository.SelectionRepo
|
||||||
import com.rtbishop.look4sat.data.source.DataSource
|
import com.rtbishop.look4sat.data.repository.SettingsRepo
|
||||||
import com.rtbishop.look4sat.data.source.LocalStorage
|
import com.rtbishop.look4sat.data.source.FileSource
|
||||||
|
import com.rtbishop.look4sat.data.source.LocalSource
|
||||||
|
import com.rtbishop.look4sat.data.source.RemoteSource
|
||||||
import com.rtbishop.look4sat.data.source.SensorSource
|
import com.rtbishop.look4sat.data.source.SensorSource
|
||||||
import com.rtbishop.look4sat.domain.repository.IDatabaseRepo
|
import com.rtbishop.look4sat.domain.repository.IDatabaseRepo
|
||||||
import com.rtbishop.look4sat.domain.repository.ISatelliteRepo
|
import com.rtbishop.look4sat.domain.repository.ISatelliteRepo
|
||||||
import com.rtbishop.look4sat.domain.repository.ISelectionRepo
|
import com.rtbishop.look4sat.domain.repository.ISelectionRepo
|
||||||
import com.rtbishop.look4sat.domain.repository.ISettingsRepo
|
import com.rtbishop.look4sat.domain.repository.ISettingsRepo
|
||||||
import com.rtbishop.look4sat.domain.source.IDataSource
|
import com.rtbishop.look4sat.domain.source.IFileSource
|
||||||
|
import com.rtbishop.look4sat.domain.source.ILocalSource
|
||||||
|
import com.rtbishop.look4sat.domain.source.IRemoteSource
|
||||||
import com.rtbishop.look4sat.domain.source.ISensorSource
|
import com.rtbishop.look4sat.domain.source.ISensorSource
|
||||||
import com.rtbishop.look4sat.domain.utility.DataParser
|
import com.rtbishop.look4sat.domain.utility.DataParser
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
@@ -34,21 +37,16 @@ import org.osmdroid.config.Configuration
|
|||||||
|
|
||||||
class MainContainer(private val context: Context) {
|
class MainContainer(private val context: Context) {
|
||||||
|
|
||||||
private val database =
|
private val localSource = provideLocalSource()
|
||||||
Room.databaseBuilder(context, MainDatabase::class.java, "Look4SatDb").addMigrations(MIGRATION_1_2)
|
val mainScope = provideMainScope()
|
||||||
.fallbackToDestructiveMigration().build()
|
|
||||||
private val localStorage = LocalStorage(database.storageDao())
|
|
||||||
private val mainHandler = CoroutineExceptionHandler { _, error -> println("Look4Sat: $error") }
|
|
||||||
private val remoteSource = provideRemoteSource()
|
|
||||||
val mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Default + mainHandler)
|
|
||||||
val settingsRepo = provideSettingsRepo()
|
val settingsRepo = provideSettingsRepo()
|
||||||
val databaseRepo = provideDatabaseRepo()
|
|
||||||
val satelliteRepo = provideSatelliteRepo()
|
|
||||||
val selectionRepo = provideSelectionRepo()
|
val selectionRepo = provideSelectionRepo()
|
||||||
|
val satelliteRepo = provideSatelliteRepo()
|
||||||
|
val databaseRepo = provideDatabaseRepo()
|
||||||
|
|
||||||
fun provideBluetoothReporter(): BluetoothReporter {
|
fun provideBluetoothReporter(): BluetoothReporter {
|
||||||
val btManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
||||||
return BluetoothReporter(btManager, CoroutineScope(Dispatchers.IO))
|
return BluetoothReporter(manager, CoroutineScope(Dispatchers.IO))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun provideNetworkReporter(): NetworkReporter {
|
fun provideNetworkReporter(): NetworkReporter {
|
||||||
@@ -56,34 +54,53 @@ class MainContainer(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun provideSensorSource(): ISensorSource {
|
fun provideSensorSource(): ISensorSource {
|
||||||
val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
val manager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
||||||
val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
|
val sensor = manager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
|
||||||
return SensorSource(sensorManager, sensor)
|
return SensorSource(manager, sensor)
|
||||||
}
|
|
||||||
|
|
||||||
private fun provideRemoteSource(): IDataSource {
|
|
||||||
val cache = Cache(context.cacheDir, 1000 * 1000 * 10L)
|
|
||||||
val httpClient = OkHttpClient.Builder().cache(cache).build()
|
|
||||||
return DataSource(context.contentResolver, httpClient, Dispatchers.IO)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun provideDatabaseRepo(): IDatabaseRepo {
|
private fun provideDatabaseRepo(): IDatabaseRepo {
|
||||||
val dataParser = DataParser(Dispatchers.Default)
|
val dataParser = DataParser(Dispatchers.Default)
|
||||||
return DatabaseRepo(Dispatchers.Default, dataParser, remoteSource, localStorage, settingsRepo)
|
val fileSource = provideFileSource()
|
||||||
|
val remoteSource = provideRemoteSource()
|
||||||
|
return DatabaseRepo(Dispatchers.Default, dataParser, fileSource, localSource, remoteSource, settingsRepo)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun provideFileSource(): IFileSource {
|
||||||
|
return FileSource(context.contentResolver, Dispatchers.IO)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun provideLocalSource(): ILocalSource {
|
||||||
|
val database = Room.databaseBuilder(context, MainDatabase::class.java, "Look4SatDb").apply {
|
||||||
|
addMigrations(MIGRATION_1_2)
|
||||||
|
fallbackToDestructiveMigration()
|
||||||
|
}.build()
|
||||||
|
return LocalSource(database.storageDao())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun provideMainScope(): CoroutineScope {
|
||||||
|
val mainHandler = CoroutineExceptionHandler { _, error -> println("Look4Sat: $error") }
|
||||||
|
return CoroutineScope(SupervisorJob() + Dispatchers.Default + mainHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun provideRemoteSource(): IRemoteSource {
|
||||||
|
val cache = Cache(context.cacheDir, 1000 * 1000 * 10L)
|
||||||
|
val httpClient = OkHttpClient.Builder().cache(cache).build()
|
||||||
|
return RemoteSource(httpClient, Dispatchers.IO)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun provideSatelliteRepo(): ISatelliteRepo {
|
private fun provideSatelliteRepo(): ISatelliteRepo {
|
||||||
return SatelliteRepo(Dispatchers.Default, localStorage, settingsRepo)
|
return SatelliteRepo(Dispatchers.Default, localSource, settingsRepo)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun provideSelectionRepo(): ISelectionRepo {
|
private fun provideSelectionRepo(): ISelectionRepo {
|
||||||
return SelectionRepo(Dispatchers.Default, localStorage, settingsRepo)
|
return SelectionRepo(Dispatchers.Default, localSource, settingsRepo)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun provideSettingsRepo(): ISettingsRepo {
|
private fun provideSettingsRepo(): ISettingsRepo {
|
||||||
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
val manager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||||
val preferences = context.getSharedPreferences("default", Context.MODE_PRIVATE)
|
val preferences = context.getSharedPreferences("default", Context.MODE_PRIVATE)
|
||||||
Configuration.getInstance().load(context, preferences)
|
Configuration.getInstance().load(context, preferences)
|
||||||
return SettingsRepo(locationManager, preferences)
|
return SettingsRepo(manager, preferences)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,8 +16,6 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
@@ -34,18 +32,11 @@ private val allModes = listOf(
|
|||||||
"LRPT", "LSB", "MFSK", "MSK", "MSK AX.100 Mode 5", "MSK AX.100 Mode 6", "OFDM", "OQPSK",
|
"LRPT", "LSB", "MFSK", "MSK", "MSK AX.100 Mode 5", "MSK AX.100 Mode 6", "OFDM", "OQPSK",
|
||||||
"PSK", "PSK31", "PSK63", "QPSK", "QPSK31", "QPSK63", "SSTV", "USB", "WSJT"
|
"PSK", "PSK31", "PSK63", "QPSK", "QPSK31", "QPSK63", "SSTV", "USB", "WSJT"
|
||||||
)
|
)
|
||||||
private val selectedModes = mutableListOf("AFSK", "AFSK S-Net")
|
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
@Preview(showBackground = true)
|
||||||
@Composable
|
@Composable
|
||||||
private fun ModesDialogPreview() {
|
private fun ModesDialogPreview() {
|
||||||
val modesList = remember { mutableStateOf(selectedModes) }
|
MainTheme { ModesDialog(allModes, listOf("AFSK", "AFSK S-Net"), {}) {} }
|
||||||
MainTheme {
|
|
||||||
ModesDialog(allModes, modesList.value, {}) {
|
|
||||||
if (modesList.value.contains(it)) modesList.value.remove(it)
|
|
||||||
else modesList.value.add(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -97,9 +97,7 @@ private fun setStationPosition(stationPos: GeoPos, mapView: MapView) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setPositions(
|
private fun setPositions(posMap: Map<Satellite, GeoPos>, mapView: MapView, action: (Satellite) -> Unit) {
|
||||||
posMap: Map<Satellite, GeoPos>, mapView: MapView, action: (Satellite) -> Unit
|
|
||||||
) {
|
|
||||||
val markers = FolderOverlay()
|
val markers = FolderOverlay()
|
||||||
try {
|
try {
|
||||||
posMap.entries.forEach {
|
posMap.entries.forEach {
|
||||||
@@ -247,9 +245,7 @@ private val tonerLiteTileSource = XYTileSource(
|
|||||||
|
|
||||||
private fun getColorFilter(): ColorMatrixColorFilter {
|
private fun getColorFilter(): ColorMatrixColorFilter {
|
||||||
val negativeMatrix = ColorMatrix(
|
val negativeMatrix = ColorMatrix(
|
||||||
floatArrayOf(
|
floatArrayOf(-1f, 0f, 0f, 0f, 260f, 0f, -1f, 0f, 0f, 260f, 0f, 0f, -1f, 0f, 260f, 0f, 0f, 0f, 1f, 0f)
|
||||||
-1f, 0f, 0f, 0f, 260f, 0f, -1f, 0f, 0f, 260f, 0f, 0f, -1f, 0f, 260f, 0f, 0f, 0f, 1f, 0f
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return ColorMatrixColorFilter(negativeMatrix)
|
return ColorMatrixColorFilter(negativeMatrix)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ import com.rtbishop.look4sat.domain.predict.SatPos
|
|||||||
import com.rtbishop.look4sat.domain.predict.Satellite
|
import com.rtbishop.look4sat.domain.predict.Satellite
|
||||||
import com.rtbishop.look4sat.domain.repository.ISatelliteRepo
|
import com.rtbishop.look4sat.domain.repository.ISatelliteRepo
|
||||||
import com.rtbishop.look4sat.domain.repository.ISettingsRepo
|
import com.rtbishop.look4sat.domain.repository.ISettingsRepo
|
||||||
import com.rtbishop.look4sat.domain.utility.QthConverter
|
|
||||||
import com.rtbishop.look4sat.domain.utility.clipLat
|
import com.rtbishop.look4sat.domain.utility.clipLat
|
||||||
import com.rtbishop.look4sat.domain.utility.clipLon
|
import com.rtbishop.look4sat.domain.utility.clipLon
|
||||||
|
import com.rtbishop.look4sat.domain.utility.positionToQth
|
||||||
import com.rtbishop.look4sat.domain.utility.toDegrees
|
import com.rtbishop.look4sat.domain.utility.toDegrees
|
||||||
import com.rtbishop.look4sat.domain.utility.toTimerString
|
import com.rtbishop.look4sat.domain.utility.toTimerString
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
@@ -66,23 +66,23 @@ class MapViewModel(private val satelliteRepo: ISatelliteRepo, settingsRepo: ISet
|
|||||||
val footprint: SharedFlow<SatPos> = _footprint
|
val footprint: SharedFlow<SatPos> = _footprint
|
||||||
|
|
||||||
private val _mapData = MutableSharedFlow<MapData>()
|
private val _mapData = MutableSharedFlow<MapData>()
|
||||||
val mapData: SharedFlow<MapData> = _mapData
|
// val mapData: SharedFlow<MapData> = _mapData
|
||||||
|
|
||||||
private val _positions = MutableSharedFlow<Map<Satellite, GeoPos>>()
|
private val _positions = MutableSharedFlow<Map<Satellite, GeoPos>>()
|
||||||
val positions: SharedFlow<Map<Satellite, GeoPos>> = _positions
|
val positions: SharedFlow<Map<Satellite, GeoPos>> = _positions
|
||||||
|
|
||||||
fun scrollSelection(decrement: Boolean) {
|
// fun scrollSelection(decrement: Boolean) {
|
||||||
if (allSatellites.isNotEmpty()) {
|
// if (allSatellites.isNotEmpty()) {
|
||||||
val index = allSatellites.indexOf(selectedSatellite)
|
// val index = allSatellites.indexOf(selectedSatellite)
|
||||||
if (decrement) {
|
// if (decrement) {
|
||||||
if (index > 0) selectSatellite(allSatellites[index - 1])
|
// if (index > 0) selectSatellite(allSatellites[index - 1])
|
||||||
else selectSatellite(allSatellites[allSatellites.size - 1])
|
// else selectSatellite(allSatellites[allSatellites.size - 1])
|
||||||
} else {
|
// } else {
|
||||||
if (index < allSatellites.size - 1) selectSatellite(allSatellites[index + 1])
|
// if (index < allSatellites.size - 1) selectSatellite(allSatellites[index + 1])
|
||||||
else selectSatellite(allSatellites[0])
|
// else selectSatellite(allSatellites[0])
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
fun selectDefaultSatellite(catnum: Int) {
|
fun selectDefaultSatellite(catnum: Int) {
|
||||||
if (allSatellites.isNotEmpty()) {
|
if (allSatellites.isNotEmpty()) {
|
||||||
@@ -178,7 +178,7 @@ class MapViewModel(private val satelliteRepo: ISatelliteRepo, settingsRepo: ISet
|
|||||||
val osmLat = clipLat(satPos.latitude.toDegrees())
|
val osmLat = clipLat(satPos.latitude.toDegrees())
|
||||||
val osmLon = clipLon(satPos.longitude.toDegrees())
|
val osmLon = clipLon(satPos.longitude.toDegrees())
|
||||||
val osmPos = GeoPos(osmLat, osmLon)
|
val osmPos = GeoPos(osmLat, osmLon)
|
||||||
val qthLoc = QthConverter.positionToQth(osmPos.latitude, osmPos.longitude) ?: "-- --"
|
val qthLoc = positionToQth(osmPos.latitude, osmPos.longitude) ?: "-- --"
|
||||||
val velocity = satPos.getOrbitalVelocity()
|
val velocity = satPos.getOrbitalVelocity()
|
||||||
val phase = satPos.phase.toDegrees()
|
val phase = satPos.phase.toDegrees()
|
||||||
val visibility = satPos.eclipsed
|
val visibility = satPos.eclipsed
|
||||||
|
|||||||
@@ -88,9 +88,7 @@ fun PassesScreen(navToRadar: (Int, Long) -> Unit) {
|
|||||||
contentDescription = null
|
contentDescription = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Column(
|
Column(verticalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.weight(1f)) {
|
||||||
verticalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.weight(1f)
|
|
||||||
) {
|
|
||||||
Text(text = timerText.value?.first ?: "Null")
|
Text(text = timerText.value?.first ?: "Null")
|
||||||
Text(text = timerText.value?.second ?: "Null")
|
Text(text = timerText.value?.second ?: "Null")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class PassesViewModel(
|
|||||||
|
|
||||||
fun getFilterSettings() = settingsRepo.passesSettings.value
|
fun getFilterSettings() = settingsRepo.passesSettings.value
|
||||||
|
|
||||||
fun shouldUseUTC() = settingsRepo.otherSettings.value.utcState
|
// fun shouldUseUTC() = settingsRepo.otherSettings.value.utcState
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch { satelliteRepo.initRepository() }
|
viewModelScope.launch { satelliteRepo.initRepository() }
|
||||||
|
|||||||
+27
-48
@@ -73,27 +73,25 @@ fun PullRefreshIndicator(
|
|||||||
val showElevation by remember(refreshing, state) {
|
val showElevation by remember(refreshing, state) {
|
||||||
derivedStateOf { refreshing || state.position > 0.5f }
|
derivedStateOf { refreshing || state.position > 0.5f }
|
||||||
}
|
}
|
||||||
|
|
||||||
Surface(
|
Surface(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.size(IndicatorSize)
|
.size(indicatorSize)
|
||||||
.pullRefreshIndicatorTransform(state, scale),
|
.pullRefreshIndicatorTransform(state, scale),
|
||||||
shape = SpinnerShape,
|
shape = spinnerShape,
|
||||||
color = backgroundColor,
|
color = backgroundColor,
|
||||||
shadowElevation = if (showElevation) Elevation else 0.dp,
|
shadowElevation = if (showElevation) elevation else 0.dp,
|
||||||
) {
|
) {
|
||||||
Crossfade(
|
Crossfade(
|
||||||
targetState = refreshing, animationSpec = tween(durationMillis = CrossfadeDurationMs)
|
targetState = refreshing,
|
||||||
|
animationSpec = tween(durationMillis = CROSSFADE_DURATION_MS),
|
||||||
|
label = "pullToRefresh"
|
||||||
) { refreshing ->
|
) { refreshing ->
|
||||||
Box(
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center
|
val spinnerSize = (arcRadius + strokeWidth).times(2)
|
||||||
) {
|
|
||||||
val spinnerSize = (ArcRadius + StrokeWidth).times(2)
|
|
||||||
|
|
||||||
if (refreshing) {
|
if (refreshing) {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
color = contentColor,
|
color = contentColor,
|
||||||
strokeWidth = StrokeWidth,
|
strokeWidth = strokeWidth,
|
||||||
modifier = Modifier.size(spinnerSize),
|
modifier = Modifier.size(spinnerSize),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -108,18 +106,12 @@ fun PullRefreshIndicator(
|
|||||||
* Modifier.size MUST be specified.
|
* Modifier.size MUST be specified.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun CircularArrowIndicator(
|
private fun CircularArrowIndicator(state: PullRefreshState, color: Color, modifier: Modifier) {
|
||||||
state: PullRefreshState,
|
|
||||||
color: Color,
|
|
||||||
modifier: Modifier,
|
|
||||||
) {
|
|
||||||
val path = remember { Path().apply { fillType = PathFillType.EvenOdd } }
|
val path = remember { Path().apply { fillType = PathFillType.EvenOdd } }
|
||||||
|
|
||||||
Canvas(modifier.semantics { contentDescription = "Refreshing" }) {
|
Canvas(modifier.semantics { contentDescription = "Refreshing" }) {
|
||||||
val values = ArrowValues(state.progress)
|
val values = ArrowValues(state.progress)
|
||||||
|
|
||||||
rotate(degrees = values.rotation) {
|
rotate(degrees = values.rotation) {
|
||||||
val arcRadius = ArcRadius.toPx() + StrokeWidth.toPx() / 2f
|
val arcRadius = arcRadius.toPx() + strokeWidth.toPx() / 2f
|
||||||
val arcBounds = Rect(
|
val arcBounds = Rect(
|
||||||
size.center.x - arcRadius,
|
size.center.x - arcRadius,
|
||||||
size.center.y - arcRadius,
|
size.center.y - arcRadius,
|
||||||
@@ -134,9 +126,7 @@ private fun CircularArrowIndicator(
|
|||||||
useCenter = false,
|
useCenter = false,
|
||||||
topLeft = arcBounds.topLeft,
|
topLeft = arcBounds.topLeft,
|
||||||
size = arcBounds.size,
|
size = arcBounds.size,
|
||||||
style = Stroke(
|
style = Stroke(width = strokeWidth.toPx(), cap = StrokeCap.Square)
|
||||||
width = StrokeWidth.toPx(), cap = StrokeCap.Square
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
drawArrow(path, arcBounds, color, values)
|
drawArrow(path, arcBounds, color, values)
|
||||||
}
|
}
|
||||||
@@ -145,11 +135,7 @@ private fun CircularArrowIndicator(
|
|||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
private class ArrowValues(
|
private class ArrowValues(
|
||||||
val alpha: Float,
|
val alpha: Float, val rotation: Float, val startAngle: Float, val endAngle: Float, val scale: Float
|
||||||
val rotation: Float,
|
|
||||||
val startAngle: Float,
|
|
||||||
val endAngle: Float,
|
|
||||||
val scale: Float
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun ArrowValues(progress: Float): ArrowValues {
|
private fun ArrowValues(progress: Float): ArrowValues {
|
||||||
@@ -164,7 +150,7 @@ private fun ArrowValues(progress: Float): ArrowValues {
|
|||||||
|
|
||||||
// Calculations based on SwipeRefreshLayout specification.
|
// Calculations based on SwipeRefreshLayout specification.
|
||||||
val alpha = progress.coerceIn(0f, 1f)
|
val alpha = progress.coerceIn(0f, 1f)
|
||||||
val endTrim = adjustedPercent * MaxProgressArc
|
val endTrim = adjustedPercent * MAX_PROGRESS_ARC
|
||||||
val rotation = (-0.25f + 0.4f * adjustedPercent + tensionPercent) * 0.5f
|
val rotation = (-0.25f + 0.4f * adjustedPercent + tensionPercent) * 0.5f
|
||||||
val startAngle = rotation * 360
|
val startAngle = rotation * 360
|
||||||
val endAngle = (rotation + endTrim) * 360
|
val endAngle = (rotation + endTrim) * 360
|
||||||
@@ -176,33 +162,26 @@ private fun ArrowValues(progress: Float): ArrowValues {
|
|||||||
private fun DrawScope.drawArrow(arrow: Path, bounds: Rect, color: Color, values: ArrowValues) {
|
private fun DrawScope.drawArrow(arrow: Path, bounds: Rect, color: Color, values: ArrowValues) {
|
||||||
arrow.reset()
|
arrow.reset()
|
||||||
arrow.moveTo(0f, 0f) // Move to left corner
|
arrow.moveTo(0f, 0f) // Move to left corner
|
||||||
arrow.lineTo(x = ArrowWidth.toPx() * values.scale, y = 0f) // Line to right corner
|
arrow.lineTo(x = arrowWidth.toPx() * values.scale, y = 0f) // Line to right corner
|
||||||
|
|
||||||
// Line to tip of arrow
|
// Line to tip of arrow
|
||||||
arrow.lineTo(
|
arrow.lineTo(x = arrowWidth.toPx() * values.scale / 2, y = arrowHeight.toPx() * values.scale)
|
||||||
x = ArrowWidth.toPx() * values.scale / 2, y = ArrowHeight.toPx() * values.scale
|
|
||||||
)
|
|
||||||
|
|
||||||
val radius = min(bounds.width, bounds.height) / 2f
|
val radius = min(bounds.width, bounds.height) / 2f
|
||||||
val inset = ArrowWidth.toPx() * values.scale / 2f
|
val inset = arrowWidth.toPx() * values.scale / 2f
|
||||||
arrow.translate(
|
arrow.translate(Offset(x = radius + bounds.center.x - inset, y = bounds.center.y + strokeWidth.toPx() / 2f))
|
||||||
Offset(
|
|
||||||
x = radius + bounds.center.x - inset, y = bounds.center.y + StrokeWidth.toPx() / 2f
|
|
||||||
)
|
|
||||||
)
|
|
||||||
arrow.close()
|
arrow.close()
|
||||||
rotate(degrees = values.endAngle) {
|
rotate(degrees = values.endAngle) {
|
||||||
drawPath(path = arrow, color = color, alpha = values.alpha)
|
drawPath(path = arrow, color = color, alpha = values.alpha)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val CrossfadeDurationMs = 100
|
private const val CROSSFADE_DURATION_MS = 100
|
||||||
private const val MaxProgressArc = 0.8f
|
private const val MAX_PROGRESS_ARC = 0.8f
|
||||||
|
private val indicatorSize = 40.dp
|
||||||
private val IndicatorSize = 40.dp
|
private val spinnerShape = CircleShape
|
||||||
private val SpinnerShape = CircleShape
|
private val arcRadius = 7.5.dp
|
||||||
private val ArcRadius = 7.5.dp
|
private val strokeWidth = 2.5.dp
|
||||||
private val StrokeWidth = 2.5.dp
|
private val arrowWidth = 10.dp
|
||||||
private val ArrowWidth = 10.dp
|
private val arrowHeight = 5.dp
|
||||||
private val ArrowHeight = 5.dp
|
private val elevation = 6.dp
|
||||||
private val Elevation = 6.dp
|
|
||||||
+3
-11
@@ -49,12 +49,10 @@ fun Modifier.pullRefreshIndicatorTransform(
|
|||||||
properties["scale"] = scale
|
properties["scale"] = scale
|
||||||
}) {
|
}) {
|
||||||
var height by remember { mutableStateOf(0) }
|
var height by remember { mutableStateOf(0) }
|
||||||
|
|
||||||
Modifier
|
Modifier
|
||||||
.onSizeChanged { height = it.height }
|
.onSizeChanged { height = it.height }
|
||||||
.graphicsLayer {
|
.graphicsLayer {
|
||||||
translationY = state.position - height
|
translationY = state.position - height
|
||||||
|
|
||||||
if (scale && !state.refreshing) {
|
if (scale && !state.refreshing) {
|
||||||
val scaleFraction = LinearOutSlowInEasing
|
val scaleFraction = LinearOutSlowInEasing
|
||||||
.transform(state.position / state.threshold)
|
.transform(state.position / state.threshold)
|
||||||
@@ -95,9 +93,7 @@ fun Modifier.pullRefresh(
|
|||||||
* [onPull] nor [onRelease] will be invoked.
|
* [onPull] nor [onRelease] will be invoked.
|
||||||
*/
|
*/
|
||||||
fun Modifier.pullRefresh(
|
fun Modifier.pullRefresh(
|
||||||
onPull: (pullDelta: Float) -> Float,
|
onPull: (pullDelta: Float) -> Float, onRelease: suspend (flingVelocity: Float) -> Unit, enabled: Boolean = true
|
||||||
onRelease: suspend (flingVelocity: Float) -> Unit,
|
|
||||||
enabled: Boolean = true
|
|
||||||
) = inspectable(inspectorInfo = debugInspectorInfo {
|
) = inspectable(inspectorInfo = debugInspectorInfo {
|
||||||
name = "pullRefresh"
|
name = "pullRefresh"
|
||||||
properties["onPull"] = onPull
|
properties["onPull"] = onPull
|
||||||
@@ -113,17 +109,13 @@ private class PullRefreshNestedScrollConnection(
|
|||||||
private val enabled: Boolean
|
private val enabled: Boolean
|
||||||
) : NestedScrollConnection {
|
) : NestedScrollConnection {
|
||||||
|
|
||||||
override fun onPreScroll(
|
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset = when {
|
||||||
available: Offset, source: NestedScrollSource
|
|
||||||
): Offset = when {
|
|
||||||
!enabled -> Offset.Zero
|
!enabled -> Offset.Zero
|
||||||
source == Drag && available.y < 0 -> Offset(0f, onPull(available.y)) // Swiping up
|
source == Drag && available.y < 0 -> Offset(0f, onPull(available.y)) // Swiping up
|
||||||
else -> Offset.Zero
|
else -> Offset.Zero
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPostScroll(
|
override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset = when {
|
||||||
consumed: Offset, available: Offset, source: NestedScrollSource
|
|
||||||
): Offset = when {
|
|
||||||
!enabled -> Offset.Zero
|
!enabled -> Offset.Zero
|
||||||
source == Drag && available.y > 0 -> Offset(0f, onPull(available.y)) // Pulling down
|
source == Drag && available.y > 0 -> Offset(0f, onPull(available.y)) // Pulling down
|
||||||
else -> Offset.Zero
|
else -> Offset.Zero
|
||||||
|
|||||||
+4
-13
@@ -53,27 +53,18 @@ fun rememberPullRefreshState(
|
|||||||
refreshingOffset: Dp = PullRefreshDefaults.RefreshingOffset,
|
refreshingOffset: Dp = PullRefreshDefaults.RefreshingOffset,
|
||||||
): PullRefreshState {
|
): PullRefreshState {
|
||||||
require(refreshThreshold > 0.dp) { "The refresh trigger must be greater than zero!" }
|
require(refreshThreshold > 0.dp) { "The refresh trigger must be greater than zero!" }
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val onRefreshState = rememberUpdatedState(onRefresh)
|
val onRefreshState = rememberUpdatedState(onRefresh)
|
||||||
val thresholdPx: Float
|
val thresholdPx: Float
|
||||||
val refreshingOffsetPx: Float
|
val refreshingOffsetPx: Float
|
||||||
|
|
||||||
with(LocalDensity.current) {
|
with(LocalDensity.current) {
|
||||||
thresholdPx = refreshThreshold.toPx()
|
thresholdPx = refreshThreshold.toPx()
|
||||||
refreshingOffsetPx = refreshingOffset.toPx()
|
refreshingOffsetPx = refreshingOffset.toPx()
|
||||||
}
|
}
|
||||||
|
|
||||||
// refreshThreshold and refreshingOffset should not be changed after instantiation, so any
|
// refreshThreshold and refreshingOffset should not be changed after instantiation, so any
|
||||||
// changes to these values are ignored.
|
// changes to these values are ignored.
|
||||||
val state = remember(scope) {
|
val state = remember(scope) { PullRefreshState(scope, onRefreshState, refreshingOffsetPx, thresholdPx) }
|
||||||
PullRefreshState(scope, onRefreshState, refreshingOffsetPx, thresholdPx)
|
SideEffect { state.setRefreshing(refreshing) }
|
||||||
}
|
|
||||||
|
|
||||||
SideEffect {
|
|
||||||
state.setRefreshing(refreshing)
|
|
||||||
}
|
|
||||||
|
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +100,7 @@ class PullRefreshState internal constructor(
|
|||||||
internal val refreshing get() = _refreshing
|
internal val refreshing get() = _refreshing
|
||||||
internal val position get() = _position
|
internal val position get() = _position
|
||||||
|
|
||||||
private val adjustedDistancePulled by derivedStateOf { distancePulled * DragMultiplier }
|
private val adjustedDistancePulled by derivedStateOf { distancePulled * DRAG_MULTIPLIER }
|
||||||
|
|
||||||
private var _refreshing by mutableStateOf(false)
|
private var _refreshing by mutableStateOf(false)
|
||||||
private var _position by mutableStateOf(0f)
|
private var _position by mutableStateOf(0f)
|
||||||
@@ -189,4 +180,4 @@ object PullRefreshDefaults {
|
|||||||
* the refresh threshold, it is the indicator position, otherwise the indicator position is
|
* the refresh threshold, it is the indicator position, otherwise the indicator position is
|
||||||
* derived from the progress).
|
* derived from the progress).
|
||||||
*/
|
*/
|
||||||
private const val DragMultiplier = 0.5f
|
private const val DRAG_MULTIPLIER = 0.5f
|
||||||
@@ -44,14 +44,9 @@ fun RadarScreen() {
|
|||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
IconButton(onClick = {}) {
|
IconButton(onClick = {}) {
|
||||||
Icon(
|
Icon(painter = painterResource(id = R.drawable.ic_filter), contentDescription = null)
|
||||||
painter = painterResource(id = R.drawable.ic_filter),
|
|
||||||
contentDescription = null
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
Column(
|
Column(verticalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.weight(1f)) {
|
||||||
verticalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.weight(1f)
|
|
||||||
) {
|
|
||||||
Text(text = "Null")
|
Text(text = "Null")
|
||||||
Text(text = "Null")
|
Text(text = "Null")
|
||||||
}
|
}
|
||||||
@@ -109,10 +104,7 @@ private fun TransmitterItem(radio: SatRadio) {
|
|||||||
.background(MaterialTheme.colorScheme.surface)
|
.background(MaterialTheme.colorScheme.surface)
|
||||||
.padding(6.dp)
|
.padding(6.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = R.drawable.ic_arrow),
|
painter = painterResource(id = R.drawable.ic_arrow),
|
||||||
contentDescription = null, modifier = Modifier.rotate(180f)
|
contentDescription = null, modifier = Modifier.rotate(180f)
|
||||||
@@ -127,10 +119,7 @@ private fun TransmitterItem(radio: SatRadio) {
|
|||||||
contentDescription = null
|
contentDescription = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Row(
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.radio_downlink, radio.downlink ?: 0L),
|
text = stringResource(id = R.string.radio_downlink, radio.downlink ?: 0L),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
|
|||||||
@@ -43,9 +43,9 @@ fun RadarViewCompose(item: SatPos, items: List<SatPos>, azimElev: Pair<Float, Fl
|
|||||||
val primaryColor = Color(0xFFFFE082)
|
val primaryColor = Color(0xFFFFE082)
|
||||||
val secondaryColor = Color(0xFFDC0000)
|
val secondaryColor = Color(0xFFDC0000)
|
||||||
val strokeWidth = 4f
|
val strokeWidth = 4f
|
||||||
val animTransition = rememberInfiniteTransition()
|
val animTransition = rememberInfiniteTransition(label = "animScale")
|
||||||
val animSpec = infiniteRepeatable<Float>(tween(1000))
|
val animSpec = infiniteRepeatable<Float>(tween(1000))
|
||||||
val animScale = animTransition.animateFloat(16f, 64f, animSpec)
|
val animScale = animTransition.animateFloat(16f, 64f, animSpec, label = "animScale")
|
||||||
val measurer = rememberTextMeasurer()
|
val measurer = rememberTextMeasurer()
|
||||||
val sweepDegrees = remember { mutableStateOf(0f) }
|
val sweepDegrees = remember { mutableStateOf(0f) }
|
||||||
val trackCreated = remember { mutableStateOf(false) }
|
val trackCreated = remember { mutableStateOf(false) }
|
||||||
|
|||||||
@@ -99,9 +99,9 @@ class RadarViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUseCompass(): Boolean = settingsRepo.otherSettings.value.sensorState
|
// fun getUseCompass(): Boolean = settingsRepo.otherSettings.value.sensorState
|
||||||
|
//
|
||||||
fun getShowSweep(): Boolean = settingsRepo.otherSettings.value.sweepState
|
// fun getShowSweep(): Boolean = settingsRepo.otherSettings.value.sweepState
|
||||||
|
|
||||||
private fun getMagDeclination(geoPos: GeoPos, time: Long = System.currentTimeMillis()): Float {
|
private fun getMagDeclination(geoPos: GeoPos, time: Long = System.currentTimeMillis()): Float {
|
||||||
val latitude = geoPos.latitude.toFloat()
|
val latitude = geoPos.latitude.toFloat()
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.rtbishop.look4sat.presentation.settings
|
package com.rtbishop.look4sat.presentation.settings
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.os.Build
|
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -61,16 +60,16 @@ fun SettingsScreen() {
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
// Permissions setup
|
// Permissions setup
|
||||||
val bluetoothContract = ActivityResultContracts.RequestPermission()
|
// val bluetoothContract = ActivityResultContracts.RequestPermission()
|
||||||
val bluetoothError = stringResource(R.string.BTremote_perm_error)
|
// val bluetoothError = stringResource(R.string.BTremote_perm_error)
|
||||||
val bluetoothPerm = when {
|
// val bluetoothPerm = when {
|
||||||
Build.VERSION.SDK_INT < Build.VERSION_CODES.S -> Manifest.permission.BLUETOOTH
|
// Build.VERSION.SDK_INT < Build.VERSION_CODES.S -> Manifest.permission.BLUETOOTH
|
||||||
else -> Manifest.permission.BLUETOOTH_CONNECT
|
// else -> Manifest.permission.BLUETOOTH_CONNECT
|
||||||
}
|
// }
|
||||||
val bluetoothRequest = rememberLauncherForActivityResult(bluetoothContract) { isGranted ->
|
// val bluetoothRequest = rememberLauncherForActivityResult(bluetoothContract) { isGranted ->
|
||||||
// viewModel.setBTEnabled(isGranted)
|
// viewModel.setBTEnabled(isGranted)
|
||||||
if (!isGranted) showToast(context, bluetoothError)
|
// if (!isGranted) showToast(context, bluetoothError)
|
||||||
}
|
// }
|
||||||
val locationContract = ActivityResultContracts.RequestMultiplePermissions()
|
val locationContract = ActivityResultContracts.RequestMultiplePermissions()
|
||||||
val locationError = stringResource(R.string.location_gps_error)
|
val locationError = stringResource(R.string.location_gps_error)
|
||||||
val locationPermCoarse = Manifest.permission.ACCESS_COARSE_LOCATION
|
val locationPermCoarse = Manifest.permission.ACCESS_COARSE_LOCATION
|
||||||
@@ -119,9 +118,7 @@ fun SettingsScreen() {
|
|||||||
val toggleSensor = { value: Boolean -> viewModel.toggleSensor(value) }
|
val toggleSensor = { value: Boolean -> viewModel.toggleSensor(value) }
|
||||||
|
|
||||||
// Screen setup
|
// Screen setup
|
||||||
LazyColumn(
|
LazyColumn(modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||||
modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)
|
|
||||||
) {
|
|
||||||
item { CardAbout(BuildConfig.VERSION_NAME) }
|
item { CardAbout(BuildConfig.VERSION_NAME) }
|
||||||
item { LocationCard(positionSettings, setGpsPos, showPosDialog, showLocDialog, dismissPos) }
|
item { LocationCard(positionSettings, setGpsPos, showPosDialog, showLocDialog, dismissPos) }
|
||||||
item { DataCard(dataSettings, updateFromWeb, updateFromFile, clearAllData) }
|
item { DataCard(dataSettings, updateFromWeb, updateFromFile, clearAllData) }
|
||||||
@@ -376,8 +373,7 @@ private fun DataCard(
|
|||||||
@Preview(showBackground = true)
|
@Preview(showBackground = true)
|
||||||
@Composable
|
@Composable
|
||||||
private fun OtherCardPreview() = MainTheme {
|
private fun OtherCardPreview() = MainTheme {
|
||||||
val values =
|
val values = OtherSettings(utcState = false, updateState = true, sweepState = true, sensorState = true)
|
||||||
OtherSettings(utcState = false, updateState = true, sweepState = true, sensorState = true)
|
|
||||||
OtherCard(settings = values, {}, {}, {}, {})
|
OtherCard(settings = values, {}, {}, {}, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,4 @@ import com.rtbishop.look4sat.domain.predict.GeoPos
|
|||||||
|
|
||||||
data class PositionSettings(val isUpdating: Boolean, val stationPos: GeoPos, val messageResId: Int)
|
data class PositionSettings(val isUpdating: Boolean, val stationPos: GeoPos, val messageResId: Int)
|
||||||
|
|
||||||
data class DataSettings(
|
data class DataSettings(val isUpdating: Boolean, val entriesTotal: Int, val radiosTotal: Int, val timestamp: Long)
|
||||||
val isUpdating: Boolean, val entriesTotal: Int, val radiosTotal: Int, val timestamp: Long
|
|
||||||
)
|
|
||||||
@@ -106,7 +106,7 @@ class SettingsViewModel(
|
|||||||
fun updateFromWeb() = viewModelScope.launch {
|
fun updateFromWeb() = viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
_dataSettings.value = _dataSettings.value.copy(isUpdating = true)
|
_dataSettings.value = _dataSettings.value.copy(isUpdating = true)
|
||||||
databaseRepo.updateFromWeb()
|
databaseRepo.updateFromRemote()
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
_dataSettings.value = _dataSettings.value.copy(isUpdating = false)
|
_dataSettings.value = _dataSettings.value.copy(isUpdating = false)
|
||||||
println(exception.printStackTrace())
|
println(exception.printStackTrace())
|
||||||
|
|||||||
@@ -147,8 +147,6 @@
|
|||||||
<string name="data_success">යාවත්කාලීනය සම්පූර්ණ කිරීම සාර්ථකයි</string>
|
<string name="data_success">යාවත්කාලීනය සම්පූර්ණ කිරීම සාර්ථකයි</string>
|
||||||
<string name="data_clear_success">දත්ත ඉවත් කිරීම සාර්ථකයි</string>
|
<string name="data_clear_success">දත්ත ඉවත් කිරීම සාර්ථකයි</string>
|
||||||
|
|
||||||
<string name="last_updated_pattern" translatable="false">d MMM yyyy - HH:mm:ss</string>
|
|
||||||
|
|
||||||
<string name="remote_title">භ්රමක මනාප</string>
|
<string name="remote_title">භ්රමක මනාප</string>
|
||||||
<string name="remote_switch">දුරස්ථ පාලනය සබල ක°</string>
|
<string name="remote_switch">දුරස්ථ පාලනය සබල ක°</string>
|
||||||
<string name="remote_ip_hint">IP ලිපිනය</string>
|
<string name="remote_ip_hint">IP ලිපිනය</string>
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
@file:Suppress("UnstableApiUsage")
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.library")
|
id("com.android.library")
|
||||||
id("com.google.devtools.ksp")
|
id("com.google.devtools.ksp")
|
||||||
id("org.jetbrains.kotlin.android")
|
id("org.jetbrains.kotlin.android")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvmToolchain(17)
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.rtbishop.look4sat.data"
|
namespace = "com.rtbishop.look4sat.data"
|
||||||
compileSdk = 33
|
compileSdk = 33
|
||||||
@@ -13,10 +15,6 @@ android {
|
|||||||
minSdk = 21
|
minSdk = 21
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ package com.rtbishop.look4sat.data.framework
|
|||||||
|
|
||||||
import android.bluetooth.BluetoothManager
|
import android.bluetooth.BluetoothManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class BluetoothReporter(
|
class BluetoothReporter(
|
||||||
private val manager: BluetoothManager,
|
private val bluetoothManager: BluetoothManager,
|
||||||
private val reporterScope: CoroutineScope
|
private val reporterScope: CoroutineScope
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ class BluetoothReporter(
|
|||||||
if (!connected) {
|
if (!connected) {
|
||||||
rotationConnectionJob = reporterScope.launch {
|
rotationConnectionJob = reporterScope.launch {
|
||||||
try {
|
try {
|
||||||
manager.adapter.getRemoteDevice(deviceId)?.let { device ->
|
bluetoothManager.adapter.getRemoteDevice(deviceId)?.let { device ->
|
||||||
device.createInsecureRfcommSocketToServiceRecord(sppid)?.let { socket ->
|
device.createInsecureRfcommSocketToServiceRecord(sppid)?.let { socket ->
|
||||||
connecting = true
|
connecting = true
|
||||||
socket.connect()
|
socket.connect()
|
||||||
|
|||||||
@@ -22,36 +22,38 @@ import com.rtbishop.look4sat.domain.model.SatEntry
|
|||||||
import com.rtbishop.look4sat.domain.model.SatRadio
|
import com.rtbishop.look4sat.domain.model.SatRadio
|
||||||
import com.rtbishop.look4sat.domain.repository.IDatabaseRepo
|
import com.rtbishop.look4sat.domain.repository.IDatabaseRepo
|
||||||
import com.rtbishop.look4sat.domain.repository.ISettingsRepo
|
import com.rtbishop.look4sat.domain.repository.ISettingsRepo
|
||||||
import com.rtbishop.look4sat.domain.source.IDataSource
|
import com.rtbishop.look4sat.domain.source.IFileSource
|
||||||
import com.rtbishop.look4sat.domain.source.ILocalStorage
|
import com.rtbishop.look4sat.domain.source.ILocalSource
|
||||||
|
import com.rtbishop.look4sat.domain.source.IRemoteSource
|
||||||
import com.rtbishop.look4sat.domain.utility.DataParser
|
import com.rtbishop.look4sat.domain.utility.DataParser
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.util.zip.ZipInputStream
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.InputStream
|
|
||||||
import java.util.zip.ZipInputStream
|
|
||||||
|
|
||||||
class DatabaseRepo(
|
class DatabaseRepo(
|
||||||
private val dispatcher: CoroutineDispatcher,
|
private val dispatcher: CoroutineDispatcher,
|
||||||
private val dataParser: DataParser,
|
private val dataParser: DataParser,
|
||||||
private val dataSource: IDataSource,
|
private val fileSource: IFileSource,
|
||||||
private val localStorage: ILocalStorage,
|
private val localSource: ILocalSource,
|
||||||
private val settingsRepository: ISettingsRepo
|
private val remoteSource: IRemoteSource,
|
||||||
|
private val settingsRepo: ISettingsRepo
|
||||||
) : IDatabaseRepo {
|
) : IDatabaseRepo {
|
||||||
|
|
||||||
override suspend fun updateFromFile(uri: String) = withContext(dispatcher) {
|
override suspend fun updateFromFile(uri: String) = withContext(dispatcher) {
|
||||||
val importedSatellites = dataSource.getFileStream(uri)?.let { importSatellites(it) }
|
val importedSatellites = fileSource.getFileStream(uri)?.let { importSatellites(it) }
|
||||||
importedSatellites?.let { localStorage.insertEntries(it) }
|
importedSatellites?.let { localSource.insertEntries(it) }
|
||||||
setUpdateSuccessful(System.currentTimeMillis())
|
setUpdateSuccessful(System.currentTimeMillis())
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateFromWeb() = withContext(dispatcher) {
|
override suspend fun updateFromRemote() = withContext(dispatcher) {
|
||||||
val sourcesMap = settingsRepository.satelliteSourcesMap
|
val sourcesMap = settingsRepo.satelliteSourcesMap
|
||||||
val importedEntries = mutableListOf<SatEntry>()
|
val importedEntries = mutableListOf<SatEntry>()
|
||||||
val importedRadios = mutableListOf<SatRadio>()
|
val importedRadios = mutableListOf<SatRadio>()
|
||||||
// fetch
|
// fetch
|
||||||
val jobsMap = sourcesMap.mapValues { async { dataSource.getNetworkStream(it.value) } }
|
val jobsMap = sourcesMap.mapValues { async { remoteSource.getRemoteStream(it.value) } }
|
||||||
val jobRadios = async { dataSource.getNetworkStream(settingsRepository.radioSourceUrl) }
|
val jobRadios = async { remoteSource.getRemoteStream(settingsRepo.radioSourceUrl) }
|
||||||
// parse
|
// parse
|
||||||
jobsMap.mapValues { job -> job.value.await() }.forEach { entry ->
|
jobsMap.mapValues { job -> job.value.await() }.forEach { entry ->
|
||||||
entry.value?.let { stream ->
|
entry.value?.let { stream ->
|
||||||
@@ -60,7 +62,7 @@ class DatabaseRepo(
|
|||||||
// parse tle stream
|
// parse tle stream
|
||||||
val satellites = importSatellites(stream)
|
val satellites = importSatellites(stream)
|
||||||
val catnums = satellites.map { it.data.catnum }
|
val catnums = satellites.map { it.data.catnum }
|
||||||
settingsRepository.saveSatType(type, catnums)
|
settingsRepo.saveSatType(type, catnums)
|
||||||
importedEntries.addAll(satellites)
|
importedEntries.addAll(satellites)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +71,7 @@ class DatabaseRepo(
|
|||||||
val unzipped = ZipInputStream(stream).apply { nextEntry }
|
val unzipped = ZipInputStream(stream).apply { nextEntry }
|
||||||
val satellites = importSatellites(unzipped)
|
val satellites = importSatellites(unzipped)
|
||||||
val catnums = satellites.map { it.data.catnum }
|
val catnums = satellites.map { it.data.catnum }
|
||||||
settingsRepository.saveSatType(type, catnums)
|
settingsRepo.saveSatType(type, catnums)
|
||||||
importedEntries.addAll(satellites)
|
importedEntries.addAll(satellites)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +80,7 @@ class DatabaseRepo(
|
|||||||
val parsed = dataParser.parseCSVStream(stream)
|
val parsed = dataParser.parseCSVStream(stream)
|
||||||
val satellites = parsed.map { data -> SatEntry(data) }
|
val satellites = parsed.map { data -> SatEntry(data) }
|
||||||
val catnums = satellites.map { it.data.catnum }
|
val catnums = satellites.map { it.data.catnum }
|
||||||
settingsRepository.saveSatType(type, catnums)
|
settingsRepo.saveSatType(type, catnums)
|
||||||
importedEntries.addAll(satellites)
|
importedEntries.addAll(satellites)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,21 +88,21 @@ class DatabaseRepo(
|
|||||||
}
|
}
|
||||||
jobRadios.await()?.let { importedRadios.addAll(dataParser.parseJSONStream(it)) }
|
jobRadios.await()?.let { importedRadios.addAll(dataParser.parseJSONStream(it)) }
|
||||||
// insert
|
// insert
|
||||||
localStorage.insertEntries(importedEntries)
|
localSource.insertEntries(importedEntries)
|
||||||
localStorage.insertRadios(importedRadios)
|
localSource.insertRadios(importedRadios)
|
||||||
setUpdateSuccessful(System.currentTimeMillis())
|
setUpdateSuccessful(System.currentTimeMillis())
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun clearAllData() = withContext(dispatcher) {
|
override suspend fun clearAllData() = withContext(dispatcher) {
|
||||||
localStorage.deleteEntries()
|
localSource.deleteEntries()
|
||||||
localStorage.deleteRadios()
|
localSource.deleteRadios()
|
||||||
setUpdateSuccessful(0L)
|
setUpdateSuccessful(0L)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun setUpdateSuccessful(timestamp: Long) = withContext(dispatcher) {
|
private suspend fun setUpdateSuccessful(timestamp: Long) = withContext(dispatcher) {
|
||||||
val satellitesTotal = localStorage.getEntriesTotal()
|
val satellitesTotal = localSource.getEntriesTotal()
|
||||||
val radiosTotal = localStorage.getRadiosTotal()
|
val radiosTotal = localSource.getRadiosTotal()
|
||||||
settingsRepository.saveDatabaseState(DatabaseState(satellitesTotal, radiosTotal, timestamp))
|
settingsRepo.saveDatabaseState(DatabaseState(satellitesTotal, radiosTotal, timestamp))
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun importSatellites(stream: InputStream): List<SatEntry> {
|
private suspend fun importSatellites(stream: InputStream): List<SatEntry> {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import com.rtbishop.look4sat.domain.predict.SatPos
|
|||||||
import com.rtbishop.look4sat.domain.predict.Satellite
|
import com.rtbishop.look4sat.domain.predict.Satellite
|
||||||
import com.rtbishop.look4sat.domain.repository.ISatelliteRepo
|
import com.rtbishop.look4sat.domain.repository.ISatelliteRepo
|
||||||
import com.rtbishop.look4sat.domain.repository.ISettingsRepo
|
import com.rtbishop.look4sat.domain.repository.ISettingsRepo
|
||||||
import com.rtbishop.look4sat.domain.source.ILocalStorage
|
import com.rtbishop.look4sat.domain.source.ILocalSource
|
||||||
import com.rtbishop.look4sat.domain.utility.round
|
import com.rtbishop.look4sat.domain.utility.round
|
||||||
import com.rtbishop.look4sat.domain.utility.toDegrees
|
import com.rtbishop.look4sat.domain.utility.toDegrees
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
@@ -35,7 +35,7 @@ import kotlinx.coroutines.withContext
|
|||||||
|
|
||||||
class SatelliteRepo(
|
class SatelliteRepo(
|
||||||
private val dispatcher: CoroutineDispatcher,
|
private val dispatcher: CoroutineDispatcher,
|
||||||
private val localStorage: ILocalStorage,
|
private val localStorage: ILocalSource,
|
||||||
private val settingsRepo: ISettingsRepo
|
private val settingsRepo: ISettingsRepo
|
||||||
) : ISatelliteRepo {
|
) : ISatelliteRepo {
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package com.rtbishop.look4sat.data.repository
|
|||||||
import com.rtbishop.look4sat.domain.model.SatItem
|
import com.rtbishop.look4sat.domain.model.SatItem
|
||||||
import com.rtbishop.look4sat.domain.repository.ISelectionRepo
|
import com.rtbishop.look4sat.domain.repository.ISelectionRepo
|
||||||
import com.rtbishop.look4sat.domain.repository.ISettingsRepo
|
import com.rtbishop.look4sat.domain.repository.ISettingsRepo
|
||||||
import com.rtbishop.look4sat.domain.source.ILocalStorage
|
import com.rtbishop.look4sat.domain.source.ILocalSource
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -15,7 +15,7 @@ import kotlinx.coroutines.withContext
|
|||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class SelectionRepo(
|
class SelectionRepo(
|
||||||
private val dispatcher: CoroutineDispatcher,
|
private val dispatcher: CoroutineDispatcher,
|
||||||
private val localStorage: ILocalStorage,
|
private val localSource: ILocalSource,
|
||||||
private val settingsRepo: ISettingsRepo
|
private val settingsRepo: ISettingsRepo
|
||||||
) : ISelectionRepo {
|
) : ISelectionRepo {
|
||||||
private val currentItems = MutableStateFlow<List<SatItem>>(emptyList())
|
private val currentItems = MutableStateFlow<List<SatItem>>(emptyList())
|
||||||
@@ -34,7 +34,7 @@ class SelectionRepo(
|
|||||||
|
|
||||||
override suspend fun getEntriesFlow() = withContext(dispatcher) {
|
override suspend fun getEntriesFlow() = withContext(dispatcher) {
|
||||||
val selectedIds = settingsRepo.satelliteSelection.value
|
val selectedIds = settingsRepo.satelliteSelection.value
|
||||||
currentItems.value = localStorage.getEntriesList().map { item ->
|
currentItems.value = localSource.getEntriesList().map { item ->
|
||||||
item.copy(isSelected = item.catnum in selectedIds)
|
item.copy(isSelected = item.catnum in selectedIds)
|
||||||
}
|
}
|
||||||
return@withContext itemsWithQuery
|
return@withContext itemsWithQuery
|
||||||
|
|||||||
+10
-9
@@ -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.framework
|
package com.rtbishop.look4sat.data.repository
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.location.Criteria
|
import android.location.Criteria
|
||||||
@@ -28,14 +28,15 @@ import com.rtbishop.look4sat.domain.model.OtherSettings
|
|||||||
import com.rtbishop.look4sat.domain.model.PassesSettings
|
import com.rtbishop.look4sat.domain.model.PassesSettings
|
||||||
import com.rtbishop.look4sat.domain.predict.GeoPos
|
import com.rtbishop.look4sat.domain.predict.GeoPos
|
||||||
import com.rtbishop.look4sat.domain.repository.ISettingsRepo
|
import com.rtbishop.look4sat.domain.repository.ISettingsRepo
|
||||||
import com.rtbishop.look4sat.domain.utility.QthConverter
|
import com.rtbishop.look4sat.domain.utility.positionToQth
|
||||||
|
import com.rtbishop.look4sat.domain.utility.qthToPosition
|
||||||
import com.rtbishop.look4sat.domain.utility.round
|
import com.rtbishop.look4sat.domain.utility.round
|
||||||
|
import java.util.concurrent.Executors
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import java.util.concurrent.Executors
|
|
||||||
|
|
||||||
class SettingsRepo(
|
class SettingsRepo(
|
||||||
private val manager: LocationManager, private val preferences: SharedPreferences
|
private val locationManager: LocationManager, private val preferences: SharedPreferences
|
||||||
) : ISettingsRepo {
|
) : ISettingsRepo {
|
||||||
|
|
||||||
private val keyModes = "satModes"
|
private val keyModes = "satModes"
|
||||||
@@ -73,12 +74,12 @@ class SettingsRepo(
|
|||||||
override val stationPosition: StateFlow<GeoPos> = _stationPosition
|
override val stationPosition: StateFlow<GeoPos> = _stationPosition
|
||||||
|
|
||||||
override fun setGpsPosition(): Boolean {
|
override fun setGpsPosition(): Boolean {
|
||||||
if (!LocationManagerCompat.isLocationEnabled(manager)) return false
|
if (!LocationManagerCompat.isLocationEnabled(locationManager)) return false
|
||||||
try {
|
try {
|
||||||
val criteria = Criteria().apply { isCostAllowed = true }
|
val criteria = Criteria().apply { isCostAllowed = true }
|
||||||
val provider = manager.getBestProvider(criteria, true) ?: defaultProvider
|
val provider = locationManager.getBestProvider(criteria, true) ?: defaultProvider
|
||||||
println("Requesting location for $provider provider")
|
println("Requesting location for $provider provider")
|
||||||
LocationManagerCompat.getCurrentLocation(manager, provider, timeoutSignal, executor) {
|
LocationManagerCompat.getCurrentLocation(locationManager, provider, timeoutSignal, executor) {
|
||||||
it?.let { setGeoPosition(it.latitude, it.longitude, it.altitude) }
|
it?.let { setGeoPosition(it.latitude, it.longitude, it.altitude) }
|
||||||
}
|
}
|
||||||
} catch (exception: SecurityException) {
|
} catch (exception: SecurityException) {
|
||||||
@@ -89,13 +90,13 @@ class SettingsRepo(
|
|||||||
|
|
||||||
override fun setGeoPosition(latitude: Double, longitude: Double, altitude: Double): Boolean {
|
override fun setGeoPosition(latitude: Double, longitude: Double, altitude: Double): Boolean {
|
||||||
val newLongitude = if (longitude > 180.0) longitude - 180 else longitude
|
val newLongitude = if (longitude > 180.0) longitude - 180 else longitude
|
||||||
val locator = QthConverter.positionToQth(latitude, newLongitude) ?: return false
|
val locator = positionToQth(latitude, newLongitude) ?: return false
|
||||||
saveGeoPos(latitude, newLongitude, altitude, locator)
|
saveGeoPos(latitude, newLongitude, altitude, locator)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setQthPosition(locator: String): Boolean {
|
override fun setQthPosition(locator: String): Boolean {
|
||||||
val position = QthConverter.qthToPosition(locator) ?: return false
|
val position = qthToPosition(locator) ?: return false
|
||||||
saveGeoPos(position.latitude, position.longitude, 0.0, locator)
|
saveGeoPos(position.latitude, position.longitude, 0.0, locator)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.rtbishop.look4sat.data.source
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.net.Uri
|
||||||
|
import com.rtbishop.look4sat.domain.source.IFileSource
|
||||||
|
import java.io.InputStream
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class FileSource(
|
||||||
|
private val contentResolver: ContentResolver, private val dispatcher: CoroutineDispatcher
|
||||||
|
) : IFileSource {
|
||||||
|
override suspend fun getFileStream(uri: String): InputStream? {
|
||||||
|
return withContext(dispatcher) { contentResolver.openInputStream(Uri.parse(uri)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-5
@@ -17,18 +17,18 @@
|
|||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.data.source
|
package com.rtbishop.look4sat.data.source
|
||||||
|
|
||||||
import com.rtbishop.look4sat.data.database.dao.StorageDao
|
|
||||||
import com.rtbishop.look4sat.domain.model.SatRadio
|
|
||||||
import com.rtbishop.look4sat.domain.predict.Satellite
|
|
||||||
import com.rtbishop.look4sat.domain.source.ILocalStorage
|
|
||||||
import com.rtbishop.look4sat.data.database.entity.SatEntry as FrameworkEntry
|
import com.rtbishop.look4sat.data.database.entity.SatEntry as FrameworkEntry
|
||||||
import com.rtbishop.look4sat.data.database.entity.SatItem as FrameworkItem
|
import com.rtbishop.look4sat.data.database.entity.SatItem as FrameworkItem
|
||||||
import com.rtbishop.look4sat.data.database.entity.SatRadio as FrameworkRadio
|
import com.rtbishop.look4sat.data.database.entity.SatRadio as FrameworkRadio
|
||||||
import com.rtbishop.look4sat.domain.model.SatEntry as DomainEntry
|
import com.rtbishop.look4sat.domain.model.SatEntry as DomainEntry
|
||||||
import com.rtbishop.look4sat.domain.model.SatItem as DomainItem
|
import com.rtbishop.look4sat.domain.model.SatItem as DomainItem
|
||||||
import com.rtbishop.look4sat.domain.model.SatRadio as DomainRadio
|
import com.rtbishop.look4sat.domain.model.SatRadio as DomainRadio
|
||||||
|
import com.rtbishop.look4sat.data.database.dao.StorageDao
|
||||||
|
import com.rtbishop.look4sat.domain.model.SatRadio
|
||||||
|
import com.rtbishop.look4sat.domain.predict.Satellite
|
||||||
|
import com.rtbishop.look4sat.domain.source.ILocalSource
|
||||||
|
|
||||||
class LocalStorage(private val storageDao: StorageDao) : ILocalStorage {
|
class LocalSource(private val storageDao: StorageDao) : ILocalSource {
|
||||||
|
|
||||||
//region # Entries region
|
//region # Entries region
|
||||||
|
|
||||||
+6
-14
@@ -17,26 +17,18 @@
|
|||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.data.source
|
package com.rtbishop.look4sat.data.source
|
||||||
|
|
||||||
import android.content.ContentResolver
|
import com.rtbishop.look4sat.domain.source.IRemoteSource
|
||||||
import android.net.Uri
|
import java.io.InputStream
|
||||||
import com.rtbishop.look4sat.domain.source.IDataSource
|
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
class DataSource(
|
class RemoteSource(
|
||||||
private val contentResolver: ContentResolver,
|
private val httpClient: OkHttpClient, private val dispatcher: CoroutineDispatcher
|
||||||
private val httpClient: OkHttpClient,
|
) : IRemoteSource {
|
||||||
private val dispatcher: CoroutineDispatcher
|
|
||||||
) : IDataSource {
|
|
||||||
|
|
||||||
override suspend fun getFileStream(uri: String): InputStream? {
|
override suspend fun getRemoteStream(url: String): InputStream? = withContext(dispatcher) {
|
||||||
return withContext(dispatcher) { contentResolver.openInputStream(Uri.parse(uri)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getNetworkStream(url: String): InputStream? = withContext(dispatcher) {
|
|
||||||
try {
|
try {
|
||||||
val request = Request.Builder().url(url).build()
|
val request = Request.Builder().url(url).build()
|
||||||
httpClient.newCall(request).execute().body?.byteStream()
|
httpClient.newCall(request).execute().body?.byteStream()
|
||||||
@@ -19,8 +19,6 @@ package com.rtbishop.look4sat.domain.model
|
|||||||
|
|
||||||
sealed class DataState<out T> {
|
sealed class DataState<out T> {
|
||||||
data class Success<out T>(val data: T) : DataState<T>()
|
data class Success<out T>(val data: T) : DataState<T>()
|
||||||
data class Error(val resId: Int?) : DataState<Nothing>()
|
|
||||||
data class Exception(val message: String?) : DataState<Nothing>()
|
data class Exception(val message: String?) : DataState<Nothing>()
|
||||||
data object Handled : DataState<Nothing>()
|
|
||||||
data object Loading : DataState<Nothing>()
|
data object Loading : DataState<Nothing>()
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@ interface IDatabaseRepo {
|
|||||||
|
|
||||||
suspend fun updateFromFile(uri: String)
|
suspend fun updateFromFile(uri: String)
|
||||||
|
|
||||||
suspend fun updateFromWeb()
|
suspend fun updateFromRemote()
|
||||||
|
|
||||||
suspend fun clearAllData()
|
suspend fun clearAllData()
|
||||||
}
|
}
|
||||||
+1
-4
@@ -19,9 +19,6 @@ package com.rtbishop.look4sat.domain.source
|
|||||||
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
interface IDataSource {
|
interface IFileSource {
|
||||||
|
|
||||||
suspend fun getFileStream(uri: String): InputStream?
|
suspend fun getFileStream(uri: String): InputStream?
|
||||||
|
|
||||||
suspend fun getNetworkStream(url: String): InputStream?
|
|
||||||
}
|
}
|
||||||
+1
-1
@@ -22,7 +22,7 @@ import com.rtbishop.look4sat.domain.model.SatItem
|
|||||||
import com.rtbishop.look4sat.domain.model.SatRadio
|
import com.rtbishop.look4sat.domain.model.SatRadio
|
||||||
import com.rtbishop.look4sat.domain.predict.Satellite
|
import com.rtbishop.look4sat.domain.predict.Satellite
|
||||||
|
|
||||||
interface ILocalStorage {
|
interface ILocalSource {
|
||||||
|
|
||||||
suspend fun getEntriesTotal(): Int
|
suspend fun getEntriesTotal(): Int
|
||||||
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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.source
|
||||||
|
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
interface IRemoteSource {
|
||||||
|
suspend fun getRemoteStream(url: String): InputStream?
|
||||||
|
}
|
||||||
@@ -1,3 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* 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.utility
|
package com.rtbishop.look4sat.domain.utility
|
||||||
|
|
||||||
import com.rtbishop.look4sat.domain.predict.DEG2RAD
|
import com.rtbishop.look4sat.domain.predict.DEG2RAD
|
||||||
|
|||||||
@@ -19,43 +19,40 @@ package com.rtbishop.look4sat.domain.utility
|
|||||||
|
|
||||||
import com.rtbishop.look4sat.domain.predict.GeoPos
|
import com.rtbishop.look4sat.domain.predict.GeoPos
|
||||||
|
|
||||||
object QthConverter {
|
fun qthToPosition(locator: String): GeoPos? {
|
||||||
|
val trimmedQth = locator.take(6)
|
||||||
fun qthToPosition(locator: String): GeoPos? {
|
if (!isValidLocator(trimmedQth)) return null
|
||||||
val trimmedQth = locator.take(6)
|
val lonFirst = (trimmedQth[0].uppercaseChar().code - 65) * 20
|
||||||
if (!isValidLocator(trimmedQth)) return null
|
val latFirst = (trimmedQth[1].uppercaseChar().code - 65) * 10
|
||||||
val lonFirst = (trimmedQth[0].uppercaseChar().code - 65) * 20
|
val lonSecond = trimmedQth[2].toString().toInt() * 2
|
||||||
val latFirst = (trimmedQth[1].uppercaseChar().code - 65) * 10
|
val latSecond = trimmedQth[3].toString().toInt()
|
||||||
val lonSecond = trimmedQth[2].toString().toInt() * 2
|
val lonThird = (((trimmedQth[4].lowercaseChar().code - 97) / 12.0) + (1.0 / 24.0)) - 180
|
||||||
val latSecond = trimmedQth[3].toString().toInt()
|
val latThird = (((trimmedQth[5].lowercaseChar().code - 97) / 24.0) + (1.0 / 48.0)) - 90
|
||||||
val lonThird = (((trimmedQth[4].lowercaseChar().code - 97) / 12.0) + (1.0 / 24.0)) - 180
|
val longitude = (lonFirst + lonSecond + lonThird).round(4)
|
||||||
val latThird = (((trimmedQth[5].lowercaseChar().code - 97) / 24.0) + (1.0 / 48.0)) - 90
|
val latitude = (latFirst + latSecond + latThird).round(4)
|
||||||
val longitude = (lonFirst + lonSecond + lonThird).round(4)
|
return GeoPos(latitude, longitude)
|
||||||
val latitude = (latFirst + latSecond + latThird).round(4)
|
}
|
||||||
return GeoPos(latitude, longitude)
|
|
||||||
}
|
fun positionToQth(latitude: Double, longitude: Double): String? {
|
||||||
|
if (!isValidPosition(latitude, longitude)) return null
|
||||||
fun positionToQth(latitude: Double, longitude: Double): String? {
|
val tempLon = if (longitude > 180.0) longitude - 180 else longitude
|
||||||
if (!isValidPosition(latitude, longitude)) return null
|
val upper = "ABCDEFGHIJKLMNOPQRSTUVWX"
|
||||||
val tempLon = if (longitude > 180.0) longitude - 180 else longitude
|
val lower = "abcdefghijklmnopqrstuvwx"
|
||||||
val upper = "ABCDEFGHIJKLMNOPQRSTUVWX"
|
val newLongitude = tempLon + 180
|
||||||
val lower = "abcdefghijklmnopqrstuvwx"
|
val newLatitude = latitude + 90
|
||||||
val newLongitude = tempLon + 180
|
val lonFirst = upper[(newLongitude / 20).toInt()]
|
||||||
val newLatitude = latitude + 90
|
val latFirst = upper[(newLatitude / 10).toInt()]
|
||||||
val lonFirst = upper[(newLongitude / 20).toInt()]
|
val lonSecond = ((newLongitude / 2) % 10).toInt().toString()
|
||||||
val latFirst = upper[(newLatitude / 10).toInt()]
|
val latSecond = (newLatitude % 10).toInt().toString()
|
||||||
val lonSecond = ((newLongitude / 2) % 10).toInt().toString()
|
val lonThird = lower[((newLongitude % 2) * 12).toInt()]
|
||||||
val latSecond = (newLatitude % 10).toInt().toString()
|
val latThird = lower[((newLatitude % 1) * 24).toInt()]
|
||||||
val lonThird = lower[((newLongitude % 2) * 12).toInt()]
|
return "$lonFirst$latFirst$lonSecond$latSecond$lonThird$latThird"
|
||||||
val latThird = lower[((newLatitude % 1) * 24).toInt()]
|
}
|
||||||
return "$lonFirst$latFirst$lonSecond$latSecond$lonThird$latThird"
|
|
||||||
}
|
private fun isValidPosition(lat: Double, lon: Double): Boolean {
|
||||||
|
return (lat >= -90.0 && lat <= 90.0) && (lon >= -180.0 && lon <= 360.0)
|
||||||
private fun isValidPosition(lat: Double, lon: Double): Boolean {
|
}
|
||||||
return (lat >= -90.0 && lat <= 90.0) && (lon >= -180.0 && lon <= 360.0)
|
|
||||||
}
|
private fun isValidLocator(locator: String): Boolean {
|
||||||
|
return locator.matches("[a-xA-X][a-xA-X]\\d\\d[a-xA-X][a-xA-X]".toRegex())
|
||||||
private fun isValidLocator(locator: String): Boolean {
|
|
||||||
return locator.matches("[a-xA-X][a-xA-X]\\d\\d[a-xA-X][a-xA-X]".toRegex())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -17,37 +17,38 @@
|
|||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.domain
|
package com.rtbishop.look4sat.domain
|
||||||
|
|
||||||
import com.rtbishop.look4sat.domain.utility.QthConverter
|
import com.rtbishop.look4sat.domain.utility.positionToQth
|
||||||
|
import com.rtbishop.look4sat.domain.utility.qthToPosition
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class QthConverterTest {
|
class QthConverterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Given valid QTH returns correct POS`() {
|
fun `Given valid QTH returns correct POS`() {
|
||||||
var result = QthConverter.qthToPosition("io91VL39FX")
|
var result = qthToPosition("io91VL39FX")
|
||||||
assert(result?.latitude == 51.4792 && result.longitude == -0.2083)
|
assert(result?.latitude == 51.4792 && result.longitude == -0.2083)
|
||||||
result = QthConverter.qthToPosition("JN58TD")
|
result = qthToPosition("JN58TD")
|
||||||
assert(result?.latitude == 48.1458 && result.longitude == 11.6250)
|
assert(result?.latitude == 48.1458 && result.longitude == 11.6250)
|
||||||
result = QthConverter.qthToPosition("gf15vc")
|
result = qthToPosition("gf15vc")
|
||||||
assert(result?.latitude == -34.8958 && result.longitude == -56.2083)
|
assert(result?.latitude == -34.8958 && result.longitude == -56.2083)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Given invalid QTH returns null`() {
|
fun `Given invalid QTH returns null`() {
|
||||||
assert(QthConverter.qthToPosition("ZZ00zz") == null)
|
assert(qthToPosition("ZZ00zz") == null)
|
||||||
assert(QthConverter.qthToPosition("JN58") == null)
|
assert(qthToPosition("JN58") == null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Given valid POS returns correct QTH`() {
|
fun `Given valid POS returns correct QTH`() {
|
||||||
assert(QthConverter.positionToQth(51.4878, -0.2146) == "IO91vl")
|
assert(positionToQth(51.4878, -0.2146) == "IO91vl")
|
||||||
assert(QthConverter.positionToQth(48.1466, 11.6083) == "JN58td")
|
assert(positionToQth(48.1466, 11.6083) == "JN58td")
|
||||||
assert(QthConverter.positionToQth(-34.91, -56.2116) == "GF15vc")
|
assert(positionToQth(-34.91, -56.2116) == "GF15vc")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Given invalid POS returns null`() {
|
fun `Given invalid POS returns null`() {
|
||||||
assert(QthConverter.positionToQth(91.0542, -170.1142) == null)
|
assert(positionToQth(91.0542, -170.1142) == null)
|
||||||
assert(QthConverter.positionToQth(89.0542, -240.1142) == null)
|
assert(positionToQth(89.0542, -240.1142) == null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user