diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3b53357c..f75ba4cc 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,11 +1,13 @@ -@file: Suppress("UnstableApiUsage") - plugins { id("com.android.application") id("com.google.devtools.ksp") id("org.jetbrains.kotlin.android") } +kotlin { + jvmToolchain(17) +} + android { namespace = "com.rtbishop.look4sat" compileSdk = 33 @@ -29,12 +31,7 @@ android { proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } composeOptions { kotlinCompilerExtensionVersion = "1.5.1" } - packaging { resources { excludes += listOf("META-INF/{AL2.0,LGPL2.1}") } } } dependencies { diff --git a/app/src/main/java/com/rtbishop/look4sat/MainApplication.kt b/app/src/main/java/com/rtbishop/look4sat/MainApplication.kt index b993f38b..570eead7 100644 --- a/app/src/main/java/com/rtbishop/look4sat/MainApplication.kt +++ b/app/src/main/java/com/rtbishop/look4sat/MainApplication.kt @@ -40,7 +40,7 @@ class MainApplication : Application() { if (timeDelta > 172800000) { // 48 hours in ms val sdf = SimpleDateFormat("d MMM yyyy - HH:mm:ss", Locale.getDefault()) println("Started periodic data update on ${sdf.format(Date())}") - container.databaseRepo.updateFromWeb() + container.databaseRepo.updateFromRemote() } } } diff --git a/app/src/main/java/com/rtbishop/look4sat/MainContainer.kt b/app/src/main/java/com/rtbishop/look4sat/MainContainer.kt index c2c0bf52..ce4cc1f8 100644 --- a/app/src/main/java/com/rtbishop/look4sat/MainContainer.kt +++ b/app/src/main/java/com/rtbishop/look4sat/MainContainer.kt @@ -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.framework.BluetoothReporter 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.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.repository.SettingsRepo +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.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.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.utility.DataParser import kotlinx.coroutines.CoroutineExceptionHandler @@ -34,21 +37,16 @@ import org.osmdroid.config.Configuration class MainContainer(private val context: Context) { - private val database = - Room.databaseBuilder(context, MainDatabase::class.java, "Look4SatDb").addMigrations(MIGRATION_1_2) - .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) + private val localSource = provideLocalSource() + val mainScope = provideMainScope() val settingsRepo = provideSettingsRepo() - val databaseRepo = provideDatabaseRepo() - val satelliteRepo = provideSatelliteRepo() val selectionRepo = provideSelectionRepo() + val satelliteRepo = provideSatelliteRepo() + val databaseRepo = provideDatabaseRepo() fun provideBluetoothReporter(): BluetoothReporter { - val btManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager - return BluetoothReporter(btManager, CoroutineScope(Dispatchers.IO)) + val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager + return BluetoothReporter(manager, CoroutineScope(Dispatchers.IO)) } fun provideNetworkReporter(): NetworkReporter { @@ -56,34 +54,53 @@ class MainContainer(private val context: Context) { } fun provideSensorSource(): ISensorSource { - val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager - val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR) - return SensorSource(sensorManager, 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) + val manager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager + val sensor = manager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR) + return SensorSource(manager, sensor) } private fun provideDatabaseRepo(): IDatabaseRepo { 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 { - return SatelliteRepo(Dispatchers.Default, localStorage, settingsRepo) + return SatelliteRepo(Dispatchers.Default, localSource, settingsRepo) } private fun provideSelectionRepo(): ISelectionRepo { - return SelectionRepo(Dispatchers.Default, localStorage, settingsRepo) + return SelectionRepo(Dispatchers.Default, localSource, settingsRepo) } 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) Configuration.getInstance().load(context, preferences) - return SettingsRepo(locationManager, preferences) + return SettingsRepo(manager, preferences) } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/dialogs/ModesDialog.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/dialogs/ModesDialog.kt index dd1859f9..39a04347 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/dialogs/ModesDialog.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/dialogs/ModesDialog.kt @@ -16,8 +16,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier 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", "PSK", "PSK31", "PSK63", "QPSK", "QPSK31", "QPSK63", "SSTV", "USB", "WSJT" ) -private val selectedModes = mutableListOf("AFSK", "AFSK S-Net") @Preview(showBackground = true) @Composable private fun ModesDialogPreview() { - val modesList = remember { mutableStateOf(selectedModes) } - MainTheme { - ModesDialog(allModes, modesList.value, {}) { - if (modesList.value.contains(it)) modesList.value.remove(it) - else modesList.value.add(it) - } - } + MainTheme { ModesDialog(allModes, listOf("AFSK", "AFSK S-Net"), {}) {} } } @Composable diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/map/MapScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/map/MapScreen.kt index 020ec7ac..5a259e2c 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/map/MapScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/map/MapScreen.kt @@ -97,9 +97,7 @@ private fun setStationPosition(stationPos: GeoPos, mapView: MapView) { } } -private fun setPositions( - posMap: Map, mapView: MapView, action: (Satellite) -> Unit -) { +private fun setPositions(posMap: Map, mapView: MapView, action: (Satellite) -> Unit) { val markers = FolderOverlay() try { posMap.entries.forEach { @@ -247,9 +245,7 @@ private val tonerLiteTileSource = XYTileSource( private fun getColorFilter(): ColorMatrixColorFilter { val negativeMatrix = ColorMatrix( - floatArrayOf( - -1f, 0f, 0f, 0f, 260f, 0f, -1f, 0f, 0f, 260f, 0f, 0f, -1f, 0f, 260f, 0f, 0f, 0f, 1f, 0f - ) + floatArrayOf(-1f, 0f, 0f, 0f, 260f, 0f, -1f, 0f, 0f, 260f, 0f, 0f, -1f, 0f, 260f, 0f, 0f, 0f, 1f, 0f) ) return ColorMatrixColorFilter(negativeMatrix) } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/map/MapViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/map/MapViewModel.kt index 3f28d87b..1f7e5d7a 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/map/MapViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/map/MapViewModel.kt @@ -28,9 +28,9 @@ 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.clipLat 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.toTimerString import java.util.Date @@ -66,23 +66,23 @@ class MapViewModel(private val satelliteRepo: ISatelliteRepo, settingsRepo: ISet val footprint: SharedFlow = _footprint private val _mapData = MutableSharedFlow() - val mapData: SharedFlow = _mapData +// val mapData: SharedFlow = _mapData private val _positions = MutableSharedFlow>() val positions: SharedFlow> = _positions - fun scrollSelection(decrement: Boolean) { - if (allSatellites.isNotEmpty()) { - val index = allSatellites.indexOf(selectedSatellite) - if (decrement) { - if (index > 0) selectSatellite(allSatellites[index - 1]) - else selectSatellite(allSatellites[allSatellites.size - 1]) - } else { - if (index < allSatellites.size - 1) selectSatellite(allSatellites[index + 1]) - else selectSatellite(allSatellites[0]) - } - } - } +// fun scrollSelection(decrement: Boolean) { +// if (allSatellites.isNotEmpty()) { +// val index = allSatellites.indexOf(selectedSatellite) +// if (decrement) { +// if (index > 0) selectSatellite(allSatellites[index - 1]) +// else selectSatellite(allSatellites[allSatellites.size - 1]) +// } else { +// if (index < allSatellites.size - 1) selectSatellite(allSatellites[index + 1]) +// else selectSatellite(allSatellites[0]) +// } +// } +// } fun selectDefaultSatellite(catnum: Int) { if (allSatellites.isNotEmpty()) { @@ -178,7 +178,7 @@ class MapViewModel(private val satelliteRepo: ISatelliteRepo, settingsRepo: ISet val osmLat = clipLat(satPos.latitude.toDegrees()) val osmLon = clipLon(satPos.longitude.toDegrees()) 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 phase = satPos.phase.toDegrees() val visibility = satPos.eclipsed diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt index 46137d5d..afdb57ad 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt @@ -88,9 +88,7 @@ fun PassesScreen(navToRadar: (Int, Long) -> Unit) { contentDescription = null ) } - Column( - verticalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.weight(1f) - ) { + Column(verticalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.weight(1f)) { Text(text = timerText.value?.first ?: "Null") Text(text = timerText.value?.second ?: "Null") } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesViewModel.kt index 83000990..bb805536 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesViewModel.kt @@ -50,7 +50,7 @@ class PassesViewModel( fun getFilterSettings() = settingsRepo.passesSettings.value - fun shouldUseUTC() = settingsRepo.otherSettings.value.utcState +// fun shouldUseUTC() = settingsRepo.otherSettings.value.utcState init { viewModelScope.launch { satelliteRepo.initRepository() } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/pullRefresh/PullRefreshIndicator.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/pullRefresh/PullRefreshIndicator.kt index 338dbede..ffeeb166 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/pullRefresh/PullRefreshIndicator.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/pullRefresh/PullRefreshIndicator.kt @@ -73,27 +73,25 @@ fun PullRefreshIndicator( val showElevation by remember(refreshing, state) { derivedStateOf { refreshing || state.position > 0.5f } } - Surface( modifier = modifier - .size(IndicatorSize) + .size(indicatorSize) .pullRefreshIndicatorTransform(state, scale), - shape = SpinnerShape, + shape = spinnerShape, color = backgroundColor, - shadowElevation = if (showElevation) Elevation else 0.dp, + shadowElevation = if (showElevation) elevation else 0.dp, ) { Crossfade( - targetState = refreshing, animationSpec = tween(durationMillis = CrossfadeDurationMs) + targetState = refreshing, + animationSpec = tween(durationMillis = CROSSFADE_DURATION_MS), + label = "pullToRefresh" ) { refreshing -> - Box( - modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center - ) { - val spinnerSize = (ArcRadius + StrokeWidth).times(2) - + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + val spinnerSize = (arcRadius + strokeWidth).times(2) if (refreshing) { CircularProgressIndicator( color = contentColor, - strokeWidth = StrokeWidth, + strokeWidth = strokeWidth, modifier = Modifier.size(spinnerSize), ) } else { @@ -108,18 +106,12 @@ fun PullRefreshIndicator( * Modifier.size MUST be specified. */ @Composable -private fun CircularArrowIndicator( - state: PullRefreshState, - color: Color, - modifier: Modifier, -) { +private fun CircularArrowIndicator(state: PullRefreshState, color: Color, modifier: Modifier) { val path = remember { Path().apply { fillType = PathFillType.EvenOdd } } - Canvas(modifier.semantics { contentDescription = "Refreshing" }) { val values = ArrowValues(state.progress) - rotate(degrees = values.rotation) { - val arcRadius = ArcRadius.toPx() + StrokeWidth.toPx() / 2f + val arcRadius = arcRadius.toPx() + strokeWidth.toPx() / 2f val arcBounds = Rect( size.center.x - arcRadius, size.center.y - arcRadius, @@ -134,9 +126,7 @@ private fun CircularArrowIndicator( useCenter = false, topLeft = arcBounds.topLeft, size = arcBounds.size, - style = Stroke( - width = StrokeWidth.toPx(), cap = StrokeCap.Square - ) + style = Stroke(width = strokeWidth.toPx(), cap = StrokeCap.Square) ) drawArrow(path, arcBounds, color, values) } @@ -145,11 +135,7 @@ private fun CircularArrowIndicator( @Immutable private class ArrowValues( - val alpha: Float, - val rotation: Float, - val startAngle: Float, - val endAngle: Float, - val scale: Float + val alpha: Float, val rotation: Float, val startAngle: Float, val endAngle: Float, val scale: Float ) private fun ArrowValues(progress: Float): ArrowValues { @@ -164,7 +150,7 @@ private fun ArrowValues(progress: Float): ArrowValues { // Calculations based on SwipeRefreshLayout specification. 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 startAngle = rotation * 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) { arrow.reset() 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 - arrow.lineTo( - x = ArrowWidth.toPx() * values.scale / 2, y = ArrowHeight.toPx() * values.scale - ) + arrow.lineTo(x = arrowWidth.toPx() * values.scale / 2, y = arrowHeight.toPx() * values.scale) val radius = min(bounds.width, bounds.height) / 2f - val inset = ArrowWidth.toPx() * values.scale / 2f - arrow.translate( - Offset( - x = radius + bounds.center.x - inset, y = bounds.center.y + StrokeWidth.toPx() / 2f - ) - ) + val inset = arrowWidth.toPx() * values.scale / 2f + arrow.translate(Offset(x = radius + bounds.center.x - inset, y = bounds.center.y + strokeWidth.toPx() / 2f)) arrow.close() rotate(degrees = values.endAngle) { drawPath(path = arrow, color = color, alpha = values.alpha) } } -private const val CrossfadeDurationMs = 100 -private const val MaxProgressArc = 0.8f - -private val IndicatorSize = 40.dp -private val SpinnerShape = CircleShape -private val ArcRadius = 7.5.dp -private val StrokeWidth = 2.5.dp -private val ArrowWidth = 10.dp -private val ArrowHeight = 5.dp -private val Elevation = 6.dp +private const val CROSSFADE_DURATION_MS = 100 +private const val MAX_PROGRESS_ARC = 0.8f +private val indicatorSize = 40.dp +private val spinnerShape = CircleShape +private val arcRadius = 7.5.dp +private val strokeWidth = 2.5.dp +private val arrowWidth = 10.dp +private val arrowHeight = 5.dp +private val elevation = 6.dp diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/pullRefresh/PullRefreshModifier.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/pullRefresh/PullRefreshModifier.kt index 353bf6b9..2a6299af 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/pullRefresh/PullRefreshModifier.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/pullRefresh/PullRefreshModifier.kt @@ -49,12 +49,10 @@ fun Modifier.pullRefreshIndicatorTransform( properties["scale"] = scale }) { var height by remember { mutableStateOf(0) } - Modifier .onSizeChanged { height = it.height } .graphicsLayer { translationY = state.position - height - if (scale && !state.refreshing) { val scaleFraction = LinearOutSlowInEasing .transform(state.position / state.threshold) @@ -95,9 +93,7 @@ fun Modifier.pullRefresh( * [onPull] nor [onRelease] will be invoked. */ fun Modifier.pullRefresh( - onPull: (pullDelta: Float) -> Float, - onRelease: suspend (flingVelocity: Float) -> Unit, - enabled: Boolean = true + onPull: (pullDelta: Float) -> Float, onRelease: suspend (flingVelocity: Float) -> Unit, enabled: Boolean = true ) = inspectable(inspectorInfo = debugInspectorInfo { name = "pullRefresh" properties["onPull"] = onPull @@ -113,17 +109,13 @@ private class PullRefreshNestedScrollConnection( private val enabled: Boolean ) : NestedScrollConnection { - override fun onPreScroll( - available: Offset, source: NestedScrollSource - ): Offset = when { + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset = when { !enabled -> Offset.Zero source == Drag && available.y < 0 -> Offset(0f, onPull(available.y)) // Swiping up else -> Offset.Zero } - override fun onPostScroll( - consumed: Offset, available: Offset, source: NestedScrollSource - ): Offset = when { + override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset = when { !enabled -> Offset.Zero source == Drag && available.y > 0 -> Offset(0f, onPull(available.y)) // Pulling down else -> Offset.Zero diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/pullRefresh/PullRefreshState.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/pullRefresh/PullRefreshState.kt index 103df10d..4c72dff2 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/pullRefresh/PullRefreshState.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/pullRefresh/PullRefreshState.kt @@ -53,27 +53,18 @@ fun rememberPullRefreshState( refreshingOffset: Dp = PullRefreshDefaults.RefreshingOffset, ): PullRefreshState { require(refreshThreshold > 0.dp) { "The refresh trigger must be greater than zero!" } - val scope = rememberCoroutineScope() val onRefreshState = rememberUpdatedState(onRefresh) val thresholdPx: Float val refreshingOffsetPx: Float - with(LocalDensity.current) { thresholdPx = refreshThreshold.toPx() refreshingOffsetPx = refreshingOffset.toPx() } - // refreshThreshold and refreshingOffset should not be changed after instantiation, so any // changes to these values are ignored. - val state = remember(scope) { - PullRefreshState(scope, onRefreshState, refreshingOffsetPx, thresholdPx) - } - - SideEffect { - state.setRefreshing(refreshing) - } - + val state = remember(scope) { PullRefreshState(scope, onRefreshState, refreshingOffsetPx, thresholdPx) } + SideEffect { state.setRefreshing(refreshing) } return state } @@ -109,7 +100,7 @@ class PullRefreshState internal constructor( internal val refreshing get() = _refreshing 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 _position by mutableStateOf(0f) @@ -189,4 +180,4 @@ object PullRefreshDefaults { * the refresh threshold, it is the indicator position, otherwise the indicator position is * derived from the progress). */ -private const val DragMultiplier = 0.5f +private const val DRAG_MULTIPLIER = 0.5f diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarScreen.kt index af7332b4..8a1dc7d6 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarScreen.kt @@ -44,14 +44,9 @@ fun RadarScreen() { modifier = Modifier.fillMaxSize() ) { IconButton(onClick = {}) { - Icon( - painter = painterResource(id = R.drawable.ic_filter), - contentDescription = null - ) + Icon(painter = painterResource(id = R.drawable.ic_filter), contentDescription = null) } - Column( - verticalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.weight(1f) - ) { + Column(verticalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.weight(1f)) { Text(text = "Null") Text(text = "Null") } @@ -109,10 +104,7 @@ private fun TransmitterItem(radio: SatRadio) { .background(MaterialTheme.colorScheme.surface) .padding(6.dp) ) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { Icon( painter = painterResource(id = R.drawable.ic_arrow), contentDescription = null, modifier = Modifier.rotate(180f) @@ -127,10 +119,7 @@ private fun TransmitterItem(radio: SatRadio) { contentDescription = null ) } - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { Text( text = stringResource(id = R.string.radio_downlink, radio.downlink ?: 0L), textAlign = TextAlign.Center, diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarView.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarView.kt index b8ae9e48..d62b0ce2 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarView.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarView.kt @@ -43,9 +43,9 @@ fun RadarViewCompose(item: SatPos, items: List, azimElev: Pair(tween(1000)) - val animScale = animTransition.animateFloat(16f, 64f, animSpec) + val animScale = animTransition.animateFloat(16f, 64f, animSpec, label = "animScale") val measurer = rememberTextMeasurer() val sweepDegrees = remember { mutableStateOf(0f) } val trackCreated = remember { mutableStateOf(false) } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarViewModel.kt index 383ba360..154adefa 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarViewModel.kt @@ -99,9 +99,9 @@ class RadarViewModel( } } - fun getUseCompass(): Boolean = settingsRepo.otherSettings.value.sensorState - - fun getShowSweep(): Boolean = settingsRepo.otherSettings.value.sweepState +// fun getUseCompass(): Boolean = settingsRepo.otherSettings.value.sensorState +// +// fun getShowSweep(): Boolean = settingsRepo.otherSettings.value.sweepState private fun getMagDeclination(geoPos: GeoPos, time: Long = System.currentTimeMillis()): Float { val latitude = geoPos.latitude.toFloat() diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreen.kt index 52e14841..05436ad5 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreen.kt @@ -1,7 +1,6 @@ package com.rtbishop.look4sat.presentation.settings import android.Manifest -import android.os.Build import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.Arrangement @@ -61,16 +60,16 @@ fun SettingsScreen() { val context = LocalContext.current // Permissions setup - val bluetoothContract = ActivityResultContracts.RequestPermission() - val bluetoothError = stringResource(R.string.BTremote_perm_error) - val bluetoothPerm = when { - Build.VERSION.SDK_INT < Build.VERSION_CODES.S -> Manifest.permission.BLUETOOTH - else -> Manifest.permission.BLUETOOTH_CONNECT - } - val bluetoothRequest = rememberLauncherForActivityResult(bluetoothContract) { isGranted -> +// val bluetoothContract = ActivityResultContracts.RequestPermission() +// val bluetoothError = stringResource(R.string.BTremote_perm_error) +// val bluetoothPerm = when { +// Build.VERSION.SDK_INT < Build.VERSION_CODES.S -> Manifest.permission.BLUETOOTH +// else -> Manifest.permission.BLUETOOTH_CONNECT +// } +// val bluetoothRequest = rememberLauncherForActivityResult(bluetoothContract) { isGranted -> // viewModel.setBTEnabled(isGranted) - if (!isGranted) showToast(context, bluetoothError) - } +// if (!isGranted) showToast(context, bluetoothError) +// } val locationContract = ActivityResultContracts.RequestMultiplePermissions() val locationError = stringResource(R.string.location_gps_error) val locationPermCoarse = Manifest.permission.ACCESS_COARSE_LOCATION @@ -119,9 +118,7 @@ fun SettingsScreen() { val toggleSensor = { value: Boolean -> viewModel.toggleSensor(value) } // Screen setup - LazyColumn( - modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp) - ) { + LazyColumn(modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { item { CardAbout(BuildConfig.VERSION_NAME) } item { LocationCard(positionSettings, setGpsPos, showPosDialog, showLocDialog, dismissPos) } item { DataCard(dataSettings, updateFromWeb, updateFromFile, clearAllData) } @@ -376,8 +373,7 @@ private fun DataCard( @Preview(showBackground = true) @Composable private fun OtherCardPreview() = MainTheme { - val values = - OtherSettings(utcState = false, updateState = true, sweepState = true, sensorState = true) + val values = OtherSettings(utcState = false, updateState = true, sweepState = true, sensorState = true) OtherCard(settings = values, {}, {}, {}, {}) } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsUiState.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsUiState.kt index 2ad41bdb..35906ff0 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsUiState.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsUiState.kt @@ -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 DataSettings( - val isUpdating: Boolean, val entriesTotal: Int, val radiosTotal: Int, val timestamp: Long -) +data class DataSettings(val isUpdating: Boolean, val entriesTotal: Int, val radiosTotal: Int, val timestamp: Long) diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsViewModel.kt index 2df66b57..66fd28f9 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsViewModel.kt @@ -106,7 +106,7 @@ class SettingsViewModel( fun updateFromWeb() = viewModelScope.launch { try { _dataSettings.value = _dataSettings.value.copy(isUpdating = true) - databaseRepo.updateFromWeb() + databaseRepo.updateFromRemote() } catch (exception: Exception) { _dataSettings.value = _dataSettings.value.copy(isUpdating = false) println(exception.printStackTrace()) diff --git a/app/src/main/res/values-si/strings.xml b/app/src/main/res/values-si/strings.xml index ce87429b..24815fe0 100644 --- a/app/src/main/res/values-si/strings.xml +++ b/app/src/main/res/values-si/strings.xml @@ -147,8 +147,6 @@ යාවත්කාලීනය සම්පූර්ණ කිරීම සාර්ථකයි දත්ත ඉවත් කිරීම සාර්ථකයි - d MMM yyyy - HH:mm:ss - භ්‍රමක මනාප දුරස්ථ පාලනය සබල ක° IP ලිපිනය diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 538c04cf..0c150564 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -1,11 +1,13 @@ -@file:Suppress("UnstableApiUsage") - plugins { id("com.android.library") id("com.google.devtools.ksp") id("org.jetbrains.kotlin.android") } +kotlin { + jvmToolchain(17) +} + android { namespace = "com.rtbishop.look4sat.data" compileSdk = 33 @@ -13,10 +15,6 @@ android { minSdk = 21 testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } } dependencies { diff --git a/data/src/main/java/com/rtbishop/look4sat/data/framework/BluetoothReporter.kt b/data/src/main/java/com/rtbishop/look4sat/data/framework/BluetoothReporter.kt index 30fe8f92..20f8af5a 100644 --- a/data/src/main/java/com/rtbishop/look4sat/data/framework/BluetoothReporter.kt +++ b/data/src/main/java/com/rtbishop/look4sat/data/framework/BluetoothReporter.kt @@ -2,15 +2,15 @@ package com.rtbishop.look4sat.data.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 +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch class BluetoothReporter( - private val manager: BluetoothManager, + private val bluetoothManager: BluetoothManager, private val reporterScope: CoroutineScope ) { @@ -30,7 +30,7 @@ class BluetoothReporter( if (!connected) { rotationConnectionJob = reporterScope.launch { try { - manager.adapter.getRemoteDevice(deviceId)?.let { device -> + bluetoothManager.adapter.getRemoteDevice(deviceId)?.let { device -> device.createInsecureRfcommSocketToServiceRecord(sppid)?.let { socket -> connecting = true socket.connect() diff --git a/data/src/main/java/com/rtbishop/look4sat/data/repository/DatabaseRepo.kt b/data/src/main/java/com/rtbishop/look4sat/data/repository/DatabaseRepo.kt index 3068e24d..aa9358a1 100644 --- a/data/src/main/java/com/rtbishop/look4sat/data/repository/DatabaseRepo.kt +++ b/data/src/main/java/com/rtbishop/look4sat/data/repository/DatabaseRepo.kt @@ -22,36 +22,38 @@ import com.rtbishop.look4sat.domain.model.SatEntry import com.rtbishop.look4sat.domain.model.SatRadio import com.rtbishop.look4sat.domain.repository.IDatabaseRepo import com.rtbishop.look4sat.domain.repository.ISettingsRepo -import com.rtbishop.look4sat.domain.source.IDataSource -import com.rtbishop.look4sat.domain.source.ILocalStorage +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.utility.DataParser +import java.io.InputStream +import java.util.zip.ZipInputStream import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.async import kotlinx.coroutines.withContext -import java.io.InputStream -import java.util.zip.ZipInputStream class DatabaseRepo( private val dispatcher: CoroutineDispatcher, private val dataParser: DataParser, - private val dataSource: IDataSource, - private val localStorage: ILocalStorage, - private val settingsRepository: ISettingsRepo + private val fileSource: IFileSource, + private val localSource: ILocalSource, + private val remoteSource: IRemoteSource, + private val settingsRepo: ISettingsRepo ) : IDatabaseRepo { override suspend fun updateFromFile(uri: String) = withContext(dispatcher) { - val importedSatellites = dataSource.getFileStream(uri)?.let { importSatellites(it) } - importedSatellites?.let { localStorage.insertEntries(it) } + val importedSatellites = fileSource.getFileStream(uri)?.let { importSatellites(it) } + importedSatellites?.let { localSource.insertEntries(it) } setUpdateSuccessful(System.currentTimeMillis()) } - override suspend fun updateFromWeb() = withContext(dispatcher) { - val sourcesMap = settingsRepository.satelliteSourcesMap + override suspend fun updateFromRemote() = withContext(dispatcher) { + val sourcesMap = settingsRepo.satelliteSourcesMap val importedEntries = mutableListOf() val importedRadios = mutableListOf() // fetch - val jobsMap = sourcesMap.mapValues { async { dataSource.getNetworkStream(it.value) } } - val jobRadios = async { dataSource.getNetworkStream(settingsRepository.radioSourceUrl) } + val jobsMap = sourcesMap.mapValues { async { remoteSource.getRemoteStream(it.value) } } + val jobRadios = async { remoteSource.getRemoteStream(settingsRepo.radioSourceUrl) } // parse jobsMap.mapValues { job -> job.value.await() }.forEach { entry -> entry.value?.let { stream -> @@ -60,7 +62,7 @@ class DatabaseRepo( // parse tle stream val satellites = importSatellites(stream) val catnums = satellites.map { it.data.catnum } - settingsRepository.saveSatType(type, catnums) + settingsRepo.saveSatType(type, catnums) importedEntries.addAll(satellites) } @@ -69,7 +71,7 @@ class DatabaseRepo( val unzipped = ZipInputStream(stream).apply { nextEntry } val satellites = importSatellites(unzipped) val catnums = satellites.map { it.data.catnum } - settingsRepository.saveSatType(type, catnums) + settingsRepo.saveSatType(type, catnums) importedEntries.addAll(satellites) } @@ -78,7 +80,7 @@ class DatabaseRepo( val parsed = dataParser.parseCSVStream(stream) val satellites = parsed.map { data -> SatEntry(data) } val catnums = satellites.map { it.data.catnum } - settingsRepository.saveSatType(type, catnums) + settingsRepo.saveSatType(type, catnums) importedEntries.addAll(satellites) } } @@ -86,21 +88,21 @@ class DatabaseRepo( } jobRadios.await()?.let { importedRadios.addAll(dataParser.parseJSONStream(it)) } // insert - localStorage.insertEntries(importedEntries) - localStorage.insertRadios(importedRadios) + localSource.insertEntries(importedEntries) + localSource.insertRadios(importedRadios) setUpdateSuccessful(System.currentTimeMillis()) } override suspend fun clearAllData() = withContext(dispatcher) { - localStorage.deleteEntries() - localStorage.deleteRadios() + localSource.deleteEntries() + localSource.deleteRadios() setUpdateSuccessful(0L) } private suspend fun setUpdateSuccessful(timestamp: Long) = withContext(dispatcher) { - val satellitesTotal = localStorage.getEntriesTotal() - val radiosTotal = localStorage.getRadiosTotal() - settingsRepository.saveDatabaseState(DatabaseState(satellitesTotal, radiosTotal, timestamp)) + val satellitesTotal = localSource.getEntriesTotal() + val radiosTotal = localSource.getRadiosTotal() + settingsRepo.saveDatabaseState(DatabaseState(satellitesTotal, radiosTotal, timestamp)) } private suspend fun importSatellites(stream: InputStream): List { diff --git a/data/src/main/java/com/rtbishop/look4sat/data/repository/SatelliteRepo.kt b/data/src/main/java/com/rtbishop/look4sat/data/repository/SatelliteRepo.kt index 701b8893..c01aab8e 100644 --- a/data/src/main/java/com/rtbishop/look4sat/data/repository/SatelliteRepo.kt +++ b/data/src/main/java/com/rtbishop/look4sat/data/repository/SatelliteRepo.kt @@ -24,7 +24,7 @@ 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.ILocalStorage +import com.rtbishop.look4sat.domain.source.ILocalSource import com.rtbishop.look4sat.domain.utility.round import com.rtbishop.look4sat.domain.utility.toDegrees import kotlinx.coroutines.CoroutineDispatcher @@ -35,7 +35,7 @@ import kotlinx.coroutines.withContext class SatelliteRepo( private val dispatcher: CoroutineDispatcher, - private val localStorage: ILocalStorage, + private val localStorage: ILocalSource, private val settingsRepo: ISettingsRepo ) : ISatelliteRepo { diff --git a/data/src/main/java/com/rtbishop/look4sat/data/repository/SelectionRepo.kt b/data/src/main/java/com/rtbishop/look4sat/data/repository/SelectionRepo.kt index 8eb0c7ef..e02903a5 100644 --- a/data/src/main/java/com/rtbishop/look4sat/data/repository/SelectionRepo.kt +++ b/data/src/main/java/com/rtbishop/look4sat/data/repository/SelectionRepo.kt @@ -3,7 +3,7 @@ package com.rtbishop.look4sat.data.repository import com.rtbishop.look4sat.domain.model.SatItem import com.rtbishop.look4sat.domain.repository.ISelectionRepo 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.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -15,7 +15,7 @@ import kotlinx.coroutines.withContext @OptIn(ExperimentalCoroutinesApi::class) class SelectionRepo( private val dispatcher: CoroutineDispatcher, - private val localStorage: ILocalStorage, + private val localSource: ILocalSource, private val settingsRepo: ISettingsRepo ) : ISelectionRepo { private val currentItems = MutableStateFlow>(emptyList()) @@ -34,7 +34,7 @@ class SelectionRepo( override suspend fun getEntriesFlow() = withContext(dispatcher) { val selectedIds = settingsRepo.satelliteSelection.value - currentItems.value = localStorage.getEntriesList().map { item -> + currentItems.value = localSource.getEntriesList().map { item -> item.copy(isSelected = item.catnum in selectedIds) } return@withContext itemsWithQuery diff --git a/data/src/main/java/com/rtbishop/look4sat/data/framework/SettingsRepo.kt b/data/src/main/java/com/rtbishop/look4sat/data/repository/SettingsRepo.kt similarity index 94% rename from data/src/main/java/com/rtbishop/look4sat/data/framework/SettingsRepo.kt rename to data/src/main/java/com/rtbishop/look4sat/data/repository/SettingsRepo.kt index bf07d0f2..dac86330 100644 --- a/data/src/main/java/com/rtbishop/look4sat/data/framework/SettingsRepo.kt +++ b/data/src/main/java/com/rtbishop/look4sat/data/repository/SettingsRepo.kt @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package com.rtbishop.look4sat.data.framework +package com.rtbishop.look4sat.data.repository import android.content.SharedPreferences 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.predict.GeoPos 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 java.util.concurrent.Executors 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 + private val locationManager: LocationManager, private val preferences: SharedPreferences ) : ISettingsRepo { private val keyModes = "satModes" @@ -73,12 +74,12 @@ class SettingsRepo( override val stationPosition: StateFlow = _stationPosition override fun setGpsPosition(): Boolean { - if (!LocationManagerCompat.isLocationEnabled(manager)) return false + if (!LocationManagerCompat.isLocationEnabled(locationManager)) return false try { 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") - LocationManagerCompat.getCurrentLocation(manager, provider, timeoutSignal, executor) { + LocationManagerCompat.getCurrentLocation(locationManager, provider, timeoutSignal, executor) { it?.let { setGeoPosition(it.latitude, it.longitude, it.altitude) } } } catch (exception: SecurityException) { @@ -89,13 +90,13 @@ class SettingsRepo( 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 + val locator = 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 + val position = qthToPosition(locator) ?: return false saveGeoPos(position.latitude, position.longitude, 0.0, locator) return true } diff --git a/data/src/main/java/com/rtbishop/look4sat/data/source/FileSource.kt b/data/src/main/java/com/rtbishop/look4sat/data/source/FileSource.kt new file mode 100644 index 00000000..7ef2a1d6 --- /dev/null +++ b/data/src/main/java/com/rtbishop/look4sat/data/source/FileSource.kt @@ -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)) } + } +} diff --git a/data/src/main/java/com/rtbishop/look4sat/data/source/LocalStorage.kt b/data/src/main/java/com/rtbishop/look4sat/data/source/LocalSource.kt similarity index 96% rename from data/src/main/java/com/rtbishop/look4sat/data/source/LocalStorage.kt rename to data/src/main/java/com/rtbishop/look4sat/data/source/LocalSource.kt index ad7f7c16..adab3a16 100644 --- a/data/src/main/java/com/rtbishop/look4sat/data/source/LocalStorage.kt +++ b/data/src/main/java/com/rtbishop/look4sat/data/source/LocalSource.kt @@ -17,18 +17,18 @@ */ 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.SatItem as FrameworkItem 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.SatItem as DomainItem 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 diff --git a/data/src/main/java/com/rtbishop/look4sat/data/source/DataSource.kt b/data/src/main/java/com/rtbishop/look4sat/data/source/RemoteSource.kt similarity index 68% rename from data/src/main/java/com/rtbishop/look4sat/data/source/DataSource.kt rename to data/src/main/java/com/rtbishop/look4sat/data/source/RemoteSource.kt index 61e17726..d75084e8 100644 --- a/data/src/main/java/com/rtbishop/look4sat/data/source/DataSource.kt +++ b/data/src/main/java/com/rtbishop/look4sat/data/source/RemoteSource.kt @@ -17,26 +17,18 @@ */ package com.rtbishop.look4sat.data.source -import android.content.ContentResolver -import android.net.Uri -import com.rtbishop.look4sat.domain.source.IDataSource +import com.rtbishop.look4sat.domain.source.IRemoteSource +import java.io.InputStream 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 { +class RemoteSource( + private val httpClient: OkHttpClient, private val dispatcher: CoroutineDispatcher +) : IRemoteSource { - override suspend fun getFileStream(uri: String): InputStream? { - return withContext(dispatcher) { contentResolver.openInputStream(Uri.parse(uri)) } - } - - override suspend fun getNetworkStream(url: String): InputStream? = withContext(dispatcher) { + override suspend fun getRemoteStream(url: String): InputStream? = withContext(dispatcher) { try { val request = Request.Builder().url(url).build() httpClient.newCall(request).execute().body?.byteStream() diff --git a/domain/src/main/java/com/rtbishop/look4sat/domain/model/DataState.kt b/domain/src/main/java/com/rtbishop/look4sat/domain/model/DataState.kt index a3784511..d4db9120 100644 --- a/domain/src/main/java/com/rtbishop/look4sat/domain/model/DataState.kt +++ b/domain/src/main/java/com/rtbishop/look4sat/domain/model/DataState.kt @@ -19,8 +19,6 @@ package com.rtbishop.look4sat.domain.model sealed class DataState { data class Success(val data: T) : DataState() - data class Error(val resId: Int?) : DataState() data class Exception(val message: String?) : DataState() - data object Handled : DataState() data object Loading : DataState() } diff --git a/domain/src/main/java/com/rtbishop/look4sat/domain/repository/IDatabaseRepo.kt b/domain/src/main/java/com/rtbishop/look4sat/domain/repository/IDatabaseRepo.kt index 845d7087..204549c1 100644 --- a/domain/src/main/java/com/rtbishop/look4sat/domain/repository/IDatabaseRepo.kt +++ b/domain/src/main/java/com/rtbishop/look4sat/domain/repository/IDatabaseRepo.kt @@ -21,7 +21,7 @@ interface IDatabaseRepo { suspend fun updateFromFile(uri: String) - suspend fun updateFromWeb() + suspend fun updateFromRemote() suspend fun clearAllData() } diff --git a/domain/src/main/java/com/rtbishop/look4sat/domain/source/IDataSource.kt b/domain/src/main/java/com/rtbishop/look4sat/domain/source/IFileSource.kt similarity index 91% rename from domain/src/main/java/com/rtbishop/look4sat/domain/source/IDataSource.kt rename to domain/src/main/java/com/rtbishop/look4sat/domain/source/IFileSource.kt index 329915e8..6f398346 100644 --- a/domain/src/main/java/com/rtbishop/look4sat/domain/source/IDataSource.kt +++ b/domain/src/main/java/com/rtbishop/look4sat/domain/source/IFileSource.kt @@ -19,9 +19,6 @@ package com.rtbishop.look4sat.domain.source import java.io.InputStream -interface IDataSource { - +interface IFileSource { suspend fun getFileStream(uri: String): InputStream? - - suspend fun getNetworkStream(url: String): InputStream? } diff --git a/domain/src/main/java/com/rtbishop/look4sat/domain/source/ILocalStorage.kt b/domain/src/main/java/com/rtbishop/look4sat/domain/source/ILocalSource.kt similarity index 98% rename from domain/src/main/java/com/rtbishop/look4sat/domain/source/ILocalStorage.kt rename to domain/src/main/java/com/rtbishop/look4sat/domain/source/ILocalSource.kt index 5b226faf..1152226f 100644 --- a/domain/src/main/java/com/rtbishop/look4sat/domain/source/ILocalStorage.kt +++ b/domain/src/main/java/com/rtbishop/look4sat/domain/source/ILocalSource.kt @@ -22,7 +22,7 @@ import com.rtbishop.look4sat.domain.model.SatItem import com.rtbishop.look4sat.domain.model.SatRadio import com.rtbishop.look4sat.domain.predict.Satellite -interface ILocalStorage { +interface ILocalSource { suspend fun getEntriesTotal(): Int diff --git a/domain/src/main/java/com/rtbishop/look4sat/domain/source/IRemoteSource.kt b/domain/src/main/java/com/rtbishop/look4sat/domain/source/IRemoteSource.kt new file mode 100644 index 00000000..84ffbd33 --- /dev/null +++ b/domain/src/main/java/com/rtbishop/look4sat/domain/source/IRemoteSource.kt @@ -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 . + */ +package com.rtbishop.look4sat.domain.source + +import java.io.InputStream + +interface IRemoteSource { + suspend fun getRemoteStream(url: String): InputStream? +} diff --git a/domain/src/main/java/com/rtbishop/look4sat/domain/utility/GeoConverter.kt b/domain/src/main/java/com/rtbishop/look4sat/domain/utility/GeoConverter.kt index ed65a6bb..2f23cbe0 100644 --- a/domain/src/main/java/com/rtbishop/look4sat/domain/utility/GeoConverter.kt +++ b/domain/src/main/java/com/rtbishop/look4sat/domain/utility/GeoConverter.kt @@ -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 . + */ package com.rtbishop.look4sat.domain.utility import com.rtbishop.look4sat.domain.predict.DEG2RAD diff --git a/domain/src/main/java/com/rtbishop/look4sat/domain/utility/QthConverter.kt b/domain/src/main/java/com/rtbishop/look4sat/domain/utility/QthConverter.kt index 1e67cb3f..85b38e06 100644 --- a/domain/src/main/java/com/rtbishop/look4sat/domain/utility/QthConverter.kt +++ b/domain/src/main/java/com/rtbishop/look4sat/domain/utility/QthConverter.kt @@ -19,43 +19,40 @@ package com.rtbishop.look4sat.domain.utility import com.rtbishop.look4sat.domain.predict.GeoPos -object QthConverter { - - fun qthToPosition(locator: String): GeoPos? { - val trimmedQth = locator.take(6) - if (!isValidLocator(trimmedQth)) return null - val lonFirst = (trimmedQth[0].uppercaseChar().code - 65) * 20 - val latFirst = (trimmedQth[1].uppercaseChar().code - 65) * 10 - val lonSecond = trimmedQth[2].toString().toInt() * 2 - val latSecond = trimmedQth[3].toString().toInt() - val lonThird = (((trimmedQth[4].lowercaseChar().code - 97) / 12.0) + (1.0 / 24.0)) - 180 - val latThird = (((trimmedQth[5].lowercaseChar().code - 97) / 24.0) + (1.0 / 48.0)) - 90 - val longitude = (lonFirst + lonSecond + lonThird).round(4) - val latitude = (latFirst + latSecond + latThird).round(4) - return GeoPos(latitude, longitude) - } - - fun positionToQth(latitude: Double, longitude: Double): String? { - if (!isValidPosition(latitude, longitude)) return null - val tempLon = if (longitude > 180.0) longitude - 180 else longitude - val upper = "ABCDEFGHIJKLMNOPQRSTUVWX" - val lower = "abcdefghijklmnopqrstuvwx" - val newLongitude = tempLon + 180 - val newLatitude = latitude + 90 - val lonFirst = upper[(newLongitude / 20).toInt()] - val latFirst = upper[(newLatitude / 10).toInt()] - val lonSecond = ((newLongitude / 2) % 10).toInt().toString() - val latSecond = (newLatitude % 10).toInt().toString() - val lonThird = lower[((newLongitude % 2) * 12).toInt()] - 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 isValidLocator(locator: String): Boolean { - return locator.matches("[a-xA-X][a-xA-X]\\d\\d[a-xA-X][a-xA-X]".toRegex()) - } +fun qthToPosition(locator: String): GeoPos? { + val trimmedQth = locator.take(6) + if (!isValidLocator(trimmedQth)) return null + val lonFirst = (trimmedQth[0].uppercaseChar().code - 65) * 20 + val latFirst = (trimmedQth[1].uppercaseChar().code - 65) * 10 + val lonSecond = trimmedQth[2].toString().toInt() * 2 + val latSecond = trimmedQth[3].toString().toInt() + val lonThird = (((trimmedQth[4].lowercaseChar().code - 97) / 12.0) + (1.0 / 24.0)) - 180 + val latThird = (((trimmedQth[5].lowercaseChar().code - 97) / 24.0) + (1.0 / 48.0)) - 90 + val longitude = (lonFirst + lonSecond + lonThird).round(4) + val latitude = (latFirst + latSecond + latThird).round(4) + return GeoPos(latitude, longitude) +} + +fun positionToQth(latitude: Double, longitude: Double): String? { + if (!isValidPosition(latitude, longitude)) return null + val tempLon = if (longitude > 180.0) longitude - 180 else longitude + val upper = "ABCDEFGHIJKLMNOPQRSTUVWX" + val lower = "abcdefghijklmnopqrstuvwx" + val newLongitude = tempLon + 180 + val newLatitude = latitude + 90 + val lonFirst = upper[(newLongitude / 20).toInt()] + val latFirst = upper[(newLatitude / 10).toInt()] + val lonSecond = ((newLongitude / 2) % 10).toInt().toString() + val latSecond = (newLatitude % 10).toInt().toString() + val lonThird = lower[((newLongitude % 2) * 12).toInt()] + 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 isValidLocator(locator: String): Boolean { + return locator.matches("[a-xA-X][a-xA-X]\\d\\d[a-xA-X][a-xA-X]".toRegex()) } diff --git a/domain/src/test/java/com/rtbishop/look4sat/domain/QthConverterTest.kt b/domain/src/test/java/com/rtbishop/look4sat/domain/QthConverterTest.kt index 0090a0bb..90d13d55 100644 --- a/domain/src/test/java/com/rtbishop/look4sat/domain/QthConverterTest.kt +++ b/domain/src/test/java/com/rtbishop/look4sat/domain/QthConverterTest.kt @@ -17,37 +17,38 @@ */ 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 class QthConverterTest { @Test 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) - result = QthConverter.qthToPosition("JN58TD") + result = qthToPosition("JN58TD") 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) } @Test fun `Given invalid QTH returns null`() { - assert(QthConverter.qthToPosition("ZZ00zz") == null) - assert(QthConverter.qthToPosition("JN58") == null) + assert(qthToPosition("ZZ00zz") == null) + assert(qthToPosition("JN58") == null) } @Test fun `Given valid POS returns correct QTH`() { - assert(QthConverter.positionToQth(51.4878, -0.2146) == "IO91vl") - assert(QthConverter.positionToQth(48.1466, 11.6083) == "JN58td") - assert(QthConverter.positionToQth(-34.91, -56.2116) == "GF15vc") + assert(positionToQth(51.4878, -0.2146) == "IO91vl") + assert(positionToQth(48.1466, 11.6083) == "JN58td") + assert(positionToQth(-34.91, -56.2116) == "GF15vc") } @Test fun `Given invalid POS returns null`() { - assert(QthConverter.positionToQth(91.0542, -170.1142) == null) - assert(QthConverter.positionToQth(89.0542, -240.1142) == null) + assert(positionToQth(91.0542, -170.1142) == null) + assert(positionToQth(89.0542, -240.1142) == null) } }