diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SatelliteRepo.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SatelliteRepo.kt index a3aad153..18a6bbd7 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SatelliteRepo.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SatelliteRepo.kt @@ -146,17 +146,18 @@ class SatelliteRepo( val passes = mutableListOf() val endDate = time + hours * 60L * 60L * 1000L val quarterOrbitMin = (this.data.orbitalPeriod / 4.0).toInt() + val decayed = this.data.hasDecayed(time) var startDate = time var shouldRewind = true var lastAosDate: Long var count = 0 if (this.willBeSeen(pos)) { if (this.data.isDeepSpace) { - passes.add(getGeoPass(this, pos, time)) + passes.add(getGeoPass(this, pos, time, decayed)) } else { do { if (count > 0) shouldRewind = false - val pass = getLeoPass(this, pos, startDate, shouldRewind) + val pass = getLeoPass(this, pos, startDate, shouldRewind, decayed) lastAosDate = pass.aosTime passes.add(pass) startDate = pass.losTime + (quarterOrbitMin * 3) * 60L * 1000L @@ -167,17 +168,17 @@ class SatelliteRepo( return passes } - private fun getGeoPass(sat: OrbitalObject, pos: GeoPos, time: Long): OrbitalPass { + private fun getGeoPass(sat: OrbitalObject, pos: GeoPos, time: Long, decayed: Boolean): OrbitalPass { val satPos = sat.getPosition(pos, time) val aos = time - 24 * 60L * 60L * 1000L val los = time + 24 * 60L * 60L * 1000L // val tca = (aos + los) / 2 val az = satPos.azimuth.toDegrees().round(1) val elev = satPos.elevation.toDegrees().round(1) val alt = satPos.altitude - return OrbitalPass(aos, az, los, az, alt.toInt(), elev, sat) + return OrbitalPass(aos, az, los, az, alt.toInt(), elev, sat, hasDecayed = decayed) } - private fun getLeoPass(sat: OrbitalObject, pos: GeoPos, time: Long, rewind: Boolean): OrbitalPass { + private fun getLeoPass(sat: OrbitalObject, pos: GeoPos, time: Long, rewind: Boolean, decayed: Boolean): OrbitalPass { val quarterOrbitMin = (sat.data.orbitalPeriod / 4.0).toInt() var calendarTimeMillis = time var elevation: Double @@ -241,6 +242,6 @@ class SatelliteRepo( val alt = tcaPos.altitude val elev = maxElevation.toDegrees().round(1) - return OrbitalPass(aos, aosAz, los, losAz, alt.toInt(), elev, sat) + return OrbitalPass(aos, aosAz, los, losAz, alt.toInt(), elev, sat, hasDecayed = decayed) } } diff --git a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/predict/OrbitalData.kt b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/predict/OrbitalData.kt index 860953f6..c9336cee 100644 --- a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/predict/OrbitalData.kt +++ b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/predict/OrbitalData.kt @@ -27,7 +27,8 @@ data class OrbitalData( val argper: Double, val meanan: Double, val catnum: Int, - val bstar: Double + val bstar: Double, + val ndot: Double = 0.0 ) { val xincl: Double = incl * DEG2RAD val xnodeo: Double = raan * DEG2RAD @@ -37,4 +38,29 @@ data class OrbitalData( val orbitalPeriod: Double = MIN_PER_DAY / meanmo val isDeepSpace: Boolean = orbitalPeriod >= 225.0 // NearEarth (period < 225 min) or DeepSpace (period >= 225 min) fun getObject(): OrbitalObject = if (isDeepSpace) DeepSpaceObject(this) else NearEarthObject(this) + + /** Check if satellite has likely decayed by the given time. */ + fun hasDecayed(currentTimeMillis: Long): Boolean { + if (ndot == 0.0) return false + val currentDaynum = (currentTimeMillis - 315446400000L) / 86400000.0 + val epochDaynum = epochToDaynum(epoch) + return CelestialComputer.hasDecayed(meanmo, ndot, epochDaynum, currentDaynum) + } + + private fun epochToDaynum(epoch: Double): Double { + var year = kotlin.math.floor(epoch * 1E-3) + val day = (epoch * 1E-3 - year) * 1000.0 + year = if (year < 57) year + 2000 else year + 1900 + // daynum = days since 31 Dec 1979, Julian date of 31Dec79 = 2444238.5 + val jan1Jd = julianDateOfYear(year) + return jan1Jd + day - 2444238.5 + } + + private fun julianDateOfYear(theYear: Double): Double { + val aYear = theYear - 1 + val a = kotlin.math.floor(aYear / 100).toLong() + val b = 2 - a + a / 4 + val i = kotlin.math.floor(365.25 * aYear).toLong() + return i + (30.6001 * 14).toLong() + 1720994.5 + b + } } diff --git a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/predict/OrbitalPass.kt b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/predict/OrbitalPass.kt index 571b342e..99575877 100644 --- a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/predict/OrbitalPass.kt +++ b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/predict/OrbitalPass.kt @@ -25,9 +25,10 @@ data class OrbitalPass( val altitude: Int = 1000, val maxElevation: Double = 75.0, val orbitalObject: OrbitalObject, - val progress: Float = 0.0f + val progress: Float = 0.0f, + val hasDecayed: Boolean = false ) { val catNum: Int = orbitalObject.data.catnum - val name: String = orbitalObject.data.name + val name: String = if (hasDecayed) "${orbitalObject.data.name} (decayed?)" else orbitalObject.data.name val isDeepSpace: Boolean = orbitalObject.data.isDeepSpace } 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 9cada20d..f82d2348 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 @@ -82,7 +82,8 @@ class DataParser(private val dispatcher: CoroutineDispatcher) { argper = values[7].toDouble(), meanan = values[8].toDouble(), catnum = values[11].toInt(), - bstar = values[14].toDouble() + bstar = values[14].toDouble(), + ndot = values[15].toDouble() ) }.onFailure { println("CSV parsing exception: $it") }.getOrNull() @@ -99,13 +100,15 @@ class DataParser(private val dispatcher: CoroutineDispatcher) { argper = line2.substring(34, 42).toDouble(), meanan = line2.substring(43, 51).toDouble(), catnum = line1.substring(2, 7).trim().toInt(), - bstar = 1e-5 * line1.substring(53, 59).toDouble() / 10.0.pow(line1.substring(60, 61).toDouble()) + bstar = 1e-5 * line1.substring(53, 59).toDouble() / 10.0.pow(line1.substring(60, 61).toDouble()), + ndot = line1.substring(33, 43).trim().toDouble() ) }.onFailure { println("TLE parsing exception: $it") }.getOrNull() - private fun getDayOfYear(year: Int, month: Int, dayOfMonth: Int): Int { - val isLeapYear = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 - val daysInMonth = intArrayOf(31, if (isLeapYear) 29 else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) + fun isLeapYear(year: Int): Boolean = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 + + 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/DataParserTest.kt b/core/domain/src/test/java/com/rtbishop/look4sat/core/domain/DataParserTest.kt index b66042f9..5d7a5588 100644 --- a/core/domain/src/test/java/com/rtbishop/look4sat/core/domain/DataParserTest.kt +++ b/core/domain/src/test/java/com/rtbishop/look4sat/core/domain/DataParserTest.kt @@ -59,10 +59,53 @@ class DataParserTest { @Test fun `Given valid CSV stream returns valid data`() = runTest(testDispatcher) { val parsedList = dataParser.parseCSVStream(validCSVStream) + assert(parsedList.size == 2) assert(parsedList[0].epoch == 21320.51955234) assert(parsedList[1].epoch == 24069.23963816) } + @Test + fun `Given valid CSV stream all orbital fields are parsed correctly`() = runTest(testDispatcher) { + val csvStream = """ + OBJECT_NAME,OBJECT_ID,EPOCH,MEAN_MOTION,ECCENTRICITY,INCLINATION,RA_OF_ASC_NODE,ARG_OF_PERICENTER,MEAN_ANOMALY,EPHEMERIS_TYPE,CLASSIFICATION_TYPE,NORAD_CAT_ID,ELEMENT_SET_NO,REV_AT_EPOCH,BSTAR,MEAN_MOTION_DOT,MEAN_MOTION_DDOT + ISS (ZARYA),1998-067A,2021-11-16T12:28:09.322176,15.48582035,.0004694,51.6447,309.4881,203.6966,299.8876,0,U,25544,999,31220,.31985E-4,.1288E-4,0 + """.trimIndent().byteInputStream() + val sat = dataParser.parseCSVStream(csvStream)[0] + assert(sat.name == "ISS (ZARYA)") + assert(sat.catnum == 25544) + assert(sat.meanmo == 15.48582035) + assert(sat.eccn == 0.0004694) + assert(sat.incl == 51.6447) + assert(sat.raan == 309.4881) + assert(sat.argper == 203.6966) + assert(sat.meanan == 299.8876) + assert(sat.bstar == 0.31985E-4) + assert(sat.ndot == 0.1288E-4) + } + + @Test + fun `Given valid CSV stream ndot is parsed for decay detection`() = runTest(testDispatcher) { + val csvStream = """ + OBJECT_NAME,OBJECT_ID,EPOCH,MEAN_MOTION,ECCENTRICITY,INCLINATION,RA_OF_ASC_NODE,ARG_OF_PERICENTER,MEAN_ANOMALY,EPHEMERIS_TYPE,CLASSIFICATION_TYPE,NORAD_CAT_ID,ELEMENT_SET_NO,REV_AT_EPOCH,BSTAR,MEAN_MOTION_DOT,MEAN_MOTION_DDOT + ISS (ZARYA),1998-067A,2021-11-16T12:28:09.322176,15.48582035,.0004694,51.6447,309.4881,203.6966,299.8876,0,U,25544,999,31220,.31985E-4,.1288E-4,0 + """.trimIndent().byteInputStream() + val sat = dataParser.parseCSVStream(csvStream)[0] + // ISS is healthy, should not be decayed even years later + assert(!sat.hasDecayed(System.currentTimeMillis())) + } + + @Test + fun `Given CSV with high drag satellite detects decay`() = runTest(testDispatcher) { + // Simulate a satellite with high drag and old epoch that should have decayed + val csvStream = """ + OBJECT_NAME,OBJECT_ID,EPOCH,MEAN_MOTION,ECCENTRICITY,INCLINATION,RA_OF_ASC_NODE,ARG_OF_PERICENTER,MEAN_ANOMALY,EPHEMERIS_TYPE,CLASSIFICATION_TYPE,NORAD_CAT_ID,ELEMENT_SET_NO,REV_AT_EPOCH,BSTAR,MEAN_MOTION_DOT,MEAN_MOTION_DDOT + DEBRIS,2020-001A,2020-01-15T00:00:00.000000,15.9,.001,51.0,100.0,200.0,300.0,0,U,99999,1,100,.5E-3,.05,0 + """.trimIndent().byteInputStream() + val sat = dataParser.parseCSVStream(csvStream)[0] + // High mean motion (15.9) + high drag (.05) + old epoch → should be decayed by now + assert(sat.hasDecayed(System.currentTimeMillis())) + } + @Test fun `Given invalid CSV stream returns empty list`() = runTest(testDispatcher) { assert(dataParser.parseCSVStream(invalidCSVStream).isEmpty()) @@ -71,10 +114,41 @@ class DataParserTest { @Test fun `Given valid TLE stream returns valid data`() = runTest(testDispatcher) { val parsedList = dataParser.parseTLEStream(validTLEStream) + assert(parsedList.size == 2) assert(parsedList[0].epoch == 21320.51955234) assert(parsedList[1].epoch == 24069.23963816) } + @Test + fun `Given valid TLE stream all orbital fields are parsed correctly`() = runTest(testDispatcher) { + val tleStream = """ + ISS (ZARYA) + 1 25544U 98067A 21320.51955234 .00001288 00000+0 31985-4 0 9990 + 2 25544 51.6447 309.4881 0004694 203.6966 299.8876 15.48582035312205 + """.trimIndent().byteInputStream() + val sat = dataParser.parseTLEStream(tleStream)[0] + assert(sat.name == "ISS (ZARYA)") + assert(sat.catnum == 25544) + assert(sat.meanmo == 15.48582035) + assert(sat.eccn == 0.0004694) + assert(sat.incl == 51.6447) + assert(sat.raan == 309.4881) + assert(sat.argper == 203.6966) + assert(sat.meanan == 299.8876) + assert(sat.ndot == 0.00001288) + } + + @Test + fun `Given valid TLE stream ndot is parsed for decay detection`() = runTest(testDispatcher) { + val tleStream = """ + ISS (ZARYA) + 1 25544U 98067A 21320.51955234 .00001288 00000+0 31985-4 0 9990 + 2 25544 51.6447 309.4881 0004694 203.6966 299.8876 15.48582035312205 + """.trimIndent().byteInputStream() + val sat = dataParser.parseTLEStream(tleStream)[0] + assert(!sat.hasDecayed(System.currentTimeMillis())) + } + @Test fun `Given invalid TLE stream returns empty list`() = runTest(testDispatcher) { assert(dataParser.parseTLEStream(invalidTLEStream).isEmpty()) @@ -85,6 +159,43 @@ class DataParserTest { assert(dataParser.parseJSONStream(validJSONStream)[0].downlinkLow == 136658500L) } + @Test + fun `Given valid JSON stream all radio fields are parsed correctly`() = runTest(testDispatcher) { + val jsonStream = """ + [{"uuid":"UzPz4gcsNBPKPKAFPmer7g","description":"Upper side band (drifting)","alive":true,"type":"Transmitter","uplink_low":145900000,"uplink_high":146000000,"uplink_drift":null,"downlink_low":136658500,"downlink_high":136700000,"downlink_drift":null,"mode":"USB","mode_id":9,"uplink_mode":"FM","invert":true,"baud":null,"sat_id":"SCHX-0895-2361-9925-0309","norad_cat_id":965,"status":"active","updated":"2019-04-18T05:39:53.343316Z","citation":"CITATION NEEDED","service":"Unknown","coordination":"","coordination_url":""}] + """.trimIndent().byteInputStream() + val radio = dataParser.parseJSONStream(jsonStream)[0] + assert(radio.uuid == "UzPz4gcsNBPKPKAFPmer7g") + assert(radio.info == "Upper side band (drifting)") + assert(radio.isAlive) + assert(radio.downlinkLow == 136658500L) + assert(radio.downlinkHigh == 136700000L) + assert(radio.downlinkMode == "USB") + assert(radio.uplinkLow == 145900000L) + assert(radio.uplinkHigh == 146000000L) + assert(radio.uplinkMode == "FM") + assert(radio.isInverted) + assert(radio.catnum == 965) + } + + @Test + fun `Given JSON with null optional fields parses without error`() = runTest(testDispatcher) { + val jsonStream = """ + [{"uuid":"abc123","description":"Beacon","alive":false,"type":"Transmitter","uplink_low":null,"uplink_high":null,"uplink_drift":null,"downlink_low":145800000,"downlink_high":null,"downlink_drift":null,"mode":null,"mode_id":null,"uplink_mode":null,"invert":false,"baud":null,"sat_id":"TEST","norad_cat_id":12345,"status":"active","updated":"2024-01-01T00:00:00Z","citation":"","service":"Unknown","coordination":"","coordination_url":""}] + """.trimIndent().byteInputStream() + val radio = dataParser.parseJSONStream(jsonStream)[0] + assert(radio.uuid == "abc123") + assert(!radio.isAlive) + assert(radio.downlinkLow == 145800000L) + assert(radio.downlinkHigh == null) + assert(radio.downlinkMode == null) + assert(radio.uplinkLow == null) + assert(radio.uplinkHigh == null) + assert(radio.uplinkMode == null) + assert(!radio.isInverted) + assert(radio.catnum == 12345) + } + @Test fun `Given invalid JSON stream returns empty list`() = runTest(testDispatcher) { assert(dataParser.parseJSONStream(invalidJSONStream).isEmpty()) @@ -95,11 +206,37 @@ class DataParserTest { assert(dataParser.parseCSVStream(validCSVStream) == dataParser.parseTLEStream(validTLEStream)) } -// @Test -// fun `Function isLeapYear returns correct data`() = runTest(testDispatcher) { -// val years = listOf(1900, 1984, 1994, 2016, 2022, 2024, 2042, 2048) -// val answers = listOf(false, true, false, true, false, true, false, true) -// val results = years.map { dataParser.isLeapYear(it) } -// assert(results == answers) -// } + @Test + fun `isLeapYear returns correct results`() { + val years = listOf(1900, 1984, 1994, 2000, 2016, 2022, 2024, 2042, 2048, 2100) + val expected = listOf(false, true, false, true, true, false, true, false, true, false) + val results = years.map { dataParser.isLeapYear(it) } + assert(results == expected) + } + + @Test + fun `getDayOfYear returns correct day for January 1st`() { + assert(dataParser.getDayOfYear(2024, 1, 1) == 1) + assert(dataParser.getDayOfYear(2023, 1, 1) == 1) + } + + @Test + fun `getDayOfYear returns correct day for March 1st in leap and non-leap years`() { + // 2024 is leap: Jan(31) + Feb(29) + 1 = 61 + assert(dataParser.getDayOfYear(2024, 3, 1) == 61) + // 2023 is not leap: Jan(31) + Feb(28) + 1 = 60 + assert(dataParser.getDayOfYear(2023, 3, 1) == 60) + } + + @Test + fun `getDayOfYear returns correct day for December 31st`() { + assert(dataParser.getDayOfYear(2024, 12, 31) == 366) // leap year + assert(dataParser.getDayOfYear(2023, 12, 31) == 365) // non-leap year + } + + @Test + fun `getDayOfYear returns correct day for November 16th`() { + // Matches the CSV test data epoch: 2021-11-16 → day 320 + assert(dataParser.getDayOfYear(2021, 11, 16) == 320) + } } diff --git a/core/presentation/src/main/java/com/rtbishop/look4sat/core/presentation/Components.kt b/core/presentation/src/main/java/com/rtbishop/look4sat/core/presentation/Components.kt index a451b1b6..9c3b3796 100644 --- a/core/presentation/src/main/java/com/rtbishop/look4sat/core/presentation/Components.kt +++ b/core/presentation/src/main/java/com/rtbishop/look4sat/core/presentation/Components.kt @@ -48,6 +48,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -151,16 +152,17 @@ fun RowScope.NextPassRow(pass: OrbitalPass, modifier: Modifier = Modifier, isUtc maxLines = 1, overflow = TextOverflow.Ellipsis ) + val elevColor = elevationColor(pass.maxElevation) Icon( painter = painterResource(R.drawable.ic_elevation), contentDescription = null, - tint = MaterialTheme.colorScheme.primary, + tint = elevColor, modifier = Modifier.size(16.dp) ) Spacer(modifier = Modifier.width(4.dp)) Text( text = "${pass.maxElevation}°", - color = MaterialTheme.colorScheme.primary + color = elevColor ) } Row( @@ -379,3 +381,12 @@ fun TopBar( TopBar { startAction(); topInfo(); bottomInfo(); endAction() } } } + +@Composable +fun elevationColor(elevation: Double): Color { + return when { + elevation < 15.0 -> Color(0xFFEF5350) // soft red for low elevation + elevation < 45.0 -> MaterialTheme.colorScheme.primary // accent yellow for normal + else -> Color(0xFF66BB6A) // soft green for high elevation + } +} diff --git a/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesDialog.kt b/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesDialog.kt index a5f5f44c..ac5fc16b 100644 --- a/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesDialog.kt +++ b/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesDialog.kt @@ -43,6 +43,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -54,6 +55,7 @@ import com.rtbishop.look4sat.core.presentation.LocalSpacing import com.rtbishop.look4sat.core.presentation.MainTheme import com.rtbishop.look4sat.core.presentation.R import com.rtbishop.look4sat.core.presentation.SharedDialog +import com.rtbishop.look4sat.core.presentation.elevationColor private val allModes = listOf( "AFSK", "AFSK S-Net", "AFSK SALSAT", "AHRPT", "AM", "APT", "BPSK", "BPSK PMT-A3", @@ -96,7 +98,8 @@ internal fun PassesDialog( value = elevationValueNew.doubleValue, displayValue = "${elevationValueNew.doubleValue.toInt()}°", valueResId = R.drawable.ic_elevation, - valueRange = 0f..60f + valueRange = 0f..60f, + accentColor = elevationColor(elevationValueNew.doubleValue) ) { elevationValueNew.doubleValue = it.toDouble() } SliderRow( title = stringResource(R.string.pass_filter_hours), @@ -117,6 +120,7 @@ private fun SliderRow( valueResId: Int, valueRange: ClosedFloatingPointRange, steps: Int = 0, + accentColor: Color = MaterialTheme.colorScheme.primary, onChange: (Float) -> Unit ) { Column( @@ -137,14 +141,14 @@ private fun SliderRow( Icon( painter = painterResource(id = valueResId), contentDescription = null, - tint = MaterialTheme.colorScheme.primary, + tint = accentColor, modifier = Modifier.size(20.dp) ) Text( text = displayValue, fontSize = 18.sp, fontWeight = FontWeight.Medium, - color = MaterialTheme.colorScheme.primary + color = accentColor ) } Slider(value = value.toFloat(), onValueChange = onChange, valueRange = valueRange, steps = steps) diff --git a/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesScreen.kt b/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesScreen.kt index 40114dfe..508902ad 100644 --- a/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesScreen.kt +++ b/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesScreen.kt @@ -75,6 +75,7 @@ import com.rtbishop.look4sat.core.presentation.ScreenColumn import com.rtbishop.look4sat.core.presentation.SwipeableItem import com.rtbishop.look4sat.core.presentation.TimerRow import com.rtbishop.look4sat.core.presentation.TopBar +import com.rtbishop.look4sat.core.presentation.elevationColor import com.rtbishop.look4sat.core.presentation.infiniteMarquee import com.rtbishop.look4sat.core.presentation.isVerticalLayout import java.text.SimpleDateFormat @@ -331,16 +332,17 @@ private fun PassItem( overflow = TextOverflow.Ellipsis, color = MaterialTheme.colorScheme.onSurface ) + val elevColor = elevationColor(pass.maxElevation) Icon( painter = painterResource(id = R.drawable.ic_elevation), contentDescription = null, - tint = MaterialTheme.colorScheme.primary, + tint = elevColor, modifier = Modifier.size(16.dp) ) Spacer(modifier = Modifier.width(4.dp)) Text( text = "${pass.maxElevation}°", - color = MaterialTheme.colorScheme.primary + color = elevColor ) } Row(