Implemented a basic map data card on MapScreen

This commit is contained in:
Arty Bishop committed 2025-12-27 15:01:47 +00:00
1 parent 880fe10bfd
commit fdbdc513f1
12 files changed
+98 -61

No files matched your search

+3 -4
View File
@@ -14,11 +14,10 @@ android {
compileSdk = 35 compileSdk = 35
defaultConfig { defaultConfig {
applicationId = "com.rtbishop.look4sat" applicationId = "com.rtbishop.look4sat"
minSdk = 26 minSdk = 24
targetSdk = 35 targetSdk = 35
versionCode = 314 versionCode = 400
versionName = "3.1.4" versionName = "4.0.0"
resourceConfigurations.addAll(listOf("en", "ru", "si", "zh-rCN", "anydpi"))
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
buildFeatures { buildFeatures {
-14
View File
@@ -1,14 +0,0 @@
# JSR 305 annotations are for embedding nullability information.
-dontwarn javax.annotation.**
# A resource is loaded with a relative path so the package of this class must be preserved.
-adaptresourcefilenames okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz
# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
-dontwarn org.codehaus.mojo.animal_sniffer.*
# OkHttp platform used only on JVM and when Conscrypt and other security providers are available.
-dontwarn okhttp3.internal.platform.**
-dontwarn org.conscrypt.**
-dontwarn org.bouncycastle.**
-dontwarn org.openjsse.**
@@ -10,18 +10,26 @@ import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
@@ -86,6 +94,7 @@ private fun MapScreen(uiState: State<MapState>) {
val rotateMod = Modifier.rotate(180f) val rotateMod = Modifier.rotate(180f)
val timeString = uiState.value.mapData?.aosTime ?: "00:00:00" val timeString = uiState.value.mapData?.aosTime ?: "00:00:00"
val isTimeAos = uiState.value.mapData?.isTimeAos ?: true val isTimeAos = uiState.value.mapData?.isTimeAos ?: true
val osmInfo = "Wikimedia maps | Map data © OpenStreetMap contributors"
Column(modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { Column(modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
TimerRow { TimerRow {
CardIcon(onClick = selectPrev, iconId = R.drawable.ic_arrow, modifier = rotateMod) CardIcon(onClick = selectPrev, iconId = R.drawable.ic_arrow, modifier = rotateMod)
@@ -93,13 +102,47 @@ private fun MapScreen(uiState: State<MapState>) {
CardIcon(onClick = selectNext, iconId = R.drawable.ic_arrow) CardIcon(onClick = selectNext, iconId = R.drawable.ic_arrow)
} }
NextPassRow(pass = uiState.value.orbitalPass) NextPassRow(pass = uiState.value.orbitalPass)
ElevatedCard(modifier = Modifier.fillMaxSize()) { ElevatedCard(modifier = Modifier.weight(1f)) {
MapView(modifier = Modifier.fillMaxSize(), isLightUi = uiState.value.isLightUi) { mapView -> Box(contentAlignment = Alignment.BottomCenter) {
MapView(isLightUi = uiState.value.isLightUi) { mapView ->
uiState.value.stationPosition?.let { setStationPosition(it, mapView) } uiState.value.stationPosition?.let { setStationPosition(it, mapView) }
uiState.value.positions?.let { setPositions(it, mapView, onItemClick) } uiState.value.positions?.let { setPositions(it, mapView, onItemClick) }
uiState.value.track?.let { setSatelliteTrack(it, mapView) } uiState.value.track?.let { setSatelliteTrack(it, mapView) }
uiState.value.footprint?.let { setFootprint(it, mapView) } uiState.value.footprint?.let { setFootprint(it, mapView) }
} }
Text(text = osmInfo, textAlign = TextAlign.Center, fontSize = 14.sp)
}
}
uiState.value.mapData?.let { mapData ->
ElevatedCard {
MapDataCard(mapData)
}
}
}
}
@Composable
private fun MapDataCard(mapData: MapData) {
Column(modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)) {
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
Text(text = stringResource(R.string.map_period, mapData.period), color = MaterialTheme.colorScheme.primary)
Text(text = stringResource(R.string.map_phase, mapData.phase), color = MaterialTheme.colorScheme.primary)
}
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
Text(text = stringResource(R.string.map_azimuth, mapData.azimuth))
Text(text = stringResource(R.string.map_elevation, mapData.elevation))
}
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
Text(text = stringResource(R.string.map_altitude, mapData.altitude), color = MaterialTheme.colorScheme.primary)
Text(text = stringResource(R.string.map_distance, mapData.range), color = MaterialTheme.colorScheme.primary)
}
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
Text(text = stringResource(R.string.map_latitude, mapData.osmPos.latitude))
Text(text = stringResource(R.string.map_longitude, mapData.osmPos.longitude))
}
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
Text(text = stringResource(R.string.map_qth, mapData.qthLoc), color = MaterialTheme.colorScheme.primary)
Text(text = stringResource(R.string.map_velocity, mapData.velocity), color = MaterialTheme.colorScheme.primary)
} }
} }
} }
@@ -195,30 +238,6 @@ private fun setFootprint(orbitalPos: OrbitalPos, mapView: MapView) {
} }
} }
//private fun handleMapData(mapData: MapData) {
// binding.apply {
// mapTimer.text = mapData.aosTime
// mapDataPeriod.text = String.format(getString(R.string.map_period, mapData.period))
// mapDataPhase.text = String.format(getString(R.string.map_phase), mapData.phase)
// mapAzimuth.text = String.format(getString(R.string.map_azimuth), mapData.azimuth)
// mapElevation.text = String.format(getString(R.string.map_elevation), mapData.elevation)
// mapDataAlt.text = String.format(getString(R.string.map_altitude), mapData.altitude)
// mapDataDst.text = String.format(getString(R.string.map_distance), mapData.range)
// mapDataLat.text =
// String.format(getString(R.string.map_latitude), mapData.osmPos.lat)
// mapDataLon.text =
// String.format(getString(R.string.map_longitude), mapData.osmPos.lon)
// mapDataQth.text = String.format(getString(R.string.map_qth), mapData.qthLoc)
// mapDataVel.text = String.format(getString(R.string.map_velocity), mapData.velocity)
// if (mapData.eclipsed) {
// mapDataVisibility.text = getString(R.string.map_eclipsed)
// } else {
// mapDataVisibility.text = getString(R.string.map_visible)
// }
// }
// binding.mapView.invalidate()
//}
@Composable @Composable
private fun MapView( private fun MapView(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@@ -237,9 +256,10 @@ private fun rememberMapViewWithLifecycle(isLightTheme: Boolean): MapView {
setMultiTouchControls(true) setMultiTouchControls(true)
setUseDataConnection(false) setUseDataConnection(false)
setTileSource(tileSource) setTileSource(tileSource)
minZoomLevel = getMinZoom(resources.displayMetrics.heightPixels) + 0.25 minZoomLevel = getMinZoom(resources.displayMetrics.heightPixels)
maxZoomLevel = 8.0 maxZoomLevel = 7.0
controller.setZoom(minZoomLevel + 1) controller.setCenter(GeoPoint(48.8575, 6.3514))
controller.setZoom(minZoomLevel + 2)
zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER) zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER)
overlayManager.tilesOverlay.loadingBackgroundColor = Color.TRANSPARENT overlayManager.tilesOverlay.loadingBackgroundColor = Color.TRANSPARENT
overlayManager.tilesOverlay.loadingLineColor = Color.TRANSPARENT overlayManager.tilesOverlay.loadingLineColor = Color.TRANSPARENT
@@ -1,8 +1,15 @@
package com.rtbishop.look4sat.presentation.radar package com.rtbishop.look4sat.presentation.radar
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.MarqueeSpacing import androidx.compose.foundation.MarqueeSpacing
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -23,6 +30,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
@@ -80,7 +88,7 @@ private fun RadarScreen(uiState: RadarState, navigateBack: () -> Unit) {
CardIcon(onClick = addToCalendar, iconId = R.drawable.ic_calendar) CardIcon(onClick = addToCalendar, iconId = R.drawable.ic_calendar)
} }
NextPassRow(pass = uiState.currentPass ?: getDefaultPass()) NextPassRow(pass = uiState.currentPass ?: getDefaultPass())
Box(contentAlignment = Alignment.Center, modifier = Modifier.aspectRatio(1f)) { Box(contentAlignment = Alignment.BottomCenter, modifier = Modifier.aspectRatio(1f)) {
uiState.orbitalPos?.let { position -> uiState.orbitalPos?.let { position ->
ElevatedCard { ElevatedCard {
RadarViewCompose( RadarViewCompose(
@@ -127,6 +135,30 @@ private fun RadarScreen(uiState: RadarState, navigateBack: () -> Unit) {
) )
} }
} }
if (uiState.orbitalPos.eclipsed) {
val infiniteTransition = rememberInfiniteTransition()
val textAlpha = infiniteTransition.animateFloat(
initialValue = 1.0f,
targetValue = 0.0f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 500,
delayMillis = 25,
easing = LinearEasing
),
repeatMode = RepeatMode.Reverse
))
Text(
text = "Eclipsed!",
fontSize = 18.sp,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier
.alpha(textAlpha.value)
.border(2.dp, MaterialTheme.colorScheme.primary, MaterialTheme.shapes.medium)
.padding(16.dp)
)
}
} }
} }
ElevatedCard(modifier = Modifier.fillMaxSize()) { ElevatedCard(modifier = Modifier.fillMaxSize()) {
@@ -53,7 +53,7 @@ fun RadarViewCompose(
val radarColor = MaterialTheme.colorScheme.secondary val radarColor = MaterialTheme.colorScheme.secondary
val trackColor = MaterialTheme.colorScheme.primary val trackColor = MaterialTheme.colorScheme.primary
val aimColor = MaterialTheme.colorScheme.error val aimColor = MaterialTheme.colorScheme.error
val strokeWidth = 4f val strokeWidth = 6f
val animTransition = rememberInfiniteTransition(label = "animScale") val animTransition = rememberInfiniteTransition(label = "animScale")
val animSpec = infiniteRepeatable<Float>(tween(1000)) val animSpec = infiniteRepeatable<Float>(tween(1000))
val animScale = animTransition.animateFloat(16f, 64f, animSpec, label = "animScale") val animScale = animTransition.animateFloat(16f, 64f, animSpec, label = "animScale")
@@ -167,7 +167,7 @@ private fun DrawScope.drawInfo(radius: Float, color: Color, measurer: TextMeasur
} }
private fun DrawScope.drawTrack(path: Path, effect: PathEffect, color: Color, effectColor: Color) { private fun DrawScope.drawTrack(path: Path, effect: PathEffect, color: Color, effectColor: Color) {
drawPath(path, color, style = Stroke(4f)) drawPath(path, color, style = Stroke(6f))
drawPath(path, effectColor, style = Stroke(pathEffect = effect)) drawPath(path, effectColor, style = Stroke(pathEffect = effect))
} }
@@ -213,7 +213,7 @@ private fun createTrackPath(positions: List<OrbitalPos>, radius: Float): Path {
private fun createTrackEffect(trackPath: Path): PathEffect { private fun createTrackEffect(trackPath: Path): PathEffect {
val shape = Path() val shape = Path()
val shapeRadius = 16f val shapeRadius = 24f
val angle = 120.0.toRadians() val angle = 120.0.toRadians()
shape.moveTo((shapeRadius * cos(angle)).toFloat(), (shapeRadius * sin(angle)).toFloat()) shape.moveTo((shapeRadius * cos(angle)).toFloat(), (shapeRadius * sin(angle)).toFloat())
for (i in 1 until 3) { for (i in 1 until 3) {
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

+1 -1
View File
@@ -12,7 +12,7 @@ android {
namespace = "com.rtbishop.look4sat.data" namespace = "com.rtbishop.look4sat.data"
compileSdk = 35 compileSdk = 35
defaultConfig { defaultConfig {
minSdk = 26 minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
} }
+6 -6
View File
@@ -1,5 +1,5 @@
[versions] [versions]
android-gradle-plugin = "8.7.3" android-gradle-plugin = "8.8.0"
google-ksp = "2.0.21-1.0.26" google-ksp = "2.0.21-1.0.26"
kotlin = "2.0.21" kotlin = "2.0.21"
@@ -7,18 +7,18 @@ androidx-core-ktx = "1.15.0"
androidx-core-splashscreen = "1.0.1" androidx-core-splashscreen = "1.0.1"
androidx-room = "2.6.1" androidx-room = "2.6.1"
compose-bom = "2024.11.00" compose-bom = "2025.01.00"
compose-activity = "1.9.3" compose-activity = "1.10.0"
compose-lifecycle = "2.8.7" compose-lifecycle = "2.8.7"
compose-navigation = "2.8.4" compose-navigation = "2.8.5"
other-coroutines = "1.9.0" other-coroutines = "1.10.1"
other-json = "20240303" other-json = "20240303"
other-okhttp = "4.12.0" other-okhttp = "4.12.0"
other-osmdroid = "6.1.20" other-osmdroid = "6.1.20"
test-junit4 = "4.13.2" test-junit4 = "4.13.2"
test-mockk = "1.13.13" test-mockk = "1.13.14"
androidTest-junit = "1.2.1" androidTest-junit = "1.2.1"
androidTest-espresso = "3.6.1" androidTest-espresso = "3.6.1"
+2 -2
View File
@@ -1,6 +1,6 @@
#Fri Oct 25 23:59:14 BST 2024 #Sat Jan 04 21:11:19 GMT 2025
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists