Compare commits

..
6 Commits
27 changed files with 501 additions and 177 deletions

No files matched your search

+4 -2
View File
@@ -17,12 +17,14 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '21'
java-version: '17'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
@@ -67,7 +69,7 @@ jobs:
serviceAccountJsonPlainText: ${{secrets.SERVICE_ACCOUNT_JSON}}
packageName: com.rtbishop.look4sat
releaseFiles: app/build/outputs/apk/release/look4sat.aab
track: beta
track: production
whatsNewDirectory: fastlane/metadata/android/en-US/whatsnew
- name: Create Release
+3 -3
View File
@@ -4,7 +4,7 @@ plugins {
}
kotlin {
jvmToolchain(21)
jvmToolchain(17)
}
android {
@@ -13,8 +13,8 @@ android {
defaultConfig {
applicationId = "com.rtbishop.look4sat"
minSdk = 24
versionCode = 400
versionName = "4.0.0"
versionCode = 402
versionName = "4.0.2"
}
buildFeatures {
compose = true
@@ -17,6 +17,9 @@
*/
package com.rtbishop.look4sat.presentation
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -76,7 +79,12 @@ fun MainScreen(navController: NavHostController = rememberNavController()) {
else -> NavigationSuiteType.ShortNavigationBarMedium
}
) {
NavHost(navController = navController, startDestination = startDestination) {
NavHost(
navController = navController,
startDestination = startDestination,
enterTransition = { fadeIn(animationSpec = tween(350)) },
exitTransition = { fadeOut(animationSpec = tween(350)) }
) {
satellitesDestination { navController.navigateUp() }
passesDestination { catNum: Int, aosTime: Long ->
val radarRoute = "${Screen.Radar.route}?catNum=${catNum}&aosTime=${aosTime}"
@@ -23,6 +23,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
@@ -210,8 +211,9 @@ fun CardButton(onClick: () -> Unit, text: String, modifier: Modifier = Modifier)
onClick = { onClick() }, colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.onSurfaceVariant
), shape = MaterialTheme.shapes.small, modifier = modifier
) { Text(text = text, fontSize = 17.sp) }
), shape = MaterialTheme.shapes.small, modifier = modifier,
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 8.dp)
) { Text(text = text, fontSize = 16.sp, textAlign = TextAlign.Center) }
}
@Composable
@@ -253,7 +255,7 @@ fun EmptyListCard(message: String) {
modifier = Modifier.padding(32.dp)
) {
Text(text = """¯\_(ツ)_/¯""", fontSize = 32.sp)
Text(text = stringResource(R.string.empty_list_message), fontSize = 21.sp)
Text(text = stringResource(R.string.empty_list_message), fontSize = 21.sp, textAlign = TextAlign.Center)
Text(text = message, fontSize = 18.sp, textAlign = TextAlign.Center)
}
}
@@ -278,7 +280,7 @@ fun SharedDialog(
) {
Text(
text = title,
fontSize = 18.sp,
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(start = padding, top = padding, end = padding)
@@ -64,6 +64,7 @@ import com.rtbishop.look4sat.domain.model.SatRadio
import com.rtbishop.look4sat.domain.utility.toDegrees
import com.rtbishop.look4sat.presentation.MainTheme
import com.rtbishop.look4sat.presentation.Screen
import com.rtbishop.look4sat.presentation.common.EmptyListCard
import com.rtbishop.look4sat.presentation.common.IconCard
import com.rtbishop.look4sat.presentation.common.NextPassRow
import com.rtbishop.look4sat.presentation.common.TimerRow
@@ -115,49 +116,56 @@ private fun RadarScreen(uiState: RadarState, navigateUp: () -> Unit) {
if(isVerticalLayout()) {
ElevatedCard(modifier = Modifier.weight(1f)) {
Box(contentAlignment = Alignment.Center) {
RadarViewCompose(
item = uiState.orbitalPos,
items = uiState.satTrack,
azimElev = uiState.orientationValues,
shouldShowSweep = uiState.shouldShowSweep,
shouldUseCompass = uiState.shouldUseCompass,
modifier = Modifier.align(Alignment.Center)
)
Column(
verticalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 6.dp, vertical = 4.dp)
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth()
) {
RadarTextTop(
uiState.orbitalPos?.azimuth ?: 0.0,
stringResource(R.string.radar_az_text),
true
)
RadarTextTop(
uiState.orbitalPos?.elevation ?: 0.0,
stringResource(R.string.radar_el_text),
false
)
if (uiState.orbitalPos == null) {
ElevatedCard(modifier = Modifier.fillMaxSize()) {
EmptyListCard(message = "")
}
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth()
}
uiState.orbitalPos?.let { position ->
RadarViewCompose(
item = position,
items = uiState.satTrack,
azimElev = uiState.orientationValues,
shouldShowSweep = uiState.shouldShowSweep,
shouldUseCompass = uiState.shouldUseCompass,
modifier = Modifier.align(Alignment.Center)
)
Column(
verticalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 6.dp, vertical = 4.dp)
) {
RadarTextBottom(
uiState.orbitalPos?.altitude ?: 0.0,
stringResource(R.string.radar_alt_text),
true
)
RadarTextBottom(
uiState.orbitalPos?.distance ?: 0.0,
stringResource(R.string.radar_dist_text),
false
)
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth()
) {
RadarTextTop(
position.azimuth,
stringResource(R.string.radar_az_text),
true
)
RadarTextTop(
position.elevation,
stringResource(R.string.radar_el_text),
false
)
}
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth()
) {
RadarTextBottom(
position.altitude,
stringResource(R.string.radar_alt_text),
true
)
RadarTextBottom(
position.distance,
stringResource(R.string.radar_dist_text),
false
)
}
}
}
}
@@ -171,7 +179,11 @@ private fun RadarScreen(uiState: RadarState, navigateUp: () -> Unit) {
modifier = Modifier.padding(32.dp)
) {
Text(text = """¯\_(ツ)_/¯""", fontSize = 32.sp)
Text(text = stringResource(R.string.empty_list_message), fontSize = 21.sp)
Text(
text = stringResource(R.string.empty_list_message),
fontSize = 21.sp,
textAlign = TextAlign.Center
)
Text(
text = stringResource(R.string.radar_no_data),
fontSize = 18.sp,
@@ -192,49 +204,56 @@ private fun RadarScreen(uiState: RadarState, navigateUp: () -> Unit) {
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
ElevatedCard(modifier = Modifier.weight(1f)) {
Box(contentAlignment = Alignment.Center) {
RadarViewCompose(
item = uiState.orbitalPos,
items = uiState.satTrack,
azimElev = uiState.orientationValues,
shouldShowSweep = uiState.shouldShowSweep,
shouldUseCompass = false,
modifier = Modifier.align(Alignment.Center)
)
Column(
verticalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 6.dp, vertical = 4.dp)
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth()
) {
RadarTextTop(
uiState.orbitalPos?.azimuth ?: 0.0,
stringResource(R.string.radar_az_text),
true
)
RadarTextTop(
uiState.orbitalPos?.elevation ?: 0.0,
stringResource(R.string.radar_el_text),
false
)
if (uiState.orbitalPos == null) {
ElevatedCard(modifier = Modifier.fillMaxSize()) {
EmptyListCard(message = "")
}
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth()
}
uiState.orbitalPos?.let { position ->
RadarViewCompose(
item = position,
items = uiState.satTrack,
azimElev = uiState.orientationValues,
shouldShowSweep = uiState.shouldShowSweep,
shouldUseCompass = false,
modifier = Modifier.align(Alignment.Center)
)
Column(
verticalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 6.dp, vertical = 4.dp)
) {
RadarTextBottom(
uiState.orbitalPos?.altitude ?: 0.0,
stringResource(R.string.radar_alt_text),
true
)
RadarTextBottom(
uiState.orbitalPos?.distance ?: 0.0,
stringResource(R.string.radar_dist_text),
false
)
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth()
) {
RadarTextTop(
position.azimuth,
stringResource(R.string.radar_az_text),
true
)
RadarTextTop(
position.elevation,
stringResource(R.string.radar_el_text),
false
)
}
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth()
) {
RadarTextBottom(
position.altitude,
stringResource(R.string.radar_alt_text),
true
)
RadarTextBottom(
position.distance,
stringResource(R.string.radar_dist_text),
false
)
}
}
}
}
@@ -248,7 +267,11 @@ private fun RadarScreen(uiState: RadarState, navigateUp: () -> Unit) {
modifier = Modifier.padding(32.dp)
) {
Text(text = """¯\_(ツ)_/¯""", fontSize = 32.sp)
Text(text = stringResource(R.string.empty_list_message), fontSize = 21.sp)
Text(
text = stringResource(R.string.empty_list_message),
fontSize = 21.sp,
textAlign = TextAlign.Center
)
Text(
text = stringResource(R.string.radar_no_data),
fontSize = 18.sp,
@@ -17,7 +17,6 @@
*/
package com.rtbishop.look4sat.presentation.radar
import android.media.SoundPool
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
@@ -27,7 +26,6 @@ import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
@@ -44,8 +42,6 @@ import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.rotate
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.TextMeasurer
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.drawText
@@ -59,15 +55,15 @@ import kotlin.math.sin
@Composable
fun RadarViewCompose(
item: OrbitalPos?,
item: OrbitalPos,
items: List<OrbitalPos>,
azimElev: Pair<Float, Float>,
shouldShowSweep: Boolean,
shouldUseCompass: Boolean,
modifier: Modifier = Modifier
) {
val context = LocalContext.current
val view = LocalView.current
// val context = LocalContext.current
// val view = LocalView.current
val radarColor = MaterialTheme.colorScheme.secondary
val trackColor = MaterialTheme.colorScheme.primary
val aimColor = MaterialTheme.colorScheme.error
@@ -75,15 +71,15 @@ fun RadarViewCompose(
val animTransition = rememberInfiniteTransition(label = "animScale")
val animSpec = infiniteRepeatable<Float>(tween(1000))
val animScale = animTransition.animateFloat(16f, 64f, animSpec, label = "animScale")
val aimThreshold = 0.05f
// val aimThreshold = 0.05f
val measurer = rememberTextMeasurer()
val sweepDegrees = remember { mutableFloatStateOf(0f) }
val trackCreated = remember { mutableStateOf(false) }
val trackPath = remember { mutableStateOf(Path()) }
val trackEffect = remember { mutableStateOf(PathEffect.cornerPathEffect(0f)) }
val soundPool = remember { mutableStateOf<SoundPool?>(null) }
val beepSoundId = remember { mutableIntStateOf(0) }
val aimTargetDifference = remember { mutableFloatStateOf(0f) }
// val soundPool = remember { mutableStateOf<SoundPool?>(null) }
// val beepSoundId = remember { mutableIntStateOf(0) }
// val aimTargetDifference = remember { mutableFloatStateOf(0f) }
// LaunchedEffect(item.azimuth, item.elevation, azimElev.first, azimElev.second) {
// val aimAzimuthRadians = azimElev.first.toDouble().toRadians()
@@ -95,13 +91,10 @@ fun RadarViewCompose(
// val satX = sph2CartX(item.azimuth, item.elevation, radius)
// val satY = sph2CartY(item.azimuth, item.elevation, radius)
// aimTargetDifference.floatValue = sqrt((satX - aimX).pow(2) + (satY - aimY).pow(2))
//
//
// val minPlaybackRate = 0.5f
// val maxPlaybackRate = 2.0f
// val playbackRate =
// maxPlaybackRate - (aimTargetDifference.floatValue / (maxPlaybackRate - minPlaybackRate))
//
// soundPool.value?.setRate(beepSoundId.intValue, playbackRate)
// }
//
@@ -127,20 +120,16 @@ fun RadarViewCompose(
// .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
// .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
// .build()
//
// soundPool.value = SoundPool.Builder()
// .setMaxStreams(1)
// .setAudioAttributes(audioAttributes)
// .build()
//
// beepSoundId.intValue = soundPool.value?.load(context, R.raw.beep, 1) ?: 0
//
// soundPool.value?.setOnLoadCompleteListener { soundPool, _, status ->
// if (status == 0) {
// soundPool.play(beepSoundId.intValue, 0.5f, 0.5f, 0, -1, 1f)
// }
// }
//
// onDispose {
// soundPool.value?.release()
// }
@@ -161,7 +150,7 @@ fun RadarViewCompose(
drawInfo(radius, trackColor, measurer, 3)
translate(center.x, center.y) {
drawTrack(trackPath.value, trackEffect.value, aimColor, trackColor)
if (item != null && item.elevation > 0) {
if (item.elevation > 0) {
drawPosition(item, radius, animScale.value, trackColor)
}
if (shouldUseCompass) {
@@ -27,6 +27,19 @@ import androidx.compose.ui.tooling.preview.Preview
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.presentation.MainTheme
import com.rtbishop.look4sat.presentation.common.SharedDialog
import com.rtbishop.look4sat.presentation.common.CardButton
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Switch
import androidx.compose.ui.unit.dp
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.rtbishop.look4sat.presentation.LocalSpacing
@Preview(showBackground = true)
@Composable
@@ -41,10 +54,16 @@ fun PositionDialog(lat: Double, lon: Double, dismiss: () -> Unit, save: (Double,
val titleText = stringResource(id = R.string.prefs_station_title)
val onAccept = { saveValues(latValue.value, lonValue.value, save).also { dismiss() } }
SharedDialog(title = titleText, onCancel = dismiss, onAccept = onAccept) {
Text(text = stringResource(id = R.string.prefs_station_lat_text))
OutlinedTextField(value = latValue.value, onValueChange = { latValue.value = it })
Text(text = stringResource(id = R.string.prefs_station_lon_text))
OutlinedTextField(value = lonValue.value, onValueChange = { lonValue.value = it })
OutlinedTextField(
value = latValue.value,
onValueChange = { latValue.value = it },
label = { Text(text = stringResource(id = R.string.prefs_station_lat_text)) }
)
OutlinedTextField(
value = lonValue.value,
onValueChange = { lonValue.value = it },
label = { Text(text = stringResource(id = R.string.prefs_station_lon_text)) }
)
}
}
@@ -67,7 +86,116 @@ fun LocatorDialog(qthLocator: String, dismiss: () -> Unit, save: (String) -> Uni
val locator = rememberSaveable { mutableStateOf(qthLocator) }
val onAccept = { save(locator.value).also { dismiss() } }
SharedDialog(title = stringResource(R.string.prefs_locator_title), onCancel = dismiss, onAccept = onAccept) {
Text(text = stringResource(id = R.string.prefs_locator_text))
OutlinedTextField(value = locator.value, onValueChange = { locator.value = it })
OutlinedTextField(
value = locator.value,
onValueChange = { locator.value = it },
label = { Text(text = stringResource(id = R.string.prefs_locator_text)) }
)
}
}
@Preview(showBackground = true)
@Composable
private fun TransceiversDialogPreview() {
MainTheme {
DataSourcesDialog(
useCustomTle = true,
useCustomTransceivers = true,
tleUrl = "https://example.com/tle.txt",
transceiversUrl = "https://example.com/tx.json",
onImportTle = {},
onImportTransceivers = {},
onDismiss = {},
onSave = { _, _, _, _ -> }
)
}
}
@Composable
fun DataSourcesDialog(
useCustomTle: Boolean,
useCustomTransceivers: Boolean,
tleUrl: String,
transceiversUrl: String,
onImportTle: () -> Unit,
onImportTransceivers: () -> Unit,
onDismiss: () -> Unit,
onSave: (Boolean, Boolean, String, String) -> Unit
) {
val padding = LocalSpacing.current.large
val isEnabledCustomTle = rememberSaveable { mutableStateOf(useCustomTle) }
val isEnabledCustomTransceivers = rememberSaveable { mutableStateOf(useCustomTransceivers) }
val urlTle = rememberSaveable { mutableStateOf(tleUrl) }
val urlTransceivers = rememberSaveable { mutableStateOf(transceiversUrl) }
val onAccept = {
onSave(isEnabledCustomTle.value, isEnabledCustomTransceivers.value, urlTle.value, urlTransceivers.value)
onDismiss()
}
val onCancel = { onDismiss() }
SharedDialog(
title = stringResource(id = R.string.prefs_data_sources_title),
onCancel = onCancel,
onAccept = onAccept,
) {
Column(modifier = Modifier.padding(horizontal = padding)) {
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
CardButton(
onClick = {
onImportTle()
onDismiss()
},
text = "TLE (3LE)\nR4UAB (.txt)",
modifier = Modifier.weight(1f)
)
CardButton(
onClick = {
onImportTransceivers()
onDismiss()
},
text = "Transceivers\nSatNOGS (.json)",
modifier = Modifier.weight(1f)
)
}
Spacer(modifier = Modifier.height(6.dp))
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(text = stringResource(id = R.string.prefs_data_sources_tle_switch))
Switch(
checked = isEnabledCustomTle.value,
onCheckedChange = { isEnabledCustomTle.value = it }
)
}
OutlinedTextField(
value = urlTle.value,
onValueChange = { urlTle.value = it },
label = { Text(text = stringResource(id = R.string.prefs_data_sources_url_title)) },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
enabled = isEnabledCustomTle.value,
)
Spacer(modifier = Modifier.height(6.dp))
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(text = stringResource(id = R.string.prefs_data_sources_transceivers_switch))
Switch(
checked = isEnabledCustomTransceivers.value,
onCheckedChange = { isEnabledCustomTransceivers.value = it }
)
}
OutlinedTextField(
value = urlTransceivers.value,
onValueChange = { urlTransceivers.value = it },
label = { Text(text = stringResource(id = R.string.prefs_data_sources_url_title)) },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
enabled = isEnabledCustomTransceivers.value,
)
}
}
}
@@ -112,8 +112,11 @@ private fun SettingsScreen(uiState: SettingsState) {
}
}
val contentContract = ActivityResultContracts.GetContent()
val contentRequest = rememberLauncherForActivityResult(contentContract) { uri ->
uri?.let { uiState.sendAction(SettingsAction.UpdateFromFile(uri.toString())) }
val contentRequestForTle = rememberLauncherForActivityResult(contentContract) { uri ->
uri?.let { uiState.sendAction(SettingsAction.UpdateTLEFromFile(uri.toString())) }
}
val contentRequestForTransceivers = rememberLauncherForActivityResult(contentContract) { uri ->
uri?.let { uiState.sendAction(SettingsAction.UpdateTransceiversFromFile(uri.toString())) }
}
// Position settings
@@ -137,10 +140,39 @@ private fun SettingsScreen(uiState: SettingsState) {
LocatorDialog(posSettings.stationPos.qthLocator, showLocDialog, setQthPos)
}
// Data sources dialog
val dataSourcesDialogState = rememberSaveable { mutableStateOf(false) }
val showDataSourcesDialog = { dataSourcesDialogState.value = true }
val dismissDataSourcesDialog = { dataSourcesDialogState.value = false }
if (dataSourcesDialogState.value) {
DataSourcesDialog(
useCustomTle = uiState.dataSourcesSettings.useCustomTLE,
useCustomTransceivers = uiState.dataSourcesSettings.useCustomTransceivers,
tleUrl = uiState.dataSourcesSettings.tleUrl,
transceiversUrl = uiState.dataSourcesSettings.transceiversUrl,
onImportTle = { contentRequestForTle.launch("*/*") },
onImportTransceivers = { contentRequestForTransceivers.launch("*/*") },
onDismiss = dismissDataSourcesDialog,
onSave = {
useCustomTle, useCustomTransceivers, tleUrl, transceiversUrl ->
if (!useCustomTle || tleUrl.isNotBlank()) {
uiState.sendDataSourcesAction(DataSourcesAction.SetUseCustomTle(useCustomTle))
uiState.sendDataSourcesAction(DataSourcesAction.SetTleUrl(tleUrl))
}
if (!useCustomTransceivers || transceiversUrl.isNotBlank()) {
uiState.sendDataSourcesAction(DataSourcesAction.SetUseCustomTransceivers(useCustomTransceivers))
uiState.sendDataSourcesAction(DataSourcesAction.SetTransceiversUrl(transceiversUrl))
}
if (useCustomTle || useCustomTransceivers) {
uiState.sendAction(SettingsAction.UpdateFromWeb)
}
}
)
}
// Data settings
val dataSettings = uiState.dataSettings
val updateFromWeb: () -> Unit = { uiState.sendAction(SettingsAction.UpdateFromWeb) }
val updateFromFile = { contentRequest.launch("*/*") }
val clearAllData: () -> Unit = { uiState.sendAction(SettingsAction.ClearAllData) }
// RC settings
@@ -209,7 +241,7 @@ private fun SettingsScreen(uiState: SettingsState) {
item {
LocationCard(posSettings, setGpsPos, showPosDialog, showLocDialog, dismissPos, uiState.sendSystemAction)
}
item { DataCard(dataSettings, updateFromWeb, updateFromFile, clearAllData) }
item { DataCard(dataSettings, updateFromWeb, clearAllData, showDataSourcesDialog) }
item { NetworkOutputCard(rcSettings, setRotatorState, setRotatorAddress, setRotatorPort) }
item { BluetoothOutputCard(rcSettings, setBluetoothState, setBluetoothAddress, setBluetoothFormat) }
item { OtherCard(otherSettings, toggleUtc, toggleUpdate, toggleSweep, toggleSensor) }
@@ -290,15 +322,15 @@ private fun LocationCard(
@Composable
private fun DataCardPreview() = MainTheme {
val settings = DataSettings(true, 5000, 2500, 0L)
DataCard(settings = settings, {}, {}) {}
DataCard(settings = settings, {}, {}, {})
}
@Composable
private fun DataCard(
settings: DataSettings,
updateFromWeb: () -> Unit,
updateFromFile: () -> Unit,
clearAllData: () -> Unit
clearAllData: () -> Unit,
showDataSourcesDialog: () -> Unit
) {
ElevatedCard(modifier = Modifier.fillMaxWidth()) {
Column(
@@ -320,21 +352,19 @@ private fun DataCard(
Text(text = stringResource(R.string.prefs_data_radios, settings.radiosTotal))
}
Spacer(modifier = Modifier.height(1.dp))
Row(horizontalArrangement = Arrangement.SpaceEvenly) {
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
CardButton(
onClick = { updateFromWeb() },
onClick = updateFromWeb,
text = stringResource(id = R.string.prefs_data_update),
modifier = Modifier.weight(1f)
)
Spacer(modifier = Modifier.width(6.dp))
CardButton(
onClick = { updateFromFile() },
onClick = showDataSourcesDialog,
text = stringResource(id = R.string.prefs_data_import),
modifier = Modifier.weight(1f)
)
Spacer(modifier = Modifier.width(6.dp))
CardButton(
onClick = { clearAllData() },
onClick = clearAllData,
text = stringResource(id = R.string.prefs_data_clear),
modifier = Modifier.weight(1f)
)
@@ -19,6 +19,7 @@ package com.rtbishop.look4sat.presentation.settings
import com.rtbishop.look4sat.domain.model.OtherSettings
import com.rtbishop.look4sat.domain.model.RCSettings
import com.rtbishop.look4sat.domain.model.DataSourcesSettings
import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.domain.predict.OrbitalPass
@@ -39,9 +40,11 @@ data class SettingsState(
val dataSettings: DataSettings,
val otherSettings: OtherSettings,
val rcSettings: RCSettings,
val dataSourcesSettings: DataSourcesSettings,
val sendAction: (SettingsAction) -> Unit,
val sendRCAction: (RCAction) -> Unit,
val sendSystemAction: (SystemAction) -> Unit
val sendSystemAction: (SystemAction) -> Unit,
val sendDataSourcesAction: (DataSourcesAction) -> Unit
)
sealed class SettingsAction {
@@ -50,7 +53,8 @@ sealed class SettingsAction {
data class SetQthPosition(val locator: String) : SettingsAction()
data object DismissPosMessages : SettingsAction()
data object UpdateFromWeb : SettingsAction()
data class UpdateFromFile(val uri: String) : SettingsAction()
data class UpdateTLEFromFile(val uri: String) : SettingsAction()
data class UpdateTransceiversFromFile(val uri: String) : SettingsAction()
data object ClearAllData : SettingsAction()
data class ToggleUtc(val value: Boolean) : SettingsAction()
data class ToggleUpdate(val value: Boolean) : SettingsAction()
@@ -72,3 +76,10 @@ sealed class RCAction {
data class SetBluetoothName(val value: String) : RCAction()
data class SetBluetoothAddress(val value: String) : RCAction()
}
sealed class DataSourcesAction {
data class SetUseCustomTle(val value: Boolean) : DataSourcesAction()
data class SetUseCustomTransceivers(val value: Boolean) : DataSourcesAction()
data class SetTleUrl(val value: String) : DataSourcesAction()
data class SetTransceiversUrl(val value: String) : DataSourcesAction()
}
@@ -24,6 +24,7 @@ import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.rtbishop.look4sat.MainApplication
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.domain.model.DataSourcesSettings
import com.rtbishop.look4sat.domain.predict.OrbitalPass
import com.rtbishop.look4sat.domain.repository.IDatabaseRepo
import com.rtbishop.look4sat.domain.repository.ISatelliteRepo
@@ -51,6 +52,12 @@ class SettingsViewModel(
private val defaultPosSettings = PositionSettings(false, settingsRepo.stationPosition.value, 0)
private val defaultDataSettings = DataSettings(false, 0, 0, 0L)
private val defaultDataSourcesSettings = DataSourcesSettings(
useCustomTLE = false,
useCustomTransceivers = false,
tleUrl = settingsRepo.dataSourcesSettings.value.tleUrl,
transceiversUrl = settingsRepo.dataSourcesSettings.value.transceiversUrl
)
private val _uiState = MutableStateFlow(
SettingsState(
nextTime = "00:00:00",
@@ -61,9 +68,11 @@ class SettingsViewModel(
dataSettings = defaultDataSettings,
otherSettings = settingsRepo.otherSettings.value,
rcSettings = settingsRepo.rcSettings.value,
dataSourcesSettings = defaultDataSourcesSettings,
sendAction = ::handleAction,
sendRCAction = ::handleAction,
sendSystemAction = ::handleAction
sendSystemAction = ::handleAction,
sendDataSourcesAction = ::handleAction
)
)
private var processing: Job? = null
@@ -113,6 +122,11 @@ class SettingsViewModel(
_uiState.update { it.copy(otherSettings = settings) }
}
}
viewModelScope.launch {
settingsRepo.dataSourcesSettings.collect { settings ->
_uiState.update { it.copy(dataSourcesSettings = settings) }
}
}
}
private fun handleAction(action: SettingsAction) {
@@ -122,7 +136,8 @@ class SettingsViewModel(
is SettingsAction.SetQthPosition -> setQthPosition(action.locator)
SettingsAction.DismissPosMessages -> dismissPosMessage()
SettingsAction.UpdateFromWeb -> updateFromWeb()
is SettingsAction.UpdateFromFile -> updateFromFile(action.uri)
is SettingsAction.UpdateTLEFromFile -> updateTLEFromFile(action.uri)
is SettingsAction.UpdateTransceiversFromFile -> updateTransceiversFromFile(action.uri)
SettingsAction.ClearAllData -> clearAllData()
is SettingsAction.ToggleUtc -> settingsRepo.setStateOfUtc(action.value)
is SettingsAction.ToggleUpdate -> settingsRepo.setStateOfAutoUpdate(action.value)
@@ -150,6 +165,15 @@ class SettingsViewModel(
}
}
private fun handleAction(action: DataSourcesAction) {
when (action) {
is DataSourcesAction.SetUseCustomTle -> settingsRepo.setUseCustomTle(action.value)
is DataSourcesAction.SetUseCustomTransceivers -> settingsRepo.setUseCustomTransceivers(action.value)
is DataSourcesAction.SetTleUrl -> settingsRepo.setTleUrl(action.value)
is DataSourcesAction.SetTransceiversUrl -> settingsRepo.setTransceiversUrl(action.value)
}
}
private fun setGpsPosition() {
if (settingsRepo.setStationPosition()) {
val messageResId = R.string.prefs_loc_success
@@ -217,11 +241,23 @@ class SettingsViewModel(
}
}
private fun updateFromFile(uri: String) = viewModelScope.launch {
private fun updateTLEFromFile(uri: String) = viewModelScope.launch {
try {
val newDataSettings = _uiState.value.dataSettings.copy(isUpdating = true)
_uiState.update { it.copy(dataSettings = newDataSettings) }
databaseRepo.updateFromFile(uri)
databaseRepo.updateTLEFromFile(uri)
} catch (exception: Exception) {
val newDataSettings = _uiState.value.dataSettings.copy(isUpdating = false)
_uiState.update { it.copy(dataSettings = newDataSettings) }
println(exception)
}
}
private fun updateTransceiversFromFile(uri: String) = viewModelScope.launch {
try {
val newDataSettings = _uiState.value.dataSettings.copy(isUpdating = true)
_uiState.update { it.copy(dataSettings = newDataSettings) }
databaseRepo.updateTransceiversFromFile(uri)
} catch (exception: Exception) {
val newDataSettings = _uiState.value.dataSettings.copy(isUpdating = false)
_uiState.update { it.copy(dataSettings = newDataSettings) }
+4
View File
@@ -108,6 +108,10 @@
<string name="prefs_data_clear_success">Datos eliminados con éxito</string>
<string name="prefs_data_update_success">Actualización completada con éxito</string>
<string name="prefs_data_sources_title">Custom data sources</string>
<string name="prefs_data_sources_tle_switch">Custom TLE URL</string>
<string name="prefs_data_sources_transceivers_switch">Custom transceivers URL</string>
<string name="prefs_net_title">Salida de datos de red</string>
<string name="prefs_net_switch">Habilitar salida</string>
<string name="prefs_net_ip_hint">Dirección IP</string>
+7 -3
View File
@@ -93,10 +93,10 @@
<string name="prefs_loc_qth_error">Неправильный локатор QTH</string>
<string name="prefs_loc_success">Позиция успешно обновлена</string>
<string name="prefs_station_title">Настройки местоположения</string>
<string name="prefs_station_lat_text">Установите широту наземной станции</string>
<string name="prefs_station_lon_text">Установите долготу наземной станции</string>
<string name="prefs_station_lat_text">Введите широту наземной станции</string>
<string name="prefs_station_lon_text">Введите долготу наземной станции</string>
<string name="prefs_locator_title">Настройки локатора QTH</string>
<string name="prefs_locator_text">Установить позицию используя локатор</string>
<string name="prefs_locator_text">Введите позицию через локатор</string>
<string name="prefs_data_title">Данные спутников</string>
<string name="prefs_data_entries">Спутников: %s</string>
@@ -107,6 +107,10 @@
<string name="prefs_data_clear_success">Очистка прошла успешно</string>
<string name="prefs_data_update_success">Обновление прошло успешно</string>
<string name="prefs_data_sources_title">Свои источники данных</string>
<string name="prefs_data_sources_tle_switch">Custom TLE URL</string>
<string name="prefs_data_sources_transceivers_switch">Custom Transceivers URL</string>
<string name="prefs_net_title">Вывод сетевых данных</string>
<string name="prefs_net_switch">Включить вывод</string>
<string name="prefs_net_ip_hint">IP адрес</string>
+4
View File
@@ -108,6 +108,10 @@
<string name="prefs_data_clear_success">දත්ත ඉවත් කිරීම සාර්ථකයි</string>
<string name="prefs_data_update_success">යාවත්කාලීනය සම්පූර්ණ කිරීම සාර්ථකයි</string>
<string name="prefs_data_sources_title">Custom data sources</string>
<string name="prefs_data_sources_tle_switch">Custom TLE URL</string>
<string name="prefs_data_sources_transceivers_switch">Custom transceivers URL</string>
<string name="prefs_net_title">ජාල ප්‍රතිදානය</string>
<string name="prefs_net_switch">ප්‍රතිදානය සක්‍රීය කරන්න</string>
<string name="prefs_net_ip_hint">IP ලිපිනය</string>
+10 -6
View File
@@ -17,7 +17,7 @@
<string name="sat_type_title">Виберіть тип супутника</string>
<string name="sat_search_hint">Id - Назва</string>
<string name="sat_search_clear">Очистити</string>
<string name="sat_clear_all">Очистити</string>
<string name="sat_clear_all">Очистити всі</string>
<string name="sat_select_all">Вибрати всі</string>
<string name="sat_empty_list_message">
Переконайтеся, що ваш пошуковий запит правильний, а база даних оновлена</string>
@@ -87,15 +87,15 @@
<string name="prefs_loc_title">Місцезнаходження</string>
<string name="prefs_loc_gps_title">GPS</string>
<string name="prefs_loc_gps_error">Перевірте дозволи на визначення місця</string>
<string name="prefs_loc_gps_error">Перевірте дозвіл на використання геолокацї</string>
<string name="prefs_loc_input_title">Ввести</string>
<string name="prefs_loc_input_error">Введені некоректні дані</string>
<string name="prefs_loc_input_error">Введено некоректну локацію</string>
<string name="prefs_loc_qth_title">QTH</string>
<string name="prefs_loc_qth_error">Некоректний локатор QTH</string>
<string name="prefs_loc_success">Позицію успішно оновлено</string>
<string name="prefs_station_title">Налаштування місцезнаходження станції</string>
<string name="prefs_station_lat_text">Задайте широту станції</string>
<string name="prefs_station_lon_text">Задайте довготу станції</string>
<string name="prefs_station_lat_text">Широта місцезнаходження станції</string>
<string name="prefs_station_lon_text">Довгота місцезнаходження станції</string>
<string name="prefs_locator_title">Налаштування QTH локатора</string>
<string name="prefs_locator_text">Задати позицію за QTH локатором</string>
@@ -108,6 +108,10 @@
<string name="prefs_data_clear_success">Дані успішно очищено</string>
<string name="prefs_data_update_success">Оновлення успішне</string>
<string name="prefs_data_sources_title">Custom data sources</string>
<string name="prefs_data_sources_tle_switch">Custom TLE URL</string>
<string name="prefs_data_sources_transceivers_switch">Custom transceivers URL</string>
<string name="prefs_net_title">Вивід мережевих даних</string>
<string name="prefs_net_switch">Увімкнути вихід</string>
<string name="prefs_net_ip_hint">IP адреса</string>
@@ -126,6 +130,6 @@
<string name="prefs_other_switch_sensors">Використовувати сенсори пристрою</string>
<string name="prefs_outro_title">Я хотів би подякувати:</string>
<string name="prefs_outro_license">Це ПЗ поставляється без жодних гарантій.</string>
<string name="prefs_outro_license">Ця програма поставляється без жодних гарантій</string>
</resources>
+4
View File
@@ -100,6 +100,10 @@
<string name="prefs_data_clear_success">数据清空成功</string>
<string name="prefs_data_update_success">更新成功</string>
<string name="prefs_data_sources_title">Custom data sources</string>
<string name="prefs_data_sources_tle_switch">Custom TLE URL</string>
<string name="prefs_data_sources_transceivers_switch">Custom transceivers URL</string>
<string name="prefs_net_title">网络数据输出</string>
<string name="prefs_net_switch">启用输出</string>
<string name="prefs_net_ip_hint">IP地址</string>
+6 -1
View File
@@ -20,7 +20,7 @@
<string name="sat_search_clear">Clear</string>
<string name="sat_clear_all">Clear all</string>
<string name="sat_select_all">Select all</string>
<string name="sat_empty_list_message">" Make sure your search query is correct and the DB is updated"</string>
<string name="sat_empty_list_message">"Make sure your search query is correct and the DB is updated"</string>
<string name="sat_warning_title">Warning\!</string>
<string name="sat_warning_message">
There are over 9000 satellites listed in this app.
@@ -131,6 +131,11 @@
<string name="prefs_data_clear_success">Data was cleared successfully</string>
<string name="prefs_data_update_success">Update completed successfully</string>
<string name="prefs_data_sources_title">Custom data sources</string>
<string name="prefs_data_sources_tle_switch">Custom TLE URL</string>
<string name="prefs_data_sources_transceivers_switch">Custom transceivers URL</string>
<string name="prefs_data_sources_url_title" translatable="false">URL (HTTPS)</string>
<string name="prefs_net_title">Network data output</string>
<string name="prefs_net_switch">Enable output</string>
<string name="prefs_net_ip_hint">IP address</string>
+1 -1
View File
@@ -4,7 +4,7 @@ plugins {
}
kotlin {
jvmToolchain(21)
jvmToolchain(17)
}
android {
@@ -39,23 +39,46 @@ class DatabaseRepo(
private val settingsRepo: ISettingsRepo
) : IDatabaseRepo {
override suspend fun updateFromFile(uri: String) = withContext(dispatcher) {
override suspend fun updateTLEFromFile(uri: String) = withContext(dispatcher) {
val importedSatellites = remoteSource.getFileStream(uri)?.let { dataParser.parseTLEStream(it) }
importedSatellites?.let { localSource.insertEntries(it) }
setUpdateSuccessful(System.currentTimeMillis())
}
override suspend fun updateTransceiversFromFile(uri: String) = withContext(dispatcher) {
val radios = remoteSource.getFileStream(uri)?.let { dataParser.parseJSONStream(it) }
radios?.let {
if(it.isNotEmpty()) {
localSource.deleteRadios()
localSource.insertRadios(it)
}
}
setUpdateSuccessful(System.currentTimeMillis())
}
override suspend fun updateFromRemote() = withContext(dispatcher) {
val importedEntries = mutableListOf<OrbitalData>()
val importedRadios = mutableListOf<SatRadio>()
// fetch
val jobsMap = Sources.satelliteDataUrls.mapValues { async { remoteSource.getNetworkStream(it.value) } }
val jobRadios = async { remoteSource.getNetworkStream(Sources.RADIO_DATA_URL) }
val tleUrls = Sources.satelliteDataUrls.toMutableMap().apply {
if (settingsRepo.dataSourcesSettings.value.useCustomTLE) {
this["Other"] = settingsRepo.dataSourcesSettings.value.tleUrl
}
}
val jobsMap = tleUrls
.filter { (_, url) -> url.isNotEmpty() }
.mapValues { (_, url) -> async { remoteSource.getNetworkStream(url) } }
val radioUrls = buildList {
add(Sources.RADIO_DATA_URL)
if (settingsRepo.dataSourcesSettings.value.useCustomTransceivers) {
add(settingsRepo.dataSourcesSettings.value.transceiversUrl)
}
}
val jobRadios = radioUrls.associateWith { url -> async { remoteSource.getNetworkStream(url) } }
// parse
jobsMap.mapValues { job -> job.value.await() }.forEach { entry ->
entry.value?.let { stream ->
when (val type = entry.key) {
"Amsat", "R4UAB" -> {
"Amsat", "R4UAB", "Other" -> {
// parse tle stream
val satellites = dataParser.parseTLEStream(stream)
val catnums = satellites.map { it.catnum }
@@ -82,7 +105,11 @@ class DatabaseRepo(
}
}
}
jobRadios.await()?.let { importedRadios.addAll(dataParser.parseJSONStream(it)) }
jobRadios.values.forEach { job ->
job.await()?.let {
importedRadios.addAll(dataParser.parseJSONStream(it))
}
}
// insert
localSource.insertEntries(importedEntries)
localSource.insertRadios(importedRadios)
@@ -27,6 +27,7 @@ import com.rtbishop.look4sat.domain.model.DatabaseState
import com.rtbishop.look4sat.domain.model.OtherSettings
import com.rtbishop.look4sat.domain.model.PassesSettings
import com.rtbishop.look4sat.domain.model.RCSettings
import com.rtbishop.look4sat.domain.model.DataSourcesSettings
import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.domain.repository.ISettingsRepo
import com.rtbishop.look4sat.domain.utility.positionToQth
@@ -68,6 +69,10 @@ class SettingsRepo(
private val keyUpdateTimestamp = "updateTimestamp"
private val keyShouldSeeWarning = "shouldSeeWarning"
private val keyShouldSeeWelcome = "shouldSeeWelcome"
private val keyUseCustomTle = "useCustomTle"
private val keyUseCustomTransceivers = "useCustomTransceivers"
private val keyTleUrl = "tleUrl"
private val keyTransceiversUrl = "transceiversUrl"
private val separatorComma = ","
//region # Satellites selection settings
@@ -330,4 +335,36 @@ class SettingsRepo(
shouldSeeWelcome = preferences.getBoolean(keyShouldSeeWelcome, true)
)
//endregion
//region # Data sources settings
private val _dataSourcesSettings = MutableStateFlow(getDataSourcesSettings())
override val dataSourcesSettings: StateFlow<DataSourcesSettings> = _dataSourcesSettings
override fun setUseCustomTle(value: Boolean) {
preferences.edit { putBoolean(keyUseCustomTle, value) }
_dataSourcesSettings.update { it.copy(useCustomTLE = value) }
}
override fun setUseCustomTransceivers(value: Boolean) {
preferences.edit { putBoolean(keyUseCustomTransceivers, value) }
_dataSourcesSettings.update { it.copy(useCustomTransceivers = value) }
}
override fun setTleUrl(value: String) {
preferences.edit { putString(keyTleUrl, value) }
_dataSourcesSettings.update { it.copy(tleUrl = value) }
}
override fun setTransceiversUrl(value: String) {
preferences.edit { putString(keyTransceiversUrl, value) }
_dataSourcesSettings.update { it.copy(transceiversUrl = value) }
}
private fun getDataSourcesSettings(): DataSourcesSettings = DataSourcesSettings(
useCustomTLE = preferences.getBoolean(keyUseCustomTle, false),
useCustomTransceivers = preferences.getBoolean(keyUseCustomTransceivers, false),
tleUrl = preferences.getString(keyTleUrl, "https://example.com/tle.txt") ?: "",
transceiversUrl = preferences.getString(keyTransceiversUrl, "https://example.com/radio.json") ?: ""
)
//endregion
}
+1 -1
View File
@@ -3,7 +3,7 @@ plugins {
}
kotlin {
jvmToolchain(21)
jvmToolchain(17)
}
dependencies {
@@ -48,3 +48,10 @@ data class OtherSettings(
val shouldSeeWarning: Boolean,
val shouldSeeWelcome: Boolean
)
data class DataSourcesSettings(
val useCustomTLE: Boolean,
val useCustomTransceivers: Boolean,
val tleUrl: String,
val transceiversUrl: String
)
@@ -18,7 +18,8 @@
package com.rtbishop.look4sat.domain.repository
interface IDatabaseRepo {
suspend fun updateFromFile(uri: String)
suspend fun updateTLEFromFile(uri: String)
suspend fun updateTransceiversFromFile(uri: String)
suspend fun updateFromRemote()
suspend fun clearAllData()
}
@@ -21,6 +21,7 @@ import com.rtbishop.look4sat.domain.model.DatabaseState
import com.rtbishop.look4sat.domain.model.OtherSettings
import com.rtbishop.look4sat.domain.model.PassesSettings
import com.rtbishop.look4sat.domain.model.RCSettings
import com.rtbishop.look4sat.domain.model.DataSourcesSettings
import com.rtbishop.look4sat.domain.predict.GeoPos
import kotlinx.coroutines.flow.StateFlow
@@ -73,4 +74,12 @@ interface ISettingsRepo {
fun setWarningDismissed()
fun setWelcomeDismissed()
//endregion
//region # Transceivers settings
val dataSourcesSettings: StateFlow<DataSourcesSettings>
fun setUseCustomTle(value: Boolean)
fun setUseCustomTransceivers(value: Boolean)
fun setTleUrl(value: String)
fun setTransceiversUrl(value: String)
//endregion
}
@@ -46,6 +46,7 @@ object Sources {
"Swarm" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=swarm&FORMAT=csv",
"Weather" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=weather&FORMAT=csv",
"X-Comm" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=x-comm&FORMAT=csv",
"Amsat" to "https://amsat.org/tle/current/nasabare.txt"
"Amsat" to "https://amsat.org/tle/current/nasabare.txt",
"Other" to "" // key for sats filter
)
}
@@ -1,9 +1,3 @@
Added support for landscape layout and tablets
Added AOS/LOS info to the countdown component
Added various minor UI fixes for readability
Cleaned up the translations and strings.xml
Moved Modes filter dialog to Passes screen
Rewrote all the screens with Jetpack Compose
Switched to using Bottom Navigation component
Switched to using unified dialogs and top bars
Updated GitHub Actions workflow deps and scripts
Added minor tweaks for UK translation, by Eugene #188
Added custom data sources import, by londre7 #189
Added various UI fixes, updated dependencies
@@ -1,9 +1,3 @@
Added support for landscape layout and tablets
Added AOS/LOS info to the countdown component
Added various minor UI fixes for readability
Cleaned up the translations and strings.xml
Moved Modes filter dialog to Passes screen
Rewrote all the screens with Jetpack Compose
Switched to using Bottom Navigation component
Switched to using unified dialogs and top bars
Updated GitHub Actions workflow deps and scripts
Added minor tweaks for UK translation, by Eugene #188
Added custom data sources import, by londre7 #189
Added various UI fixes, updated dependencies
+1 -1
View File
@@ -1,7 +1,7 @@
[versions]
android-gradle-plugin = "9.0.0"
google-ksp = "2.3.2"
kotlin = "2.3.0"
kotlin = "2.3.10"
androidx-core-ktx = "1.17.0"
androidx-core-splashscreen = "1.2.0"