From fb2cd85b11938510ee143834ac38fbd00fac48af Mon Sep 17 00:00:00 2001 From: Arty Bishop Date: Fri, 24 Apr 2026 17:16:09 +0100 Subject: [PATCH] Migrated to KotlinX Serialization library for JSON parsing --- .../look4sat/convention/CoreDataPlugin.kt | 2 +- .../look4sat/convention/CoreDomainPlugin.kt | 5 ++- build.gradle.kts | 1 + .../core/data/database/entity/SatRadio.kt | 8 ++-- .../look4sat/core/domain/model/SatRadio.kt | 26 +++++++------ .../core/domain/utility/DataParser.kt | 38 +++++++------------ .../core/domain/TransponderMapperTest.kt | 4 +- .../look4sat/feature/radar/RadarScreen.kt | 4 +- gradle/libs.versions.toml | 25 ++++++------ 9 files changed, 56 insertions(+), 57 deletions(-) diff --git a/build-logic/convention/src/main/java/com/rtbishop/look4sat/convention/CoreDataPlugin.kt b/build-logic/convention/src/main/java/com/rtbishop/look4sat/convention/CoreDataPlugin.kt index 4cdf9deb..3a1e7ab1 100644 --- a/build-logic/convention/src/main/java/com/rtbishop/look4sat/convention/CoreDataPlugin.kt +++ b/build-logic/convention/src/main/java/com/rtbishop/look4sat/convention/CoreDataPlugin.kt @@ -33,7 +33,7 @@ internal class CoreDataPlugin : Plugin { implementation(libs.androidx.room) implementation(libs.androidx.room.runtime) ksp(libs.androidx.room.compiler) - implementation(libs.other.coroutines) + implementation(libs.kotlin.coroutines) implementation(libs.other.okhttp) } } diff --git a/build-logic/convention/src/main/java/com/rtbishop/look4sat/convention/CoreDomainPlugin.kt b/build-logic/convention/src/main/java/com/rtbishop/look4sat/convention/CoreDomainPlugin.kt index 228bd935..19531a5d 100644 --- a/build-logic/convention/src/main/java/com/rtbishop/look4sat/convention/CoreDomainPlugin.kt +++ b/build-logic/convention/src/main/java/com/rtbishop/look4sat/convention/CoreDomainPlugin.kt @@ -25,10 +25,11 @@ import org.gradle.kotlin.dsl.dependencies internal class CoreDomainPlugin : Plugin { override fun apply(target: Project) = with(target) { applyPlugin(libs.plugins.kotlin.jvm) + applyPlugin(libs.plugins.kotlin.serialization) setupKotlin() dependencies { - implementation(libs.other.coroutines) - implementation(libs.other.json) + implementation(libs.kotlin.coroutines) + implementation(libs.kotlin.serialization) } } } diff --git a/build.gradle.kts b/build.gradle.kts index 642237d4..6692654b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,6 +4,7 @@ plugins { alias(libs.plugins.compose.compiler) apply false alias(libs.plugins.google.ksp) apply false alias(libs.plugins.kotlin.jvm) apply false + alias(libs.plugins.kotlin.serialization) apply false } tasks.register("clean", Delete::class.java) { diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/entity/SatRadio.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/entity/SatRadio.kt index bc1404f1..ba9eccdf 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/entity/SatRadio.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/entity/SatRadio.kt @@ -25,11 +25,11 @@ data class SatRadio( @PrimaryKey val uuid: String, val info: String, val isAlive: Boolean, - var downlinkLow: Long?, - var downlinkHigh: Long?, + val downlinkLow: Long?, + val downlinkHigh: Long?, val downlinkMode: String?, - var uplinkLow: Long?, - var uplinkHigh: Long?, + val uplinkLow: Long?, + val uplinkHigh: Long?, val uplinkMode: String?, val isInverted: Boolean, val catnum: Int? diff --git a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/model/SatRadio.kt b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/model/SatRadio.kt index bc36adc4..b057fb4e 100644 --- a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/model/SatRadio.kt +++ b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/model/SatRadio.kt @@ -17,16 +17,20 @@ */ package com.rtbishop.look4sat.core.domain.model +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable data class SatRadio( - val uuid: String, - val info: String, - val isAlive: Boolean, - var downlinkLow: Long?, - var downlinkHigh: Long?, - val downlinkMode: String?, - var uplinkLow: Long?, - var uplinkHigh: Long?, - val uplinkMode: String?, - val isInverted: Boolean, - val catnum: Int? + @SerialName("uuid") val uuid: String, + @SerialName("description") val info: String, + @SerialName("alive") val isAlive: Boolean, + @SerialName("downlink_low") val downlinkLow: Long?, + @SerialName("downlink_high") val downlinkHigh: Long?, + @SerialName("mode") val downlinkMode: String?, + @SerialName("uplink_low") val uplinkLow: Long?, + @SerialName("uplink_high") val uplinkHigh: Long?, + @SerialName("uplink_mode") val uplinkMode: String?, + @SerialName("invert") val isInverted: Boolean, + @SerialName("norad_cat_id") val catnum: Int? ) diff --git a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/utility/DataParser.kt b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/utility/DataParser.kt index 5ece9a94..dda7d4c5 100644 --- a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/utility/DataParser.kt +++ b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/utility/DataParser.kt @@ -21,13 +21,19 @@ import com.rtbishop.look4sat.core.domain.model.SatRadio import com.rtbishop.look4sat.core.domain.predict.OrbitalData import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.withContext -import org.json.JSONArray -import org.json.JSONObject +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.decodeFromJsonElement import java.io.InputStream import kotlin.math.pow class DataParser(private val dispatcher: CoroutineDispatcher) { + private val json = Json { + ignoreUnknownKeys = true + coerceInputValues = true + } + suspend fun parseCSVStream(stream: InputStream): List = withContext(dispatcher) { stream.bufferedReader().useLines { lines -> lines.drop(1).mapNotNull { parseCSV(it.split(",")) }.toList() @@ -43,8 +49,12 @@ class DataParser(private val dispatcher: CoroutineDispatcher) { suspend fun parseJSONStream(stream: InputStream): List = withContext(dispatcher) { runCatching { - val jsonArray = JSONArray(stream.bufferedReader().readText()) - (0 until jsonArray.length()).mapNotNull { parseJSON(jsonArray.getJSONObject(it)) } + val root = json.parseToJsonElement(stream.bufferedReader().readText()) + (root as? JsonArray)?.mapNotNull { element -> + runCatching { json.decodeFromJsonElement(element) } + .onFailure { println("JSON parsing exception: $it") } + .getOrNull() + } ?: emptyList() }.getOrDefault(emptyList()) } @@ -95,26 +105,6 @@ class DataParser(private val dispatcher: CoroutineDispatcher) { ) }.onFailure { println("TLE parsing exception: $it") }.getOrNull() - private fun parseJSON(json: JSONObject): SatRadio? = runCatching { - SatRadio( - uuid = json.getString("uuid"), - info = json.getString("description"), - isAlive = json.getBoolean("alive"), - downlinkLow = json.optLongOrNull("downlink_low"), - downlinkHigh = json.optLongOrNull("downlink_high"), - downlinkMode = json.optStringOrNull("mode"), - uplinkLow = json.optLongOrNull("uplink_low"), - uplinkHigh = json.optLongOrNull("uplink_high"), - uplinkMode = json.optStringOrNull("uplink_mode"), - isInverted = json.getBoolean("invert"), - catnum = json.optIntOrNull("norad_cat_id") - ) - }.onFailure { println("JSON parsing exception: $it") }.getOrNull() - - private fun JSONObject.optStringOrNull(key: String): String? = if (isNull(key)) null else getString(key) - private fun JSONObject.optLongOrNull(key: String): Long? = if (isNull(key)) null else getLong(key) - private fun JSONObject.optIntOrNull(key: String): Int? = if (isNull(key)) null else getInt(key) - private fun getDayOfYear(year: Int, month: Int, dayOfMonth: Int): Int { val daysInMonth = intArrayOf(31, if (isLeapYear(year)) 29 else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) return daysInMonth.take(month - 1).sum() + dayOfMonth diff --git a/core/domain/src/test/java/com/rtbishop/look4sat/core/domain/TransponderMapperTest.kt b/core/domain/src/test/java/com/rtbishop/look4sat/core/domain/TransponderMapperTest.kt index cba6ce11..b1aa5e58 100644 --- a/core/domain/src/test/java/com/rtbishop/look4sat/core/domain/TransponderMapperTest.kt +++ b/core/domain/src/test/java/com/rtbishop/look4sat/core/domain/TransponderMapperTest.kt @@ -2,9 +2,9 @@ package com.rtbishop.look4sat.core.domain import com.rtbishop.look4sat.core.domain.model.SatRadio import com.rtbishop.look4sat.core.domain.utility.TransponderMapper +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull import org.junit.Test -import kotlin.test.assertEquals -import kotlin.test.assertNull class TransponderMapperTest { diff --git a/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarScreen.kt b/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarScreen.kt index c0511126..7a0a9a40 100644 --- a/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarScreen.kt +++ b/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarScreen.kt @@ -149,8 +149,8 @@ private fun RadarScreen( @Composable private fun RadarCard(uiState: RadarState, modifier: Modifier = Modifier) { - val isEclipsed = uiState.orbitalPos?.eclipsed == true - val borderModifier = if (isEclipsed) { + val satellitePos = uiState.orbitalPos + val borderModifier = if (satellitePos?.aboveHorizon == true && satellitePos.eclipsed) { val infiniteTransition = rememberInfiniteTransition(label = "eclipsedBorder") val borderAlpha by infiniteTransition.animateFloat( initialValue = 1.0f, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7037402c..bfa675d5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,9 +12,7 @@ jdkVersion = "17" #noinspection UnusedVersionCatalogEntry packageName = "com.rtbishop.look4sat" -android-gradle-plugin = "9.1.1" -google-ksp = "2.3.6" -kotlin = "2.3.20" +android-gradle-plugin = "9.2.0" androidx-core-ktx = "1.18.0" androidx-core-splashscreen = "1.2.0" @@ -25,8 +23,12 @@ compose-activity = "1.13.0" compose-lifecycle = "2.10.0" compose-navigation = "2.9.7" -other-coroutines = "1.10.2" -other-json = "20251224" +google-ksp = "2.3.6" + +kotlin = "2.3.20" +kotlin-coroutines = "1.10.2" +kotlin-serialization = "1.11.0" + other-okhttp = "5.3.2" other-osmdroid = "6.1.20" @@ -64,18 +66,18 @@ compose-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", compose-debug-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } compose-debug-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } -kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } +#noinspection UnusedVersionCatalogEntry +kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" } +kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } +#noinspection UnusedVersionCatalogEntry +kotlin-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlin-serialization" } -#noinspection UnusedVersionCatalogEntry -other-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "other-coroutines" } -#noinspection UnusedVersionCatalogEntry -other-json = { module = "org.json:json", version.ref = "other-json" } #noinspection UnusedVersionCatalogEntry other-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "other-okhttp" } #noinspection UnusedVersionCatalogEntry other-osmdroid = { module = "org.osmdroid:osmdroid-android", version.ref = "other-osmdroid" } -test-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "other-coroutines" } +test-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlin-coroutines" } test-junit4 = { module = "junit:junit", version.ref = "test-junit4" } androidTest-junit = { module = "androidx.test.ext:junit", version.ref = "androidTest-junit" } @@ -87,6 +89,7 @@ android-library = { id = "com.android.library", version.ref = "android-gradle-pl compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } google-ksp = { id = "com.google.devtools.ksp", version.ref = "google-ksp" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } # plugins defined by this project convention-applicationPlugin = { id = "com.rtbishop.look4sat.convention.applicationPlugin" } convention-coreDataPlugin = { id = "com.rtbishop.look4sat.convention.coreDataPlugin" }